========================================================================
LINUX ASSEMBLER TUTORIAL
by
Robin Miyagi
========================================================================
При программировании под линукс нужно помнить , что это
операционная система с защищенным режимом . Это означает ,
что пользовательским процессам недоступны например DMA или
доступ к IO портам . С другой стороны , написание модулей ядра
позволяет получить доступ к железу напрямую . Пользовательские
процессы могут иметь доступ к железу с помощью файлов устройств.
* Системные вызовы
------------------------------------------------------------------------
В программировании под ДОС вы возможно ипользовали 21-е прерывание.
В линуксе аналогичную роль играет прерывание 0x80.
Основным параметром прерывания является его номер - system call number -
это параметр передавается в регистр EAX. Другие регистры используются
также для получения других параметров прерывания.Мы будем рассматривать
только такие прерывания, в которых не более 5 параметров.
В противном случае параметры прерывания надо размещать где-то в памяти.
Список системных вызовов можно найти в хидере /usr/include/asm/unistd.h.
Для получения хэлпа можно набрать что-то типа `man 2 write' .
В файле /usr/include/asm/unistd.h в заголовке можно увидеть следующее :
#define __NR_write 4
Это говорит о том , что регистр EAX должен быть установлен в 4 перед вызовом.
* GNU Assembler
------------------------------------------------------------------------
GNU C compiler (gcc) использует ассемблер , называемый как `as' .
`As' использует AT&T синтаксис.
Для получения информации можно набрать
`info as'
** GNU assembler / Intel Syntax
------------------------------------------------------------------------
Сравним интеловский и айтишный синтаксисы :
- В `as' источник находится перед приемником
- перед операндами ставится суффикс , указывающий на его размер
( `l' - dword, `w' - word, `b' - byte).
- операнды идут с префиксом `$', регистры с префиксом `%'.
- эффективный адрес имеет синтаксис DISP(BASE,INDEX,SCALE).
Пример :
movl mem_location(%ebx,%ecx,4), %eax
Что эквивалентно интеловскому :
mov eax, [eax + ecx*4 + mem_location]
Пример :
------------------------------------------------------------------------
## hello-world.s
## by Robin Miyagi
## http://www.geocities.com/SiliconValley/Ridge/2544/
## Компиляция :
## -------------------------------------------------------------
## as -o hello-world.o hello-world.s
## ld -o hello-world -O0 hello-world.o
## Программа выводит строку на экран
.section .data
hello:
.ascii "Hello, world!\n"
hello_len:
.long . - hello
.section .text
.globl _start
_start:
## используется системный вызов write ()
xorl %ebx, %ebx # %ebx = 0
movl $4, %eax # write () system call
xorl %ebx, %ebx # %ebx = 0
incl %ebx # %ebx = 1, fd = stdout
leal hello, %ecx # %ecx ---> hello
movl hello_len, %edx # %edx = count
int $0x80 # execute write () system call
## terminate program via _exit () system call
xorl %eax, %eax # %eax = 0
incl %eax # %eax = 1 system call _exit ()
xorl %ebx, %ebx # %ebx = 0 normal program return code
int $0x80 # execute system call _exit ()
------------------------------------------------------------------------
`#' - строка с комментариями .
Комментарии можно также писать с помощью си-шного синтаксиса /* */
Сегменты начинаются с ключевых слов .text и .data.
Есть еще одна секция - .bss - для хранения неинициализированных
данных .
------------------------------------------------------------------------
* Доступ к параметрам командной строки и переменным окружения
При старте приложения параметры командной строки и переменные
окружения доступны в стеке . Доступ к ним можно получить с
помощью указателя , который хранится в ESP.
Число аргументов командной строки плюс имя программы хранится
как целое число в [esp]. Далее , по адресу стека [esp+4]
хранится указатель на первый аргумент . Последующие соответственно
в [esp+8], [esp+12], и т.д. В конце идет NULL-указатель . После него
идут указатели на переменные окружения и после опять
NULL-указатель .
(%esp) argc, count of arguments (integer)
4(%esp) char *argv (pointer to first command line argument)
... pointers to the rest of the command line arguments
?(%esp) NULL pointer
... pointers to environment variables
??(%esp) NULL pointer
Еще одна программа :
## Показан доступ к параметрам командной строки
## Compile Instructions:
## -------------------------------------------------------------
## as -o stack-param.o stack-param.s
## ld -O0 -o stack-param stack-param.o
.section .data
new_line_char:
.byte 0x0a
.section .text
.globl _start
.align 4
_start:
movl %esp, %ebp # store %esp in %ebp
again:
addl $4, %esp # %esp ---> next parameter on stack
movl (%esp), %eax # move next parameter into %eax
testl %eax, %eax # %eax (parameter) == NULL pointer?
jz end_again # get out of loop if yes
call putstring # output parameter to stdout.
jmp again # repeat loop
end_again:
xorl %eax, %eax # %eax = 0
incl %eax # %eax = 1, system call _exit ()
xorl %ebx, %ebx # %ebx = 0, normal program exit.
int $0x80 # execute _exit () system call
## prints string to stdout
putstring: .type @function
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
xorl %edx, %edx
count_chars:
movb (%ecx,%edx,$1), %al
testb %al, %al
jz done_count_chars
incl %edx
jmp count_chars
done_count_chars:
movl $4, %eax
xorl %ebx, %ebx
incl %ebx
int $0x80
movl $4, %eax
leal new_line_char, %ecx
xorl %edx, %edx
incl %edx
int $0x80
movl %ebp, %esp
popl %ebp
ret
------------------------------------------------------------------------
* Пакет Binutils
Этот пакет включает полезные тулзы , включая дебаг .
** Objdump
------------------------------------------------------------------------
Выводит информацию об обьектных файлах .
Например :
objdump -x param-stack | less
Вывод :
----------------------------------------------------------------
stack-param: file format elf32-i386
stack-param
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048074
Program Header:
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x000000be memsz 0x000000be flags r-x
LOAD off 0x000000c0 vaddr 0x080490c0 paddr 0x080490c0 align 2**12
filesz 0x00000001 memsz 0x00000004 flags rw-
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000004a 08048074 08048074 00000074 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000001 080490c0 080490c0 000000c0 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 080490c4 080490c4 000000c4 2**2
ALLOC
SYMBOL TABLE:
08048074 l d .text 00000000
080490c0 l d .data 00000000
080490c4 l d .bss 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
080490c0 l .data 00000000 new_line_char
08048076 l .text 00000000 again
08048087 l .text 00000000 end_again
0804808e l .text 00000000 putstring
08048096 l .text 00000000 count_chars
080480a0 l .text 00000000 done_count_chars
00000000 F *UND* 00000000
080480be g O *ABS* 00000000 _etext
08048074 g .text 00000000 _start
080490c1 g O *ABS* 00000000 __bss_start
080490c1 g O *ABS* 00000000 _edata
080490c4 g O *ABS* 00000000 _end
----------------------------------------------------------------
Информация извлекается из хидера обьектного ELF-файла .
Включается информация о секциях
Objdump можно использовать для дизассемблирования бинарных файлов :
objdump -d stack-param | less
Вывод этой команды :
----------------------------------------------------------------
stack-param: file format elf32-i386
Disassembly of section .text:
08048074 <_start>:
8048074: 89 e5 movl %esp,%ebp
08048076 :
8048076: 83 c4 04 addl $0x4,%esp
8048079: 8b 04 24 movl (%esp,1),%eax
804807c: 85 c0 testl %eax,%eax
804807e: 74 07 je 8048087
8048080: e8 09 00 00 00 call 804808e
8048085: eb ef jmp 8048076
08048087 :
8048087: 31 c0 xorl %eax,%eax
8048089: 40 incl %eax
804808a: 31 db xorl %ebx,%ebx
804808c: cd 80 int $0x80
0804808e :
804808e: 55 pushl %ebp
804808f: 89 e5 movl %esp,%ebp
8048091: 8b 4d 08 movl 0x8(%ebp),%ecx
8048094: 31 d2 xorl %edx,%edx
08048096 :
8048096: 8a 04 11 movb (%ecx,%edx,1),%al
8048099: 84 c0 testb %al,%al
804809b: 74 03 je 80480a0
804809d: 42 incl %edx
804809e: eb f6 jmp 8048096
080480a0 :
80480a0: b8 04 00 00 00 movl $0x4,%eax
80480a5: 31 db xorl %ebx,%ebx
80480a7: 43 incl %ebx
80480a8: cd 80 int $0x80
80480aa: b8 04 00 00 00 movl $0x4,%eax
80480af: 8d 0d c0 90 04 08 leal 0x80490c0,%ecx
80480b5: 31 d2 xorl %edx,%edx
80480b7: 42 incl %edx
80480b8: cd 80 int $0x80
80480ba: 89 ec movl %ebp,%esp
80480bc: 5d popl %ebp
80480bd: c3 ret
----------------------------------------------------------------
Параметр `-d' указывает objdump делать дизассемблинг кодового сегмента .
Опция `-D' указывает на все сегменты .
Objdump может выводить имена меток .
Первый столбик в последнем выводе - виртуальные адреса.
Второй выводит машинные коды .
Третий выводит собственно ассемблерный код .
------------------------------------------------------------------------
** Определение размера используемой памяти
Команда
`ls -l stack-param'
выдаст
-rwxrwxr-x 1 robin robin 932 Sep 15 18:21 stack-param
Файл имеет размер 932 байта.
Для определения памяти , которую может использовать эта программа при выполнении :
size stack-param
Получим результат :
text data bss dec hex filename
74 1 0 75 4b stack-param
Кодовый сегмент использует 74 байта , данные - 1 байт , итого - 75 байт .
------------------------------------------------------------------------
* Дебаг и gdb
Дебаг - один из самых сложных аспектов программирования .
Пример программы , которая выходит с сигналом SIG_SEGV
## stack-param-error.s
## В примере описывается доступ командным параметрам
## Compile Instructions:
## -------------------------------------------------------------
## as --gstabs -o stack-param-error.o stack-param-error.s
## ld -O0 -o stack-param-error stack-param-error.o
.section .data
new_line_char:
.byte 0x0a
.section .text
.globl _start
.align 4
_start:
movl %esp, %ebp # store %esp in %ebp
again:
addl $4, %esp # %esp ---> next parameter on stack
leal (%esp), %eax # move next parameter into %eax
testl %eax, %eax # %eax (parameter) == NULL pointer?
jz end_again # get out of loop if yes
call putstring # output parameter to stdout.
jmp again # repeat loop
end_again:
xorl %eax, %eax # %eax = 0
incl %eax # %eax = 1, system call _exit ()
xorl %ebx, %ebx # %ebx = 0, normal program exit.
int $0x80 # execute _exit () system call
## prints string to stdout
putstring: .type @function
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
xorl %edx, %edx
count_chars:
movb (%ecx,%edx,$1), %al
testb %al, %al
jz done_count_chars
incl %edx
jmp count_chars
done_count_chars:
movl $4, %eax
xorl %ebx, %ebx
incl %ebx
int $0x80
movl $4, %eax
leal new_line_char, %ecx
xorl %edx, %edx
incl %edx
int $0x80
movl %ebp, %esp
popl %ebp
ret
Программа откомпилирована с опцией `--gstabs'.
Она выводит отладочную информацию в бинарник .
Теперь найдем ошибку с помощтю команды :
gdb stack-param-error
После чего появится приглашение для дебага :
(gdb) run eat my shorts
/home/robin/programming/asm-tut/stack-param-error
eat
my
shorts
Program recieved SIGSEGV, segmentation fault
count_chars () at stack-param-error.s:47
47 movb (%ecx,%edx,$1), %al
Current language: auto; currently asm
(gdb) q
[~]$ _
Здесь приведен не весь вывод .
Мы видим , что ошибка произошла 47-й строке .
А сама проблема живет в 29-й строке .
reads `movl (%esp), %eax'.
Мы загружаем в eax нулевой несуществующий указатель .
Цикл в функции _start () никогда не закончится .
Для получения общей информации о gdb наберите
`info gdb'
--------------------------------------------------------------------
Comments and suggestions
You are free to make verbatim copies of this file, providing that this
notice is preserved.
|