我最近一直试图通过使用不同汇编运算符的缓冲区和RAW十六进制等效函数来实现C ++中的动态函数。为了说明简单的跳转:
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 ++代码,下面是&#39; TheTest&#39;的ASM。功能。人们可以立即注意到堆栈被推送24(0x18)字节(如前所述,我没有使用汇编经验,所以我可能不会使用正确的术语和/或完全正确)。这对我没有任何意义。当只使用2个不同的整数时,如何才需要24个字节?变量&#39; x&#39;使用,这是4个字节,值&#39; 5&#39;它也使用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
这里唯一的区别(值除外)是我使用了一个&#39; char&#39; (1个字节)而不是整数。如果我们再查看汇编代码,则只会将堆栈指针推送8个字节。与前一个示例相比, 16 字节的差异。作为一个彻头彻尾的C ++人,我不知道发生了什么。如果有人能就这个问题给我启发,我真的很感激!
注意:我之所以在这里发帖而不是阅读ASM书籍,是因为我需要为这个一个功能使用程序集。因此,我不想阅读40行代码的整本书......
编辑:我也不关心平台依赖,我只关心Linux 32bit:)答案 0 :(得分:2)
在TheTest
中创建的堆栈帧包含本地(自动)变量和函数的参数,例如由MyTest
调用的NewTest
和TheTest
。框架由TheTest
推送和弹出,只要它足够大以容纳它所调用的函数的参数,尺寸就不重要了。
您看到的编译器输出是编译器多次传递的结果。每个传递可以执行转换和优化,以减少所需的帧大小;我怀疑在某些早期状态下编译器需要24个字节的帧,并且即使代码已经优化也不会减少它。
平台上编译器的ABI将建立一些必须遵循的堆栈对齐规则,因此框架大小会向上舍入以满足这些要求。
这些函数使用帧指针%ebp%
,虽然这不是代码大小或性能的胜利;不过,这可能有助于调试。
答案 1 :(得分:1)
在我看来,你的编译器正在为第一个函数犯错(可能缺少堆栈使用优化)。你的编译器使用两条指令(移动到预先分配的堆栈槽)而不是一条推送指令也很奇怪。
您是否在没有优化的情况下进行编译? 你可以发布你的编译器命令行吗?
答案 2 :(得分:1)
这是为了使堆栈与32个字节的多个对齐,以便SIMD指令可以与堆栈中的变量一起使用。
答案 3 :(得分: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.
}
}