2015-07-21 56 views
37

声明:我刚开始使用x86汇编。我在大学学到了一点SPIM,但是这不值得一提。手动装配vs GCC

我以为我从libc,abs()中最简单的函数开始。

long myAbs(long j) { 
    return j < 0 ? -j : j; 
} 

我的版本中装配:用C非常简单

.global myAbs 
    .type myAbs, @function 
    .text 

myAbs: 
    test %rdi, %rdi 
    jns end 
    negq %rdi 
end: 
    movq %rdi, %rax 
    ret 

(这并不适用于32位整数的工作,可能是因为RAX是一个64位的寄存器和标志可能是在错误的位置 - 我必须调查)。

现在这里是gcc在(GCC -02 -S myAbs.c):

 .file "myAbs.c" 
     .section  .text.unlikely,"ax",@progbits 
.LCOLDB0: 
     .text 
.LHOTB0: 
     .p2align 4,,15 
     .globl myAbs 
     .type myAbs, @function 
myAbs: 
.LFB0: 
     .cfi_startproc 
     pushq %rbp 
     .cfi_def_cfa_offset 16 
     .cfi_offset 6, -16 
     movq %rsp, %rbp 
     .cfi_def_cfa_register 6 
     subq $4144, %rsp 
     orq  $0, (%rsp) 
     addq $4128, %rsp 
     movq %rdi, %rdx 
     sarq $63, %rdx 
     movq %fs:40, %rax 
     movq %rax, -8(%rbp) 
     xorl %eax, %eax 
     movq %rdi, %rax 
     xorq %rdx, %rax 
     subq %rdx, %rax 
     movq -8(%rbp), %rcx 
     xorq %fs:40, %rcx 
     jne  .L5 
     leave 
     .cfi_remember_state 
     .cfi_def_cfa 7, 8 
     ret 
.L5: 
     .cfi_restore_state 
     call [email protected] 
     .cfi_endproc 
.LFE0: 
     .size myAbs, .-myAbs 
     .section  .text.unlikely 
.LCOLDE0: 
     .text 
.LHOTE0: 
     .ident "GCC: (Gentoo Hardened 5.1.0 p1.2, pie-0.6.3) 5.1.0" 
     .section  .note.GNU-stack,"",@progbits 

为什么这样大的差别?海湾合作委员会产生更多的指示我无法想象这不会比我的代码慢。 我错过了什么吗?或者我在这里做了一些严重错误的事情?

+2

也许你的海湾合作委员会感到不适?矿产生[减少指令](https://goo.gl/wG2v1X)。 (和Clang喜欢[有条件的移动](https://goo.gl/xpxKyi)。) –

+7

“GCC :(Gentoo硬化5.1.0 p1.2,饼-0.6.3)5.1.0” - 我认为这是线索。强化的C编译器集成了堆栈粉碎保护或类似的功能。 – davmac

+2

许多开始的调用是设置堆栈并保存返回地址(你没有做的事情)。看起来像是一些堆栈保护正在进行。也许你可以调整你的编译器设置以消除一些开销。 – carloabelli

回答

40

对于那些谁不知道什么生成的代码的来源,首先要注意的是,当GCC编译myAbs与堆栈保护它转化成这种形式

long myAbs(long j) { 
    uintptr_t canary = __stack_chk_guard; 

    register long result = j < 0 ? -j : j; 

    if ((canary = canary^__stack_chk_guard) != 0) 
     __stack_chk_fail(); 
} 

简单地进行j < 0 ? -j : j;的代码是

movq %rdi, %rdx  ;RDX = j 
movq %rdi, %rax  ;RAX = j 
sarq $63, %rdx  ;RDX = 0 if j >=0, 0fff...ffh if j < 0 
xorq %rdx, %rax  ;Note: x xor 0ff...ffh = Not X, x xor 0 = x 
         ;RAX = j if j >=0, ~j if j < 0 
subq %rdx, %rax  ;Note: 0fff...ffh = -1 
         ;RAX = j+0 = j if j >= 0, ~j+1 = -j if j < 0 
         ;~j+1 = -j in two complement 

分析生成的代码我们得到

pushq %rbp 
    movq %rsp, %rbp  ;Standard prologue 

    subq $4144, %rsp  ;Allocate slight more than 4 KiB  
    orq  $0, (%rsp)  ;Perform a useless RW operation to test if there is enough stack space for __stack_chk_fail 

    addq $4128, %rsp  ;This leave 16 byte allocated for local vars 

    movq %rdi, %rdx  ;See above 
    sarq $63, %rdx  ;See above 

    movq %fs:40, %rax  ;Get the canary 
    movq %rax, -8(%rbp) ;Save it as a local var 
    xorl %eax, %eax  ;Clear it 

    movq %rdi, %rax  ;See above 
    xorq %rdx, %rax  ;See above 
    subq %rdx, %rax  ;See above 

    movq -8(%rbp), %rcx ;RCX = Canary 
    xorq %fs:40, %rcx  ;Check if equal to the original value 
    jne  .L5    ;If not fail 

    leave 
    ret 
.L5: 
    call [email protected] ;__stack_chk_fail is noreturn 

所以所有额外的指令都是为了实现Stack Smashing Protector

感谢FUZxxl指出在序幕之后使用第一条指令。

+0

该代码正在减去4144,然后将4128添加到'%rsp'以确保有更多额外的堆栈空间可用。如果没有,可以通过先排空堆栈来绕过堆栈检查(导致进程崩溃的原因不是堆栈检查失败)。 – fuz

+0

@FUZxxl你能否详细说明一下?你的意思是它应该防止堆栈溢出?我对该说明感到困惑 – 2015-07-21 13:36:01

+1

考虑堆栈几乎已满并且有人覆盖金丝雀的情况。在这种情况下,代码可能会检测到堆栈检查失败,但没有足够的空间来运行'__stack_chk_fail',因此不是打印一个警告堆栈检查失败的代码,而是因为分段错误而崩溃,隐藏有人试图闯入该计划的事实。减法,写入和加法序列确保大约4 KiB的堆栈空间仍然足以让'__stack_chk_fail'运行。 – fuz

6

许多开始调用是设置堆栈并保存返回地址(你没有做的事情)。似乎有一些堆栈保护继续。也许你可以调整你的编译器设置以消除一些开销。

也许向您的编译器添加标志,例如:-fno-stack-protector可以最小化这种差异。

是的这可能比你的手写汇编慢,但提供更多的保护,可能值得轻微的开销。

至于为什么堆栈保护仍然存在,即使它是叶函数see here