Вызов c -функции из jit-кода по адресу - PullRequest
1 голос
/ 17 июня 2020

В настоящее время я пытаюсь выполнить JIT через python. Я нашел персиковый через другой вопрос SO. По большей части это просто, но я не могу использовать внешние 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, но ни один действительно не помог решить эту проблему , и код на самом деле является результатом этого. Самым полезным было следующее: Обработка вызовов (потенциально) далеких от времени скомпилированных функций из JITed-кода

Изменить: еще несколько вещей, которые я пробовал.

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)

Это изменяет ошибку на cra sh с кодом выхода 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 

С 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)

1 Ответ

1 голос
/ 18 июня 2020

С помощью @PeterCordes я выяснил важные проблемы.

  • Я неправильно понял соглашение о вызовах windows. Вам нужно зарезервировать теневое пространство и выровнять стек, поэтому требуется '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 

И работает точно так, как ожидалось.

(Просто запомните, какие регистры сохраняются / нужно сохранить.)

...