【发布时间】:2024-04-19 21:00:02
【问题描述】:
我在 i386 上使用 GNU 汇编器,通常在 32 位 Linux 下(我也打算在 Cygwin 下寻找解决方案)。
我有一个“存根”函数:
.align 4
stub:
call *trampoline
.align 4
stub2:
trampoline:
...
这个想法是 stub 和 stub2 之间的数据将与函数指针和一些上下文数据一起复制到分配的内存中。当内存被调用时,其中的第一条指令将压入下一条指令的地址并转到trampoline,它将从堆栈中读取地址并找出伴随数据的位置。
现在,stub 被编译为:
ff 15 44 00 00 00 call *0x44
66 90 xchg %ax,%ax
这是对绝对地址的调用,这很好,因为call 的地址是未知的。填充已经变成了我猜是什么都不做的操作,这很好,无论如何它永远不会被执行,因为trampoline会在跳转到函数指针之前重写堆栈。
问题是这个调用推送的返回地址将指向未对齐的xchg 指令,而不是刚刚经过它的对齐数据。这意味着trampoline 需要更正对齐才能找到数据。这不是一个严重的问题,但最好生成以下内容:
66 90 xchg %ax,%ax
ff 15 44 00 00 00 call *0x44
# Data will be placed starting here
使返回地址直接指向数据。那么问题来了:如何填充指令以使其末尾对齐?
编辑一点背景(对于那些还没有猜到的人)。我正在尝试实现闭包。在语言中,
(int -> int) make_curried_adder(int x)
{
return int lambda (int y) { return x + y; };
}
(int -> int) plus7;
plus7 = make_curried_adder(7);
print("7 + 5 = ", plus7(5));
{ return x + y } 被翻译成一个普通但匿名的两个参数函数。分配一块内存并用存根指令、函数地址和值 7 填充。这由 make_curried_adder 返回,调用时会将附加参数 7 压入堆栈,然后跳转到匿名函数。
更新
我接受了 Pascal 的回答,即汇编程序往往被编写为一次性运行。我认为一些汇编程序确实有不止一次处理像“call x; ... ; x: ...”这样的代码,它有一个前向引用。 (事实上,我很久以前就写过一个——一旦到达 x,它就会返回并填写正确的地址。)或者也许所有这些漏洞都留给链接器关闭。 end-padding 的另一个问题是你需要语法说“插入填充 here 以便 there 对齐”。我可以想到一种算法可以适用于这样的简单情况,但它可能是一个晦涩难懂的功能,不值得实现。使用嵌套填充的更复杂的情况可能会产生矛盾的结果...
【问题讨论】:
-
你想做什么?使用 jmp 而不是调用会起作用吗?
-
跳跃效果肯定是我想要的;但是我需要的另一件事是数据的地址,它可以在内存中的任何位置,但总是靠近调用(或跳转)指令。 Call 方便地为我将 EIP 放入堆栈。
-
出于好奇,这是用于什么语言的?这是将以源代码形式发布的东西吗?
-
一个业余编译器,从“类 C”语言(即有很多 {},上面的示例)到 i386 机器代码。在使用会计软件 1 小时和在其上进行文书工作 7 小时后只需一些补偿。我的目标是省略一些东西(类型语法、指针)并添加一些其他东西(字符串、元组、闭包、gc)。它目前可以很好地编译基本整数算术(如果效率低,因为它不进行寄存器重新分配),并且可以进行 TCO 和内联。到目前为止,仅部分支持元组。此问题的代码构成 RT 库的一部分。