Python 按地址从jitted代码调用c函数
我目前正试图通过python实现JIT。我通过另一个问题找到了答案。在大多数情况下,这很容易,但我没有使用外部c函数。我想调用putchar,所以是一个只有一个参数的函数。由于我在windows上,使用x86-64,我希望将单个参数放入Python 按地址从jitted代码调用c函数,python,x86-64,jit,calling-convention,Python,X86 64,Jit,Calling Convention,我目前正试图通过python实现JIT。我通过另一个问题找到了答案。在大多数情况下,这很容易,但我没有使用外部c函数。我想调用putchar,所以是一个只有一个参数的函数。由于我在windows上,使用x86-64,我希望将单个参数放入rcx,然后使用函数指针地址运行call。为此,我编写了以下代码: 来自peachpy导入的* 从peachpy.x86_64导入* 导入ctypes putchar\u address=ctypes.addressof(ctypes.cdll.msvcrt.pu
rcx
,然后使用函数指针地址运行call
。为此,我编写了以下代码:
来自peachpy导入的*
从peachpy.x86_64导入*
导入ctypes
putchar\u address=ctypes.addressof(ctypes.cdll.msvcrt.putchar)
c=参数(uint64\u t)
将函数(“p”、(c)、int64_t)作为asm_函数:
LOAD.ARGUMENT(rcx,c)
MOV(r8,putchar_地址)
呼叫(r8)
返回(rax)
raw=asm_function.finalize(abi.detect()).encode()
python_function=raw.load()
打印(python_函数(48))
这会在最后一个代码上写入0x0000029E58C1A978时崩溃,错误:异常:访问冲突
我看了很多其他的SO答案,但是没有一个能真正帮助解决这个问题,代码实际上就是这些答案的结果。最有用的是这个:
编辑:我还尝试了一些事情
PeachPy没有明确地直接公开rsp,声称它已经正确地处理了它。但我仍然可以直接影响它,导致以下代码:
从peachpy.x86_64.0导入rsp
#...
LOAD.ARGUMENT(rcx,c)
秘书长(副秘书长,40)
MOV(r8,putchar_地址)
呼叫(r8)
加(rsp,40)
返回(rax)
这会将错误更改为崩溃,退出代码为0xC0000409,这意味着堆栈访问超出堆栈顶部
以下是PeaachPy生成的结果:
无rsp
0: 49 b8 a8 a8 1a 84 1f movabs r8,0x21f841aa8a8
7: 02 00 00
a: 41 ff d0 call r8
d: c3 ret
0: 48 83 ec 28 sub rsp,0x28
4: 49 b8 a8 98 ad 9e ac movabs r8,0x1ac9ead98a8
b: 01 00 00
e: 41 ff d0 call r8
11: 48 83 c4 28 add rsp,0x28
15: c3 ret
带rsp
0: 49 b8 a8 a8 1a 84 1f movabs r8,0x21f841aa8a8
7: 02 00 00
a: 41 ff d0 call r8
d: c3 ret
0: 48 83 ec 28 sub rsp,0x28
4: 49 b8 a8 98 ad 9e ac movabs r8,0x1ac9ead98a8
b: 01 00 00
e: 41 ff d0 call r8
11: 48 83 c4 28 add rsp,0x28
15: c3 ret
(来自)
基于c编译器(此处:)的输出,我创建了以下代码
MOV([rsp+16],rdx)
MOV([rsp+8],rcx)
秘书长(副秘书长,40)
MOV(rcx,[rsp+56])
呼叫([rsp+48])
加(rsp,40)
返回(rax)
它创建与c编译器相同的汇编代码:
0: 48 89 54 24 10 mov QWORD PTR [rsp+0x10],rdx
5: 48 89 4c 24 08 mov QWORD PTR [rsp+0x8],rcx
a: 48 83 ec 28 sub rsp,0x28
e: 48 8b 4c 24 38 mov rcx,QWORD PTR [rsp+0x38]
13: ff 54 24 30 call QWORD PTR [rsp+0x30]
17: 48 83 c4 28 add rsp,0x28
1b: c3 ret
这失败了,意味着问题不在生成的代码中。(我没有使用putchar,我仍然得到相同的退出代码0xC0000409)在@PeterCordes的帮助下,我解决了重要的问题
- 我误解了windows呼叫约定。您需要保留阴影空间并对齐堆栈,因此需要“sub-rsp,40”
给出的不是代码的开头,而是指向代码开头的指针的地址ctypes.addressof(ctypes.cdll.msvcrt.putchar)
c\u void\u p\u p=ctypes.POINTER(ctypes.c\u void\u p)
putchar\u address=ctypes.addressof(ctypes.cast(ctypes.cdll.msvcrt.putchar,c\u void\u p\u p).contents)
func_ptr=参数(ptr())
c=参数(uint64\u t)
将函数(“p”、(c)、int64_t)作为asm_函数:
MOV(r12,putchar_地址)
秘书长(副秘书长,40)
呼叫(r12)
加(rsp,40)
返回()
raw=asm_function.finalize(abi.detect()).encode()
打印(原始.code\u节.content.hex())
python_function=raw.load()
打印(python_函数(54))
这将生成此程序集:
0: 41 54 push r12
2: 49 bc 90 77 75 4d fa movabs r12,0x7ffa4d757790
9: 7f 00 00
c: 48 83 ec 28 sub rsp,0x28
10: 41 ff d4 call r12
13: 48 83 c4 28 add rsp,0x28
17: 41 5c pop r12
19: c3 ret
并且完全按照预期工作
(请记住哪些寄存器已保存/需要保存。)Windows x64使用RCX中的第一个参数,而不是RDX。查看C编译器输出以获取示例。此外,它还需要在调用(推送返回地址)之前将堆栈对齐16,并在RSP上方留出32字节的阴影空间。因此,您可能需要在通话前使用
子rsp,40
,或者使用jmp
@PeterCordes来跟踪通话。我将添加一些信息。我忘了我已经试过了。我建议你做一个似乎可行的,避免那个明显的错误,然后!Windows x64肯定在RCX中使用第一个参数,并且还需要阴影空间@彼得·科尔德:这不会改变结果。Peachpy甚至对此进行了优化,因为该值已经存在于rcx
@PeterCordes中,我实际上已经计算出了上半部分。ctypes中的地址I git不是指向函数的指针,而是指向指针的指针。所以我现在可以调用一个函数,但是如果我尝试调用两个函数,它就会中断。稍后我会发布一个答案。你可以使用RAX而不是R12,避免了推/弹出指令的需要。(并在调用rax
上保存REX前缀)。但是,如果你想重复调用同一个函数,你可以把它的地址保存在寄存器中。