суббота, 25 февраля 2012 г.

64-битный ассемблерный helloworld в linux

Цель - осилить 64-битный linux ассемблер.
Литературы и примеров под 64 бита сильно меньше, чем под 32. Поэтому начата попытка адаптации некоторых под x86_64. Для начала был написан helloworld.


Здесь описан тот же код, что и в викиучебнике и http://callumscode.com/blog/3
От первого отличается использованием 64-битного ABI , а от второго использованием GNU Assebler вместо nasm.

Что надо сделать, чтобы вывести на экран строку? Надо сделать системный вызов WRITE. Чтобы его сделать, необходимо знать следующие вещи: номер системного вызова и способ передачи аргументов. То, как передается номер системного вызова и его параметы описано в документе System V Application Binary Interface AMD64 Architecture Processor Supplement (A.2.1 Calling Conventions)

В данном случае:
  • Номер системного вызова - 1, передается через регистр %rax
  • Первый аргумент передается через регистр %rdi. Это номер файлового дескриптора. В данном случае это 1, т.к. используется стандартный поток вывода
  • Второй аргумент - указатель на строку, передается через регистр %rsi
  • Третий аргумент - длина строки, через регистр %rdx
  • После установки аргументов вызываем команаду syscall для системного вызова(К.О.)
Для корректного выхода из программу нужно повторить все это для системного вызова EXIT. Его номер - 60, а аргумент всего один - статус завершения. В данном случае ноль - процесс завершился успешно.

Номера 64-битных системных вызовов можно найти в системе /usr/include/asm/unistd_64.h или например здесь http://www.acsu.buffalo.edu/~charngda/linux_syscalls_64bit.html

Версия первая, с функцией main и прилинкованной стандартной библиотекой. Это фактически клон из викиучебника, поэтому за пояснениями можно обратиться туда.

/* GNU Assembler Hello World 64 bit edition */
/* Compile: gcc hello_world_64_with_main.s -o hello_world_64_with_main.bin 
 * Run: ./hello_world_64_with_main.bin
 */
/*
 * Начало сегмента данных
 */
.data
/*
 * Строка, которую будем печатать. Точнее метка указывающая
 * на ее начало.
 */
hello_str:
        /*
         * А это сами данные строки.
         */
        .string "Hello, 64-bit world!\n"
        /*
         * Вычисление длины строки и помещение ее(длины) в символ
         * hello_str_length. -1 нужен, чтобы не выводить нулевой
         * символ, который вставляет .string
         */
        .set hello_str_length, . - hello_str - 1

/*
 * Начало сегмента кода.
 */
.text

/*
 * Объявляем main глобавльным символом.
 */
.globl  main                  
.type   main, @function             /* main - функция (а не данные)       */
main:
        movq    $1, %rax            /*  поместить номер системного вызова 
                                        write = 1 в регистр %rax */     
        movq    $1, %rdi            /*  первый параметр - в регистр %ebx;
                                        номер файлового дескриптора 
                                        stdout - 1  */

        movq    $hello_str, %rsi    /*  второй параметр - в регистр %rsi;
                                        указатель на строку            */
        movq    $hello_str_length, %rdx /* третий параметр - в регистр
                                           %rdx; длина строки       */
        syscall             /*  системный вызов */
        movq    $60, %rax   /* номер системного вызова exit - 60   */
        movq    $0, %rdi    /* передать 0 как значение параметра  */
        syscall             /* вызвать exit(0)                    */
        .size   main, . - main    /* размер функции main            */
/*---------------------------end---------------------------------------------*/

Компилируем:
$ gcc hello_world_64_with_main.s -o hello_world_64_with_main.bin
Запускаем:
$  ./hello_world_64_with_main.bin
Hello, 64-bit world!
Смотрим, от каких разделяемых библиотек зависит программа:
$ ldd hello_world_64_with_main.bin 
        linux-vdso.so.1 =>  (0x00007fff3eeff000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f49fc054000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f49fc3bc000)
Смотрим размер файла:
$ stat hello_world_64_with_main.bin 
  File: `hello_world_64_with_main.bin'
  Size: 7850            Blocks: 16         IO Block: 4096   regular file
7850 - байт

Версия вторая, без функции main, без стандартной библиотеки.
/* GNU Assembler Hello World 64 bit edition */
/* Compile: gcc -nostdlib hello_world_64_without_main.s -o hello_world_64_without_main.bin 
 * Run: ./hello_world_64_without_main.bin
 */
/*
 * Начало сегмента данных
 */
.data
/*
 * Строка, которую будем печатать. Точнее метка указывающая
 * на ее начало.
 */
hello_str:
        /*
         * А это сами данные строки.
         */
        .string "Hello, 64-bit world!\n"
        /*
         * Вычисление длины строки и помещение ее(длины) в символ
         * hello_str_length. -1 нужен, чтобы не выводить нулевой
         * символ, который вставляет .string
         */
        .set hello_str_length, . - hello_str - 1

/*
 * Начало сегмента кода.
 */
.text

/*
 * Объявляем _start глобавльным символом.
 */
.globl  _start                  
.type   _start, @function             /* _start - функция (а не данные)       */
_start:
        movq    $1, %rax            /*  поместить номер системного вызова 
                                        write = 1 в регистр %rax */     
        movq    $1, %rdi            /*  первый параметр - в регистр %ebx;
                                        номер файлового дескриптора 
                                        stdout - 1  */

        movq    $hello_str, %rsi    /*  второй параметр - в регистр %rsi;
                                        указатель на строку            */
        movq    $hello_str_length, %rdx /* третий параметр - в регистр
                                           %rdx; длина строки       */
        syscall             /*  системный вызов */
        movq    $60, %rax   /* номер системного вызова exit - 60   */
        movq    $0, %rdi    /* передать 0 как значение параметра  */
        syscall             /* вызвать exit(0)                    */
/*---------------------------end---------------------------------------------*/

Компилируем:
$ gcc -nostdlib hello_world_64_without_main.s  -o hello_world_64_without_main.bin
Запускаем:
$ ./hello_world_64_without_main.bin
Hello, 64-bit world!
Смотрим от каких разделяемых библиотек зависит программа:
$ ldd hello_world_64_without_main.bin
        not a dynamic executable
Опа, а ничего ей не нужно.

Смотрим размер файла:
$ stat hello_world_64_without_main.bin
  File: `hello_world_64_without_main.bin'
  Size: 1003            Blocks: 8          IO Block: 4096   regular file
Почти в 8 раз меньше.
Пока наверное все.
Код доступен на github:

Ссылки по теме:




Комментариев нет:

Отправить комментарий