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


Copyright (C)2003 Sandeep S.


This document is free; you can redistribute and/or modify this under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.


This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.



2. Общий обзор.

Под понятием inline-кода имеются ввиду функции , код которых реально вставляется в том месте, откуда он вызывается . Это немного похоже на макросы .


В чем преимущество inline functions?


Этот метод уменьшает функциональный оверхед . Для обьявления такой функции нужно использовать ключевое слово inline .


inline assembly - это несколько строчек кода на асме , записанных в теле inline functions. Этот прием используется в системном программировании . Для декларирования inline assembly функции используется ключевое слово asm.


Внутри таких функций можно оперировать внешними си-шными переменными .

3. GCC Assembler Syntax.

GCC, GNU C компилятор для Linux, использует AT&T& UNIX синтаксис. AT&T syntax немного отличается от Intel syntax , который используется например в винде . Отличия :

Иной порядок Source-Destination .

В Intel 1-й операнд - destination, 2-й - source . В AT&T все наоборот : 1-й - source и 2-й - destination. Т.е.


"Op-code dst src" - Intel


"Op-code src dst" - AT&T .

Наименовани регистров

В AT&T перед именем регистра идет префикс процент % , т.е например для eax будет %eax.

Immediate Operand.

AT&T immediate - операнд идет с префиксом доллара ’$’. Для статических "C" также идет префикс доллара ’$’. Для Intel , 16-ричные константы идут с префиксом ’h’ , а для AT&T идет другой префикс ’0x’ . В результате для AT&T для 16-ричных мы имеем сначала ’$’, потом ’0x’ и потом уже сам операнд.

Operand Size.

Для AT&T это префиксы ’b’, ’w’, и ’l’ определяют byte(8-bit), word(16-bit), и long(32-bit) . В Intel это соответственно ’byte ptr’, ’word ptr’, и ’dword ptr’.


Например Intel-ская инструкция "mov al, byte ptr foo" трансформируется в "movb foo, %al" для AT&T .

Memory Operands.


В Intel базовый регистр закрывается квадратными скобками ’[’ и ’]’ в то время как в AT&T они круглые ’(’ и ’)’. В Intel это похоже на


section:[base + index*scale + disp],


section:disp(base, index, scale) для AT&T.


Не забывайте что для констант disp/scale, нужен префикс доллара’$’



А теперь несколько примеров
+------------------------------+------------------------------------+
| Intel Code | AT&T Code |
+------------------------------+------------------------------------+
| mov eax,1 | movl $1,%eax |
| mov ebx,0ffh | movl $0xff,%ebx |
| int 80h | int $0x80 |
| mov ebx, eax | movl %eax, %ebx |
| 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 |
+------------------------------+------------------------------------+


4. Basic Inline.

Базовый формат inline assembly :


asm("assembly code");


Пример
asm("movl %ecx %eax"); /* содержимое ecx копируется в eax */
/* копирование байта из регистра bh в память по адресу в eax*/
__asm__("movb %bh (%eax)");



Здесь можно заметить разницу в использовании asm и __asm__. Оба варианта верные. Можно использовать __asm__ если ключевое слово asm конфликтует с чем-нибудь в программе. Если инструкций больше одной , последующая выносится на новую строку и ставяься двойные кавычки, и строка в конце квотируется ’\n’ and ’\t’ .


Пример
__asm__ ("movl %eax, %ebx\n\t"
"movl $56, %esi\n\t"
"movl %ecx, $label(%edx,%ebx,$4)\n\t"
"movb %ah, (%ebx)");




5. Extended Asm.



В базовом inline assembly, мы можем оперировать только с инструкциями. В расширенном варианте можно оперировать с операндами . Можно определить входящие и выходящие регистры . Базовый формат расширенного варианта :

asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);



Шаблон включает инструкции . Двоеточие разделяет инпут:аутпут . Запятая разделяет операнды внутри группы . Общее число операндов ограничено десятью .


Если output-операнды отсутствуют , это место нужно ограничить двумя двоеточиями.


Пример:
asm ("cld\n\t"
"rep\n\t"
"stosl"
: /* output отсутствует */
: "c" (count), "a" (fill_value), "D" (dest)
: "%ecx", "%edi"
);




Что же выполняет эта невнятная конструкция ? Содержимое fill_value будет скопировано count раз по адресу , который находится в в регистре edi. Еще один пример:

int a=10, b;
asm ("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);




Тут смысл такой : сначала переменная a копируется в регистр eax , а потом содержимое регистра eax копируется в переменную b , т.е. переменные a и b становятся равны . Нужно учесть следующее :

"b" - это output-операнд , который ссылается на %0 и "a" input-операнд и ссылается на %1.
"r" - это конструктор . Знак = перед ним указывает на то , что операнд имеет атрибут write
Два знака процент % - это не опечатка. В данном контексте 2 процента отличают регистр от операндов , которые имеют один процент .
Регистр %eax после третьего двоеточия говорит о том , что его значение модифицировано внутри блока asm .


После того как мы выйдем из "asm"-блока , значение переменной "b" будет равно 10.

5.2 Operands.

В шаблоне каждый операнд есть ссылка на число . Первый операнд имеет индекс ноль , и далее по возрастанию .


Давайте рассмотрим пример с умножением числа на 5. Для этого используем инструкцию lea.

asm ("leal (%1,%1,4), %0"
: "=r" (five_times_x)
: "r" (x)
);




Здесь инпутом служит внешняя си-ная переменная ’x’. Мы специально не оговариваем регистр , который будет использоваться . GCC сам выберет регистры для input и output и сделает то , что мы захотели. С помощью конструкторов (constraint) этот пример можно переписать так :

asm ("leal (%0,%0,4), %0"
: "=r" (five_times_x)
: "0" (x)
);




В этом случае input и output будут попадать уже в один регистр. Но мы пока не знаем в какой . Если мы специально хотим его определить , тогда :
asm ("leal (%%ecx,%%ecx,4), %%ecx"
: "=c" (x)
: "c" (x)
);



5.3 Clobber List.

Регистры , помещаемые в этот список , говорят компилятору , что их будут модифицировать внутри asm. Нам этот список не нужен , потому что мы раньше указали , с каким регистром мы работаем явно .

Наполнив этот список какими-то регистрами , мы можем много раз их использовать Рассмотрим пример с вызовом процедуры _foo , аргументы которой прописаны в регистрах eax и ecx.

asm ("movl %0,%%eax;
movl %1,%%ecx;
call _foo"
: /* no outputs */
: "g" (from), "g" (to)
: "eax", "ecx"
);



5.4 Volatile ...?

Если вам приходилось копаться в исходниках ядра , вы наверно обратили внимание на ключевое слово volatile или __volatile__.


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

asm volatile ( ... : ... : ... : ...);

Обычно не нужно злоупотреблять этим ключевым словом и нужно давать компилятору возможность оптимизировать код - он это делает лучше нас :-)
6. Constraints.

Констрэйнты выполняют большой обьем работы внутря inline assembly. Они указывают на регистр , в котором находится операнд , или на память , и т.д.

6.1 Часто используемые constraints.

Register operand constraint(r)

Если операнд определен с помощью констрэйнта , он хранится в General Purpose Registers(GPR). Пример :


asm ("movl %%eax, %0\n" :"=r"(myval));


В данном случае переменная myval хранится в регистре , затем значение регистра eax копируется в этот регистр, после чего значение этого регистра копируется в память по адресу , в котором находится значение переменной myval. При использовании "r" gcc может хранить переменную myval в любом из доступных GPR. Для определения этого регистра можно сделать так :

+---+--------------------+
| r | Register(s) |
+---+--------------------+
| a | %eax, %ax, %al |
| b | %ebx, %bx, %bl |
| c | %ecx, %cx, %cl |
| d | %edx, %dx, %dl |
| S | %esi, %si |
| D | %edi, %di |
+---+--------------------+


Matching(Digit) constraints

Иногда одна переменная может обслуживать одновременно и input и output-операнды.

asm ("incl %0" :"=a"(var):"0"(var));


В данном случае регистр eax используется для input и output . В этом примере переменная var попадает в eax , там изменяется , и это измененное значение регистра eax попадает назад в память по адресу , в котором находится переменная var :-)


Другие случаи применения constraints:
"m" : memory operand
"o" : memory operand с адресом , к которому прибавляется смещение из таблицы
"V" : противовес предыдущему варианту
"i" : immediate integer operand с заранее неизвестным значением
"n" : immediate integer operand с заданным значением
"g" : любой регистр , память или переменная

Следующие констрэйнты специфичны только для x86 .

"r" : Register operand constraint, look table given above.
"q" : Registers a, b, c or d.
"I" : Constant in range 0 to 31 (for 32-bit shifts).
"J" : Constant in range 0 to 63 (for 64-bit shifts).
"K" : 0xff.
"L" : 0xffff.
"M" : 0, 1, 2, or 3 (shifts for lea instruction).
"N" : Constant in range 0 to 255 (for out instruction).
"f" : Floating point register
"t" : First (top of stack) floating point register
"u" : Second floating point register
"A" : Specifies the `a’ or `d’ registers. This is primarily useful for 64-bit integer values intended to be returned with the `d’ register holding the most significant bits and the `a’ register holding the least significant bits.


6.2 Constraint Modifiers.
"=" Операнд типа output write-only
"&" : Означает , что операнд будет модифицирован прежде чем input-инструкция будет закончена .


7. Несколько полезных примеров.

Программа для сложения 2-х чисел :

int main(void)
{
int foo = 10, bar = 15;
__asm__ __volatile__("addl %%ebx,%%eax"
:"=a"(foo)
:"a"(foo), "b"(bar)
);
printf("foo+bar=%d\n", foo);
return 0;
}




Мы сохраняем переменную foo в регистре %eax, переменную bar в регистре %ebx и результат помещаем в %eax. Символ ’=’ указывает на то , что это output-register. Теперь прибавим число к переменной :

__asm__ __volatile__(
" lock ;\n"
" addl %1,%0 ;\n"
: "=m" (my_var)
: "ir" (my_int), "m" (my_var)
: /* no clobber-list */
);




Это пример с атомарным сложением - на это указывает инструкция ’lock’ . В output "=m" мы говорим , что переменная my_var находится в памяти. Префикс "ir" говорит , что my_int - это целое и должно быть помещено в регистр .


Теперь несколько операций с регистрами/переменными и сравнениями :

__asm__ __volatile__( "decl %0; sete %1"
: "=m" (my_var), "=q" (cond)
: "m" (my_var)
: "memory"
);




Переменная my_var уменьшается на единицу и если результат равен нулю , то устанавливается переменная cond .

Можно использовать "incl %0" вместо "decl %0" для увеличения переменной my_var.


my_var - переменная в памяти , переменная cond - ее значение может быть произвольным образом положено компилятором в любой из регистров в любой из регистров eax, ebx, ecx and edx. constraint "=q" это гарантирует . Поскольку в clobber list находится ссылка на ключевое слово memory , то код изменяет контент памяти .


Как установить/очистить бит в регистре ?
__asm__ __volatile__( "btsl %1,%0"
: "=m" (ADDR)
: "Ir" (pos)
: "cc"
);




Здесь бит в позиции ’pos’ для переменной ADDR (в памяти) устанавливается в 1 Можно использовать ’btrl’ для ’btsl’ для установки бита в ноль. Констрэйнт "Ir" для pos говорит , что pos храним в регистре, и его значение в диапазоне 0-31 .


Копирование строки :
static inline char * strcpy(char * dest,const char *src)
{
int d0, d1, d2;
__asm__ __volatile__( "1:\tlodsb\n\t"
"stosb\n\t"
"testb %%al,%%al\n\t"
"jne 1b"
: "=&S" (d0), "=&D" (d1), "=&a" (d2)
: "0" (src),"1" (dest)
: "memory");
return dest;
}




Адрес источника хранится в esi, приемника - в edi, и после того как мы начинаем копирование и достигаем 0, копирование завершается. Констрэйнты "&S", "&D", "&a" говорят , что регистры esi, edi и eax находятся в списке clobber registers. Память тоже в clobberlist.


Макрос для копирования массива double words :
#define mov_blk(src, dest, numwords) \
__asm__ __volatile__ ( \
"cld\n\t" \
"rep\n\t" \
"movsl" \
: \
: "S" (src), "D" (dest), "c" (numwords) \
: "%ecx", "%esi", "%edi" \
)



Здесь нет outputs, меняется контент регистров ecx, esi и edi , поэтому они добавлены в clobber list.


В Linux, system calls реализованы через GCC inline assembly. Посмотрим на пример такой реализации. Все system calls находятся в (linux/unistd.h). В следующем примере определен system call с 3-мя аргументами :

#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ( "int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
__syscall_return(type,__res); \
}




Номер системного вызова находится в eax, параметры - в ebx, ecx, edx. И вызов "int 0x80" запускает сам system call . Возвращаемое значение попадает в eax.


Вызов system call Exit :
{
asm("movl $1,%%eax; /* SYS_exit is 1 */
xorl %%ebx,%%ebx; /* Argument is in ebx, it is 0 */
int $0x80" /* Enter kernel mode */
);
}




Номер exit = "1" и параметр = 0. eax =1 и ebx = 0 , int $0x80, exit(0) :
 
« Пред.
 
 
 
19.06.2018 г.
up!