Avatar ·

Как получить ассемблерный листинг из C/C++ кода в GCC?

Всем привет. Я изучаю C++ и пытаюсь понять, как компилятор транслирует мой код в машинные инструкции. Хочется посмотреть, во что превращается, например, простой цикл или вызов функции. Я использую GCC (g++) под Linux. Пробовал флаг `-S`, он вроде создаёт файл `.s`, но там куча мусора — метки, директивы `.cfi_*`, непонятные имена вроде `.LC0`. Даже простой `hello.cpp`: ```cpp #include int main() { std::cout << "Hello\n"; return 0; } ``` После `g++ -S hello.cpp` в `hello.s` я вижу не только код, но и кучу лишнего. Как получить чистый ассемблерный листинг, где видно только код моей функции, без всей этой служебной информации? Ещё интересует: можно ли посмотреть ассемблер прямо в терминале, не создавая файл? И как сделать листинг с перемешанным исходным кодом, чтобы было наглядно видно, какая строка C++ во что превращается? В общем, подскажите, пожалуйста, лучшие практики для получения ассемблерного вывода из C/C++ в GCC. Спасибо! <<>>

Основной способ: флаг -S

Самый простой способ — использовать флаг -S. Он заставляет компилятор остановиться после стадии генерации ассемблера и выдать файл с расширением .s. Однако, как вы заметили, по умолчанию вывод содержит много служебной информации (директивы отладки, CFI, секции и т.д.). Чтобы сделать его чище, добавьте флаг -fno-asynchronous-unwind-tables (убирает таблицы раскрутки стека для исключений) и -fno-dwarf2-cfi-asm (отключает часть CFI-директив). Для полной "чистоты" можно использовать -O0 (без оптимизаций) или -O1/-O2 для оптимизированного кода.

Пример команды:

g++ -S -O2 -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm hello.cpp -o hello.s

В файле hello.s останется в основном только код функций и минимальные директивы.

Просмотр в терминале без создания файла

Используйте флаг -c (только компиляция) и передайте вывод в утилиту objdump с флагом -d (дизассемблирование). Это не даст вам "чистый" ассемблер, но покажет машинные инструкции. Для перемешивания с исходным кодом добавьте -S (заглавная S).

g++ -c -O2 hello.cpp -o /dev/stdout | objdump -d -S -

Флаг -S в objdump (заглавная S) попытается вставить строки исходного кода, если они есть в отладочной информации. Для этого нужно компилировать с -g:

g++ -c -g -O2 hello.cpp -o /dev/stdout | objdump -d -S -

Лучший способ: использовать GCC с флагом -masm=intel и -fverbose-asm

Для максимально читаемого вывода рекомендую:

  • -masm=intel — выводить ассемблер в синтаксисе Intel (более привычный для многих, чем AT&T).
  • -fverbose-asm — добавлять комментарии с именами переменных и выражений из исходного кода.
  • -O0 или -O1 — для простоты чтения (без сильных оптимизаций).
  • -fno-asynchronous-unwind-tables — убрать лишние директивы.

Пример для вашего hello.cpp:

g++ -S -O0 -masm=intel -fverbose-asm -fno-asynchronous-unwind-tables hello.cpp

В файле hello.s вы увидите примерно такой код (упрощённо):

main:
    push    rbp
    mov     rbp, rsp
    ; std::cout << "Hello\n"
    mov     esi, OFFSET FLAT:.LC0   ; "Hello"
    mov     edi, OFFSET FLAT:std::cout
    call    std::basic_ostream<char, std::char_traits<char>>::operator<<(char const*)
    mov     eax, 0
    pop     rbp
    ret
.LC0:
    .string "Hello"

Онлайн-инструменты

Если не хотите возиться с командной строкой, используйте Compiler Explorer (godbolt.org). Там можно выбрать компилятор, флаги, и сразу видеть ассемблерный код с подсветкой соответствия строк C++.

Резюме

  • Для файла: g++ -S -O2 -masm=intel -fverbose-asm -fno-asynchronous-unwind-tables source.cpp
  • Для терминала с исходным кодом: g++ -c -g -O2 source.cpp -o /dev/stdout | objdump -d -S -
  • Для экспериментов: Compiler Explorer

Важно: при высоких уровнях оптимизации (-O2, -O3) код может быть сильно переписан (циклы развёрнуты, функции встроены), и соответствие исходному коду будет менее очевидным. Для обучения лучше начинать с -O0.

<<>>

Альтернативный подход: использование GDB

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

Скомпилируйте программу с отладочной информацией (-g) и без оптимизаций (-O0), затем запустите GDB:

g++ -g -O0 hello.cpp -o hello
gdb ./hello
(gdb) break main
(gdb) run
(gdb) display /i $pc   # показывать текущую инструкцию при каждом шаге
(gdb) stepi            # шаг на одну машинную инструкцию
(gdb) layout asm       # переключиться в режим просмотра ассемблера
(gdb) layout split     # разделить экран: исходный код + ассемблер

Команда layout split особенно полезна — вы видите одновременно исходный код на C++ и соответствующий ему ассемблер. Перемещаться между инструкциями можно клавишами Up/Down, а выполнять шаги — командой stepi.

Этот метод удобен для глубокого понимания, как работают вызовы функций, передача аргументов, работа со стеком и регистрами. Он также позволяет увидеть влияние оптимизаций в реальном времени, если перекомпилировать с -O2.