Введение, или для чего все это нужно. Как всегда извечный вопрос: А зачем? Для меня ответ звучит так: Ассемблер - это язык процессора, следовательно он будет существовать, и будет необходим, пока существуют микропроцессоры. И изучение асма дает мне возможность изучить с троение и архитектуру компьютера. Да, и еще, тут вы найдете множество "ссылок вперед", впрочем как и в любой программистской литературе, так как проще изучать не по голой теории, а во многом на практике =) А предназначена эта книга для людей знакомых с программированием, но малознакомых с асмом, или для тех людей, кто пытался изучить асм, но не смог из-за узости попавшей ему литературы. (ps: асм, асэм, asm,assembler,-значит ассемблер) Глава 0 Повторение. Бит - квант информации, т.е. еденица информации. Используется потому что проще создавать системы использующие двоичный код(1,0 есть напряжение, нет напряжения) Байт - восемь бит, используется для упрощения жизни программисту. Основные части компьютера: процессор, оперативка, материнская плата, винт, ввод и вывод, и т.д. Процессор - обработка информации. Оперативка - временное хранение информации, для более быстрого доступа. Материнская плата - то что все это соединяет в еднный 'организм'. Винт - постоянное хранение информации. Байт = 8бит 2 байта - слово 2 слова - двойное слово. BIOS: Basic Input Output System(Базовая Система Ввода Вывода) Предназначена собсно говоря для ввода и вывода инфы. Её функции мы и будем для начала использовать. Процессор состоит из: Регистров. Регистры - сверхбыстрые ячейки памяти, находящиеся непосредственно в процессоре. Всего остального. Это нам пока не необходимо знать. Изучение. Для начала начнем с DOS, или с консольных программ. Далее по увеличении наших знаний перейдем к win программированию. Чтобы не устанавливать ДОС на свой жесткий, мы будем использовать виртуальную машину. Я рекомендую Connectix Virtual PC 5.2, т.к. он не особенно тяжелый, а также потому что используя его, или его потомков вы сможете не заморачиваться с установкой доса на виртуальный диск, т.к. я уже сделал это, и советую вам скачать архив с VHDD(Virtual Hard Disk Drive). http://ass3mbler.narod.ru/dos.part01 http://ass3mbler.narod.ru/dos.part02 http://ass3mbler.narod.ru/dos.part03 http://ass3mbler.narod.ru/dos.part04 http://ass3mbler.narod.ru/dos.part05 http://ass3mbler.narod.ru/dos.part06 http://ass3mbler.narod.ru/dos.part07 http://ass3mbler.narod.ru/dos.part08 http://ass3mbler.narod.ru/dos.part09 грузим, устанавливаем, выбираем диск с досом. Во всех серьёзных книгах по программированию повествование начинают с простейшей программки типа 'Hello World!'. Ну чтоже, не буду отступать от традиции, но прошу вас пока не пытаться особо понять что вы написали, т.к. поймете вы это немного позже :) ;-------------------------------- TITLE HELLO_WORLD CODSEG SEGMENT ;сегмент кода ASSUME CS:CODSEG, DS:CODSEG, SS:CODSEG, ES:CODSEG ;определяем все сегменты на сегм. кода ORG 100H ; необходимо для нормальной работы exe BEGIN: JMP BEG_CODE ;перепрыгиваем на BEG_CODE TEXT DB 'HELLO WORLD!$' ;переменная с текстом BEG_CODE: MOV AX,CS ;записываем в DS расположение сегмента кода MOV DS,AX LEA DX,TEXT ;помещаем в DX адрес переменной с текстом MOV AH,09h ;помещаем в AH номер функции дос INT 21H ;выводим её на экран(вызывая 9 функцию дос) MOV AH,4CH ;выходим в О.С. INT 21H CODSEG ENDS END BEGIN ;======================листинг 1=========== далее, мы сохраняем под именем к примеру hello.asm и через службу шаринга(settings - Shared folders , share folder (так мы одну какую то папку определяем как диск в DOS)) заливаем на дос машину, и помещаем к примеру в каталог masm61, дальше заходим в каталог bin и выполняем команду masm.exe ..\hello.asm,,,; и если вы не ошиблись, программа скомпонована, далее мы её линкуем(обьединяем в единый exe файл): link.exe ..\hello.obj,,,; теперь, если вы запустите hello.exe то увидите собственно говоря надпись hello world! Глава 1 Немного о железе. Память о... Память ЭВМ состоит из однобайтовых ячеек, каждой ячейке присваивается адрес(от 0 и т.д.). Этот адрес - физическим называется. Однако мы пока записывать адрес будем немного по другому, в виде двух компонент, это связано с тем, что регистр не может вместить число больше FFFFH поэтому, для формирования адреса будем использовать два регистра. Записывать мы будем его так: SA:OA где SA -адрес сегмента OA - смещение в этом сегменте. Обычный физический адрес получается из двухкомпонентного по формуле SA*16+OA. Умножение на 16 равносильно сдвигу влево на 4 бита.(сначала к примеру 0000 0110 1011 0000, потом 0110 1011 0000 0000) Использование двухкомпонентного адреса приводит к разбиению памяти на сегменты. Максимальный размер сегмента - 64Кб. Адресом любой цепочки байт(числа, буквы, слова) является адрес младшего байта. То-есть, если сушествует переменная PER равная 6977 то команда MOV AL,BYTE PTR PER, поместит в регистр AL число 77. Команда эта помещает значение из участка памяти, на который указывает PER, в регистр AL. Слова BYTE PTR указывают что загружать надо именно байт. Для того чтобы загрузить старший байт, служит команда MOV AL,BYTE PTR PER+1. Значение смещения измеряется в байтах, поэтому PER+1 - дает нам значение старшего байта. Переменная PER(она же метка) содержит адрес числа 6977 в оперативке. При компиляции всем меткам программы присваивается соответтвуещее смещение в сегменте. Для загрузки всего числа в регистр к примеру AX можно сделать так: MOV AX,LAB. Чтобы зарезервировать определенное количество байт, чтобы использовать их к примеру для хранения каких либо данных можно использовать такие служебные слова: DB 78H ;зарезервировать байт и присвоить ему значение 78H DW 1234H ;Зарезервировать 2 байта и в младший будет помещено число 34Н а в старший - 12Н DB 12,34 ;Два байта: младший 12, старший 34 PER DB 6977 ;Два байта: младший 77, старший 69 и устанавливаем метку PER на адрес байта содержащего ;77 Если в тексте программы встречается группа символов, то транслятор поменяет их на байты сответствующие их ASCII кодам. К примеру: DB 'HEllo World!' ; резервируется 12 байт куда помещаются ASCII коды букв(см. листинг 1). Типы адресации. Работать с памятью можно по разному, и с первым способом мы познакомились(это просто указать метку). Этот тип адресации называется прямой. К метке можно добавить какое-то число. Команда MOV AX,[BX] загружает в AX слово, адрес которого лежит в BX. Это - косвенная адресация. (использует регистр BX, или BP). Адрес в BX можно загрузить командой MOV BX,OFFSET PER или LEA BX,PER К регистру BX можно прибавить смещение, к примеру: MOV AX,[BX]+2 или MOV AX,[BX]2 , в результате в AX загрузится слово адрес которого получается сложением адреса загруженного в BX и 2 (т.е. BX+2). Этот тип называется адресацией по базе. Следующий тип - прямая адресация с индексированием. Тут используется регистры DI и SI. К примеру команды: MOV DI,7 MOV AX,PER[DI] приведут к тому что в AX будет загружено слово находящееся по адресу LAB+7 Индексация по базе с индедексированием. Пример: MOV AX,[BX][DI]+2. Загр. слово по адресу BX+7+2. Относительно BX и BP. По умолчанию используя BP мы загружаем данные из сегмента стека: MOV AX,[BX] ;загрузка из сегмента данных(DS) MOV AX,[BP] ;загрузка из сегмента стека(SS) Однако сегмент можно указать явно, и к примеру команда MOV CX,SS:[BX] грузит в CX данные из сегмента стека, а MOV AX,DS:[BP] - из сегмента данных. Абсолютно прямая адресация примеры: mov ax,word ptr [0000] ;записать слово по адресу ds:0000 в регистр ax mov ax,word ptr cs:[0561] ;записать слово по адресу cs:0561 в регистр ax Все это способы адресации можно использовать не только в командах загрузки, но и в любых других коммандах, к примеру арифмерических: ADD DI,LAB[SI] Эта команда прибавляет значение ячейки по адресу LAB+SI к значению содержащемуся в регистре DI и помещает результат в регистр DI. Структура программы. Поскольку наша программа будет загружаться в память, то следовательно она должна быть разбита на несколько сегментов, что и можно увидеть в примере(1.1). Минимальное количество сегментов - 1. Классическое строение программы содержит три сегмента (DS,CS,SS)- данных, кода, стека. Однако не зачем ограничивать себя одним DS, CS, или SS, можно сделать несколько сегментов кода, данных, стека. У нас в данный момент имеется 4 сегментныз регистра - CS,DS,SS,ES. В CS - содержится адрес сегмента кода, в DS - адрес сегмента данных, в SS - соответственно стека, и ES - дополнительный. Однако это не значит что у нас может быть только 4 сегмента, т.к. к примеру DS сначала указывал на один сегмент данных, а потом стал указывать на другой. Вот вам пример проги с двумя сегментами данных. ;-=-=-=-=-=-=-=-=-=- TITLE EXAMPLEDS ;=-=-=-=-сегмент данных 1=-=-=-=-=--=- DSEG1 SEGMENT TEXT DB 'STARTING (DS1).$' DSEG1 ENDS ;-=-=-=-=СЕГМЕНТ ДАННЫХ 2-=-=-=-=-=-=- DSEG2 SEGMENT TEXT1 DB 'THE END.(DS2)$' DSEG2 ENDS ;=-=-=-=-СЕГМЕНТ СТЕКА ;РЕЗЕРВИРУЕТСЯ 20 БАЙТ STSEG SEGMENT stack DB 20 DUP(0) ;20 БАЙТАМ ПРИСВАИВАЕТСЯ ЗНАЧЕНИЕ 0 STSEG ENDS ;-=-=-=-=СЕГМЕНТ КОДА CODSEG SEGMENT ASSUME CS:CODSEG, DS:DSEG1, SS:STSEG BEGIN: ;УСТАНАВЛИВАЕМ СЕГМЕНТ СТЕКА MOV AX,STSЕG MOV SS,AX ;DS НАПРАВЛЯЕМ НА ПЕРВЫЙ СЕГМЕНТ ДАННЫХ MOV AX,DSEG1 MOV DS,AX ;РАБОТАЕМ LEA DX,TEXT1 ; DS:DX УКАЗЫВАЕТ ТЕПЕРЬ НА TEXT1 MOV AH,9 INT 21H ; ВЫЗЫВАЕМ 9 ФУНКЦИЮ 21-ГО ПРЕРЫВАНИЯ (ПЕЧАТАЕТ НА ЭКРАН ВСЕ ЧТО НАХОДИТСЯ ПО АДРЕСУ ; НАХОДЯЩЕМУСЯ В DX ДО ПЕРВОЙ ВСТРЕТИВШЕЙСЯ $) MOV AH,0 INT 16H ; ЖДЕМ НАЖАТИЯ КЛАВИШИ ;DS НАПРАВЛЯЕМ НА ВТОРОЙ СЕГМЕНТ ДАННЫХ MOV AX,DSEG2 MOV DS,AX LEA DX,TEXT1 MOV AH,9 INT 21H ;ВЫХОДИМ В О.С. MOV AH,4CH INT 21H CODSEG ENDS END BЕGIN ;=-=-=-=-=-ЛИСТИНГ 1,2 Служебное слово ASSUME является директивой транслятора, оно сообщает ему значения сегментных регистров. Для CS такая директива обязательна. Для других можно в общем то и не делать. Вектора Вектор - адрес некой процедуры, расположенной в памяти. В старших байтах содержится смещение, в младших - адрес сегмента. К примеру B085H:58F3H. Начало оперативки отводится как раз под такие векторы, которые во время загрузки ОС устанавливаются на свои процедуры. Векторы нумеруются(для удобства) и если номер вектора X, то он расположен по адресу 0:4*X. Команда Int 21H есть вызов процедуры ОС, на которую указывает вектор под номером 21H (он расположен по адресу 21H*4=0:84H, 0 в данном случае - адрес сегмента). ПРИМЕЧАНИЕ: команда jmp аналогична команде goto в других языках Глава 2 Правила дорожного движения. При инициализации сегментов(mov ax,data ; mov ds,ax) мы делаем такие движения потому что у сегментных регистров возможен обмен только регистр - регистр. Стек. Стек - структура памяти, которая чаще всего используется для временного хранения данных, передачи параметров, и др.. Элементом стека является слово. Принцып хранения данных - первым пришел, последним ушел. (FILO First In Last Out) Чтобы понять как это представьте себе стопку блинов, где первый положенный блин будет взят последним. В любой момент времени нам доступно только слово находящееся в вершине стека. На это слово указывает регистр SP(Stack Pointer). Сам стек находится в сегменте, на который указывает регистр SS. У нас имеется две основные команды для работы со стеком POP и PUSH. PUSH AX - положить в стек. Она уменьшает значение регистра SP на 2 и в слово, на которое теперь указывает SP помещает значение из AX. ПРИМЕЧАНИЕ ends означает end segment т.е. конец сегмента. Вот пример работы со стеком. title stack data segment db 10 dup(0) db 'BCE PAbOTAET!!!',13,10,'$' data ends stackseg segment stack db 40 dup(1) stackseg ends codesegm segment ASSUME CS:codesegm,DS:data,SS:stackseg begin: mov ax,data mov ds,ax mov ax,stackseg mov ss,ax loo: mov ax,77h push ax ;записываем число 77h в стек mov bp,sp ;помещаем в bp текущий адрес вершины стека cmp [bp],77h ;сравниваем число находящееся по адресу содержещемуся в bp с 77h jl loo ;если оно меньше, то переходим на метку loo jg loo ;если больше, тоже переходим на метку loo mov dx,10 mov ah,09h int 21h mov ah,01h int 21h mov ah,4ch int 21h codesegm ends end begin О процедурах. Процедуры, они же функции по своей сути являются лиш обычными метками, на которые можно просто перейти. У нас имеется команда cаll, которая по сути является переходом на метку, с сохранением адреса возврата в стеке. Команда ret берет адрес возврата из стека, и переходит на него. Вот программа, иллюстрирующая возможности процедур. title shiffle datas segment enter db 13,10,'$' ; перевод строки xep db 30 dup(0) max db 255 ; буфер для функции 0Ah ass db ? ; резервный байт stroka db 255 DUP(0) ; место для строки db ? error2 db '3na4ki and 4isla ne pepevidim!',13,10,'poprobuyte sna4ala',13,10,'$' datas ends stak segment stack db 15 dup(10) stak ends ;==================================сегмент кода cod segment assume cs:cod,ds:datas,ss:stak org 100h start: JMP init enter proc lea dx,enter ; перевод строки mov ah,09h int 21h ret enter endp inpstr proc ;===============ввод строки=========== mov dx,offset max mov ah,0ah int 21h mov bl,ass mov bh,0 mov [bx+stroka],'$' ; подготовка строки к преобразованию call enter ret inpstr endp ;------------------------------- outs proc mov ah,09h lea dx,stroka int 21h mov ah,01h int 21h ret outs endp ;------------------------------ ;------------------------------for small chars cicleM proc sub al,20h mov [bx],al add bx,1 ret cicleM endp ;-----------------------------for big chars cicleB proc add al,20h mov [bx],al add bx,1 ret cicleB endp ;-----------------procedura obrabotki ошибок error proc call enter mov dx,offset error2 int 21h jmp start error endp ;-------------------------------------------------------- ;------------------main procedure init: mov ax,datas mov ds,ax mov ax,stak mov ss,ax call inpstr call skan mov ah,4ch int 21h ;------------------procedura proverki na ошибки и высоту букв skan proc lea bx,stroka cicl1: mov al,byte ptr [bx] ; помещаем байт по адресу находящемуся в bx в al cmp al,24h ; сравниваем al c 24h je ou ;if al=24h, goto ou cmp al,41h jl error ;if al <41h, goto error cmp al,5bh jl CcicleB ;if al<5Bh goto CcicleB cmp al,60h jl error ;if al<60h goto error cmp al,7bh jl ccicleM ;if al<7Bh goto ccicleM jge error ;if al>=7Bh goto error ou: call outs ret ccicleB: call cicleB jmp cicl1 ccicleM: call cicleM jmp cicl1 skan endp cod ends end start ЗАДАНИЕ составьте простенький калькулятор, который вводит два числа, действие, и выводит ответ. Знак + - eго ASCII код - 2Bh, - 2Dh, * 2Ah, / 2Fh Подсказка: для получения числа из его asсii кода следует отнять от него(кода числа) число 30h.