Ensamblaje manual frente a GCC

Ensamblaje manual frente a GCC


Descargo de responsabilidad:recién estoy comenzando con el ensamblaje x86. Aprendí un poco de SPIM en la universidad, pero no vale la pena mencionarlo.


Pensé en comenzar con lo que probablemente sea la función más simple en libc, abs(). Bastante sencillo en C:


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

Mi versión en ensamblado:


    .global myAbs
.type myAbs, @function
.text
myAbs:
test %rdi, %rdi
jns end
negq %rdi
end:
movq %rdi, %rax
ret

(Esto no funciona para números enteros de 32 bits, probablemente porque RAX es un registro de 64 bits y el signo probablemente esté en la posición incorrecta; tengo que investigar eso).


Ahora esto es lo que hace 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

¿Por qué esta gran diferencia? GCC produce sustancialmente más instrucciones. No puedo imaginar que esto no sea más lento que mi código.
¿Me estoy perdiendo algo? ¿O estoy haciendo algo muy mal aquí?


Respuestas:


Para aquellos que se preguntan de dónde proviene el código generado, primero tenga en cuenta que cuando GCC compila myAbs con protección de pila lo transforma en esta forma


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

El código para realizar simplemente j < 0 ? -j : j; es


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

Analizando el código generado obtenemos


    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

Por lo tanto, todas las instrucciones adicionales son para implementar Stack Smashing Protector.


Gracias a FUZxxl por señalar el uso de las primeras instrucciones después del prólogo.