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

Rus*_*lan 5 c++ x86 gcc

我正在尝试使用 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";
}
Run Code Online (Sandbox Code Playgroud)

我是这样编译的:

g++ -std=c++11 test.cpp -O3 -march=native -S -masm=intel -Wall -Wextra
Run Code Online (Sandbox Code Playgroud)

函数的代码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
Run Code Online (Sandbox Code Playgroud)

让我想知道的是.L8. 也就是说,它已经写入零地址,这保证了分段错误,除非ds有一些非默认选择器。那么为什么要额外的ud2呢?写入零地址不是已经保证会崩溃吗?或者 GCC 不相信ds有默认选择器并试图确保崩溃?

Mat*_*son 4

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

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

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

program p;

var
   ptr : ^integer;

begin
   ptr := NIL;
   ptr^ := 42;
end.
Run Code Online (Sandbox Code Playgroud)

进入:

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    
Run Code Online (Sandbox Code Playgroud)

我仍在尝试找出 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++);
  }
}
Run Code Online (Sandbox Code Playgroud)

特别是中间的评论,它说“而不是陷入随机代码”。在您的代码中,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");
    ... 
}
Run Code Online (Sandbox Code Playgroud)

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

我试图找出这个功能是什么时候引入的,但这个功能本身起源于 2007 年,但当时它并没有完全用于这个目的,这使得很难弄清楚为什么会这样使用它。