Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...5164 
 Trees...935 
 Максвелл 3...861 
 Go Web ...814 
 William Gropp...796 
 Ethreal 3...779 
 Ethreal 4...766 
 Gary V.Vaughan-> Libtool...765 
 Rodriguez 6...756 
 Steve Pate 1...749 
 Ext4 FS...748 
 Clickhouse...748 
 Ethreal 1...736 
 Secure Programming for Li...721 
 C++ Patterns 3...711 
 Ulrich Drepper...693 
 Assembler...687 
 DevFS...655 
 Стивенс 9...644 
 MySQL & PosgreSQL...622 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Inline asm for x86

                                       
    Colin Plumb (colin@nyx.net)
    Mon, 20 Apr 1998 18:56:24 -0600 (MDT)
    colin@nyx.net, 20 April 1998
 

Краткое руководство GCC inline asm

Рассматривается один из инструментов ускорения работы ядра - расширенный ассемблер.

Рассмотрим пример :


   asm("foo %1,%2,%0" : "=r" (output) : "r" (input1), "r" (input2));
 
Ее можно усложнить следующим образом :

   asm("foo %1,%2,%0" : "=r" (ptr->vtable[3](a,b,c)->foo.bar[baz]) :
       : "r" (gcc(is) + really(damn->cool)), "r" (42));
 
GCC интерпретирует это следующим образом :

 register int t0, t1, t2;
   t1 = gcc(is) + really(damn->cool);
   t2 = 42;
   asm("foo %1,%2,%0" : "=r" (t0) : "r" (t1), "r" (t2));
   ptr->vtable[3](a,b,c)->foo.bar[baz] = t0;
 
Основная форма asm() :

   asm( "code" : outputs : inputs : clobbers);
 
Внутри "code" , %0 - это обычно 1-й аргумент output. %1 - второй аргумент , и т.д. Их может быть не более %9. Если вы хотите в "code" втиснуть несколько операторов , прийдется их разделять "\n\t". Но разделять так необязательно - можно все записать и в одной строке - для этого достаточно расставить";" .

Как output , так и input состоят из 2-х частей - "constraints" и (value). Для outputs (value) - возвращаемое значение.

outputs должен быть промаркирован знаком равенства "=".

"r" - или "rm" - буквально - register or memory. "ri" - register или immediate value. "g" - "general" - может быть и тем и другим. "o" похоже на "m" - "offsettable", означающее , что вы можете прибавить к нему смещение. Для x86 все операнды в памти - offsettable, поддерживающее индексацию.

* Несколько слов о inputs

input может быть временной переменной.

GCC может разместить output в том же регистре, в котором приходит input ,если последний более не нужен.

* x86 assembly code

GNU использует AT&T-синтаксис,который отличается от Intel-синтаксиса. В AT&T например отсутствует DWORD PTR . Intel использует "op dest,src", AT&T использует "op src,dest".

AT&T использует знак процента для обозначения регистров - %eax, %ebx. Не нужно ставить символ подчеркивания перед переменными и именами функций.

Отличие также в том , что в AT&T размер операнда зашит в саму инструкцию - например , вместо inc нужно - "incb", "incw" ,"incl" соответственно для 8, 16 или 32 бит. Иногда размер операнда указывать необязательно - например можно написать "inc %eax" - и так понятно,что речь идет о 2-х байтах. Но если вы работаете с операндами в памяти , нужно писать"incl foo", а не "inc foo",иначе будет ошибка. "incl %al" - это конечно тоже ошибка.

Перед именем внешней си-шной переменной можно ставить символ доллара - $.
При этом , "movl foo,%eax" копирует содержимое памяти, расположенной по адресу foo , в регистр %eax.
А "movl $foo,%eax" даст совершенно другой эффект - будет скопирован адрес переменной foo.
"movl 42,%eax" - копирование абсолютного значения.

Адресные режимы реализованы с помощью формата offset(base,index,scale). Например , -44(%ebx,%eax) будет эквивалентно -44(%ebx,%eax,1). В качестве шкалы можно использовать 1, 2 ,4 , 8.

* Equivalence constraints

Иногда может возникнуть ситуация,когда для ввода и вывода нужен один и тот же регистр. Для этого можно использовать специальный constraint "0":


   asm("foo %1,%0" : "=r" (output) : "r" (input1), "0" (input2));
 
Поскольку здесь %2 опущен, output и input2 будут использовать один и тот же регистр . Противоречия здесь нет , поскольку GCC сам сгенерит временные регистры.

* Constraints on the x86

i386 имеет достаточно типов регистров для прикладного использования. Основные типы :


 g - general effective address
 m - memory effective address
 r - register
 i - immediate value, 0..0xffffffff
 n - immediate value known at compile time.
 
i386-specific (i386.h):

 q - регистры с байтовой адресацией (eax, ebx, ecx, edx)
 A - eax или edx
 a, b, c, d, S, D - eax, ebx, ecx, edx, esi, edi соответственно
 
 I - immediate 0..31
 J - immediate 0..63
 K - immediate 255
 L - immediate 65535
 M - immediate 0..3 (shifts that can be done with lea)
 N - immediate 0..255 (one-byte immediate value)
 O - immediate 0..32
 
Пример использования "I" для rotate left:

 
   asm("roll %1,%0" : "=g" (result) : "cI" (rotate), "0" (input));
 

* Advanced constraints

= используется для маркировки output.  

% говорит о том , что данный и следующий операнды могут
 обменять свои приоритеты

, разделитель constraints.  
 x86 разрешает операции типа register-memory и  memory-register , 
 но запрещает memory-memory:

     asm("add %1,%0" : "=r,rm" (output) : "%g,ri" (input1), "0,0" (input2));
 
Если output - register, input1 может быть чем угодно. Но если output - memory, input может быть только register либо immediate value. И input2 должен быть в одном месте с output

& output operand пишется перед inputs , поэтому output и input 
 должны быть в разных регистрах.

* const and volatile

Есть 2 хинта , которые можно применить к asm- выражениям.

asm volatile(...) - ключевое слово volatile говорит компилятору , что он не может его оптимизировать и обязан выполнять именно так , как оно записано.

asm const() - может быть оптимизировано как обычное subexpression optimization.


 Пример:
   int foo(int x);
   {
     int i, y, total;
 
     total = 0;
     for (i = 0; i < 100; i++) {
       asm volatile("foo %1,%0" : "=r" (y) : "g" (x));
       total += y;
     }
     return total;
   }
 
Можно поставить ключевое слово "const". В первом случае , без "const" , будет сгенерирован код :

 func1:
         xorl %ecx,%ecx
         pushl %ebx
         movl %ecx,%edx
         movl 8(%esp),%ebx
         .align 4
 .L7:
 #APP
         foo %ebx,%eax
 #NO_APP
         addl %eax,%ecx
         incl %edx
         cmpl $99,%edx
         jle .L7
         movl %ecx,%eax
         popl %ebx
         ret
 >
С использованием const будет сгенеирован другой код :

 func2:
         xorl %edx,%edx
 #APP
         foo 4(%esp),%ecx
 #NO_APP
         movl %edx,%eax
         .align 4
 .L13:
         addl %ecx,%edx
         incl %eax
         cmpl $99,%eax
         jle .L13
         movl %edx,%eax
         ret
 

* Alternate keywords

__asm__() - еще одна альтернатива для asm(), при том что не генерирует варнинги.

* Output substitutions

Иногда возникает необходимость включить какую-то нестандартную конструкцию типа :

   asm("lea %1(%2,%3,1<<%4),%0" : "=r" (out)
       : "%i" (in1), "r" (in2), "r" (in3), "M" (logscale));
 
При этом GCC может сгенерировать что-то подобное :

         lea $-44(%ebx,%eax,1<<$2),%ecx
 
на самом деле это синтаксическая ошибка. Символ $ для констант в данном случае бесполезен. Существуют символы-модификаторы. Один из них - "c", который проигнорирует immediate value. Правильный вариант :

   asm("lea %c1(%2,%3,1<<%c4),%0" : "=r" (out)
       : "%i" (in1), "r" (in2), "r" (in3), "M" (logscale));
 
будет сгенерировано

         lea -44(%ebx,%eax,1<<2),%ecx
 
 Еще один символ-модификатор - n
 %n0 работает аналогично %c0, но делает значение отрицательным
 %l0 аналогично %c0, для jump target.
 
 %k0 выводит  32-битную форму операнда.  %eax, etc.
 %w0 выводит  16-битную форму операнда.  %ax, etc.
 %b0 выводит  8-битную форму операнда.  %al, etc.
 %h0 выводит старшие  8 бит регистра.  %ah, etc.
 %z0 выводит opcode операнда, b, w или l.
 
По умолчанию %0 выводит регистр в форме соответствующей размеру аргумента. Т.е.

asm("inc %0" : "=r" (out) : "0" (in))

выведет "inc %al", "inc %ax" или "inc %eax" в зависимости от типа "out".

Можно использовать %w и %b для обьектов не-регистрового типа. Рассмотрим пример:


   #define xchg(m, in, out)      \
     asm("xchg%z0 %2,%0" : "=g" (*(m)), "=r" (out) : "1" (in))
 
   int
   bar(void *m, int x)
   {
     xchg((char *)m, (char)x, x);
     xchg((short *)m, (short)x, x);
     xchg((int *)m, (int)x, x);
     return x;
   }
 
Будет сгенерирован код :

 
 .globl bar
         .type    bar,@function
 bar:
         movl 4(%esp),%eax
         movb 8(%esp),%dl
 #APP
         xchgb %dl,(%eax)
         xchgw %dx,(%eax)
         xchgl %edx,(%eax)
 #NO_APP
         movl %edx,%eax
         ret
 

* Extra % patterns

Избыточный % не несет в себе никакой дополнительной информации - примером является %%.

Еще одним примером является %=, генерирующий уникальное число для asm()-блока. Это может быть использовано для меток-шаблонов.

* Examples

Пример кода из include/asm-i386/system.h:

 
 #define _set_tssldt_desc(n,addr,limit,type) \
 __asm__ __volatile__ ("movw %3,0(%2)\n\t" \
         "movw %%ax,2(%2)\n\t" \
         "rorl $16,%%eax\n\t" \
         "movb %%al,4(%2)\n\t" \
         "movb %4,5(%2)\n\t" \
         "movb $0,6(%2)\n\t" \
         "movb %%ah,7(%2)\n\t" \
         "rorl $16,%%eax" \
         : "=m"(*(n)) : "a" (addr), "r"(n), "ri"(limit), "i"(type))
 
Этот пример можно переписать с использованием любого другого регистра , отличного от %eax:

 #define _set_tssldt_desc(n,addr,limit,type) \
 __asm__ __volatile__ ("movw %w3,0(%2)\n\t" \
         "movw %w1,2(%2)\n\t" \
         "rorl $16,%1\n\t" \
         "movb %b1,4(%2)\n\t" \
         "movb %4,5(%2)\n\t" \
         "movb $0,6(%2)\n\t" \
         "movb %h1,7(%2)\n\t" \
         "rorl $16,%1" \
         : "=m"(*(n)) : "q" (addr), "r"(n), "ri"(limit), "ri"(type))
 
Проблема в том , что нет возможности закодировать смещение для данного адреса. Если адрес равен "40(%eax)" то смещение на 2 может быть сделано как "2+" . Но если адрес "(%eax)" то "2+(%eax)" будет неверно.

Пример:


 #define _set_tssldt_desc(n,addr,limit,type) \
 __asm__ __volatile__ ("movw %w2,%0\n\t" \
         "movw %w1,2+%0\n\t" \
         "rorl $16,%1\n\t" \
         "movb %b1,4+%0\n\t" \
         "movb %3,5+%0\n\t" \
         "movb $0,6+%0\n\t" \
         "movb %h1,7+%0\n\t" \
         "rorl $16,%1" \
         : "=o"(*(n)) : "q" (addr), "ri"(limit), "i"(type))
 
constraint "o" - то же самое что и "m";

Пример:


 __asm__ __volatile__ ("movw %w7,%0\n\t" \
         "movw %w6,%1\n\t" \
         "rorl $16,%6\n\t" \
         "movb %b6,%2\n\t" \
         "movb %b8,%3\n\t" \
         "movb $0,%4\n\t" \
         "movb %h6,%5\n\t" \
         "rorl $16,%6" \
         : "=m"(*(n)), \
           "=m"((n)[2]), \
           "=m"((n)[4]), \
           "=m"((n)[5]), \
           "=m"((n)[6]), \
           "=m"((n)[7]) \
        : "q" (addr), "g"(limit), "iqm"(type))
 

Пример


 {
         int a=10, b;
         asm ("movl %1, %%eax;
               movl %%eax, %0;"
                 :"=r"(b)        /* output */
                 :"r"(a)         /* input */
                 :"%eax");       /* clobbered register */
 }
 
В этом примере переменная b приравнивается к a .
      * "b" - output operand, ссылка на %0 , и "a" - input operand, ссылка на %1.
      * "r" - constraint , указывает на то , что переменные  "a" и "b" 
        хранятся в регистрах.
      * Регистр %eax идет с двойным префиксом %
      * clobbered register %eax говорит GCC о том , что значение %eax модифицируется
        внутри"asm", поэтому GCC не использует этот регистр для хранения
        чего-то еще
      * movl %1, %%eax - копируем "a" в %eax, и movl %%eax, %0 -копируем
         %eax в "b".
      * после выполнения "asm" изменеие "b" внутри блока становится
        видно снаружи , потому что b определено как output-операнд	
 

Пример

В следующем примере инструкция cpuid получает параметр из регистра %eax и результат выводит в 4 регистрах: %eax, %ebx, %ecx, %edx. input - переменная "op" - передается в блок asm через регистр eax . a,b, c, d - constraints.

    
     asm ("cpuid"
                 : "=a" (_eax),
                   "=b" (_ebx),
                   "=c" (_ecx),
                   "=d" (_edx)
                 : "a" (op));
 
Ниже приводится код , который генерирует компилятор :

    
     movl -20(%ebp),%eax /* store 'op' in %eax -- input */
 #APP
         cpuid
 #NO_APP
         movl %eax,-4(%ebp)      /* store %eax in _eax -- output */
         movl %ebx,-8(%ebp)      /* store other registers in
         movl %ecx,-12(%ebp)        respective output variables */
         movl %edx,-16(%ebp)
 

Пример

Функция strcpy может быть реализована с помощью "S" и "D" constraints :

     asm ("cld\n
               rep\n
               movsb"
               : /* no input */
               :"S"(src), "D"(dst), "c"(count));
 
Источник src копируется в %esi с помощью "S" constraint, приемник dst копируется в %edi с помощью "D" constraint. Счетчик копируется в %ecx.

Пример

Увеличим значение переменной i на единицу :

         int i = 0;
         __asm__("
           pushl %%eax\n
           movl %0, %%eax\n
           addl $1, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           :
           : "g" (i)
         );
 
В следующем примере мы складываем 2 переменных и результат храним в первой переменной - i :

         int i=0, j=1;
         __asm__ __volatile__("
           pushl %%eax\n
           movl %0, %%eax\n
           addl %1, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           :
           : "g" (i), "g" (j)
         );
           /* i = i + j; */
 
В следующем примере переменной i мы присвоим 1, используя = :

         int i=0;
         __asm__ __volatile__("
           pushl %%eax\n
           movl $1, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           : "=g" (i)
         );
           /* i=1; */
 
В следующем примере мы результат сложения 2-х переменных положим в 3-ю - переменную k :
 
         int i=0, j=1, k=0;
         __asm__ __volatile__("
           pushl %%eax\n
           movl %1, %%eax\n
           addl %2, %%eax\n
           movl %%eax, %0\n
           popl %%eax"
           : "=g" (k)
           : "g" (i), "g" (j)
         );
           /* k = i + j; */
 
В следующем примере мы говорим компилятору , чтобы он сохранил значение регистра ax после входа в asm-блок и после выхода из него , т.е. ничего специально сами не делаем для его восстановления :

         int i=0, j=1, k=0;
         __asm__ __volatile__("
           movl %1, %%eax\n
           addl %2, %%eax\n
           movl %%eax, %0"
           : "=g" (k)
           : "g" (i), "g" (j)
           : "ax", "memory"
         );
           /* k = i + j; */
 
Локальные метки внутри инлайна нужно заканчивать "b" или "f" , в зависимости от того , спереди или сзади они стоят :

         __asm__ __volatile__("
         0:\n
           ...
           jmp 0b\n
           ...
           jmp 1f\n
           ...
         1:\n
           ...
         );
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье