Ассемблер в *nix – удел извращенца..? — Архив WASM.RU
Ассеблер в *nix – удел извращенца..?
A lot of people ask me..
stupid fuckin questions.
A lot of people think that..
ассемблера в *nix не существует.
(С) типа EminemДа, асм в *nix таки существует. Просто многие в это почему-то отказываются верить. Данная статья скромно претендует развеять все мифы и загадки вокруг этого загадочного явления. Не ищите здесь подробного описания AT&T синтаксиса и системных вызовов – я просто попробую описать те трудности и невзгоды, которые обязательно придется преодолеть смельчаку, желающему полностью овладеть *nix-ом через асм (в смысле - научиться программировать на асме под *nix, прим. для CyberManiac-а ).
Миф I:
“Синтаксис асма под *nix в корне отличается от оного под DOS/WIN, он кривой и к нему невозможно привыкнуть”.
Действительно, синтаксис, предложенный компанией AT&T, немного (безусловно, в лучшую сторону) отличается от Intel’овского. Но это ничего не значит. На самом деле, если бы все еще с детства начали кодировать, используя AT&T синтаксис, то интеловский очень скоро загнулся бы и не дожил до наших дней. Просто AT&T в свое время не стремилась делать свои поделки достоянием народных масс, это прерогатива MS и Intel (а вообще у AT&T уже существовала своя развитая культура и традиции, когда MS и Intel только спускались с деревьев ).
А уж если кто-то переборол свои страхи и все же перешел к AT&T с Intel, того точно уже за уши не оттянешь назад. По этому поводу когда-то даже родился проект DJGPP (GNU Binutils) - асм с AT&T синтаксисом под DOS (http://www.delorie.com/djgpp/). Однако в силу непонятных причин проект не прижился.
Если все эти доводы кому-то показались слишком хлипкими и невразумительными, то нет ничего проще - http://sf.net/projects/nasm/. Качайте себе NASM под *nix и будьте с Intel «вместе навсегда».
Миф II:
“Асм под *nix никому не нужен и вообще все это изврат, С – вот единственная дорога в светлое будущее”.
Существует известная в широких кругах поговорка: «Линукс писан программистами для программистов». На самом деле полностью она звучит так: «Линукс писан сишными программистами для сишных программистов». И с этим никто не спорит. Дело в том, что любому, кто попытается сунуться в такой монастырь как *nix со своим уставом (асм), тому суждено упереться в огненные стены и рвы с крокодилами (об этом ниже). Однако не все так трагично – уменьшение размера кода в сто и более раз, увеличение производительности в десятки раз, да и чего греха таить, – само удовольствие, которое может доставить только кодинг на живом языке - все это стоит того чтобы научиться асму под *nix.
Теперь, возможно это кого-то сильно удивит, но программирование на асме под *nix по своему стилю очень напоминает... программирование на оном под DOS (да! Именно под DOS). Многие скажут: *nix – полностью 32-х разрядная ОС, и работает в защищенном режиме, используя flat-модель памяти. При чем здесь 16-битная ОС реального режима DOS? Про такого с уверенностью можно сказать: он просто никогда не писал на асме под *nix. На самом деле можно сделать еще более шокирующее заявление: писать программы на асме в *nix НАМНОГО проще чем под DOS... Самое важное – не сойти “с пути истинного” в самом начале его...
#1: когда только начинаешь писать на асме под *nix то возникает интересное ощущение: вроде бы ты попал в грязный пятибаксовый мотель (из тех, возле которых обязательно проходит метро и когда едет поезд на потолке дрожит дешевая люстра и мигает свет); здесь давно нет горячей воды, обои уродливыми клочьями свисают со стен, с потолка капает какая то мерзкая гадость и пахнет плесенью, все удобства – во дворе... На мотеле (подпертые кем-то неизвестным) стоят уже давно покосившиеся со временем неоновые буквы «*NIX для ассемблерщиков» (половина букв давно не горит, а половина с треском догорает). У мотеля нет своих постояльцев. Сюда заезжают лишь переночевать, чтобы на следующее утро убраться подальше...
Самое мерзкое во всем этом то, что через единственное окно в этой конуре, через дорогу, как будто специально, вырос семизвездочный отель, весь в рекламе, бассейнах и пальмах... Прямо над входом (к которому то и дело поминутно подъезжают все более и более крутые тачки) сверкает золотом надпись: “*NIX для сишников”. Вон видно как по террасам ходят пузатые мужики в обнимку с дорогими бабами, потягивая коктейли и куря сигары, им прислуживает армия официантов и слуг; все они смеются и живут.
Всем им наплевать на мотель напротив...
Но в миру ходят легенды, что в том самом мотельчике существует некая потайная дверь, которая открывает путь в Вечное... Ради этой двери мотель и стоит. По крайней мере, ручеек из желающих приобщиться к Вечности никогда не пересыхает.
Я говорю к тому, что ассеблерщик, сунувшийся в *nix не найдет практически никакой документации, описывающей системные вызовы на низком уровне. Здесь (http://www.lxhp.in-berlin.de/lhpsyscal.html) об этом можно получить кое-какую захудалую информацию, но многие функции описаны неправильно, либо вообще не описаны. Иногда порядок расположения параметров в регистрах при передаче в ту или иную функцию приходиться подбирать буквально вручную, методом научного тыка. Но на самом деле настоящего асм-кодера все это может только раззадорить..
Для вызова любой системной функции используется команда INT 80h (вспомните DOS – там для этой цели использовался INT 21h). Параметры передаются через регистры. Номер функции – в АХ. Вся проблема в том, что найти полное описание того, в каком регистре какой параметр и для чего передается крайне проблематично, ресурс, указанный выше частично решает эту проблему.
Когда я говорил про все неземные блага, которые предоставляются для сишников, я имел ввиду полную документированность любой запятой, с которой только можно встретиться в увлекательном процессе программирования на С под *nix.
#2: вот это действительно самый большой фак: за всю историю существования *NIX никто не написал ни одного стоящего отладчика асм-кода (а может и написал, но не захотел поделиться с общественностью). Все что удалось найти (был перерыт буквально весь Интернет и опрошены десятки знающих людей) – ALD (Assembly Linux Debugger). Все что про него можно сказать – да, он действительно чем-то круче MS debug-а. Вот только чем именно - сказать довольно сложно. Все остальные отладчики дальше С-шного кода ничерта не видят. Писать программы без отладчика (а тем более на асме) – это верх извращенческого гения.
Конечно, существует еще и скромный аналог сайса под Линукс – PrivateICE (http://sourceforge.net/projects/pice). Единственная проблемка – на последних версиях ядер Линукса он не компилируется (вот вам и переносимость С).
Прим.: 9 июня 2003 года на sourceforge появился новый билд pice-а. Однако вот что пишет сам автор: Since this project was abandoned a few years ago there is no active maintainence. [skipped]. The files provided here are as it is and there is no garuantee that they will compile. [skipped]. Of course you are welcome to send in bug reports or comments on this code, just don't expect that they can be compiled out of the box. These sources will compile on 2.4.18. They might give problems with non-SMP enabled kernels.
Скомпилировать так ничего и не удалось . Если кто-то вдруг найдет заклинание, по которому pice можно скомпилировать под последние ASP, Mandrake или RedHat– сообщите плз на ящик внизу.
#3: проблема заголовочных файлов. Чуть ли не большую часть времени желающему покодить на асме в *nix придется провести за увлекательным поиском необходимой информации по заголовочным файлам. Искать придется практически все буквенные названия, которые могут встретиться в процессе (это и названия самих системных вызовов, и параметров, передаваемых в них, и вообще любых переменных). Все они как будто специально порастасканы по тысячам *.h – файлов, которые находятся в самых неожиданных местах. Сишнику в семизвездочном отеле достаточно всего лишь щелкнуть пальцем и сделать include ….h – все работает. Ассемблерщику в мотелишке напротив – сначала понять к чему тот или иной параметр, затем устроить поиск по всем системным директориям, найти заголовочный файл, содержащий этот параметр, затем скопировать его в «свой», который будет понимать GAS или NASM (или, если ассемблерщик попался реальный, то запомнить его, и везде использовать численные значения параметров ?), а в довершение еще и усадить в правильный регистр перед отправкой в недра функции.
#4: прога, написанная для *nix на асме теряет переносимость на другие *nix-платформы. Для перекомпиляции под другую *nix платформу придется изрядно повозиться, в некоторых случаях проще переписать заново весь код, чем переделывать старый с Linux под BSD. Но BSD пока не так распространен, а самые попсовые версии линуксов (Red Hat, Mandrake, ASP) используют одно и то же ядро, и данный фак вообщем-то не так уж страшен как его малюют.
Ну вот, а теперь вы сами убедитесь, что писать программы на асме под Линукс не сложнее чем под DOS, а может даже и проще.
Рассмотрим пример написания простейшего клиент-серверного приложения, использующего в качестве взаимодействия стек протоколов TCP/IP (подразумевается, что вы более-менее знакомы с сетевым программированием, и знаете хотя бы, что такое сокет).
Клиентское приложение посылает серверу символьную строку; Сервер шифрует символьную строку по следующему алгоритму шифрования: выполняет замену одного символа – буквы, на символ располагающийся на две позиции правее в алфавите, для последнего и предпоследнего символов в алфавите выполняется кольцевой сдвиг, например, для «Y» это будет буква «А» для «Z» - «В» (это шифр Цезаря). Сервер отправляет зашифрованное сообщение обратно; Клиент выбирает шифрограмму и выводит на экран.
Сообщения, подлежащие шифрованию, вводятся с клавиатуры. Программа сервера работает в бесконечном цикле.
Ну что ж, начнем с сервера. Я по ходу пьесы попытаюсь максимально комментировать происходящее. Начну с того, что наш сервер будет демоном (для пущего понту). Что такое демон? Демон на языке DOS-ассемблерщиков – резидент. Все. Так что ничего страшного.
# ******************************************************* # Server (daemon) # by Broken Sword [HI-TECH] # (for Linux based on Intel x86 only) # brokensword@mail.ru # www.wasm.ru # Compile Instructions: # ------------------------------------------------------- # as server.s # ld --strip-all -o server a.out # ******************************************************* # ******************************************************* # этот файлик содержит выдранные из какого-то файла # определения системных вызовов (см. FUCK #3) .include "syscalls.inc" # а этот – все остальные, которые только могут # встретиться .include "def.inc" .text # начало сегмента кода # метка, с которой все начинается (нужно чтоб она была # глобальной) .globl _start _start: # итак, начинаем лепить нашего демона # процесс создания демона в *.nix и создание резидента в DOS в корне различаются # начинается любой демон с того, что нужно создать дочерний процесс. # Создать дочерний процесс в линуксе проще # пареной репы – достаточно поместить номер сис. # вызова в EAX и сделать «а-ля int 21h», т.е. int 80h movl $SYS_fork,%EAX int $0x80 # все. # Теперь у нас параллельно сосуществуют ДВА процесса: # родительский (в котором исполнялись все предыдущие # команды) и дочерний. Что же содержит дочерний код? # А все то же самое, что и родительский. # Т.е. важно понять, что # весь нижеследующий (и выше тоже) # код находиться в памяти в ДВУХ разных местах. # Как процессор переключается между # ними (и всеми остальными живыми процессами) # – читайте «Переключение задач» в интеловском мануале. test %EAX,%EAX # вот эту команду необходимо осознать. # Прежде всего, важно понять, что данная команда # существует и в родительском # и в дочернем процессах (об этом выше). # Следовательно выполниться она и там и там. # Все дело в том, что после # int 80h родительскому процессу вернется PID сына # (в EAX ессесно, вообще все возвращается в EAX, как и в винде) # а что же вернется сыне? Правильно, нолик. # Именно поэтому следующий jmp будет выполнен # в дочернем процессе и # не будет выполнен в родительском. # ребенок улетает на метку _cont1 jz _cont1 # ...а в это время, в родительском процессе: xorl %EBX,%EBX # EBX=status code xorl %EAX,%EAX # incl %EAX # SYS_exit # завершаем родительский процесс. int $0x80 # Теперь все дети # управляются процессом INIT _cont1: movl $SYS_setsid,%EAX # сделаем нашего ребенка главным в группе int $0x80 movl $1,%EBX # SIGHUP movl $1,%ECX # SIG_IGN movl $SYS_signal,%EAX # далее сигнал SIGHUP будет игнорироваться int $0x80 movl $SYS_fork,%EAX # наш ребенок уже подрос и теперь сам может родить сына int $0x80 # (по сути – это уже внук нашему изначальному # родительскому процессу) # EAX=0 в дочернем и EAX=PIDдочернего в родительском test %EAX,%EAX jz _cont2 # внук нашего родительского (которого уже давно нет в # живых) улетает на метку _cont2, однако отец все еще # жив!!! (все как в мексиканском сериале) xorl %EBX,%EBX # EBX=status code xorl %EAX,%EAX # incl %EAX # SYS_exit int $0x80 # вот уже и отец отправлен к деду на небеса (да, # злостная программка, недаром демоном зовется) # ..а в это время внучок получает все наследство и _cont2: # продолжает жить # далее, после того, # как все кровавые разборки и отцеубийства благополучно завершены, # внучок,продавший душу демону, # преспокойно создает сокет. # Дело в том, что в линуксе есть такие системные вызовы, # для вызова которых их номер не # помещается в EAX. # Вместо этого в EAX помещается номер функции-мультиплексора, # реализующий вызов конкретной # функции номер которой помещается в EBX. # Так, например, происходит при вызове IPC и SOCKET-функций. # Кроме того, # при вызове SOCKET-функций параметры располагаются не в регистрах, # а в стеке. Смотри как все просто: pushl $0 # протокол pushl $SOCK_STREAM # тип pushl $AF_INET # домен # ECX должен указывать на кадр в стеке, содержащий movl %ESP,%ECX # параметры, такая уж у него судьба... movl $SYS_SOCKET,%EBX # а вот это уже номер той самой конкретной функции # SOCKET – создать сокет movl $SYS_socketcall,%EAX # в EAX - номер функции мультиплексора (по сути он # просто перенаправит вызов в функцию, указанную в EBX int $0x80 # сокет создан! Ура товарищи. В EAX возвратиться его дескриптор. # «очистим» стек (по сути это выражение придумано специально # для HL-программистов, на самом деле ничего не # очищается, данную операцию необходимо производить только # для того чтобы в дальнейшем не произошло переполнение # стека, но в таких маленьких программках это делать вовсе # не обязательно): addl $0xC,%ESP movl %EAX,(sockfd) # сохраним дескриптор созданного сокета в переменной # sockfd # далее необходимо осуществить операцию BIND, # которая называется «привязка имени сокету», # хотя суть этого названия # слабо отражает смысл происходящего на самом деле. # На самом деле BIND просто назначает конкретному сокету IP- # адрес и порт, через который с ним можно взаимодействовать: # размер передаваемой структуры (вообще подобран pushl $0x10 # методом тыка, потому что логически непонятно почему # именно 16) # указатель на структуру sockaddr_in pushl $sockaddr_in # дескриптор нашего сокета pushl %EAX # ECX указывает на параметры в стеке movl %ESP,%ECX # номер функции BIND – в EBX movl $SYS_BIND,%EBX # функция-мультиплексор movl $SYS_socketcall,%EAX int $0x80 # теперь сокет «привязан» к конкретному IP-шнику и порту # поднимем ESP на место addl $0xC,%ESP # далее что-либо подробно описывать я не вижу смысла, # любой желающий сам без труда разберется, опираясь на # полученные выше знания. pushl $0 # backlog movl (sockfd),%EAX pushl %EAX movl %ESP,%ECX movl $SYS_LISTEN,%EBX movl $SYS_socketcall,%EAX int $0x80 addl $0x8,%ESP _wait_next_client: pushl $0 # addrlen pushl $0 # cliaddr movl (sockfd),%EAX pushl %EAX # sockfd movl %ESP,%ECX movl $SYS_ACCEPT,%EBX movl $SYS_socketcall,%EAX int $0x80 addl $0xC,%ESP movl %EAX,(connfd) movl $SYS_fork,%EAX int $0x80 # create child process test %EAX,%EAX jnz _wait_next_client _next_plain_text: movl (connfd),%EBX movl $buf,%ECX # ECX->buf movl $1024,%EDX # 1024 bytes movl $SYS_read,%EAX int $0x80 # wait plain_text movl $buf,%ESI movl %ESI,%EDI movl %EAX,%ECX movl %EAX,%EDX _encrypt: lodsb cmp $0x41,%AL # A jb _next cmp $0x5A,%AL # Z ja _maybe_small incb %AL incb %AL # encryption ;) cmp $0x5A,%AL jle _next sub $26,%AL _maybe_small: cmp $0x61,%AL # a jb _next cmp $0x7A,%AL # z ja _next incb %AL incb %AL # encryption ;) cmp $0x7A,%AL jle _next sub $26,%AL _next: stosb loop _encrypt movl (connfd),%EBX movl $buf,%ECX # ECX->chiper_text movl $SYS_write,%EAX int $0x80 # send plain_text jmp _next_plain_text # ***************************************************** .data sockfd: .long 0 connfd: .long 0 sockaddr_in: sin_family: .word AF_INET sin_port: .word 0x3930 # port:12345 sin_addr: .long 0 # INADDR_ANY buf: # ***************************************************** #Клиент пишется по аналогии с сервером, # думаю сами без труда разберетесь: # ********************************************************* # Client # by Broken Sword [HI-TECH] # (for Linux based on Intel x86 only) # brokensword@mail.ru # www.wasm.ru # Compile Instructions: # --------------------------------------------------------- # as client.s # ld --strip-all -o client a.out # ********************************************************* # ********************************************************* .include "syscalls.inc" .include "def.inc" .text .globl _start _start: pushl $0 # protocol pushl $SOCK_STREAM # type pushl $AF_INET # domain movl %ESP,%ECX movl $SYS_SOCKET,%EBX movl $SYS_socketcall,%EAX int $0x80 addl $0xC,%ESP movl %EAX,(sockfd) pushl $0x10 # addrlen pushl $sockaddr_in pushl %EAX # sockfd movl %ESP,%ECX movl $SYS_CONNECT,%EBX movl $SYS_socketcall,%EAX int $0x80 addl $0xC,%ESP _next_plain_text: xorl %EBX,%EBX # stdin movl $buf,%ECX # ECX->buf movl $1024,%EDX # 1024 bytes movl $SYS_read,%EAX int $0x80 # read from stdin movl (sockfd),%EBX movl $buf,%ECX # ECX->plain_text movl %EAX,%EDX # bytes read movl $SYS_write,%EAX int $0x80 # send plain_text movl $SYS_read,%EAX int $0x80 # wait chiper_text xorl %EBX,%EBX incl %EBX # EBX=1 (stdout) movl $SYS_write,%EAX int $0x80 # disp chiper_text jmp _next_plain_text # ********************************************************* .data sockfd: .long 0 sockaddr_in: sin_family: .word AF_INET sin_port: .word 0x3930 # port:12345 sin_addr: .long 0x0100007F # 127.0.0.1 buf: # *********************************************************Вот так вот все просто (вся сложность на самом деле заключена в понимании стека TCP/IP, а не в том, как закодировать все эти действия на асме).
Все - запускайте бесов server, а за ним client.
p.s. использовать данное приложение для шифрования важных данных на диске не рекомендуется ).
Благодарности (в алфавитном порядке ):
Aquila [HI-TECH]
CyberManiac [HI-TECH]
Edmond [HI-TECH]
Vladimir [HI-TECH]...и всей группе HI-TECH! Держитесь, ребята!
© Broken Sword
Ассемблер в *nix – удел извращенца..?
Дата публикации 1 сен 2003