手动装配与GCC

时间:2015-07-21 12:59:48

标签: c gcc assembly

免责声明:我刚刚开始使用x86程序集。我在大学时确实学到了一点SPIM,但这几乎不值得一提。

我认为我从libc中最简单的函数abs()开始。在C中非常简单:

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

汇编中的我的版本:

    .global myAbs
    .type   myAbs, @function
    .text

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

(这对于32位整数不起作用,可能是因为RAX是一个64位寄存器而且符号可能位于错误位置 - 我必须对此进行调查。)

现在这里是gcc的作用(gcc -O2 -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    __stack_chk_fail@PLT
        .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

为什么会有这么大的差异? GCC产生了更多的指令。我无法想象这不会比我的代码慢。 我错过了什么吗?或者我在做一些严重错误的事情?

2 个答案:

答案 0 :(得分:41)

对于那些想知道生成的代码来自何处的人,请注意当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    __stack_chk_fail@PLT  ;__stack_chk_fail is noreturn

因此,所有额外的说明都是为了实现Stack Smashing Protector

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

答案 1 :(得分:6)

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

也许为您的编译器添加标志,例如:-fno-stack-protector可以最大限度地减少这种差异。

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

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