DLLpiton.net.ru
Главная arrow Статьи arrow GCC расширенный ассемблер
Главная
Форум
Магазин
- - - - - - -
Исходники
Статьи
Материалы
FTP
- - - - - - -
Поиск
Каталог ссылок
Контакты
Ленты новостей
Ча.Во. (FAQ)
Анекдоты
Java программы
Доска заказов программ
Лицензия Dllpiton
Кто на сайте?
 
GCC расширенный ассемблер Печать E-mail
Inline asm for x86

Colin Plumb ( Этот e-mail защищен от спам-ботов. Для его просмотра в вашем браузере должна быть включена поддержка Java-script )
Mon, 20 Apr 1998 18:56:24 -0600 (MDT)
Этот e-mail защищен от спам-ботов. Для его просмотра в вашем браузере должна быть включена поддержка Java-script , 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
...
);
 
« Пред.   След. »
 
 
 
19.06.2018 г.
up!