Python 按地址从jitted代码调用c函数

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

我目前正试图通过python实现JIT。我通过另一个问题找到了答案。在大多数情况下,这很容易,但我没有使用外部c函数。我想调用putchar,所以是一个只有一个参数的函数。由于我在windows上,使用x86-64,我希望将单个参数放入
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)
    给出的不是代码的开头,而是指向代码开头的指针的地址
问题1很容易解决,问题2需要稍加修改。最后,该代码起作用:

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前缀)。但是,如果你想重复调用同一个函数,你可以把它的地址保存在寄存器中。