Как передать структуры в функцию C из сборки x86-64 на Mac (NASM) - PullRequest
4 голосов
/ 23 марта 2019

С здесь :

nanosleep((const struct timespec[]){{0, 500000000L}}, NULL);

Он передает структуру. Я не уверен, как передавать структуры в системные вызовы или библиотечные функции через регистры. Хотите знать, можете ли вы показать привет-пример мира сборки NASM, передающей структуру этому системному вызову.

Кроме того, если я оберну эту функцию в C, она больше не будет системным вызовом. Я хотел бы знать, как написать сборку, чтобы она могла работать в этом случае предпочтительно. Итак, в основном, как создать структуру в сборке и передать ее функции C в x86-64 на Mac. Есть много функций библиотеки C, которые принимают структуры, поэтому мне интересно посмотреть, как в общем случае передать им структуру.

Ответы [ 2 ]

5 голосов
/ 23 марта 2019

IIRC в x86_64 System V Небольшие структуры ABI, подобные этой, просто «взрываются» на регистры регулярных аргументов; но это не так - nanosleep принимает указатель на эту структуру (и вообще, я даже не думаю, что соглашение о вызовах системного вызова позволяет передавать структуры по значению).

IOW, этот код в значительной степени эквивалентен:

struct timespec ts{0, 500000000L};
nanosleep(&ts, NULL);

Итак, вам придется вырезать 16 байт стекового пространства для ts и заполнить его (вы можете даже получить два push), получить указатель на него (вам может понадобиться lea ) и передайте результат в качестве первого параметра в nanosleep (то есть в rdi, с 0 в rsi).

В Linux это будет что-то вроде:

push 500000000  ; push last 64 bit of ts
push 0          ; push first 64 bit of ts
mov rdi,rsp     ; the stack pointer now points to ts; use it as first arg
xor esi,esi     ; the second arg is NULL
mov eax,35      ; syscall 35 -> nanosleep
syscall
add rsp,16      ; restore the stack

в macOS AFAIK он должен быть таким же, единственное отличие - номер системного вызова.

4 голосов
/ 23 марта 2019

Если бы вы скомпилировали это с помощью компилятора C и посмотрели на вывод asm, вы бы увидели, что он просто передает указатель в структуру.

C создает анонимный массив из struct timespec[], который является lvalue, и, таким образом, он может "затухать" до указателя при передаче в
int nanosleep(const struct timespec *req, struct timespec *rem);

Если вы посмотрите справочную страницу системного вызова, вы увидите, что оба аргумента принимают за указатели.

На самом деле нет системных вызовов POSIX, которые принимают struct args по значению . Этот выбор дизайна имеет смысл, поскольку не все соглашения о вызовах во всех архитектурах обрабатывают структуры передачи одинаково. Соглашения о вызовах системных вызовов часто не совпадают точно с соглашениями о вызовах функций, и, как правило, не имеют правил ни для чего, кроме целочисленных типов / указателей.

Системные вызовы обычно ограничены максимум 6 аргументами, без возврата к памяти стека для больших аргументов. Ядру необходим общий механизм для сбора аргументов из пространства пользователя и отправки их в функцию ядра из таблицы указателей функций, поэтому все системные вызовы должны иметь сигнатуры, совместимые с syscall(uintptr_t a, uintptr_t b, ... uintptr_t f) на уровне asm.

Если операционная система вводит системный вызов, который принимает структуру по значению, она должна будет определить детали ABI для передачи его в каждой поддерживаемой архитектуре. Это может быть сложно, например 16-байтовая структура, такая как struct timespec в 32-битной архитектуре, заняла бы 4 слота передачи аргументов ширины регистра. (Предположим, что время еще 64-битное, в противном случае у вас проблема с опрокидыванием year-2038 .)


Как говорит Маттео, x86-64 System V упаковывает до 16 байтов в до 2 регистров для вызова функций . Правила хорошо документированы в ABI , но обычно проще всего написать простую тестовую функцию, которая сохраняет свои аргументы в volatile long x или возвращает одно из них, и скомпилировать ее с включенной оптимизацией.

например. на Годболте

#include <stdint.h>
struct padded {
    int16_t a;
    int64_t b;
};
int64_t ret_a(int dummy, padded s) { return s.a;  }
int64_t ret_b(int dummy, padded s) { return s.b; }

Компилирует для x86-64 System V в этот ассемблер, поэтому мы можем видеть, что структура передается в RDX: RSI с неиспользуемыми старшими 6 байтами RSI (потенциально удерживающими мусор), так же, как представление объекта в памяти с 6 байты заполнения, поэтому элемент int64_t имеет выравнивание alignof(int64_t) = 8.

ret_a(int, padded):
        movsx   rax, si
        ret
ret_b(int, padded):
        mov     rax, rdx
        ret

Запись вызывающего, который помещает аргументы в правильные регистры, должна быть очевидной.

...