Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Secure Programming for Li...6507 
 Linux Kernel 2.6...5280 
 Trees...1119 
 Максвелл 3...1051 
 William Gropp...991 
 Go Web ...963 
 Ethreal 3...930 
 Ethreal 4...918 
 Gary V.Vaughan-> Libtool...914 
 Ext4 FS...905 
 Clickhouse...902 
 Rodriguez 6...900 
 Ethreal 1...897 
 Steve Pate 1...886 
 C++ Patterns 3...864 
 Assembler...854 
 Ulrich Drepper...844 
 DevFS...788 
 MySQL & PosgreSQL...774 
 Стивенс 9...758 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

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
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье