Assembly 在编译器中实现绑定函数参数
我有一个关于函数编程语言设计的想法,它大量使用绑定函数参数。我试图在x86汇编中表达绑定函数参数,作为编译器实现的一部分Assembly 在编译器中实现绑定函数参数,assembly,compiler-construction,programming-languages,compilation,functional-programming,Assembly,Compiler Construction,Programming Languages,Compilation,Functional Programming,我有一个关于函数编程语言设计的想法,它大量使用绑定函数参数。我试图在x86汇编中表达绑定函数参数,作为编译器实现的一部分 var add = function(x,y) { return x + y; }; var add2 = add.bind({}, 2); console.log( add2(3) ); // prints 5 出于互操作性的原因,我希望生成裸函数指针,因此我的第一个概念想法是在堆上分配一些可执行内存,并在存根中复制(a)推送一个额外的参数,(b)调用目标函数
var add = function(x,y) { return x + y; };
var add2 = add.bind({}, 2);
console.log( add2(3) ); // prints 5
出于互操作性的原因,我希望生成裸函数指针,因此我的第一个概念想法是在堆上分配一些可执行内存,并在存根中复制(a)推送一个额外的参数,(b)调用目标函数。这将是标准库的一部分,并将返回一个本机函数指针,我可以从x86汇编程序的其余部分使用它
我想我在这种方法中遇到了一个问题-如果存根使用调用
到达目标函数,那么堆栈包含一个返回地址,该地址最终被解释为函数参数!如果存根使用jmp
到达目标函数,那么调用方和被调用方都不知道函数返回时如何清理堆栈
如何解决这个问题?我想可以永久保留一个寄存器作为这种行为的标志,但这并不优雅
如何在函数式语言的本机编译器中实现bind()?您不能将预先传递的参数存储在内存中吗。然后,当您看到“add2”时,您从内存中收集参数,将它们推送到堆栈中,将其他参数推送到堆栈中(根据需要),然后像正常一样进行函数调用
我大部分时间都在大声思考,我不知道这是答案,但它看起来对我有效。你不能将预先传递的参数存储在内存中吗。然后,当您看到“add2”时,您从内存中收集参数,将它们推送到堆栈中,将其他参数推送到堆栈中(根据需要),然后像正常一样进行函数调用
我大部分时间都在大声思考我不知道这是答案,但它看起来对我很有效。进一步考虑,我认为这可以通过使用被叫方清理约定并手动管理我的所有返回地址来实现。它与stdcall类似,但不完全相同,因为不能使用call/ret(?) 伪代码:
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _create_trampoline
; make the call
push [return address]
push 0x03 ;arg1
jmp add2
; the resulting stub, held on the heap somewhere
add2:
push 0x02 ;bound argument
jmp add
; var add(x,y)
add:
local x = pop
local y = pop
eax = x + y;
jmp pop
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _magic(0x02, &add);
; make the call
push 0x03;
call add2;
add2:
ebx = pop; ;the return address goes in a temporary
push 0x02;
push ebx;
jmp add
; var add(x,y)
add:
push ebp;
mov ebp, esp;
; local variables are [ebp+8] and [ebp+12]
perform calculation into eax
leave
ret
这样,add
就知道堆栈的布局是yx[ptr]
并且执行返回正确
在这个问题上失去call/ret似乎有点极端,而且函数add
的堆栈框架非常脆弱,因此我将把这个问题至少再保留24小时,以期找到更好的解决方案
编辑:进一步考虑,只需在绑定的蹦床中携带返回地址(只需敲击一个寄存器,或将其移到堆栈中再移回来),就可以保留cdecl、调用方清理、调用/ret以及所有内容 伪代码:
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _create_trampoline
; make the call
push [return address]
push 0x03 ;arg1
jmp add2
; the resulting stub, held on the heap somewhere
add2:
push 0x02 ;bound argument
jmp add
; var add(x,y)
add:
local x = pop
local y = pop
eax = x + y;
jmp pop
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _magic(0x02, &add);
; make the call
push 0x03;
call add2;
add2:
ebx = pop; ;the return address goes in a temporary
push 0x02;
push ebx;
jmp add
; var add(x,y)
add:
push ebp;
mov ebp, esp;
; local variables are [ebp+8] and [ebp+12]
perform calculation into eax
leave
ret
在这里,结果是一种相当简洁的技术,可以将绑定函数参数作为堆上的可执行对象来实现,从而维护cdecl调用约定。毫无疑问,这种方法在实施时会出现问题,但我认为它是可行的,效率也不会太低。进一步考虑,我认为这可以通过使用被叫方清理约定并手动管理我的所有返回地址来实现。它与stdcall类似,但不完全相同,因为不能使用call/ret(?) 伪代码:
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _create_trampoline
; make the call
push [return address]
push 0x03 ;arg1
jmp add2
; the resulting stub, held on the heap somewhere
add2:
push 0x02 ;bound argument
jmp add
; var add(x,y)
add:
local x = pop
local y = pop
eax = x + y;
jmp pop
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _magic(0x02, &add);
; make the call
push 0x03;
call add2;
add2:
ebx = pop; ;the return address goes in a temporary
push 0x02;
push ebx;
jmp add
; var add(x,y)
add:
push ebp;
mov ebp, esp;
; local variables are [ebp+8] and [ebp+12]
perform calculation into eax
leave
ret
这样,add
就知道堆栈的布局是yx[ptr]
并且执行返回正确
在这个问题上失去call/ret似乎有点极端,而且函数add
的堆栈框架非常脆弱,因此我将把这个问题至少再保留24小时,以期找到更好的解决方案
编辑:进一步考虑,只需在绑定的蹦床中携带返回地址(只需敲击一个寄存器,或将其移到堆栈中再移回来),就可以保留cdecl、调用方清理、调用/ret以及所有内容 伪代码:
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _create_trampoline
; make the call
push [return address]
push 0x03 ;arg1
jmp add2
; the resulting stub, held on the heap somewhere
add2:
push 0x02 ;bound argument
jmp add
; var add(x,y)
add:
local x = pop
local y = pop
eax = x + y;
jmp pop
main:
; create stub, copy in 0x02 and &add, the resulting function pointer goes in add2
local add2 = _magic(0x02, &add);
; make the call
push 0x03;
call add2;
add2:
ebx = pop; ;the return address goes in a temporary
push 0x02;
push ebx;
jmp add
; var add(x,y)
add:
push ebp;
mov ebp, esp;
; local variables are [ebp+8] and [ebp+12]
perform calculation into eax
leave
ret
在这里,结果是一种相当简洁的技术,可以将绑定函数参数作为堆上的可执行对象来实现,从而维护cdecl调用约定。毫无疑问,这种方法在实现时会有问题,但我认为它是可行的,效率也不会太低。我看不出序列
推送参数
+调用函数有任何问题。这是使用参数调用函数的一种相当典型的方法。我不明白回信地址的问题。函数忽略它,它们只使用它返回调用者。在这种情况下,使用push
和call
,我认为堆栈包含2、[ptr],3。原始函数add()
希望堆栈顶部有两个int作为参数,但结果是2和[ptr]。。。当您多次调用bind()
时,返回地址都与您想要的实际参数交织在一起。简单地按正确的顺序推送参数如何?顺便说一句,在call
之后,您不能在调用者中推送任何额外的参数,因为call
传输控制权。因此,只需按正确的顺序推送所有参数(包括绑定的参数),然后调用即可。因为这些参数不一定同时都已知(假设add2作为原始函数指针传递给外部库)。此外,调用
的目标函数可能是以这种方式生成的另一个绑定函数。我认为序列推送参数
+调用函数
没有任何问题。这是使用参数调用函数的一种相当典型的方法。我不明白回信地址的问题。函数忽略它,它们只使用它返回调用者。在这种情况下,使用push
和call
,我认为堆栈包含2、[ptr],3。原始函数add()
希望堆栈顶部有两个int作为参数,但h