Ассемблер для самых маленьких План: 1) Введение 2) Пару слов про асм 3) Tasm 4) Регистры, сегменты 5) Команды асма 6) Первая прога 7) Разбор программы 8) Функции 9) Стек 10) Команды перехода 11) Циклы 12) Процедуры 13) Include 14) Написание программы, используя полученные знания 15) Вывод Начнем. I Введение Здравствуйте, Античатовыцы. Эту статью я хочу посвятить тем, кто хочет изучать ассемблер (т.е. тем, кто еще не начал, ну или начал, но совсем чуть-чуть). На нашем форуме я не обнаружил ни одной статьи для начинающих учить ассемблер. Есть много постов, где выкладывают книги по асму, так же есть тема, где новички задают вопросы, и статья про cracking, но именно статьи про программирование на асме для новичковнет. Именно это побудило меня её написать. Для начала я хочу сказать, что эта статья только для новичков, так как в ней нету ничего сложного. Просто, например, когда я начинал учить асм, при прочтении книжек я много чего не понимал, почему это именно сбда и так далее. В этой статье я же постараюсь объяснить как можно более простым языком, что к чему. И последнее во введении, я бы написал, что это моя первая статья, и что я новичок, и что не надо тапками кидать, но меня это жутко бесит, поэтому исправляйте и дополняйте меня пожалуйста, но не обзывайтесь, если можете... Это немного обидно) II Пару слов про асм Язы́к ассе́мблера (в отечественных источниках, также автокод) — язык программирования низкого уровня, мнемонические команды которого (за редким исключением) соответствуют инструкциям процессора вычислительной системы. Трансляция программы в исполняемый машинный код производится ассемблером (от англ. assembler - сборщик) - программой-транслятором, которая и дала языку ассемблера его название. (источник Википедия) III Tasm Что нам понадобится. Tasm.exe (сам ассемблер), tlink.exe (линковщик), rtm.exe. Вот этих 3 программок хватит. В конце статьи я выложу ссылки на эти программки. Думаю все уже догадались, что всё мы будем делать в Tasm'е) И хоть многие говорят, что Tasm фигня, и т.д., ИМХО ТАСМ -- удобная штука. Вобщем я дам толчок, а дальше Вы уже сами решайте какой ассемблер Вам использовать. Чтобы было удобнее компилировать программы, можете написать небольшой *.bat файл, с содержанием, подобным этому: bin/tasm название_файла.asm > asm_log.txt bin/tlink название_файла.obj > link_log.txt Думаю не сложно догадаться, что этот файл делает =) IV Регистры, сегменты Наверно большинство тех, кто сейчас это читает, знают, что почти всё в программировании на ассемблере связано с регистрами. Прежде всего нужно знать, что регистр -- это сверхбыстрая оперативная память (СОЗУ) внутри процессора. Различают несколько видов регистров: регистры общего назначения (eax/ax/ah/al и т.д.), сегментные регистры (cs, ss, ds и т.д.) и регистры состояния и управления (eip/ip, eflags/flags). Регистры общего назначения используются для хранения промежуточных данных, таких как: операдны* логических и арифметических операций, указатели на ячейки памяти и так далее. Примером могут послужить следующие регистры: регистр-аккумулятор (EAX) -- применяется для хранения промежуточных данных (немного позже Вы увидите как он используется); базовый регистр (EBX) -- применяется для хранения базового адреса некоторого обьекта в памяти (если не поняли, особо не думайте об этом =) ); регистр-счетчик (ECX) -- регистр используется в основном для организации циклов (о циклах будет рассказано позже); регистр данных (EDX) -- так же как и регистр EAX, хранит промежуточные данные. Среди всех регистров общего назначения, хочется уделить немного больше внимания регистру ESP. Его не стоит использовать для хранения промежуточных данных, так как в нем хранится указатель на вершину стека (про стек будет дальше). Еще хочется отметить, что регистры общего назначения разделяются на несколько меньших, ну... вобщем вот пример: EAX (регистр-аккумулятор) -- 32 бита, его 16-ти байтный регистр -- AX, который, в свою очередь, разделяется на регистры AH и AL, по 8 байт. (EBX/BX/BH/BL; ECX/CX/CH/CL и т.д.). Сегментные регистры. В процессорах Intel поддерживается сегментная организация программы. Любая программа состоит из трех регистров: стека, данных и кода. К каждому сегменту относится определённый регистр. Всего существует 6 сегментных регистров: CS, SS, DS, ES, GS, FS. Сегмент кода -- думаю не сложно догадаться, что именно в этом сегменте содержатся все команды программы =) Особо заморачиваться в начале с этим не надо, просто запомните, что сегменту кода соответствует регистр CS. Что с этим делать я покажу позже. Сегмент данных -- тут содержатся данные, которые обрабатываются командами, которые находятся в сегменте кода. Сегменту данных соответствует регистр DS. Сегмент стека -- про стек расскажу чуть позже, пока запомните, что сегменту стека соответствует регистр SS. Дополнительный сегмент данных -- думаю название говорит само за себя, особо об этом сегменте не думайте. Этому сегменту соответствуют остальные сегментные регистры (ES, GS, FS). Регистры состояния и управления. В процессор включены два регистра, которые содержат информацию о самом процессоре, и о программе, которая сейчас выполняется. Регистр-указатель -- EIP/IP; регистр флагов -- EFLAGS/FLAGS. С помощью этих регистров можно управлять состоянием процессора. Тут можно много чего писать, так что если Вам, дорогой читатель, интересно, прочитайте сами. *операнд -- это такая штука, обозначающая объекты, над которыми производятся действия. V Команды ассемблера Думаю те, кто читает эту статью не раз видели ассемблер "в лицо" ) Система команд в этом языке программирования не так сложна, как кажется. Главное запомнить, что в конце каждой строки не обязательно ставить точку с запятой (хотя это может только у меня такая проблемма после си), так как точка с запятой -- это комментарии. Ни для кого не секрет, что внутри компьютера всё записывается как нули и единички (образно говоря), т.е. в двоичном коде. Программирования первых компьютеров именно так и осуществлялось, с помощью только нулей и единиц. Вскоре люди поняли, что это не очень удобно использовать только нолики и единички, поэтому был придуман язык ассемблера, как симолический аналог машинного языка. Отсюда можно сделать вывод, что машинные команды -- это определенно сформированная последовательность нулей и единиц. Что бы было более понятно, приведу пример самой распространенной команды: mov ebx, eax* Эта команда копирует содержимое регистра eax в регистр ebx. Такая же машинная команда будет выглядеть так: 8B D8 Значение 8B -- код операции. Вот еще один пример: mov ecx, 128 Эта команда записывает в регистр ecx десятичное число 128. Такая же машинная команда выглядет так: B9 00000080 Как вы видите, несмотря на то, что команда в обеих примерах одна и та же (MOV), коды машинных команд разные. Отсюда можно сделать вывод, что большинство команд ассемблера могут по разному переводится в машинные команды, это зависит от операндов, с которыми они работают. *mov приемник, источник. Запомните эту команду, она вам очень понадобится. VI Первая программа Итак, пришло, наконец-то, время для написания нашей первой программы. Это будет самая простая программка "Hello Antichat!". Думаю вы поняли, что она будет делать (кто не понял, объясняю, это программка будет выводить сообщение "Hello Antichat!"). Я сразу выложу вам код, а после него мы разберем эту программку. Code: MODEL SMALL ; Объявляем модель памяти STACK 256 ; Объявляем размер стека DATASEG ; Начало сегмента данных msg db 'Hello Antichat! $' ; создаем переменную msg с текстом "Hello Antichat!" CODESEG ; Начало сегомента кода start: ; "вход в главную часть" mov ax, @data ; связываем сегмент данных mov ds, ax ; с регистром ds mov dx, offset msg ; в регистр dx аддрес переменной msg mov ah, 09h ; в регистр ah 09h int 21h ; прерывание №21 mov ah, 1h ; в регистр ah - 1h int 21h ; прерывание 21 mov ah, 04ch ; в регистр ah 04ch int 21h ; прерываение 21 end start ;закончить "главную часть" VII Разбор программы Итак, теперь по порядку. 1 строка -- эта команда определяет модель сегментации программы, чтоб сильно Вас не нагружать скажу в двух словах, для небольших программок (а мы пока что такие и пишем), используйте модель SMALL (т.е. код занимает один сегмент, данные объеденены в одну группу с именем DGROUP.) В конце статьи я укажу ссылки, и литературу, где можно об этом прочитать. 2 строка -- в комментарии уже сказал, добавить нечего, объявляем размер стека 4 строка -- создаем переменную msg типа db. На этом мне бы хотелось немного остановится. Совсем капельку. Существует три типа переменных db (Data Byte), dw (Data Word) и dd (Data Doubleword). Что ж, я, например, в большнистве случаев использую db и, пока-что, в начале обучения, советую тоже использовать db. Вы спросите, зачем знак доллара в конце строки? Я отвечу, знак доллара мы пишем, чтоб можно было использовать удобную функцию ассемблера для вывода строки. Об этом я скажу позже. 7-8 строка -- помните я говорил, что существуют сегментные регистры, и что каждый из них соответствует определенному сегмунту? Так вот, в этих двух строках, используя промежуточный регистр ax, вы записываем в регистр ds физический адресс сегмента данных. 9 строка -- в регистр dx помещаем адресс переменной msg, почему именно так, я расскажу слудеющем разделе. 10 строка -- в ah записываем 09h, зачем это делать, я опять таки расскажу в следующем разделе. 11 строка -- операция прерывания дос. Int сокращенно от interupt, что переводится как прерывание. 12 строка -- в регистр ah 04ch 13 строка -- операция прерывания 14 строка -- закончить "главную часть" Думаю стало немного более понятно. VIII Функции Когда я начинал учить ассемблер, мне было не понятно, почему именно те числа помещаем именно в тот регистр, и почему именно то, помещаем сюда. Объясняю для таких, как я =) В ассемблере тоже есть функции, с некоторыми из них мы встречались в шестом разделе, когда писали первую программку. Давайте вспомним: 1) mov dx, offset msg mov ah, 09h int 21h 2) mov ah, 04ch int 21h Итак, как я уже говорил, в ассемблере существуют функции. В нашем случае первая функция -- это вывод информации на экран. Вторая -- выход из программы. Давайте разберем подробней несколько функций. Функция вывода строки на экран: в ah надо поместить 09h, а в dx то, что надо выводить, в нашем случае, мы поместили в регистр dx адресс нашей строки, так как наша строка слишком длинная для помещения её в этот регистр. Что бы использовать функцию 09h, надо, чтоб в конце строки был символ доллара (вот мы и узнали зачем он там). Функция выхода из программы: в ah нужно поместить значение 04ch, и вызвать операцию прерывания. Eщу одна функция вывода строки (мы её не использовали, так как она сложнее той, что мы использовали): в регистр dx -- то, что надо вывести в регистр cx -- количество выводимых символов в регистр bx -- устройство, куда записывать, в нашем случае 1 в регистр ah -- 40h, сам код этой функции Считать символ (там много чего запутано, будем пока его использовать, что после выполения программы, окно доса не закрывалось, а ждало пока мы нажмем клавишу) в ah -- 1h в al -- появится символ, который вы ввели. Ну...я думал дать еще пару функций, потом передумал, так как решил, что они вам пока не нужны =) Как вы уже заметили, после того, как всё помещено по нужным регистрам, нудо вызвать прерывание. Запомните это. Такс, с функциями слегка разобрались. Пойдем дальше. IX Стек Нууу, стек -- это такая область памяти. Адресацией в стеке занимается регистр es. В основном, стек используется для временного хранения содержимого регистров. Стоит помнить, что именно для ВРЕМЕННОГО. Стек работает по принципу LIFO, Last In Firs Out. Т.е. последний элемент, который положили в стек, должен первым оттуда выйти. Примером может послужить обойма автомата. Думаю все когда нибудь держали в руках обойму от автомата. В школе, в старших классах на подготовке к армии должны были. Первая пуля, которая была туда засунута вылетит последней, а последняя засунутая -- первой. Команды для работы со стеком следующие: push -- положить в стек pop -- вытащить из стека Как пример, возьмем кусок кода программы: blah-blah ; какие-то действия mov ax, 21 ; в регистр ax число 21 push ax ; значение регистра ax в стек mov ah, 09h ; какие то оперции, в которых замешан регистр ax blah-blah pop ax ; вытащить из стека В результате, в регистре ax будет число 21. Думаю и со стеком разобрались. Повторяю, я не углубляюсь в каждую из тем, я просто хочу дать первый толчок людям, которые начали изучать ассемблер. X Команды перехода Ну а как же без них. Хех. Благодаря командам перехода, на ассемблере мы можем программировать типовые управляющие структуры, if-else и так далее. Но для этого надо познакомится с командой cmp. Те, кто учил си, должны знать, что в си есть функция strcmp, которая сравнивает строки. Подобная команда есть и в асме. CMP (сокращенно от compare) команда, которая сравнивает два значение. Синтаксис у неё следующий: cmp операнд1,операнд2 Следующим этапом, чтобы понятькоманды перехода, надо понять, что такое метки. Метки, в языке ассемблера, используются, чтоб сделать программу более понятной, ну и чтобы можно было, при определенных условиях, на них переходить. Метка объявляется следующим образом: Имя_метки: Т.е. пишем название метки, и двоеточие. Такс, с метками тоже разобрались, ну это не сложно, но метки очень полезны. И наконец команды перехода. Их можно разделить на две группы: команды безусловного перехода и команды условного перехода. Команды безусловного перехода. Единственная команда безусловного перехода, которую мы рассмотрим -- jmp. Все мы знаем слово "jump", т.е. прыжок, вот сокращение этого слова, jmp, и есть команда безусловного перехода. Не, ну всё логично, прыгнуть на определенную метку. Так, что-то я отвлекся, продолжим. Синтаксис у этой команды такой: jmp метка Думаю тут ничего сложного нет. Команды условного перехода. То есть те команды перехода, которые выполняются по определенному условию. Вспомним начало этого раздела, команда cmp, в основном, команды условного перехода выполняются после этой команды. Я приведу небольшой список этих команд: je / jne прыгнуть, если равны / если не равны jg / jng / jge прыгнуть, если больше / если не больше / если больше или равны jl / jnl / jle прыгнуть, если меньше / если не меньше / если меньше или равны ja / jna / jae прыгнуть, если выше (в смысле больше) / если не выше (в смысле больше) / если выше (в смысле больше) или равны jb / jnb / jbe прыгнуть, если ниже (в смысле меньше) / если не ниже (в смысле меньше) / если ниже (в смысле меньше) или равны Вначале каждой команды стоит буква j, означающая jump, прыгнуть. Далее идет условие (чтобы понять почему именно так, надо вспомнить некоторые английские слова: equal, not, above, below, greater, less, перевод найдите в словаре). Лучше посмотреть на примере. Я не буду писать целую программу. только небольшой её кусочек. mov ax,22 mov bx,21 cmp ax,bx je Equal jg Greater jl Less Equal: ; вывести сообщение, что равны Greater: ; вывести сообщение, что операнд 1 больше оперенда 2 Less: ; вывести сообщение, что операнд 1 меньше оперенда 2 На этом, про команды перехода хватит. XI Циклы Думаю не стоит объяснять, что такое циклы, поэтому сразу перейдем к делу. Для того, чтобы организовать цикл на ассемблере, есть два пути (может есть и больше, но мы их рассматривать не будем). Первый способ: в этом способе нам придется использовать команду условного перехода jcxz, переход будет выполнен только если в регистре cx будет 0. Давайте я приведу пример кода. ; blah-blah ; объявляем какие то данные mov cx, 10 ; кидаем в регистр cx число, сколько будет тиков в цикле m1: ; первая метка dec cx ; вы еще не знакомы с этой командой, но она очень проста, декремент регистра cx, подобная команда есть inc -- инкремент mov dx, offset msg ; mov ah, 09h ; выводим какое-то сообщение int 21h ; jcxz exit ; если регистр cx равен нулю, переходим на метку exit jmp m1 ; иначе переходим на метку m1 exit: ; метка exit mov ah, 04ch ; выход из программы int 21h Как вы видите, всё просто, помещаем в регистр cx сколько раз должен повторится цикл, уменьшаем cx на один, выполняем действия, проверяем равен ли cx нулю, если да -- переходим на метку exit, если нет -- повторяем. Второй способ: второй способ не требует использования команды перехода, во втором способе используется команда loop. Эта команда переводит нас на указанную метку пока cx не равен нулю. Давайте перепишем раннее написаный кусок кода, использовав команду loop. ; blah-blah ; объявляем какие то данные mov cl, 10 ; кидаем в регистр cx (cl) число, сколько будет тиков в цикле m1: ; первая метка mov dx, offset msg ; mov ah, 09h ; выводим какое-то сообщение int 21h ; loop m1 ; повторить m1, если cx не равен нулю exit: ; метка exit mov ah, 04ch ; выход из программы int 21h Вот и разобрались с циклами. XII Процедуры Мы уже "много" чего умеем, не так ли?) Но Вы не заметили, что некоторые участки кода повторяются? Конечно же заметили, для того, чтобы сделать код более читабельным и понятным придумали некие процедуры. Думаю вы уже догадались, что это такое, и объяснять это не стоит. Простая процедура объявляется следующим образом: имя_процедуры PROC какие-то_действия ret имя_процедуры ENDP Как вы видите, ничего сложного нет. Например мы знаем, что для того, чтобы вывести текст на экран, надо в регистр ah поместить 09h, и вызвать прерывание 21 (int 21). Давайте объеденим это в процедуру. Получится что-то такое: Write PROC mov ah, 09h int 21h ret Write ENDP Используйте процедуры, чтобы сделать код понятнее. XIII Include В прошлом разделе мы познакомились с вами с процедурами. Благодаря процедурам можно сделать код понятнее. Так же есть директива include, которая позволяет нам включать куски кода из одного файла в другой. Уловили мысль? Мы напишем нужные процедуры, поместим их в отдельный файл, и просто его проинклудим. Таким образом код станет еще проще. Синтаксис у этой команды такой: include файл.asm Ничего сложно. В следующем шаге, мы напишем программу, используя эту директиву, поэтому в этом разделе я не буду приводить пример. XIV Написание программы, используя полученные знания Сейчас я приведу вам код программы, используя все знания, которые Вы получили (ну по крайней мере, могли бы получить). Сама программка будет очень глупой, и мы будем делать много лишних действий, но так надо, чтобы использовать все полученные знания =) После кода программы, я построчно приведу объяснение. В самом коде я не буду оставлять комментариев (слегка геморойно мне сейчас это делать), а после программы всё объясню. Итак, код: Code: ; ach.asm MODEL SMALL STACK 256 DATASEG bye db 'The end!) $' msg1 db 'CX < 5 || $' msg2 db 'CX > 5 || $' msg3 db 'CX = 5 || $' CODESEG start: mov ax, @data mov ds, ax mov dx, offset bye mov cx, 5 push dx push cx jmp begin include achadd.asm begin: mov cx, 7 cmp cx, 5 je m3 jg m2 jl m1 m3: mov dx, offset msg3 call write jmp preexit1 m2: mov dx, offset msg2 call write jmp preexit1 m1: mov dx, offset msg1 call write preexit1: pop cx pop dx preexit2: call write loop preexit2 ext: call exit end start ;############################# Code: ;achadd.asm write proc mov ah,09h int 21h ret write endp ;+++++++++++++++++++++++++; exit proc mov ah, 1h int 21h mov ah, 04ch int 21h ret exit endp Тут у нас два файла, в первом главный код, во втором две процедуры. Начнем с первого файла: 1 строка -- объявляем модель памяти 2 строка -- объявляем размер стека 3 строка -- начало сегмента данных 4-7 строки -- создаем переменные с сообщениями 8 строка -- начало сегмента кода 9 строка -- входим в "главную часть" 10-11 строки -- связываем данные с их сегментным регистром 13 строка -- записываем в регистр cx адрес переменной bye 14 строка -- в регистр cx помещаем 5 15 строка -- помещаем регистр dx в стек 16 строка -- помещаем регистр cx в стек 18 строка -- перепрыгиваем на метку begin 19 строка -- инклудим файл achadd.asm 20 строка -- объявляем метку begin 21 строка -- записываем в регистр cx число 7 22 строка -- сравниваем то, что в регистре сч с числом 5 23 строка -- если равные переходим на метку m3 24 строка -- если cx больше 5, переходим на метку m2 25 строка -- если cx меньше 5, переходим на метку m1 26 строка -- объявляем метку m3 27 строка -- записываем в dx адрес переменной msg3 28 строка -- вызываем процедуру write 29 строка -- прыгаем на метку preexit1 30-36 строки -- объявляем метки, записываем определенные сообщения и вызываем процедуру write (долго расписывать) 37 строка -- объявляем метку preexit1 38 строка -- вытаскиваем значение со стека в регистр cx 39 строка -- вытаскиваем значение со стека в регистр dx 40 строка -- объявляем метку preexit2 41 строка -- вызываем процедуру write 42 строка -- повторям, пока cx не станет равен нулю 43 строка -- объявляем метку ext 44 строка -- вызываем процедуру exit 45 строка -- end start Второй файл, разберите сами, это будет как маленькое д.з.)) там ничего сложного нет. XV Вывод Ну, вот и всё) в этой статье я хотел показать, что ассемблер не такой уж и сложный язык (пока Вы не начнете учить что-то по-серьезнее, хе-хе). И хоть мы рассмотрели только основы, они будут очень полезны всем, кто хочет кодить не только на делфи) Ну и ссылка на тасм, http://letitbit.net/download/5627.a5de7462230205a7a148f13dd/1065_tasm.rar.html Рекомендую для прочтения книгу В.И. Юрова, Assembler 2-е издание. Удачи в изучении ассемблера)
Люди, я специально не выкладывал много литературы, на античате есть тема, где есть много книг по ассемблеру https://forum.antichat.ru/thread94535-%EB%E8%F2%E5%F0%E0%F2%F3%F0%E0+%E0%F1%F1%E5%EC%E1%EB%E5%F0.html
Я и не собирался писать про прерывания) просто вскольз про них упомянул, чтоб люди знали, что такое существует)
все это и даже больше ест ьв книге калашникова, да и про сегменты там написано подробней, т.к. считается что для новичков освоение сегментов сложно дается А статья неплохая
В книгах обычно всегда лучше расписано) на то они и книги, а это статья) которая мб подтолкнет прочитать книгу)