2012-05-05 86 views
2

我最近一直試圖通過使用不同彙編操作符的緩衝區和RAW十六進制等價物來實現C++中的動態函數。爲了說明一個簡單的跳躍:內聯彙編-cdecl和堆棧準備

byte * buffer = new buffer[5]; 
*buffer = '0xE9'; // Hex for jump 
*(uint*)(buffer + 1) = 'address destination'; 

我不是在組裝經驗,但我知道足以創造非常簡單的功能。現在我正在原始內存中創建cdecl函數。問題是,我不知道我想用多少sub來推棧(用於內存)。讓我們以這個功能作爲一個例子:

int MyTest(int x, int y) { return x + y; } 

long TheTest(int x, int y) 
{ 
    return MyTest(x, 5); 
} 

08048a20 <_Z6TheTestii>: 
_Z6TheTestii(): 
8048a20: 55      push %ebp 
8048a21: 89 e5     mov %esp,%ebp 
8048a23: 83 ec 18    sub $0x18,%esp 
8048a26: c7 44 24 04 05 00 00 movl $0x5,0x4(%esp) 
8048a2d: 00 
8048a2e: 8b 45 08    mov 0x8(%ebp),%eax 
8048a31: 89 04 24    mov %eax,(%esp) 
8048a34: e8 c2 ff ff ff   call 80489fb <_Z6MyTestii> 
8048a39: c9      leave 
8048a3a: c3      ret  

正如你所看到的,第一個是C++代碼和下面是「TheTest」功能的ASM。人們可以立即注意到堆棧被壓入24(0x18)字節(如前所述,我沒有經歷使用匯編,因此我可能不會使用正確的術語和/或完全正確)。這對我來說沒有任何意義。當僅使用2個不同的整數時,需要24個字節?使用變量'x',它是4個字節,值'5'也使用4個字節(記住它是cdecl,所以調用函數處理關於函數參數的內存)不能彌補24。 ...

現在,這裏是一個額外的例子讓我真的難怪圍繞組件輸出:

int NewTest(int x, char val) { return x + val; } 

long TheTest(int x, int y) 
{ 
    return NewTest(x, (char)6); 
} 

08048a3d <_Z6TheTestiiii>: 
_Z6TheTestiiii(): 
8048a3d: 55      push %ebp 
8048a3e: 89 e5     mov %esp,%ebp 
8048a40: 83 ec 08    sub $0x8,%esp 
8048a43: c7 44 24 04 06 00 00 movl $0x6,0x4(%esp) 
8048a4a: 00 
8048a4b: 8b 45 08    mov 0x8(%ebp),%eax 
8048a4e: 89 04 24    mov %eax,(%esp) 
8048a51: e8 ca ff ff ff   call 8048a20 <_Z7NewTestic> 
8048a56: c9      leave 
8048a57: c3      ret  

這裏唯一的不同(除了值)是我用一個「字符的事實'(1字節)而不是整數。如果我們再看看彙編代碼,那麼只會將堆棧指針壓入8個字節。這與上例相差字節。作爲一個徹頭徹尾的C++人,我不知道發生了什麼。我真的很感激,如果有人可以啓發我的話題!

注意:之所以我在這裏發佈而不是閱讀ASM書,是因爲我需要使用這個一個函數的程序集。所以,我不想讀一整本書的代碼40行...

編輯:我也不在乎的平臺依賴性,我只照顧關於Linux 32位:)

+0

你爲什麼不只是使用libffi? –

+0

@DavidHeffernan那沒什麼好玩的:) –

+0

@Elliott爲什麼不使用調試器,看看'TheTest'裏的堆棧框架是什麼樣的,看看額外的空間是用來幹什麼的? –

回答

2

TheTest處創建的堆棧幀保持本地的(自動)的變量和函數的參數,如MyTestNewTest,通過TheTest調用。該框架入棧和出棧由TheTest,所以只要它足夠大來保存參數到它調用的功能,大小沒有多大關係。

您看到的編譯器輸出是編譯器幾次傳遞的結果。每一遍都可以執行轉換和優化,以減少所需的幀大小;我懷疑,在一些早期的狀態,編譯器所必需的框的24個字節,並且從來沒有減少它,即使代碼進行了優化。

你的平臺上編譯器的ABI將建立關於棧對齊一些規則,你必須遵守,所以幀尺寸四捨五入到滿足這些要求。

這些函數使用幀指針%ebp%,雖然這不是代碼大小或性能的勝利;但這可能有助於調試。

+1

什麼有趣的解釋!是的,我已經沒有任何的優化,使ASM編譯保持簡單但是,只要它足夠大以保持所調用的函數的參數,尺寸並不重要,那麼對於_so的陳述是多麼真實呢?事情是,我有一個包含結構的向量。結構定義了不同的類型(它們的大小,以及它們是否是一個指針/引用)。我能計算所有參數的大小,然後推棧(也將它對齊到2的冪)嗎?對於一個骯髒的例子:http: //pastebin.com/mV4bHkVr –

+1

是的,但你可能應該舍入爲8的倍數:sizeToPush =(sizeToPush + 7)& -8; –

+1

哇,那是一些代碼的重要部分,我通常理解代碼片段,所以這有點讓人難堪ing,但我可以依靠該代碼片段將我的代碼對齊到8的倍數?不想結束與拆卸軟件:)編輯:它確實與無符號字符工作,我拿它? –

0

有是插入到這些函數中的一些序言和尾聲代碼。嘗試編寫組件在裸功能,即

__declspec(naked) void UsernameIdTramp() // 10 byter, 5 bytes saves + 5 bytes for tramp 
{ 
    __asm 
    { 
     nop; nop; nop; nop; nop; // 5 bytes copied from target - 
     nop; nop; nop; nop; nop; // 5 bytes for the jump back. 
    } 
} 
1

它看起來像我的編譯器在第一個函數中犯了一個錯誤(可能缺少堆棧使用優化)。編譯器使用兩條指令(移動到預分配的堆棧槽)而不是單條推送指令也很奇怪。

你沒有編譯優化? 你可以發佈你的編譯器命令行嗎?

+0

是的,我編譯不使用任何優化避免二進制補碼問題。也許這是一個愚蠢的舉動?由於我是程序集初學者,我認爲編譯沒有任何優化是有意義的,所以代碼將保持簡單。 –

1

這是爲了保持堆棧對齊到32個字節的倍數,以便SIMD指令可以與堆棧中的變量一起使用。