gpt4 book ai didi

使用 Apple 的 LLVM 编译器编译 -O 时出现 C++ 代码段错误,但使用 g++ -7.2.0 时则不会

转载 作者:可可西里 更新时间:2023-11-01 15:51:49 27 4
gpt4 key购买 nike

更新:我创建了一个更多的 M,但仍然是重现崩溃的 CVE。摘要:删除了 Base 类中对 Bool* bools_ 字段的所有使用(但它仍然必须定义,否则不会发生崩溃)。还从 Base 及其后代中删除了 Base::Initialize() 和虚方法 Rule。附上新的 MCVE。

我已经设法为此代码创建了一个 MCVE 并将其发布在下方。

一些描述性细节:代码使用虚基类和派生类,某些实例化的派生类具有调用从“基”类继承的非虚方法的构造函数(实际上是派生类,但在继承层次结构比我所说的“派生”类)来初始化“基”类数据。该方法调用在派生类中重写的虚方法。我意识到这是一件危险的事情,但根据我对 C++ 的(可能有限的)理解,它似乎应该起作用,因为派生类构造函数的主体在设置“基”类虚拟表之前不会执行.在任何情况下,段错误都不会在调用“基”类的初始化方法期间发生。

段错误发生在“基”类构造函数中,并且仅当构造函数的主体为空时。如果我向构造函数添加调试行以在到达该点时打印出来,则调试行被打印出来并且代码正常运行。我的猜测是,出于某种原因,编译器正在优化应该在执行“基”类的构造函数主体之前发生的初始化,包括 vtable 的设置。

如主题行所述,此代码在使用 Apple 的 g++ 或 g++ 7.2.0 进行编译且未进行优化时运行良好,并且在使用 g++ 7.2.0 甚至 -O3 进行编译时也运行良好。它仅在使用 Apple 的 g++ LLVM 实现编译 -O2-O3 时出现段错误。该编译器的 g++ --version 的输出是:

% /usr/bin/g++ --version

Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Content

紧随其后的是 MCVE。

#include <iostream>

using namespace std;

class OriginalBaseClass {
public:
OriginalBaseClass(long double data1 = 1, long int data2 = 1) : data1_(data1), data2_(data2) { cout << "In OriginalBaseClass constructor\n"; }
private:
long double data1_;
long int data2_;
};

class Base : public virtual OriginalBaseClass {
public:
Base(long int data1 = 0, long int data2 = 0) : data1_(data1), data2_(data2) { cout << "In Base constructor\n"; }
virtual ~Base();
private:
bool* bools_;
long int data1_;
long int data2_;
};

Base::~Base()
{
cout << "In Base destructor\n";
}

class Derived_A : public virtual Base {
public:
Derived_A() { cout << "In Derived_A constructor\n"; }
};

class Derived_B : public Derived_A {
public:
Derived_B() : OriginalBaseClass(), Base(4, 1), Derived_A() { cout << "In Derived_B constructor\n"; }
};

int main()
{
Derived_B Derb;
}

错误报告链接:https://bugreport.apple.com/web/

引用编号 36382481

最佳答案

这看起来像是 Clang 中的错误,由无效生成未对齐的 SSE 存储引起。以下是基于您的代码的最小示例:

struct alignas(16) Base1 { };

struct Base2 : virtual Base1 {
__attribute__((noinline)) Base2() : data1_(0), data2_(0) { }

long dummy_, data1_, data2_;
};

struct Base3 : virtual Base2 { };

int main() { Base3 obj; }

这是由 Clang 生成的布局(GCC 使用相同的布局):

*** Dumping AST Record Layout
0 | struct Base1 (empty)
| [sizeof=16, dsize=16, align=16,
| nvsize=16, nvalign=16]

*** Dumping AST Record Layout
0 | struct Base2
0 | (Base2 vtable pointer)
8 | long dummy_
16 | long data1_
24 | long data2_
0 | struct Base1 (virtual base) (empty)
| [sizeof=32, dsize=32, align=16,
| nvsize=32, nvalign=8]

*** Dumping AST Record Layout
0 | struct Base3
0 | (Base3 vtable pointer)
0 | struct Base1 (virtual base) (empty)
8 | struct Base2 (virtual base)
8 | (Base2 vtable pointer)
16 | long dummy_
24 | long data1_
32 | long data2_
| [sizeof=48, dsize=40, align=16,
| nvsize=8, nvalign=8]

我们可以看到Base3Base1合并了,所以他们共享地址。 Base2Base3 实例化,之后以 8 字节 偏移放置,将 Base2 实例对齐在 8字节数,即使 alignof(Base2) 为 16。这仍然是正确的行为,因为这是 Base2 中所有成员字段之间的最大对齐方式。继承自虚基类Base1的对齐不需要保留,因为Base1由派生类Base3实例化,负责对齐 >Base1 正确。

问题出在 Clang 生成的代码上:

mov    rbx,rdi ; rdi contains this pointer
...
xorps xmm0,xmm0
movaps XMMWORD PTR [rbx+0x10],xmm0

Clang 决定使用一条需要 16 字节对齐的 movaps 指令同时初始化 data1_data2_,但是 Base2 实例仅 8 字节对齐,导致段错误。

看起来 Clang 假设它可以使用 16 字节对齐存储,因为 alignof(Base2) 是 16,但对于具有虚拟基类的类,这种假设是错误的。

如果您需要临时解决方案,您可以使用 -mno-sse 标志禁用 SSE 指令。请注意,这可能会影响性能。


Itanium ABI 文档可以在这里找到:https://refspecs.linuxfoundation.org/cxxabi-1.75.html

它明确提到nvalign:

nvalign(O): the non-virtual alignment of an object, which is the alignment of O without virtual bases.

然后是关于如何分配的解释:

Allocation of Members Other Than Virtual Bases

If D is not an empty base class or D is a data member: Start at offset dsize(C), incremented if necessary for alignment to nvalign(D) for base classes or to align(D) for data members. Place D at this offset unless doing so would result in two components (direct or indirect) of the same type having the same offset. If such a component type conflict occurs, increment the candidate offset by nvalign(D) for base classes or by align(D) for data members and try again, repeating until success occurs (which will occur no later than sizeof(C) rounded up to the required alignment).

看起来 Clang 和 GCC 都支持 Itanium ABI,使用非虚拟对齐正确对齐 Base2。我们还可以在上面的记录布局转储中看到这一点。


您可以使用 -fsanitize=undefined(GCC 和 Clang)编译您的程序,以在运行时获取此误报警告消息:

main.cpp:29:5: runtime error: constructor call on misaligned address 0x7ffd3b895dd8 for type 'Base2', which requires 16 byte alignment
0x7ffd3b895dd8: note: pointer points here
e9 55 00 00 ea c6 2e 02 9b 7f 00 00 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 f8 97 95 34

因此目前存在三个错误。我已经报告了所有这些:

关于使用 Apple 的 LLVM 编译器编译 -O 时出现 C++ 代码段错误,但使用 g++ -7.2.0 时则不会,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48158288/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com