2015-07-21 54 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