Подтвердить что ты не робот

Ручная сборка против GCC

Отказ от ответственности: я только начинаю с сборки 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    [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

Почему эта большая разница? GCC дает значительно больше инструкций. Я не могу себе представить, что это будет не медленнее, чем мой код. Я что-то упускаю? Или я делаю здесь что-то серьезное?

4b9b3361

Ответ 1

Для тех, кто задается вопросом, из чего генерируется сгенерированный код, сначала обратите внимание, что когда 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 для указания использования первых инструкций после пролога.

Ответ 2

Многие из начальных вызовов - это настройка стека и сохранение адреса возврата (что-то, чего вы не делаете). Похоже, theres - это некоторая защита стека. Возможно, вы можете настроить свои настройки компилятора, чтобы избавиться от некоторых накладных расходов.

Возможно, добавление флагов к вашему компилятору, например: -fno-stack-protector, может свести к минимуму эту разницу.

Да, это, вероятно, медленнее, чем ваша рукописная сборка, но предлагает гораздо больше защиты и, вероятно, стоит небольших затрат.

Что касается того, почему защита стека все еще существует, хотя это функция листа см. здесь.