我正在使用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后填写正确的地址。)或者所有这些漏洞都留给链接器关闭。结束填充的另一个问题是你需要语法来说“在这里插入填充 ,以便那里对齐”。我可以想到一个适用于这种简单案例的算法,但它可能是一个不值得实现的模糊特征。嵌套填充的更复杂的情况可能会产生相反的结果......
答案 0 :(得分:1)
在xchg
之前添加自己的call
指令是否有问题?由于您在存根之前有一个对齐,因此对齐应该是一致的。
答案 1 :(得分:1)
不幸的是,大多数汇编程序都是一次通过简单的转换程序,这限制了它们可以提供的对齐指令的灵活性。即使在几个通道中组装的装配工可以提供的所有对齐选项中,也有许多被忽略,因为它们太具体了。我担心,你的是其中之一。它可以在一次通过汇编程序中工作,只要它只是你想要移动的一条指令,但它非常具体。
我已经看过一本复杂的多遍汇编程序的手册,它允许你减去两个标签的地址以获得一系列指令的长度,并且可以让你插入一个指令来插入一系列NOP,比方说, (4 - 这个长度模4)在你选择的地方(只要它仍然可以收敛到每个指令的确定位置)。我不记得它是什么汇编程序。绝对不是gas
,据我所知,这是一次通过。它可能是令人尊敬的A386。
答案 2 :(得分:1)
您是否考虑过将数据放在代码之前?
这种方式只是减去(存根代码的长度加上一些常量偏移量)来获取数据的地址,所以它是一条指令而不是两条指令,因为你已经准备好了。而且我相信gas
将为您提供存根代码的长度(作为两个标签的差异)而没有问题,因为在这种情况下定义后使用了标签。
假设数据由32位字组成,与初始解决方案相比,填充也更少(尽管我不确定为什么在初始解决方案中有这么多.align
指令,可能是一些正交约束你没有进入。)