Украина, г. Киев |
Hello, world!
При изучении нового языка принято писать самой первой программу, выводящую на экран строку Hello, world!. Сейчас мы не ставим перед собой задачу понять всё написанное. Главное - посмотреть, как оформляются программы на ассемблере, и научиться их компилировать.
Вспомним, как вы писали Hello, world! на Си. Скорее всего, приблизительно так:
Вот только printf(3) - функция стандартной библиотеки Си, а не операционной системы. "Чем это плохо?" - спросите вы. Да, в общем, всё нормально, но, читая этот учебник, вы, вероятно, хотите узнать, что происходит "за кулисами" функций стандартной библиотеки на уровне взаимодействия с операционной системой. Это, конечно же, не значит, что из ассемблера нельзя вызывать функции библиотеки Си. Просто мы пойдём более низкоуровневым путём.
Как вы уже, наверное, знаете, стандартный вывод (stdout), в который выводит данные printf(3), является обычным файловым дескриптором, заранее открываемый операционной системой. Номер этого дескриптора - 1. Теперь нам на помощь придёт системный вызов write(2).
WRITE(2) Руководство программиста Linux WRITE(2) ИМЯ write - писать в файловый дескриптор ОБЗОР #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); ОПИСАНИЕ write пишет count байт в файл, на который ссылается файловый
дескриптор fd, из буфера, на который указывает buf.
Почему sizeof(str) - 1? Потому, что строка в Си заканчивается нулевым байтом, а его нам печатать не нужно.
Теперь скопируйте следующий текст в файл hello.s. Файлы исходного кода на ассемблере имеют расширение .s.
.data /* поместить следующее в сегмент данных */ hello_str: /* наша строка */ .string "Hello, world!\n" /* длина строки */ .set hello_str_length, . - hello_str - 1 .text /* поместить следующее в сегмент кода */ .globl main /* main - глобальный символ, видимый за пределами текущего файла */ .type main, @function /* main - функция (а не данные) */ main: movl $4, %eax /* поместить номер системного вызова write = 4 в регистр %eax */ movl $1, %ebx /* первый параметр - в регистр %ebx; номер файлового дескриптора stdout - 1 */ movl $hello_str, %ecx /* второй параметр - в регистр %ecx; указатель на строку */ movl $hello_str_length, %edx /* третий параметр - в регистр %edx; длина строки */ int $0x80 /* вызвать прерывание 0x80 */ movl $1, %eax /* номер системного вызова exit - 1 */ movl $0, %ebx /* передать 0 как значение параметра */ int $0x80 /* вызвать exit(0) */ .size main, . - main /* размер функции main */
Напомним, сейчас наша задача - скомпилировать первую программу. Подробное объяснение этого кода будет потом.
[user@host:~]$ gcc hello.s -o hello [user@host:~]$
Если компиляция проходит успешно, GCC ничего не выводит на экран. Кроме компиляции, GCC автоматически выполняет и компоновку, как и при компиляции программ на C. Теперь запускаем нашу программу и убеждаемся, что она корректно завершилась с кодом возврата 0.
[user@host:~]$ ./hello Hello, world! [user@host:~]$ echo $? 0
Теперь было бы хорошо прочитать последнюю лекцию про отладчик. Он вам понадобится для исследования работы ваших программ. Возможно, сейчас вы не всё поймёте, но эта лекция специально расположена в конце, так как задумана больше как справочная, нежели обучающая. Для того, чтобы научиться работать с отладчиком, с ним нужно просто работать.