Структурно, программа состоит из секции кода, объявленной директивой ".text" и секции данных (".data"), которые могут располагаться в любом порядке, на размер сгенерированного файла это никак не влияет, все равно линкер переставит их по-своему.
Объявлять вызываемые libc-функции "внешними" (директива ".extern"), как это советует целый ряд авторов, совершенно необязательно. Имена функций пишутся как они есть, то есть без всяких там символов прочерка, на которые в частности ссылается Зубков в своей книге "Assembler — язык неограниченных возможностей", дескать иначе под BSD программа ассемблироваться не будет. Ничего подобного! Все работает только так!
Точка входа в программу означается меткой main, которая обязательно должна быть объявлена как global. В действительности, при запуске программы, первым управление получает стартовый код библиотеки libc, который уже и вызывает main. Если же такой метки там не окажется, линкер сообщит о неразрешимой ссылке и все.
Выходить из main можно как по exit(err_code), так и по машинной команде RET, возвращающей нас в стартовый код, корректно завершающий выполнение. Это короче, однако, в последнем случае мы теряем возможность передавать код возврата, который можно "подсмотреть" командой "echo $?"
после завершения работы программы.
Согласно Си-соглашению, аргументы функций заносятся в стек справа налево, стек "чистит" вызывающий код. Вот, собственно, и все. С полученным "багажом" знаний уже можно писать программу. В нашем случае она будет выглядеть так (см. листинг 1).
.text
// используемые функции объявлять внешними необязательно
//.extern write
//.extern exit
.global main
main:
pushl $len
pushl $msg
pushl $1
call write
addl $12, %esp
ret
.data
msg: .ascii "hello,elf\n"
len = . - msg