#assembly #x86 #inline-assembly
#сборка #x86 #встроенная сборка
Вопрос:
Я пишу крошечный компилятор на платформе x86. Когда я тестировал встроенный ассемблерный код в Linux и mac OS, я обнаружил, что этот фрагмент кода имеет разное поведение на этих двух платформах.
#include <iostream>
#include <vector>
#include <cassert>
using namespace std;
void ret() {
std::cout << "haha" << std::endl;
}
int main(int argc, char *argv[])
{
asm volatile ("sub $0x08, %rsp"); // on linux okay, but on macOS will SIGSEGV. After changing it to `asm volatile ("sub $0x10, %rsp");`, macOS platform behaves well.
asm volatile ("mov %r15, 0x0(%rsp)");
asm volatile ("mov %0, %%r10"::"r"((void*)ret));
asm volatile ("call *%r10");
asm volatile ("int $0x03");
}
Сбой до достижения int3
точки останова программного обеспечения.
Я хочу сохранить (нажать) регистр r15, чтобы сохранить его (хотя я знаю, что это сохраненный регистр вызываемого пользователя), и я не думал, что sub $0x08, %rsp
что-то не так. Спасибо.
Комментарии:
1. Все это абсолютно небезопасно; вы изменяете регистры, не сообщая об этом компилятору!!! Включая даже указатель стека! Удивительно, что это работает где угодно без сбоев в сегментации при
main
возврате. (Я предполагаю, что вы, должно быть, скомпилировали с отключенной оптимизацией, поэтому, возможно,main
восстановили RSP из RBP перед возвратом.) Вы используете GNU C Basic Asm, а не расширенный. Кроме того, это приведет к блокировке красной зоны под RSP, где компилятор мог бы сохранять локальные файлы. Кроме того, вы помещаете каждую инструкцию в отдельный оператор, поэтому нет гарантии, что компилятор не поместил код между ними.2. Это недопустимое использование встроенной сборки. Поведение не определено.
3.Возможно, компилятор, который вы использовали в OS X, использовал
leave
вместо этого толькоpop %rbp
в нижней частиmain
. Или, возможно, вы допустили ошибку из-за неправильного выравнивания стека при выполнении вызова функции. Вы не можете знать, будет ли сгенерированный компилятором код выполнять четное или нечетное количествоpush
инструкций перед вашим кодом, поэтому вы не знаете,sub
16*n 0
или16*n 8
необходимо восстановить выравнивание в 16 байт. Еслиostream::operator<<
случится так, что реализацияmovaps
предполагает, что ее стек выровнен по 16 байтам и использует для копирования 16 байт, это может привести к сбою. (например, современный Linux glibc scanf завершит работу подобным образом)4. О, так вы говорите, что
main
segfaults перед достижением точки останова вашего программного обеспечения. Это не было в вопросе. Да, выравнивание является наиболее вероятной причиной. Всем функциям разрешается выполнять segfault, если вы вызываете их со смещенным стеком, и некоторые из них действительно выполняются на практике, в зависимости от системы и способа компиляции libc.5. @wind2412 Я понимаю, почему вы пишете ASM-код. Но вы могли бы написать тот же код ASM в исходном файле сборки, и это было бы намного проще для отладки, потому что вы можете избежать всей неопределенности в отношении макетов стека и т.д. это происходит при использовании встроенной сборки.