AT&T Tutorial
Столкнуться с необходимостью такого сравнения можно в
любой программе, принимающей входные данные от пользователя, например
авторизация. Для этого существует специальная инструкция сравнения
строк - cmpsb, она берет данные из регистров %esi, %edi и %ecx:
-------------------------------------------------------------------------
/* strcmp.s */
.globl _start
_start: movl $str1,%esi # кладем первую строку в %esi
movl $str2,%edi # вторую в %edi
movl $4,%ecx # количество байт для сравнения
cld # очистить флаг D, иначе сравнение
# пойдет в обратном порядке
rep cmpsb # сравнение
jz yes # если строки равны то прыгаем в yes,
# если нет, то exit
exit:
movl $1,%eax # выход
int $0x80 yes:
movl $4,%eax # write
movl $1,%ebx # stdout
movl $msg,%ecx # наше сообщение
movl $msglen,%edx # его длина
int $0x80
jmp exit
.data
str1: .string "asdf"
str2: .string "asdf"
msg: .string "The strings are equal! Quiting...\n"
msglen = . -msg # вычислить длину msg
/* End */
-------------------------------------------------------------------------
Компиляция:
as strstr.s -o strstr.o
ld strstr.o
Можно использовать gcc: gcc strstr.s , предварительно заменив "_start" на "main".
Для этого будем использовать интсрукцию movsb, первую
строку кладем в %esi и буфер, в котором получим результат, в %edi.
Взгляни на пример:
-------------------------------------------------------------------------
/* strcat.s */
.globl main
main:
movl $str1,%esi # кладем строку "AT&T" в %esi
movl $buf,%edi # буфер с будущим результатом
movl $6,%ecx # размер стринга
cld
rep movsb # после выполнения в $buf находится str1
movl $str2,%esi # далее производим выше описаные операции с str2
movl $9,%ecx
cld
rep movsb # вуаля, теперь у нас в $buf имеется str1+str2,
# т.е "AT&T is cool\n"
movl $4,%eax # write
movl $1,%ebx # stdout
movl $buf,%ecx # "AT&T is cool\n"
movl $15,%edx # size of $buf
int $0x80
movl $1,%eax # exit
int $0x80 .data
str1: .string "AT&T "
str2: .string "is cool\n"
buf: .byte 15 # static buffer
/* End */
-------------------------------------------------------------------------
Что за программа без счетчика ? Да, счетчик часто
используемая функция. Для визуальной демонстрации работы нашего
счетчика будет использован вывод строки через write, поэтому регистры
%eax,%ebx,%ecx,%edx будут заняты, но %esi и %edi останутся, их-то мы и
будем использовать:
-------------------------------------------------------------------------
/* counter.s */
.globl main
main:
movl $5,%esi # число повторений , используй %esi или %edi не важно
loop: # write (stdout,str,5);
movl $4,%eax
movl $1,%ebx
movl $str,%ecx
movl $5,%edx
int $0x80 decl %esi # уменьшить %esi на 1
tesl %esi,%esi # если %esi 0
jz exit # jmp exit
jmp loop # прыжок на начало
exit:
movl $1,%eax
int $0x80
/* End */
-------------------------------------------------------------------------
Для реализации этой функции мы прибегнем к небольшому
трюку. Будем использовать инструкцию lodsb для проверки строки байт за
байтом, попутно увеличивая счетчик для вычисления длины. Сперва
необходимо поместить эфективный адрес строки в %esi, %edi будет
счетчиком.
-------------------------------------------------------------------------
/* strlen.s */
.globl main
main:
xorl %edi,%edi
leal str,%esi # эфективный адрес в %esi abc:
lodsb # читает байт в строке и автоматически устанавливает
# следующую позицию
cmpb $0,%al # если байт равен 0, значит end of string
jz continue
incl %edi # счетчик
jmp abc # повторить... continue:
decl %edi ... # в %edi готовый результат - длина (int) .data
str: .string "CheckMylength" # если в строке присутствует \n, длина увеличивается
# дополнительно на 1 байт
/* End */
-------------------------------------------------------------------------
Небольшой перерыв для нескольких советов. Почему бы не
использовать переменные в более дружественном виде ? int $sysint -
выглядит лучше чем int $0x80, объявляется это так - sysint = 0x80, все
что захочешь:
int = 1
char = 97 # character "a"
(dec) char = 0x61 # character "a" (hex)
etc...
Но не забывай ! Это постоянные (статические)
переменные, устанавливающиеся во время компиляции. В итоге невозможно
увеличить\уменьшить их в коде. К этому методу можно прибегнуть если
большой код расчитан на анализ другими людьми, в таком случае
осмысленные названия будут более понятны при изучении.
[ Continue... ]
-------------------------------------------------------------------------
/* Small C example */
int main()
{
char a[10];
int b; for (b=0;b!=10;b++)
{
a[b]='a';
printf("%c",a[b]);
}
}
/* End */
-------------------------------------------------------------------------
Этот пример пихает символ "a" в каждый элемент массива, вывод программы
выглядит так:aaaaaaaaaa
На мой взгляд это подходящий пример.
-------------------------------------------------------------------------
/* array.s */ count = 10 # счетчик
a = -12 # смещение в %ebp, место для нашего массива -
# как char a[10];
b = -16 # int b;
char = 97 # символ 'a' (dec)
sysint = 0x80
.globl main
main:
movl $0,b(%ebp) # for(b=0; loop:
cmpl $count,b(%ebp) # b != 10;
jnz lp # jump if not equal
jmp exit # jump if counter reached lp:
leal a(%ebp),%edi # a[
movl b(%ebp),%esi # b]
movb $char,(%esi,%edi) # ='a';
incl b(%ebp) # b++);
# write(stdout,a[b],1);
movl $4,%eax
movl $1,%ebx
leal (%esi,%edi),%ecx
movl $1,%edx
int $sysint
jmp loop
exit:
movl $1,%eax
int $sysint
/* End */
-------------------------------------------------------------------------
Как можно заметить, область массива в %edi и его элементы в %esi, готовый массив находится в (%esi,%edi).
Для чего это надо ? Например syscall write работает
только со строками, и если мы сделаем write(stdout,numeric,length),
будет выдан символ из ASCII таблицы с номером numeric. Числа в ASCII
таблице начинаются с 48 (decimal 0), поэтому мы просто увеличиваем наш
numeric на 48 и получаем аскишный эквивалент необходимой цифры. Перед
тем как выполнить это на асме, взглянем на процедуру выполненную в
С...Так как система счисления десятеричная, то соответственно и делить
необходимо на 10:
-------------------------------------------------------------------------
/* num2ascii.c */
int main()
{
char a[10];
int x = 1234,b=0; do // отделяем каждую цифру от целого числа
{
a[b]=48 + x % 10; // кладем в массив, первый элемент содержит "4", второй "3"
// etc... - обратный порядок
b++;
} while (x /= 10);
do // перевернем 4321 в необходимое 1234
{
b--;
printf("%d\n",a[b]);
} while(b!=0);
}
/* End */
-------------------------------------------------------------------------
Это приблизительная реализация, если перевести ее шаг за
шагом на асм, мы получим большой код в пределах ~1kb, поэтому пример на
асме имеет немного другую структуру.
-------------------------------------------------------------------------
/* num2ascii.s */
int = 1234 .globl main
main:
xorl %esi,%esi
movl $int,%eax
loop:
movl $0,%edx
movl $10,%ebx
divl %ebx # деление, результат в %eax (123) и остаток в %edx (4)
addb $48,%dl # +48
pushl %edx # на стек
incl %esi # счетчик цифр
cmpb $0,%al
jz next
jmp loop
next:
popl (%ecx) # взять со стека
testl %esi,%esi
jz exit
decl %esi
movl $4,%eax
movl $1,%ebx
movl $2,%edx
int $0x80
jmp next
exit:
movl $1,%eax
int $0x80
/* End */
-------------------------------------------------------------------------
Такая нужда у нас появляется при работе с сокетами, а точнее htons(port).
-------------------------------------------------------------------------
/* htons.s */
int = 10
.globl main
main:
movl $int,%edi
movl %edi,%eax
rol $8,%ax # сдвиг, rol или ror здесь роли не играют
# сейчас %eax содержит 10 в nbo
... /* End */
-------------------------------------------------------------------------
|