Цель - осилить 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, а аргумент всего один - статус завершения. В данном случае ноль - процесс завершился успешно.
Версия первая, с функцией 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:
Ссылки по теме: