Manuelle Montage vs. GCC

Manuelle Montage vs. GCC


Haftungsausschluss:Ich fange gerade erst mit der x86-Assemblierung an. Ich habe ein bisschen SPIM an der Universität gelernt, aber das ist kaum der Rede wert.


Ich dachte, ich beginne mit der wahrscheinlich einfachsten Funktion in libc, abs(). Ziemlich einfach in C:


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

Meine Version in Assembler:


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

(Dies funktioniert nicht für 32-Bit-Ganzzahlen, wahrscheinlich weil RAX ein 64-Bit-Register ist und das Vorzeichen wahrscheinlich an der falschen Position steht - das muss ich untersuchen).


Hier ist, was gcc macht (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

Warum dieser große Unterschied? GCC produziert wesentlich mehr Anweisungen. Ich kann mir nicht vorstellen, dass das nicht langsamer sein wird als mein Code.
Übersehe ich etwas? Oder mache ich hier etwas ernsthaft falsch?


Antworten:


Für diejenigen, die sich fragen, woher der generierte Code stammt, beachten Sie zunächst, dass beim Kompilieren von GCC myAbs mit Stapelschutz wandelt es in diese Form um


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

Der Code zum einfachen Ausführen von j < 0 ? -j : j; ist


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

Analysieren des generierten Codes, den wir erhalten


    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

Alle zusätzlichen Anweisungen beziehen sich also auf die Implementierung des Stack Smashing Protector.


Danke an FUZxxl für den Hinweis auf die Verwendung der ersten Anweisungen nach dem Prolog.