【问题标题】:Inline assembly - cdecl and preparing the stack内联汇编 - cdecl 和准备堆栈
【发布时间】:2012-05-14 21:23:24
【问题描述】:

我最近一直在尝试通过使用缓冲区和不同汇编运算符的 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++ 代码,下面是“TheTest”函数的 ASM。可以立即注意到堆栈被推送了 24 (0x18) 个字节(如前所述,我没有使用汇编的经验,因此我可能不会使用正确的术语和/或完全正确)。这对我来说没有任何意义。当只使用 2 个不同的整数时,为什么需要 24 个字节?变量 'x' 被使用,它是 4 个字节,而值 '5' 也使用 4 个字节(记住它是 cdecl,所以调用函数负责处理函数 arguments 的内存)补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    

这里唯一的区别(除了值)是我使用'char'(1字节)而不是整数。如果我们再看一下汇编代码,这只会将堆栈指针推入 8 个字节。这与前面的示例相差 16 个字节。作为一个彻头彻尾的 C++ 人,我不知道发生了什么。如果有人能在这个问题上启发我,我将不胜感激!

注意:我之所以在这里发帖而不是阅读 ASM 书籍,是因为我需要为这个 one 函数使用汇编。所以我不想为了 40 行代码读一本书……

编辑:我也不关心平台依赖性,我关心 Linux 32bit :)

【问题讨论】:

  • 你为什么不直接使用 libffi?
  • @DavidHeffernan 这不好玩:)
  • @Elliott 为什么不使用调试器,看看TheTest 中的堆栈帧是什么样子,看看额外的空间是用来做什么的?
  • @SethCarnegie 确实如此,但 Elliott 可能正在寻找一种快速的解决方案,而不是有趣
  • @DavidHeffernan 不,我的意思是 我们没有乐趣

标签: c++ linux gcc assembly 32-bit


【解决方案1】:

这是为了保持堆栈与 32 字节的倍数对齐,以便 SIMD 指令可以与堆栈上的变量一起使用。

【讨论】:

    【解决方案2】:

    在我看来,您的编译器对第一个函数有误(可能缺少堆栈使用优化)。你的编译器使用两条指令(移动到预先分配的堆栈槽)而不是一条推送指令也很奇怪。

    您是否在没有优化的情况下进行编译? 你能发布你的编译器命令行吗?

    【讨论】:

    • 是的,我正在编译没有任何优化。也许这是一个愚蠢的举动?由于我是汇编初学者,我认为无需任何优化即可编译是有意义的,因此代码会保持简单......
    【解决方案3】:

    TheTest 中创建的堆栈帧包含本地(自动)变量和函数的参数,例如MyTestNewTest,由TheTest 调用。框架由TheTest 推送和弹出,所以只要它足够大以容纳它调用的函数的参数,大小就无关紧要了。

    您看到的编译器输出是编译器多次传递的结果。每次传递都可以执行减少所需帧大小的转换和优化;我怀疑在某些早期状态下编译器需要 24 字节的帧,即使代码经过优化,也从未减少它。

    您平台上编译器的 ABI 将建立一些您必须遵循的堆栈对齐规则,因此帧大小会向上取整以满足这些要求。

    这些函数使用帧指针%ebp%,尽管这不是代码大小或性能的胜利;不过,这可能有助于调试。

    【讨论】:

    • 多么有趣的解释!是的,我在没有任何优化的情况下进行编译以使 ASM 保持简单。但是你关于的说法有多真实,只要它足够大,可以容纳它调用的函数的参数,大小并不重要?问题是,我有一个包含结构的向量。这个结构定义了不同的类型(它们的大小以及它们是否是指针/引用)。我可以只计算所有参数的大小然后推送堆栈(并将其对齐到 2 的幂)吗?举个肮脏的例子:pastebin.com/mV4bHkVr
    • 是的,但您可能应该四舍五入到 8 的倍数:sizeToPush = (sizeToPush + 7) & -8;
    • 哇,那是一段很长的代码。我通常理解代码 sn-ps,所以这有点尴尬,但我可以指望那个代码 sn-p 将我的代码对齐到 8 的倍数?不想以被拆毁的软件告终 :) 编辑:它确实适用于我接受的未签名字符?
    • 是的,sn-p 只是四舍五入到最接近的 8 倍数。以这种方式使用 -8 取决于二进制补码算法,C 标准不能保证这一点,但所有常见的都是如此处理器。如果您知道要调整的值的大小,则可以通过使用无符号数来避免二进制补码问题,例如: sizeToPush = (sizeToPush + 7) & 0xFFFFFFF8
    【解决方案4】:

    在这些函数中插入了一些序言和尾声代码。尝试用裸函数编写程序集,即

    __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.
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-12-19
      • 2018-07-28
      • 1970-01-01
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 2022-11-06
      • 1970-01-01
      • 2017-11-18
      相关资源
      最近更新 更多