- Broken Sword, 20.10.03, Wasm.ru -
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! Держитесь, ребята!
Примеры к статье.
[C] Broken Sword
|