2012-05-05 87 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指令可以与堆栈中的变量一起使用。