x64 asm как установить указатель на функцию _cdecl C и вызвать ее?

Я пытаюсь сделать что-то довольно простое в x64 asm:

  1. Иметь функцию asm, которая берет указатель на функцию и устанавливает его в переменной. Эта функция вызывается из кода Си.

  2. Есть другая функция asm, которая вызывает указатель на функцию, если не ноль, этот указатель функции также является функцией C (как установлено функцией в 1).

Вот то, что у меня есть для C-стороны:

extern "C" void _asm_set_func_ptr(void* ptr);

void _cdecl c_call_back()
{

}

void init()
{
_asm_set_func_ptr(c_call_back);
}

И асм сторона:

.DATA

g_pFuncPtr QWORD 0

.CODE             ;Indicates the start of a code segment.

_asm_set_func_ptr PROC fPtr:QWORD
mov     [rsp+qword ptr 8], rcx
mov     rax, [rsp+qword ptr 8]
mov     g_pFuncPtr, rax
ret
_asm_set_func_ptr ENDP

_asm_func PROC

push RBX
push RBP
push RDI
push RSI
push RSP
push R12
push R13
push R14
push R15

CMP g_pFuncPtr, 0
JE SkipCall
MOV RAX, [ g_pFuncPtr ];
CALL RAX;
SkipCall:

pop RBX
pop RBP
pop RDI
pop RSI
pop RSP
pop R12
pop R13
pop R14
pop R15
ret

_asm_func ENDP

Но кажется, что я повредил стек после вызова _asm_set_func_ptr (), также я не уверен, правильно ли, как я вызываю g_pFuncPtr в _asm_func? Что не так с моим кодом? Я строю это с VS2013 MASM64.

2

Решение

Во-первых, вам, как правило, нужно извлекать регистры в обратном порядке, в котором вы их нажимаете, т.е.
push RBX, push RBPpush R15 -> pop R15pop RSI, pop RBX, ret, Это определенно сломает абонента _asm_func,


Далее вы должны посмотреть на Соглашение о вызовах Windows x64 что все необходимо для правильного вызова функций. Очень важно правильно выполнить все требования, иначе все может сломаться и даже очень поздно в чужом коде, что не самая лучшая вещь для отладки.

Например, вам не нужно сохранять все регистры. Если функция обратного вызова уничтожит их, она сохранит и восстановит их сама. Таким образом, нет необходимости толкать и хлопать там, RAX в любом случае может быть признан недействительным, в нем не передается аргумент.

Но затем обратите внимание на эту часть:

В соответствии с соглашением о вызовах Microsoft x64, вызывающий объект должен выделить 32 байта «теневого пространства» в стеке непосредственно перед вызовом функции (независимо от фактического количества используемых параметров) и вытолкнуть стек после вызова.

Так что вы должны сделать SUB ESP, 32 перед вашим кодом, тогда ADD ESP, 32 перед RET,

Существует также требование длястек выровнен по 16 байтам«, но в настоящее время вам не нужно обращаться к этому, потому что» 8 байтов адреса возврата + 32 байта теневого пространства + 8 байтов следующего адреса возврата «выровнены на 16 байтов.

Кроме того, ABI для Windows x64 предъявляет также строгие требования к обработке исключений и правильному разматыванию. Как отметил Рэймонд в комментарии, поскольку ваша функция не является конечной (вызывает другие функции), вместо этого вам нужно предоставить правильный пролог и эпилог — см. Вот.


Временное сохранение RCX в начале _asm_set_func_ptr не нужно

Иначе я не вижу там никаких проблем.


Наконец, точка с запятой ; не нужны в конце строк в файлах ассемблера.

3

Другие решения

Вы проверяете много регистров перед проверкой g_pFuncPtr, но не выталкиваете их обратно из стека, если он не был установлен. Если вы положите что-то в стек & тогда не звоните и не возвращайте их обратно, ваш стек быстро заполнится.

Вы ДОЛЖНЫ выдвигать регистры в обратном порядке нажатия на них, иначе вы получите неправильные регистры.

Наконец, не тратьте время & Циклы процессора вообще их толкают, если только вы не имеете с ними ничего общего:

    CMP g_pFuncPtr, 0
JE SkipCall

PUSH RBX
PUSH RBP
PUSH RDI
PUSH RSI
PUSH RSP
PUSH R12
PUSH R13
PUSH R14
PUSH R15
MOV RAX, [ g_pFuncPtr ];
CALL RAX;
POP R15
POP R14
POP R13
POP R12
POP RSP
POP RSI
POP RDI
POP RBP
POP RBX
SkipCall:
ret

… и, пожалуйста … пожалуйста, ознакомьтесь с настройкой стековых фреймов и управлением стековыми фреймами внутри вызовов. Вызовы C и ASM обрабатывают кадры стека очень по-разному.

1

По вопросам рекламы [email protected]