通过地址从 jitted 代码调用 c 函数
Calling a c-function from jitted code by address
我目前正在尝试通过 python 进行 JIT。我通过另一个 SO 问题找到了 peachpy。在大多数情况下,这很容易,但我没有使用外部 c 函数。我想调用 putchar,这是一个只有一个参数的函数。由于我在 windows 上,使用 x86-64,我希望将单个参数放入 rcx
,然后 运行 call
和函数指针地址。为此,我写了这段代码:
from peachpy import *
from peachpy.x86_64 import *
import ctypes
putchar_address = ctypes.addressof(ctypes.cdll.msvcrt.putchar)
c = Argument(uint64_t)
with Function("p", (c,), int64_t) as asm_function:
LOAD.ARGUMENT(rcx, c)
MOV(r8, putchar_address)
CALL(r8)
RETURN(rax)
raw = asm_function.finalize(abi.detect()).encode()
python_function = raw.load()
print(python_function(48))
最终代码 OSError: exception: access violation writing 0x0000029E58C1A978
崩溃。
我看了很多其他的 SO 答案,但是 none 确实有助于解决这个问题,代码实际上是这些的结果。最有用的是这个:
编辑:我尝试了一些其他的东西。
PeachPy 并没有直接公开 rsp
,声称它已经正确处理了它。但我仍然可以直接影响它,导致这段代码:
from peachpy.x86_64.registers import rsp
#...
LOAD.ARGUMENT(rcx, c)
SUB(rsp, 40)
MOV(r8, putchar_address)
CALL(r8)
ADD(rsp, 40)
RETURN(rax)
这会将错误更改为带有退出代码 0xC0000409
的崩溃,这意味着超出堆栈顶部的堆栈访问。
PeachPy 生成的反汇编结果如下:
没有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
和rsp
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
(来自 https://defuse.ca/online-x86-assembler.htm)
根据 c 编译器的输出(此处:https://godbolt.org/z/BKgk7Y),我创建了以下代码
MOV([rsp + 16], rdx)
MOV([rsp + 8], rcx)
SUB(rsp, 40)
MOV(rcx, [rsp + 56])
CALL([rsp + 48])
ADD(rsp, 40)
RETURN(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 调用约定。需要预留影子space和对齐堆栈,所以需要'sub rsp, 40'。
ctypes.addressof(ctypes.cdll.msvcrt.putchar)
给出的不是代码的开始,而是指向代码开始的指针的地址。
问题 1 很容易解决,问题 2 需要一些修修补补。最后,这段代码有效:
c_void_p_p = ctypes.POINTER(ctypes.c_void_p)
putchar_address = ctypes.addressof(ctypes.cast(ctypes.cdll.msvcrt.putchar, c_void_p_p).contents)
func_ptr = Argument(ptr())
c = Argument(uint64_t)
with Function("p", (c,), int64_t) as asm_function:
MOV(r12, putchar_address)
SUB(rsp, 40)
CALL(r12)
ADD(rsp, 40)
RETURN()
raw = asm_function.finalize(abi.detect()).encode()
print(raw.code_section.content.hex())
python_function = raw.load()
print(python_function(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
并且完全按照预期工作。
(只要记住saved/need要保存哪些寄存器即可。)
我目前正在尝试通过 python 进行 JIT。我通过另一个 SO 问题找到了 peachpy。在大多数情况下,这很容易,但我没有使用外部 c 函数。我想调用 putchar,这是一个只有一个参数的函数。由于我在 windows 上,使用 x86-64,我希望将单个参数放入 rcx
,然后 运行 call
和函数指针地址。为此,我写了这段代码:
from peachpy import *
from peachpy.x86_64 import *
import ctypes
putchar_address = ctypes.addressof(ctypes.cdll.msvcrt.putchar)
c = Argument(uint64_t)
with Function("p", (c,), int64_t) as asm_function:
LOAD.ARGUMENT(rcx, c)
MOV(r8, putchar_address)
CALL(r8)
RETURN(rax)
raw = asm_function.finalize(abi.detect()).encode()
python_function = raw.load()
print(python_function(48))
最终代码 OSError: exception: access violation writing 0x0000029E58C1A978
崩溃。
我看了很多其他的 SO 答案,但是 none 确实有助于解决这个问题,代码实际上是这些的结果。最有用的是这个:
编辑:我尝试了一些其他的东西。
PeachPy 并没有直接公开 rsp
,声称它已经正确处理了它。但我仍然可以直接影响它,导致这段代码:
from peachpy.x86_64.registers import rsp
#...
LOAD.ARGUMENT(rcx, c)
SUB(rsp, 40)
MOV(r8, putchar_address)
CALL(r8)
ADD(rsp, 40)
RETURN(rax)
这会将错误更改为带有退出代码 0xC0000409
的崩溃,这意味着超出堆栈顶部的堆栈访问。
PeachPy 生成的反汇编结果如下:
没有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
和rsp
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
(来自 https://defuse.ca/online-x86-assembler.htm)
根据 c 编译器的输出(此处:https://godbolt.org/z/BKgk7Y),我创建了以下代码
MOV([rsp + 16], rdx)
MOV([rsp + 8], rcx)
SUB(rsp, 40)
MOV(rcx, [rsp + 56])
CALL([rsp + 48])
ADD(rsp, 40)
RETURN(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 调用约定。需要预留影子space和对齐堆栈,所以需要'sub rsp, 40'。
ctypes.addressof(ctypes.cdll.msvcrt.putchar)
给出的不是代码的开始,而是指向代码开始的指针的地址。
问题 1 很容易解决,问题 2 需要一些修修补补。最后,这段代码有效:
c_void_p_p = ctypes.POINTER(ctypes.c_void_p)
putchar_address = ctypes.addressof(ctypes.cast(ctypes.cdll.msvcrt.putchar, c_void_p_p).contents)
func_ptr = Argument(ptr())
c = Argument(uint64_t)
with Function("p", (c,), int64_t) as asm_function:
MOV(r12, putchar_address)
SUB(rsp, 40)
CALL(r12)
ADD(rsp, 40)
RETURN()
raw = asm_function.finalize(abi.detect()).encode()
print(raw.code_section.content.hex())
python_function = raw.load()
print(python_function(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
并且完全按照预期工作。
(只要记住saved/need要保存哪些寄存器即可。)