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

时间:2016-02-21 13:15:48

标签: c++ gcc x86

我正在尝试使用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有默认选择器并尝试确定崩溃?

1 个答案:

答案 0 :(得分:2)

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

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

有趣的是,LLVM本身就是这样做的 - 我没有UD2指针访问的特殊检测,但是我的pascal编译器编译了这个:

NIL

成:

program p;

var
   ptr : ^integer;

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

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

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

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    

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

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++);
  }
}

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

我试图找出何时引入它,但是函数本身起源于2007年,但它当时并没有用于这个目的,这使得很难弄清楚为什么这样使用它。 / p>