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

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

Автор:

GNU Assembler и синтаксис AT&T

Прежде, чем перейти к рассмотрению собственно синтаксиса и семантики ассемблерных вставок, рассмотрим подробнее синтаксис AT&T, используемый (для x86) GNU Assembler.
Многим программистам более знаком синтаксис Intel, соответственно большая часть раздела посвящена их сравнению, и переводу из одного в другой.
Синтаксис AT&T задуман как более регулярный и более удобный для автоматической генерации и чтения.
Написание и чтение его людьми напрямую является более вторичной целью. Соответственно, многие программисты находят его менее комфортным для написания/чтения (лично у автора данной статьи нет особых предпочтений в ту или другую сторону).
Стоит отметить, что GNU Assembler использует свой диалект AT&T синтаксиса, имеющий лёгкие отличия от других альтернатив.

Документация по GNU Assembler доступна по адресу http://sourceware.org/binutils/docs-2.26/as/index.html .

Основные сведения (см. также http://sourceware.org/binutils/docs-2.26/as/i386_002dVariations.h… 02dVariations ):
1. Мнемоники инструкций в основном те же, что и в синтаксисе Intel. Примеры: nop, mov, ret, rdtsc, pshufb.

2. Имена регистров предваряются знаком %. Пример:

IntelAT&T
xor eax,eaxxor %eax,%eax

3. Непосредственные операнды предваряются знаком $. Значения по умолчанию в десятичной системе, для шестнадцатеричной используется префикс 0x, как в C/C++. Примеры:

IntelAT&T
ret 10ret $10
int 80hint $0x80

4. Размер операнда определятся суффиксом инструкции (в синтаксисе Intel используются спецификаторы вида DWORD PTR и подобные):

Размер (байт)СуффиксЭтимология
1bbyte
2wword
4llong
8qquad
10tten

Суффикс t в основном используется в командах FPU.
Если размер однозначно определяется из аргументов, то GNU Assembler принимает также инструкцию без суффикса, см. пример с xor %eax,%eax выше. Тот же пример может быть записан как

xorl %eax,%eax

Мнемоники инструкций SIMD (SSE, AVX и т. д.) записываются так же, как в синтаксисе Intel, без суффиксов.

5. Порядок аргументов в синтаксисе AT&T обратен по сравнению с синтаксисом Intel. Для безаргументных и одноаргументных инструкций разница не проявляется. Для двухаргументных инструкций:

IntelAT&T
инструкция приёмник,источникинструкция источник,приёмник

Исключения: bound, invlpga, инструкции с 2-мя непосредственными операндами (enter и т. д.).
Для трёхаргументных инструкций ситуация несколько сложнее.
Примеры:
IntelAT&T
mov eax,ebxmovl %ebx,%eax
xor eax,1xorl $1,%eax
pshufd xmm1,xmm0,0pshufd $0,%xmm0,%xmm1

6. Инструкции fsubp/fsubrp (но не fsub/fsubr!!!) имеют обратную семантику:

IntelAT&T
fsubp st(1)fsubrp %st(1)
fsubrp  st(1)fsubp %st(1)

См. также: http://sourceware.org/binutils/docs-2.26/as/i386_002dBugs.html#i386_002dBugs .

7. Косвенная адресация. Адрес, в синтаксисе Intel записываемый как

section:[base + index*scale + disp]

в синтаксисе AT&T будет записан:

section:disp(base, index, scale)

Примеры (взяты из http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html ):

IntelAT&T
mov eax,[ecx]movl (%ecx),%eax
mov eax,[ebx+3]movl 3(%ebx),%eax
mov eax,[ebx+20h]movl 0x20(%ebx),%eax
add eax,[ebx+ecx*2h]addl (%ebx,%ecx,0x2),%eax
lea eax,[ebx+ecx]leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h]subl -0x20(%ebx,%ecx,0x4),%eax

Подробное рассмотрение псевдокоманд (предваряемых точкой ".") не приводится, т. к. они выходят за рамки статьи (которая не является учебником по ассемблеру).

Можно отметить псевдокоманды .intel_syntax и .att_syntax (см. http://sourceware.org/binutils/docs-2.26/as/i386_002dVariations.html), позволяющие переключать ассемблер между синтаксисами Intel и AT&T. Также они принимают аргумент (prefix или noprefix), задающий, требуется ли префикс % для названий регистров.
Это псевдокоманды можно использовать для написания (с использованием дополнительных макросов) ассемблерных вставок, работающих в разных компиляторах (напр. GCC и MSVC).
Следует отметить, что сам GCC генерирует код именно в синтаксисе AT&T, поэтому после использования .intel_syntax типично в конце ассемблерной вставки нужно будет переключаться обратно.

Также можно упомянуть псевдокоманды для непосредственного включения данных (соответствующие директивам DB и т. д. в синтаксисе Intel):
.byte, .word, .short, .hword, .int, .long, .float, .single, .double, .ascii, .asciz

Т. к. GNU Assembler в первую очередь ориентирован на работу с кодом сгенерированным автоматически (в частности - компилятором), то он, иногда, довольно небрежно относится к обработке ошибок.
В частности код (пример адаптирован из https://mobile.twitter.com/rygorous/status/659148274167734272?p=v )

    asm(
      ".intel_syntax noprefix\n\t"
      "lea rax, [rax + rbx*4 + rcx*8 + rdx*8 + rsi*8]\n\t"
      ".att_syntax prefix\n\t");

вполне принимается. Реально сгенерированный код в этом случае эквивалентен

leaq (%rax,%rsi,8),%rax

Написание многострочных команд


Текст ассемблерной вставки является синтаксически C строкой, с обычными возможностями: esc-последовательностями и авто-конкатенацией. В связи с этим сформировалась определённая традиция написания многострочных команд.

Код

#include <cstdio>

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

конечно же не компилируется (точнее, не ассемблируется), т. к. авто-конкатенация не добавляет переводы строки:

Assembler messages:
Error: no such instruction: `nopnopnopnopnop'

Версия

#include <cstdio>

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

работает, но asm код выглядит следующим образом:

  .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
 # 12 "test_asm.cpp" 1
  nop
nop
nop
nop
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

Отсутствие выравнивания выглядит эстетическим недостатком.
Поэтому многострочные ассемблерные вставки обычно записывают в следующем стиле:

#include <cstdio>

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

Что приводит к asm коду:

  .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
 # 12 "test_asm.cpp" 1
  nop
  nop
  nop
  nop
  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

Можно заметить запись \t после \n, а не в начале следующей строки. Это на практике оказывается удобнее и облегчает чтение.
Иногда вместо

"nop\n\t"

встречается запись вида

"nop" "\n\t"

В C++11 возможна также запись с использованием raw-strings (компилятор понимает их в ассемблерных вставках):

#include <cstdio>

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

Стоит учитывать, что в зависимости от настроек редактора (IDE) отступы в начале в этом случае могут оказаться как пробелами, так и табуляцией (табуляциями).
В C поддержка raw-strings отсутствует.

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

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

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

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