1
  1. Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.
Приветствуем вас,Гость, на форуме IFUD.WS. Обязательно рекомендуется к прочтению правила форума http://ifud.ws/threads/obnovleno-pravila-foruma.7759

Ассемблер для самых маленьких

Тема в разделе "Чужие", создана пользователем Alex-ey, 8 авг 2013.

  1. TopicStarter Overlay
    Alex-ey

    Alex-ey

    Регистрация:
    26 май 2012
    Сообщения:
    513
    Симпатии:
    323
    Ассемблер для самых маленьких
    План:
    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!").​
    Я сразу выложу вам код, а после него мы разберем эту программку.​
    Код:
    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 Написание программы, используя полученные знания
    Сейчас я приведу вам код программы, используя все знания, которые Вы получили (ну по крайней мере, могли бы получить). Сама программка будет очень глупой, и мы будем делать много лишних действий, но так надо, чтобы использовать все полученные знания =)
    После кода программы, я построчно приведу объяснение. В самом коде я не буду оставлять комментариев (слегка геморойно мне сейчас это делать), а после программы всё объясню. Итак, код:
    Код:
    ; 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
    Код:
    ;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 Вывод

    Ну, вот и всё) в этой статье я хотел показать, что ассемблер не такой уж и сложный язык (пока Вы не начнете учить что-то по-серьезнее, хе-хе). И хоть мы рассмотрели только основы, они будут очень полезны всем, кто хочет кодить не только на делфи)

    Рекомендую для прочтения книгу В.И. Юрова, Assembler 2-е издание.
    (™) Vollkorn
     
    • Like Like x 4
    Метки:

Поделиться этой страницей

Загрузка...