GNU ассемблер, или просто gas или as входит в пакет Binutils, а это значит, что он скорее всего уже есть на Вашей Linux. В gas по умолчанию используется AT&T синтаксис, более полно можно изучить его здесь. Впрочем gas позволяет использовать синтаксис Intel и даже порядок аргументов как в синтаксисе Intel, но сейчас речь не об этом. Здесь и далее я буду придерживаться AT&T синтаксиса, как родного для gas. GAS, как и всякий уважающий себя ассемблер имеет мощный макро-язык. Единственный макрос, который присутствует в нашей библиотеке:
.macro fn name
.global \name
.type \name, @function
\name:
.endmРассмотрим его подробнее. Собственно начало макроса .macro, конец макроса .endm, тело располагается посередине. Сразу после директивы .macro следует имя макроса fn и аргументы name, если они нужны (как в нашем случае). Что же за кулисами? Использование аргументов внутри макроса возможно через конструкцию \arg, в нашем случае \name. Первая директива .global \name. .global - это встроенный макрос, который определяет глобальный символ (метку), в нашем случае как раз и нужна глобальная метка. На второй строке используется встроенный макрос .type, который определяет тип метки - в нашем случае это функция. Ни наконец сама метка.
Например для fn SomeName это макрос развернётся в следующую конструкцию:
.global SomeName
.type SomeName, @function
SomeName:Обращаю Ваше внимание, что .global и .type не определяют метки, а только дают им особые свойства. Их вообще можно прописать в начале файла, а саму метку разместить в конце.
Этот макрос облегчает написание функций для нашей библиотеки. Замечу, что макрос .type вовсе не обязателен. Имя метки - это и есть имя функции, и метка эта должна быть глобальной. Например метку some_fn впоследствии можно будет вызвать из C как функцию some_fn().
В общем виде функция выгладит следующим образом:
имя_функции:
# тело функции
ret # возврат из функциинапример функция которая ничего не делает
.global lazy
lazy:
nop # ничего не делает
retв Си будет выглядеть так:
void lazy(void) {
return;
}По поводу параметра -nostdlib, если Вы заглянете в Makefile то увидите, что программа собирается с этой опцией. Эта опция не подключает стандартную библиотеку со всеми вытекающими, т.е. итоговый файл становится намного легче, да ещё и сокращается время запуска. Но вот беда, точка входа - это функция main, как бы не так, если собирать со стандартной библиотекой, то да (если быть точнее - то запускается сначала init, а потом main). Но без неё - точка входа - это метка _start. И если у Вас по каким либо причинам не будет запускаться итоговый исполняемый файл - то переименуйте функцию main в _start. Вообще она называется main только для виду, Вы можете назвать её x или entry например. Во время компиляции Вы увидите:
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400350
Что означает: "не могу найти символ _start; использую по умолчанию 0000000000400350" - это адрес. Т.е. так выходит, что не найдя метки _start компилятор отмечает точку входа и она совпадает (удивительным образом) с началом первой функции. Но, стоит добавить перед этой функцией ещё одну - и ошибка сегментирования гарантирована. Правильно использовать точку входа - функцию _start, если сборка идёт с флагом -nostdlib. Такие аргументы функции main как argc, argv, env не будут присутствовать.
Makefile берёт всю рутину на себя. Синтаксис его прост. Используйте команду make для компиляции и запуска. Если на каком либо этапе произойдёт ошибка, процесс прервётся. Есть одно замечание по поводу команды echo $? - это печать кода выхода последней завершённой программы. Нормально завершившаяся программа возвращает 0, но наша должна возвращать 23. Вы можете запустить её и выяснить с каким кодом она завершилась
./hello
echo $?Порядок передачи параметров в Си функцию: %rdi, %rsi, %rdx, %rcx, %r8, %r9. Справедливо для целочисленных параметров и указателей, для чисел с плавающей точкой используются %xmm-регистры. Более подробно вы можете почитать погуглив: linux x64 abi.
Для передачи параметров системной функции есть различия:%rdi, %rsi, %rdx, %r10, %r8, %r9, номер системного вызова кладётся в %eax.
Как видите в нашей функции os.Exit первый параметр кладётся в %rdi, он же является и первым параметром для системной функции, поэтому никаких перемещений не производится.
Инструкция movq hw@GOTPCREL(%rip), %rsi - что это такое? Дело в том, что во время линковки, не будет известно в по какому адресу будет располагаться та или иная секция библиотеки, как и сама библиотека. Поэтому все символы относительны GOT - Global Offset Table, глобальной таблицы смещений. Если писать код для исполняемого файла, то можно было просто прописать movq $hw, %rsi. Так в регистр %rsi будет положен адрес, на который ссылается hw. Если писать код для разделяемой библиотеки, то hw при ассемблировании будет транслировано в простое число. Но в момент загрузки библиотеки происходят перемещения секций и это число уже будет неактуальным. Поэтому используется форма записи относительно GOT. Переменная hw@GOTPCREL - это указатель на данные, расположенные по метке hw. Запись hw@GOTPCREL(%rip) позволяет не беспокоится ни о чём. Просто: там где в исполняемом файле вы бы записали movq $x, %reg для разделяемой библиотеки стоит писать mpwq x@GOTPCREL, %reg.
На этом всё.
Божественный туториал. В избранное)