В этом коде, который определяет функцию в глобальной области видимости (с базовой сборкой):
void my_asm_func(void);
__asm__(
".global my_asm_func;"
"my_asm_func:;"
"call abort;"
"ret;"
);
Вы нарушаете одно из правил ABI System V x86-64 (AMD64), которое требует выравнивания стека 16 байт (может быть больше в зависимости от параметров) в точке непосредственно перед тем, как будет CALL
.
3.2.2 Рамка стека
В дополнение к регистрам у каждой функции есть кадр в стеке времени выполнения. Этот стек растет сверху вниз
адреса. На рисунке 3.3 показана организация стека.
T конец области входного аргумента должен быть выровнен по 16 (32, если передано __m256
в стеке) граница байта . Другими словами, значение (% rsp + 8)
всегда кратно 16 (32), когда управление передается
точка входа в функцию. Указатель стека,% rsp, всегда указывает на
конец последнего выделенного стекового кадра.
При входе в функцию стек будет смещен на 8, поскольку 8-байтовый адрес возврата теперь находится в стеке. Чтобы выровнять стек по 16-байтовой границе, вычтите 8 из RSP в начале функции и добавьте 8 обратно к RSP, когда закончите. Вы также можете просто нажать любой регистр, например RBP в начале, и нажать его после, чтобы получить тот же эффект.
Эта версия кода должна работать:
void my_asm_func(void);
__asm__(
".global my_asm_func;"
"my_asm_func:;"
"push %rbp;"
"call abort;"
"pop %rbp;"
"ret;"
);
Относительно этого кода, который работал:
__asm__("call abort");
Компилятор, вероятно, сгенерировал функцию main
так, чтобы стек был выровнен по 16-байтовой границе до этого вызова, так что это сработало. Вы не должны полагаться на это поведение. Существуют и другие потенциальные проблемы с этим кодом, но в этом случае их нельзя представить как сбой. Стек должен быть правильно выровнен перед вызовом; вы должны быть обеспокоены в целом красной зоной; и вы должны указать все энергозависимые регистры в соглашениях о вызовах как клобберы, включая RAX / RCX / RDX / R8 / R9 / R10 / R11 , регистры FPU и регистры SIMD. В этом случае abort
никогда не возвращается, так что это не проблема, связанная с вашим кодом.
Красная зона определяется в ABI следующим образом:
128-байтовая область за пределами места, на которое указывает% rsp, считается
зарезервировано и не должно изменяться обработчиками сигналов или прерываний.8 Следовательно,
функции могут использовать эту область для временных данных, которые не нужны для всей функции
звонки. В частности, листовые функции могут использовать эту область для всего кадра стека,
вместо настройки указателя стека в прологе и эпилоге. Эта область
известный как красная зона .
Обычно плохая идея вызывать функцию во встроенной сборке. Пример вызова printf
можно найти в этом другом ответе Stackoverflow , который показывает сложности выполнения CALL
, особенно в 64-битном коде с красной зоной. Дэвида Уолферда: «Не используйте встроенный ассм» - всегда хорошее чтение.
Этот код работал:
void my_asm_func(void);
__asm__(
".global my_asm_func;"
"my_asm_func:;"
"lea puts_message(%rip), %rdi;"
"call puts;"
"ret;"
"puts_message: .asciz \"hello puts\""
);
но вам, вероятно, повезло, что puts
не нуждался в правильном выравнивании, и вы случайно не потерпели неудачу. Вы должны выровнять стек перед вызовом puts
, как описано ранее, с my_asm_func
, который вызвал abort
. Обеспечение соответствия ABI является ключом к тому, чтобы код работал должным образом.
Что касается ошибок перемещения, возможно, это связано с тем, что используемая версия Ubuntu по умолчанию использует независимый от позиции код (PIC) для генерации кода GCC. Вы можете решить эту проблему, вызвав библиотечные вызовы C через таблицу связей процедур 1070 *, добавив @plt
к именам функций, которые вы CALL
. Peter Cordes написал соответствующий Stackoverflow ответ на эту тему.