2016-04-08 92 views
2

我想了解一個簡單的C函數的底層彙編。反彙編簡單的C函數

program1.c

void function() { 
    char buffer[1]; 
} 

=>

push %ebp 
mov %esp, %ebp 
sub $0x10, %esp 
leave 
ret 

不知道它是如何到達這裏爲0x10?不是字符1字節,它是8位,所以它應該是0x08?


program2.c

void function() { 
    char buffer[4]; 
} 

=>

push %ebp 
mov %esp, %ebp 
sub $0x18, %esp 
mov ... 
mov ... 
[a bunch of random instructions] 

不知道它是如何到達這裏爲0x18要麼?另外,爲什麼在SUB指令後有這麼多附加指令?我所做的只是將陣列的長度從1更改爲4.

+0

這些數字是字節。由於對齊和禁用優化,它們比您要求的大。 – Jester

+0

哦,所以0x10是10個字節而不是10位? –

+0

在這種情況下,計數以字節完成,以位計數是毫無意義的。 –

回答

6

gcc uses -mpreferred-stack-boundary=4 by default適用於x86 32位和64位ABI,因此它保持與16B對齊的%esp

我能夠重現你的輸出與gcc 4.8.2 -O0 -m32 on the Godbolt Compiler Explorer

void f1() { char buffer[1]; } 
    pushl %ebp 
    movl %esp, %ebp  # make a stack frame (`enter` is super slow, so gcc doesn't use it) 
    subl $16, %esp 
    leave     # `leave` is not terrible compared to mov/pop 
    ret 

您必須使用一個版本的默認啓用的gcc with -fstack-protector的。較新的gcc通常不會被配置爲這樣做,所以您不會得到相同的標記值並將檢查寫入堆棧。 (嘗試在godbolt鏈接一個新的gcc)

void f4() { char buffer[4]; } 

    pushl %ebp # 
    movl %esp, %ebp  # make a stack frame 
    subl $24, %esp  # IDK why it reserves 24, rather than 16 or 32B, but prob. has something to do with aligning the stack for the possible call to __stack_chk_fail 
    movl %gs:20, %eax # load a value from thread-local storage 
    movl %eax, -12(%ebp) # store it on the stack 
    xorl %eax, %eax  # tmp59 
    movl -12(%ebp), %eax # D.1377, tmp60 
    xorl %gs:20, %eax # check that the sentinel value matches what we stored 
    je  .L3 #, 
    call __stack_chk_fail  # 
.L3: 
    leave 
    ret 

顯然GCC認爲char buffer[4]一個 「弱勢對象」,但不char buffer[1]。在沒有-fstack-protector的情況下,即使在-O0,在asm中也幾乎沒有差別。

+1

堆棧保護!謝謝你。 – SergeyA

+0

這是一個很棒的答案。謝謝 –

+0

@sir_thursday:如果你使用的是一個Ubuntu衍生產品(我猜你是),那麼GCC會在它周圍加強一個包裝,這會增加額外的標誌。對於GCC <4.9最近的Ubuntu版本將使用'-fstack-protector -param ssp-buffer-size = 4',對於GCC> = 4.9則使用'-fstack-protector-strong'。你可以用'-fno-stack-protector'來禁用 –

-1

每當您分配內存時,您的操作系統幾乎都不會實際給出您的數量,除非您使用像pvalloc這樣的函數,對齊的字節量(通常爲4K)。相反,您的操作系統假設您將來可能需要更多,因此繼續並提供更多。

要禁用此行爲,請使用不執行緩衝的較低級別的系統調用,如sbrk()。這些講義是一個很好的資源: http://web.eecs.utk.edu/~plank/plank/classes/cs360/360/notes/Malloc1/lecture.html

+1

雖然這是真的,更多與malloc/new/mmap或其他免費商店分配相關,並且這裏沒有發生這種情況。 – Useless

2

不是字符1個字節,這是8位,所以應該爲0x08?

這個值不是位,它們是字節。

不知道它是如何到達0x10在這裏?

這行:

push %ebp 
mov %esp, %ebp 
sub $0x10, %esp 

在棧上分配空間,16個字節的存儲器被保留用於該功能的執行。需要

所有這些字節來存儲類似的信息:

  • A 4字節存儲器地址將在ret指令要跳轉到指令
  • 的函數的局部變量
  • Data structure alignment
  • 其他我現在不記得的東西:)

在你的例子中,分配了16個字節。其中4個用於下一個將被調用的指令的地址,所以我們剩下12個字節。 1個字節用於大小爲1的字符數組,這可能會被編譯器優化爲單個字符。最後11個字節可能用於存儲一些我不記得的東西以及編譯器添加的填充。

不知道它是如何到達0x18在這裏嗎?

第二個示例中的每個附加字節都增加了2個字節的堆棧大小,1個字節的char和1個可能用於內存對齊的目的。

此外,爲什麼在SUB指令後還有這麼多附加指令?

請按照說明更新問題。

+0

在這些例子中沒有返回值... – Useless

+0

函數返回的地址的返回值,將被調用的下一條指令的地址 –

+1

但'call'推(和'ret'彈出)返回地址,表示在函數序言顯示之前它已經在堆棧上。 – Useless

2

此代碼只是設置堆棧幀。這用作局部變量的暫存空間,並且會有某種對齊要求。

你還沒有提到你的平臺,所以我不能確切地告訴你對你的系統有什麼要求,但顯然這兩個值都至少是8字節對齊的(所以你局部變量的大小被四捨五入所以%esp仍然是8的倍數)。

搜索「C函數序言收尾」「C函數調用棧」在這個領域找到更多的資源。

編輯 - Peter Cordes的答案解釋了這種差異和神祕的額外說明


以及物品是否完整,但法比奧已經回答了這個部分:

不知道它是如何到達這裏爲0x10?不是字符1字節,它是8位,所以它應該是0x08?

在x86,%esp是堆棧指針,和指針存儲地址,而這些都是字節的地址。子字節尋址是很少使用(參見Peter的評論)。如果您想檢查一個字節中的單個位,通常使用按位(&,|,~,^)操作該值,但不更改地址。 (你也可以爭辯說,子緩存行尋址是一種方便的虛構,但我們正在迅速脫離主題)。

+0

386位字符串指令('bt' /'bts' /'btr' /'btc')實際上會對存儲器地址進行位偏移。例如'bt [bitmap],1234'測試'[bitmap + 154]'中的位2。這是一種瘋狂的CISC語義,不幸的是'bts [mem],reg'非常慢:在Intel Haswell上有10個uops。 (對於'bts [mem],imm'或者'uts'來說,對於'bts reg,r/i'來說是3個uops)。在兩個操作數都準備好之前,CPU不知道內存地址。 –

+0

Eww,我不知道這些,但我想我並不完全感到驚訝。讓我不小心混淆了C指針,地址,'%esp'和指令操作數。 – Useless

+0

C指針和其他東西非常直接轉換爲x86機器指令。編譯器將使用'bt *'指令,但通常不使用內存操作數。 [我試圖讓gcc/clang/icc用'lock bts'(https://godbolt.org/g/sIGPfJ)在位圖中進行原子測試並設置,但gcc只是使用'lock cmpxchg' :(即使使用'-Os',它也不會爲非原子版本使用'bts'。 –