ПрограммированиеСтатьиОбщее

Ассемблерные вставки в GCC

Автор:

Статья является попыткой систематизировать brain-dump известной автору информации по ассемблерным вставкам в GCC.

Автор не считает себя авторитетом в данном вопросе, и статья может содержать неточности. Замечания, дополнения и исправления приветствуются.
Вопрос, зачем вообще использовать встроенный ассемблер, выходит за рамки данной статьи.
Предполагается, что читатель знаком с каким-либо ассемблером: учебником по языку per se статья не является.
За исключением случаев, когда явно оговорено обратное, информация в равном мере применима к C и к C++.
Все примеры в статье - для архитектуры x86 (и x86_64), но сама поддержка ассемблерных вставок в GCC не ограничивается этой архитектурой, и большинство информации применимо и для других.
Все отрывки кода, если явно не оговорено обратное и не указано авторство, находятся в общем доступе (public domain).

Общие сведения
Концептуальная модель
GNU Assembler и синтаксис AT&T
Написание многострочных команд
Синтаксис и семантика
  Базовая форма
  Расширенная форма
  Операнды
    OutputOperands
    InputOperands
    Clobbers
    GotoLabels
  Constraints
Примеры
Переходы и метки
Диспетчеризация CPU
Дополнительно
  Модификаторы операндов
  Явное указание регистров для переменных
  Явное указание имён
  Вычисление размера кода
  Недокументированные возможности
Литература

Общие сведения

Язык C++ поддерживает ассемблерные вставки, что отражено в Стандарте языка.
Однако данная возможность глубоко специфична индивидуальным компилятору/архитектуре, поэтому Стандарт немногословен и ограничивается следующим текстом:

7.4 The asm declaration [dcl.asm]
An asm declaration has the form
    asm-definition:
asm( string-literal ) ;
The asm declaration is conditionally-supported; its meaning is implementation-defined. [ Note: Typically it is used to pass information through the implementation to an assembler. —end note ]

Стандарт языка C ассемблерные вставки вообще не определяет, и упоминает их только в списке распространённых расширений:

J.5.10 The asm keyword
The asm keyword may be used to insert assembly language directly into the translator
output (6.8). The most common implementation is via a statement of the form:
asm ( character-string-literal );

Большинство популярных компиляторов поддерживают эту возможность в том или ином виде.
Компилятор MSVC поддерживает их для 32-битного, но не для 64-битного кода. Также, как можно заметить, синтаксис ассемблерных вставок MSVC отличается от указанного в Стандарте.

Под GCC здесь и далее (если не оговорено обратное) подразумевается компилятор, поддерживающий набор расширений GCC. Это, с некоторых пор, официальное определение макро __GNUC__ (см. https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html , https://sourceforge.net/p/predef/wiki/Compilers/ и далее по ссылкам). В частности компиляторы clang и icc определяют __GNUC__; оба этих компилятора (так же как и сам GCC) поддерживают ассемблерные вставки в синтаксисе GCC.

Концептуальная модель

Типично, компилятор превращает исходный код в объектный код, который потом сборщиком (линкером) собирается в исполняемый файл.
GCC использует несколько необычное решение: компиляторы C и C++ во многом близки к языковым трансляторам ( https://en.wikipedia.org/wiki/Source-to-source_compiler ), и результатом их работы является файл на языке ассемблера (именно на текстовом (мнемоническом) ассемблере, а не в машинном коде), который далее преобразуется в объектный код штатным ассемблером (утилита as; ассемблер, входящий в состав GNU, обычно в документации называют GNU Assembler, сокращённо gas, несмотря на то, что сама утилита называется именно as).
Обычно при вызове компилятор GCC сам производит соответствующие действия (вызов as и т. д.) "под капотом", и для создания объектного файла никаких дополнительных указаний не требуется.
Можно указать компилятору опцию -S (большая буква S; у малой s другое значение), для того чтобы произвести только перевод программы на ассемблер.
Рассмотрим пример. Создадим файл test_asm.cpp со следующим кодом:

#include <cstdio>

int main()
{
  printf("Hello world!\n");
  return 0;
}

Выполнив команду

gcc test_asm.cpp -S

получим файл test_asm.s (традиционным расширением файлов на языке ассемблера для GCC является .s):

  .file  "test_asm.cpp"
  .def  ___main;  .scl  2;  .type  32;  .endef
  .section .rdata,"dr"
LC0:
  .ascii "Hello world!\0"
  .text
  .globl  _main
  .def  _main;  .scl  2;  .type  32;  .endef
_main:
  pushl  %ebp
  movl  %esp, %ebp
  andl  $-16, %esp
  subl  $16, %esp
  call  ___main
  movl  $LC0, (%esp)
  call  _puts
  movl  $0, %eax
  leave
  ret
  .ident  "GCC: (tdm-1) 4.9.2"
  .def  _puts;  .scl  2;  .type  32;  .endef

Организация кода, который генерирует GCC из исходника на C++ выходит за рамки данной статьи. При беглом просмотре можно заметить:
1. Префикс _ (подчёркивание) для экспортированных функций C (и для main). Для функций на C++ используется несколько более сложный name mangling.
2. Функция __main (два подчёркивания), в которой GCC производит некоторую инициализацию (её реализация находится отдельно, в составе стандартных файлов GCC).
3. printf был соптимизирован до puts (причём никаких опций оптимизации указано не было).

При желании, можно получить несколько более подробный код (с дополнительными комментариями), воспользовавшись опцией -fverbose-asm.
Построчный листинг (в файле test_asm.lst) C++ и ассемблера можно получить, воспользовавшись командой (подробнее см. http://www.delorie.com/djgpp/v2faq/faq8_20.html ):

gcc -c -g -Wa,-a,-ad test_asm.cpp > test_asm.lst

Данная модель достаточно естественным образом приводит к логике ассемблерных вставок, которую и использует GCC.
GCC вставляет, после возможных макроподстановок, текст ассемблерной вставки непосредственно в выходной файл на языке ассемблера.
Пример (о конкретном синтаксисе ассемблерных вставок см. ниже):

#include <cstdio>

int main()
{
  printf("Before asm.\n");
  asm("nop");
  printf("After asm.\n");
  return 0;
}

преобразуется в

  .file  "test_asm.cpp"
  .def  ___main;  .scl  2;  .type  32;  .endef
  .section .rdata,"dr"
LC0:
  .ascii "Before asm.\0"
LC1:
  .ascii "After asm.\0"
  .text
  .globl  _main
  .def  _main;  .scl  2;  .type  32;  .endef
_main:
  pushl  %ebp
  movl  %esp, %ebp
  andl  $-16, %esp
  subl  $16, %esp
  call  ___main
  movl  $LC0, (%esp)
  call  _puts
/APP
 # 6 "test_asm.cpp" 1
  nop
 # 0 "" 2
/NO_APP
  movl  $LC1, (%esp)
  call  _puts
  movl  $0, %eax
  leave
  ret
  .ident  "GCC: (tdm-1) 4.9.2"
  .def  _puts;  .scl  2;  .type  32;  .endef

Как видим, GCC отметил вставку в коде явным образом.

Модель, используемая GCC имеет несколько важных свойств.

Во-первых, компилятор почти не "подглядывает" в указанный программистом код ассемблерной вставки. Он при необходимости проводит макроподстановки (см. ниже), но в общем передаёт код ассемблеру почти "как есть", и почти не имеет представления о происходящем внутри. В частности, обнаружением ошибок занимается именно ассемблер (GCC передаёт программисту сообщения об ошибках от ассемблера).

Во-вторых, как следствие, ассемблерная вставка является для компилятора (и в частности оптимизатора) единой непрозрачной командой (чёрным ящиком). Всё нужную ему информацию о том, как этот блок взаимодействует с окружающим миром, компилятор получает напрямую от программиста, из явно указанных операндов ассемблерной вставки (задающих связи ассемблерного кода с переменными C++ и список задействованных ресурсов (регистров и т. д.) и изменений (состояния флагов, памяти и т. д.) в ассемблерной вставке), а не из детального рассмотрения текста ассемблерной вставки (которого он не производит). Технически говоря, GCC предоставляет программисту интерфейс к Register Transfer Language. Ответственность о соответствии действительности информации, указанной в операндах, целиком лежит на программисте.
В рамках этих ограничений, компилятор волен обращаться с ассемблерной вставкой (как и с другими командами) так, как ему вздумается: перемещать, дублировать (напр. при подстановке inline-функций), или вообще выбросить, если оптимизатор придёт к такому решению.

Страницы: 1 2 3 4 5 Следующая »

#GCC, #inline, #ассемблер

30 апреля 2016 (Обновление: 4 мая 2016)

Комментарии [32]