[C] Broken Sword Ассеблер в *nix – удел извращенца..?
Да, асм в *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). Единственная проблемка – на последних версиях ядер Линукса он не компилируется (вот вам и переносимость С).
Скомпилировать так ничего и не удалось :). Если кто-то вдруг найдет заклинание, по которому 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. использовать данное приложение для шифрования важных данных на диске не рекомендуется ).
...и всей группе HI-TECH! Держитесь, ребята!
|
||||||||