gpt4 book ai didi

c++ - 写入 [ds :0x0]? 后添加 ud2 有什么意义

转载 作者:太空狗 更新时间:2023-10-29 21:38:19 24 4
gpt4 key购买 nike

我正在试验 GCC,试图说服它假设代码的某些部分是不可访问的,以便捕获机会进行优化。我的一个实验给了我一些奇怪的代码。这是来源:

#include <iostream>

#define UNREACHABLE {char* null=0; *null=0; return {};}

double test(double x)
{
if(x==-1) return -1;
else if(x==1) return 1;
UNREACHABLE;
}

int main()
{
std::cout << "Enter a number. Only +/- 1 is supported, otherwise I dunno what'll happen: ";
double x;
std::cin >> x;
std::cout << "Here's what I got: " << test(x) << "\n";
}

这是我编译它的方式:

g++ -std=c++11 test.cpp -O3 -march=native -S -masm=intel -Wall -Wextra

test 函数的代码如下所示:

_Z4testd:
.LFB1397:
.cfi_startproc
fld QWORD PTR [esp+4]
fld1
fchs
fld st(0)
fxch st(2)
fucomi st, st(2)
fstp st(2)
jp .L10
je .L11
fstp st(0)
jmp .L7
.L10:
fstp st(0)
.p2align 4,,10
.p2align 3
.L7:
fld1
fld st(0)
fxch st(2)
fucomip st, st(2)
fstp st(1)
jp .L12
je .L6
fstp st(0)
jmp .L8
.L12:
fstp st(0)
.p2align 4,,10
.p2align 3
.L8:
mov BYTE PTR ds:0, 0
ud2 // This is redundant, isn't it?..
.p2align 4,,10
.p2align 3
.L11:
fstp st(1)
.L6:
rep; ret

让我感到奇怪的是 .L8 中的代码。也就是说,它已经写入零地址,这保证了段错误,除非 ds 有一些非默认选择器。那么为什么要额外的 ud2 呢?写入零地址不是已经保证崩溃了吗?还是 GCC 不相信 ds 有默认选择器并试图确保崩溃?

最佳答案

因此,您的代码正在写入地址零 (NULL),它本身被定义为“未定义的行为”。由于未定义的行为涵盖任何事物,而且对于这种情况最重要的是,“它做了你可能想象它会做的事情”(换句话说,写入地址零而不是崩溃)。然后编译器决定通过添加 UD2 指令来告诉您。也有可能是为了防止从具有进一步未定义行为的信号处理程序继续。

是的,在大多数情况下,大多数机器都会因 NULL 访问而崩溃。但这并不能 100% 保证,正如我上面所说的,可以在信号处理程序中捕获段错误,然后尝试继续 - 在尝试写入 NULL 之后实际继续并不是一个好主意,因此编译器添加了 UD2 以确保您不会继续......它使用了 2 个字节以上的内存,除此之外我看不出它有什么危害[毕竟,它是未定义的发生了 - 如果编译器希望这样做,它可以将文件系统中的随机图片通过电子邮件发送给英国女王...我认为 UD2 是更好的选择...]

有趣的是,LLVM 自己做了这个——我没有对 NIL 指针访问进行特殊检测,但我的 pascal 编译器编译了这个:

program p;

var
ptr : ^integer;

begin
ptr := NIL;
ptr^ := 42;
end.

进入:

0000000000400880 <__PascalMain>:
400880: 55 push %rbp
400881: 48 89 e5 mov %rsp,%rbp
400884: 48 c7 05 19 18 20 00 movq $0x0,0x201819(%rip) # 6020a8 <ptr>
40088b: 00 00 00 00
40088f: 0f 0b ud2

我仍在尝试找出在 LLVM 中发生这种情况的位置,并尝试理解 UD2 指令本身的用途。

我认为答案就在这里,在 llvm/lib/Transforms/Utils/Local.cpp 中

void llvm::changeToUnreachable(Instruction *I, bool UseLLVMTrap) {
BasicBlock *BB = I->getParent();
// Loop over all of the successors, removing BB's entry from any PHI
// nodes.
for (succ_iterator SI = succ_begin(BB), SE = succ_end(BB); SI != SE; ++SI)
(*SI)->removePredecessor(BB);

// Insert a call to llvm.trap right before this. This turns the undefined
// behavior into a hard fail instead of falling through into random code.
if (UseLLVMTrap) {
Function *TrapFn =
Intrinsic::getDeclaration(BB->getParent()->getParent(), Intrinsic::trap);
CallInst *CallTrap = CallInst::Create(TrapFn, "", I);
CallTrap->setDebugLoc(I->getDebugLoc());
}
new UnreachableInst(I->getContext(), I);

// All instructions after this are dead.
BasicBlock::iterator BBI = I->getIterator(), BBE = BB->end();
while (BBI != BBE) {
if (!BBI->use_empty())
BBI->replaceAllUsesWith(UndefValue::get(BBI->getType()));
BB->getInstList().erase(BBI++);
}
}

特别是中间的评论,它说“而不是陷入随机代码”。在您的代码中,在 NULL 访问之后没有代码,但想象一下:

void func()
{
if (answer == 42)
{
#if DEBUG
// Intentionally crash to avoid formatting hard disk for now

char *ptr = NULL;
ptr = 0;
#endif
// Format hard disk.
... some code to format hard disk ...
}
printf("We haven't found the answer yet\n");
...
}

因此,这应该会崩溃,但如果它没有崩溃,编译器将确保您不会在它之后继续...它会使 UB 崩溃更加明显(并且在这种情况下会阻止硬盘被格式化。 ..)

我想知道这是什么时候引入的,但是这个函数本身起源于 2007 年,但当时并没有完全用于这个目的,这使得很难弄清楚为什么这样使用它。

关于c++ - 写入 [ds :0x0]? 后添加 ud2 有什么意义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35536597/

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