Запуск стека GNU C basic asm перед вызовом функции вызывал разное поведение на Mac и Linux

#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 в исходном файле сборки, и это было бы намного проще для отладки, потому что вы можете избежать всей неопределенности в отношении макетов стека и т.д. это происходит при использовании встроенной сборки.