Linux Assembly "Hello World" Tutorial, CS 200
by Bjorn Chambless
- Введение
-
Эта статья описывает некоторые аспекты программирования для x86-ассемблера (AT&T
style, который генерит gcc) под Linux . Описывается , как дебажить такие программы .
Необходимы:
- i386 PC под Linux
- GCC, GNU C-compiler
- GDB, GNU debugger
Все тестировалось на GCC версия 2.95.4 и GDB 19990928 под Linux 2.4.9
Не мешало бы ознакомиться с документацией
"as"
и "gdb"
.
- Source Code
-
Рассмотрим hello.c
/* hello.c */
#include
main()
{
printf("hello\n").
}
которая после запуска печатает сообщение
Компиляция :
linuxbox> gcc -o hello hello.c
linuxbox> ./hello
hello
Что происходит при компиляции можно посмотреть с помощью опции -v
linuxbox> gcc -v -o hello hello.c
Reading specs from /usr/lib/gcc-lib/i386-linux/2.95.4/specs
gcc version 2.95.4 20010902 (Debian prerelease)
/usr/lib/gcc-lib/i386-linux/2.95.4/cpp0 -lang-c -v -D__GNUC__=2
-D__GNUC_MINOR__=95 -D__ELF__ -Dunix -D__i386__ -Dlinux -D__ELF__
-D__unix__ -D__i386__ -D__linux__ -D__unix -D__linux -Asystem(posix)
-Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ hello.c
/tmp/ccCGCFmG.i
GNU CPP version 2.95.4 20010902 (Debian prerelease) (i386 Linux/ELF)
#include "..." search starts here:
#include <...> search starts here: /usr/local/include
/usr/lib/gcc-lib/i386-linux/2.95.4/include /usr/include
End of search list.
The following default directories have been omitted from the search
path: /usr/lib/gcc-lib/i386-linux/2.95.4/../../../../include/g++-3
/usr/lib/gcc-lib/i386-linux/2.95.4/../../../../i386-linux/include
End of omitted list. /usr/lib/gcc-lib/i386-linux/2.95.4/cc1
/tmp/ccCGCFmG.i -quiet -dumpbase hello.c -version -o /tmp/ccI5CJce.s
GNU C version 2.95.4 20010902 (Debian prerelease) (i386-linux) compiled
by GNU C version 2.95.4 20010902 (Debian prerelease). as -V -Qy -o
/tmp/cc2lti5K.o /tmp/ccI5CJce.s
GNU assembler version 2.11.90.0.31 (i386-linux) using BFD version
2.11.90.0.31 /usr/lib/gcc-lib/i386-linux/2.95.4/collect2 -m elf_i386
-dynamic-linker /lib/ld-linux.so.2 -o hello /usr/lib/crt1.o
/usr/lib/crti.o /usr/lib/gcc-lib/i386-linux/2.95.4/crtbegin.o
-L/usr/lib/gcc-lib/i386-linux/2.95.4 /tmp/cc2lti5K.o -lgcc -lc -lgcc
/usr/lib/gcc-lib/i386-linux/2.95.4/crtend.o /usr/lib/crtn.o
При этом:
- Идет Preprocess исходника cpp.
- Компиляция cc в асм-код.
Использование AT&T-синтаксиса под linux отличается от программирования асм-а под винду.
Функция main вызывается как обычная функция .
При этом не нужно инициализировать сегментные регистры .
Далее идет код программы , которую мы назовем "average.s".
Обраите внимание , как инициализируются 3 переменных в секторе данных -
на инициализацию каждой переменной уходит триада - тип : размер : значение
/* linux version of AVTEMP.ASM CS 200, fall 1998 */
.data /* начало data segment */
/* hi_temp data item */
.type hi_temp,@object /* declare as data object */
.size hi_temp,1 /* declare size in bytes */
hi_temp:
.byte 0x92 /* set value */
/* lo_temp data item */
.type lo_temp,@object
.size lo_temp,1
lo_temp:
.byte 0x52
/* av_temp data item */
.type av_temp,@object
.size av_temp,1
av_temp:
.byte 0
/* segment registers set up by linked code */
/* beginning of text(code) segment */
.text
.align 4 /* set 4 double-word alignment */
.globl main /* make main global for linker */
.type main,@function /* declare main as a function */
main:
pushl %ebp /* function requirement */
movl %esp,%ebp /* function requirement */
movb hi_temp,%al
addb lo_temp,%al
movb $0,%ah
adcb $0,%ah
movb $2,%bl
idivb %bl
movb %al,av_temp
leave /* function requirement */
ret /* function requirement */
Генерация обьектного кода:
as -a --gstabs -o average.o average.s
Опция "-a" печатает протокол памяти.
Выводится расположение переменных в памяти относительно кодовых и дата-сегментов .
"--gstabs" сохраняет отладочную информацию в исполняемый файл .
(используемый потом gdb).
Опция "-o" задает имя average.o .
Обьектный файл (average.o) затем будет залинкован к системным обьектным файлам
crt1.o, crti.o и crtn.o. crt1.o и crti.o делают инициализацию кода и crtn.o очистку.
Они должны лежать в "/usr/lib" .
Их можно поискать командой :
find / -name "crt*" -print
Линковка:
ld -m elf_i386 -static /usr/lib/crt1.o /usr/lib/crti.o -lc average.o /usr/lib/crtn.o
Инструкция "-m elf_i386" использует файловый формат ELF . "-static"
- статическая линковка . "-lc" - линковка к стандартной библиотеке (libc.a).
Возможно , при ненахождении последней вам прийдется запустить
"-I/libdirectory" .
Ну и статус исполняемого файла меняем как "chmod +x ./a.out".
После чего можно запустить скомпилированный исполняемый файл - кстати , он ничего не печатает .
Вообще говоря , можно создать Makefile с более упрощенным вариантом команды ld:
a.out: average.o
ld average.o -lc
chmod +x ./a.out
average.o: average.s
as -a --gstabs -o average.o average.s
clean:
rm -fr average.o a.out
Опция "--gstabs" позволит нам отдебажить программу с помощью gdb.
Итак запускаем gdb:
gdb ./a.out
типа:
[bjorn@pomade src]$ gdb ./a.out
GNU gdb 4.17
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb)
Команда "l" будет выводить по порядку команды :
(gdb) l
1 /* linux version of AVTEMP.ASM CS 200, fall 1998 */
2 .data /* beginning of data segment */
3
4 /* hi_temp data item */
5 .type hi_temp,@object /* declare as data object */
6 .size hi_temp,1 /* declare size in bytes */
7 hi_temp:
8 .byte 0x92 /* set value */
9
10 /* lo_temp data item */
(gdb)
Поставим точку прерывания на функции main :
(gdb) break main
Breakpoint 1 at 0x80480f7
(gdb)
Теперь выполняем :
(gdb) run
Starting program: /home/bjorn/src/./a.out
Breakpoint 1, main () at average.s:31
31 movb hi_temp,%al
Current language: auto; currently asm
(gdb)
Можно проверить значения регистров :
(gdb) info registers
eax 0x8059200 134582784
ecx 0xbffffd94 -1073742444
edx 0x0 0
ebx 0x8097bf0 134839280
esp 0xbffffdd8 0xbffffdd8
ebp 0xbffffdd8 0xbffffdd8
esi 0x1 1
edi 0x8097088 134836360
eip 0x80480f7 0x80480f7
eflags 0x246 582
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb)
Или набрать "p/x $eax" , что выведет значение EAX register .
Префикс "E" перед именем регистра говорит о том , что он 32-битный.
Они появились начиная с версии 80386.
Linux использует "flat" и protected-модель памяти, для этого EIP хранит текущий адрес.
(gdb) p/x $eax
$4 = 0x8059200
(gdb)
Команда "p" выводит, "/x" устанавливает 16-ричный формат.
команды "s" или "step" - суть одно и то же - выводят следующую инструкцию.
(gdb) step
32 addb lo_temp,%al
(gdb)
заметим что 92H загружается младший значащий бит регистра EAX
(те в AL ) командой movb .
(gdb) p/x $eax
$6 = 0x8059292
(gdb)
Продолжим....
(gdb) s
33 movb $0,%ah
(gdb) s
34 adcb $0,%ah
(gdb) s
35 movb $2,%bl
(gdb) s
36 idivb %bl
(gdb) s
37 movb %al,av_temp
(gdb) s
38 leave
И если теперь проверить регистр EAX и переменную av_temp after
мы увидим - 72H.
(gdb) p/x $eax
$9 = 0x8050072
(gdb) p/x av_temp
$10 = 0x72
(gdb)
Понятно , что при листинге инструкция выполняется :-)
Теперь другой вариант Hello world , который-таки выводит на экран :
.data # section declaration
msg:
.ascii "Hello, world!\n" # our dear string
len = . - msg # length of our dear string
.text # section declaration
# we must export the entry point to the ELF linker or
.global _start # loader. They conventionally recognize _start as their
# entry point. Use ld -e foo to override the default.
_start:
# write our string to stdout
movl $len,%edx # third argument: message length
movl $msg,%ecx # second argument: pointer to message to write
movl $1,%ebx # first argument: file handle (stdout)
movl $4,%eax # system call number (sys_write)
int $0x80 # call kernel
# and exit
movl $0,%ebx # first argument: exit code
movl $1,%eax # system call number (sys_exit)
int $0x80 # call kernel
Компиляция :
as -o hello.o hello.S
ld -s -o hello hello.o
|
|