Стейтменты
В ассемблере есть три вида стейтментов:
Выполняемые инструкции (или просто «инструкции») — сообщают процессору, что нужно делать. Каждая инструкция хранит в себе код операции (или «опкод») и генерирует одну инструкцию на машинном языке.
Директивы ассемблера — сообщают программе об аспектах компиляции. Они не генерируют инструкции на машинном языке.
Макросы — являются простым механизмом вставки кода.
В ассемблере на одну строку приходится один стейтмент, который должен соответствовать следующему формату:
mnemonic
1 | меткаmnemonicоперанды;комментарий |
Базовая инструкция состоит из названия инструкции () и операндов (они же «параметры»). Вот примеры типичных стейтментов ассемблера:
INC COUNT ; выполняем инкремент переменной памяти COUNT
MOV TOTAL, 48 ; перемещаем значение 48 в переменную памяти TOTAL
ADD AH, BH ; добавляем содержимое регистра BH к регистру AH
AND MASK1, 128 ; выполняем операцию AND с переменной MASK1 и 128
ADD MARKS, 10 ; добавляем 10 к переменной MARKS
MOV AL, 10 ; перемещаем значение 10 в регистр AL
1 |
INC COUNT;выполняеминкрементпеременнойпамятиCOUNT MOV TOTAL,48;перемещаемзначение48впеременнуюпамятиTOTAL ADD AH,BH;добавляемсодержимоерегистраBHкрегиструAH ANDMASK1,128;выполняемоперациюANDспеременнойMASK1и128 ADD MARKS,10;добавляем10кпеременнойMARKS MOV AL,10;перемещаемзначение10врегистрAL |
Инструкция AND
Инструкция AND выполняет побитовую операцию И. Побитовая операция И возвращает , если совпадающие биты обоих операндов равны , в противном случае — возвращается . Например:
Операция AND может быть использована для сброса одного или нескольких бит. Например, допустим, что регистр BL содержит . Если вам необходимо сбросить старшие биты в ноль, то вы выполняете операцию AND между этим регистром и числом :
AND BL, 0FH ; эта строка устанавливает для регистра BL значение 0000 1010
1 | ANDBL,0FH; эта строка устанавливает для регистра BL значение 0000 1010 |
Давайте рассмотрим другой пример. Если вы хотите проверить, является ли определенное число чётным или нечётным, то с помощью простого теста вы сможете проверить младший значимый бит числа. Если им окажется , то число нечётное, если же — число чётное.
Например, если число находится в регистре AL, то мы можем написать следующее:
AND AL, 01H ; выполняем операцию AND с 0000 0001
JZ EVEN_NUMBER
1 |
ANDAL,01H; выполняем операцию AND с 0000 0001 JZEVEN_NUMBER |
Следующая программа это проиллюстрирует:
Live Demo
section .text
global _start ; должно быть объявлено для использования gcc
_start: ; сообщаем линкеру входную точку
mov ax, 8h ; записываем 8 в регистр AX
and ax, 1 ; выполняем операцию AND с AX
jz evnn
mov eax, 4 ; номер системного вызова (sys_write)
mov ebx, 1 ; файловый дескриптор (stdout)
mov ecx, odd_msg ; сообщение для вывода на экран
mov edx, len2 ; длина сообщения
int 0x80 ; вызов ядра
jmp outprog
evnn:
mov ah, 09h
mov eax, 4 ; номер системного вызова (sys_write)
mov ebx, 1 ; файловый дескриптор (stdout)
mov ecx, even_msg ; сообщение для вывода на экран
mov edx, len1 ; длина сообщения
int 0x80 ; вызов ядра
outprog:
mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра
section .data
even_msg db ‘Even Number!’ ; сообщение с чётным числом
len1 equ $ — even_msg
odd_msg db ‘Odd Number!’ ; сообщение с нечётным числом
len2 equ $ — odd_msg
1 |
LiveDemo section.text global_start; должно быть объявлено для использования gcc _start; сообщаем линкеру входную точку movax,8h; записываем 8 в регистр AX andax,1; выполняем операцию AND с AX jzevnn moveax,4; номер системного вызова (sys_write) movebx,1; файловый дескриптор (stdout) movecx,odd_msg; сообщение для вывода на экран movedx,len2; длина сообщения int0x80; вызов ядра jmpoutprog evnn movah,09h moveax,4; номер системного вызова (sys_write) movebx,1; файловый дескриптор (stdout) movecx,even_msg; сообщение для вывода на экран movedx,len1; длина сообщения int0x80; вызов ядра outprog moveax,1; номер системного вызова (sys_exit) int0x80; вызов ядра section.data even_msgdb’Even Number!’; сообщение с чётным числом len1equ$-even_msg odd_msgdb’Odd Number!’; сообщение с нечётным числом len2equ$-odd_msg |
Результат выполнения программы:
Изменяем значение в регистре AX, указав нечётную цифру:
mov ax, 9h ; записываем 9 в регистр AX
1 | movax,9h; записываем 9 в регистр AX |
И результат:
Вы можете точно так же очистить весь регистр, используя AND с .
Битовые операции
Мнемоника | Описание | Операция | Флаги |
---|---|---|---|
CBR Rd, K | Очистка разрядов регистра | Rd ← Rd and (0FFH – K) | Z, N, V |
SBR Rd, K | Установка разрядов регистра | Rd ← Rd or K | Z, N, V |
CBI P, b | Сброс разряда I/O-регистра | P.b ← 0 | — |
SBI P, b | Установка разряда I/O-регистра | P.b ← 1 | — |
BCLR s | Сброс флага SREG | SREG.s ← 0 | SREG.s |
BSET s | Установка флага SREG | SREG.s ← 1 | SREG.s |
BLD Rd, b | Загрузка разряда регистра из флага T | Rd.b ← T | — |
BST Rr, b | Запись разряда регистра во флаг T | T ← Rd.b | T |
CLC | Сброс флага переноса | C ← 0 | C |
SEC | Установка флага переноса | C ← 1 | C |
CLN | Сброс флага отрицательного числа | N ← 0 | N |
SEN | Установка флага отрицательного числа | N ← 1 | N |
CLZ | Сброс флага нуля | Z ← 0 | Z |
SEZ | Установка флага нуля | Z ← 1 | Z |
CLI | Общий запрет прерываний | I ← 0 | I |
SEI | Общее разрешение прерываний | I ← 1 | I |
CLS | Сброс флага знака | S ← 0 | S |
SES | Установка флага знака | S ← 1 | S |
CLV | Сброс флага переполнения дополнительного кода | V ← 0 | V |
SEV | Установка флага переполнения дополнительного кода | V ← 1 | V |
CLT | Сброс пользовательского флага T | T ← 0 | T |
SET | Установка пользовательского флага T | T ← 1 | T |
CLH | Сброс флага половинного переноса | H ← 0 | H |
SEH | Установка флага половинного переноса | H ← 1 | H |
Инструкция CMP
Инструкция CMP (от англ. «COMPARE») сравнивает два операнда. Фактически, она выполняет операцию вычитания между двумя операндами для проверки того, равны ли эти операнды или нет. Используется вместе с инструкцией условного прыжка.
Синтаксис инструкции CMP:
Инструкция CMP сравнивает два числовых поля. Операнд назначения может находиться либо в регистре, либо в памяти. Исходным операндом () могут быть константы, регистры или память. Например:
CMP DX, 00 ; сравниваем значение регистра DX с нулем
JE L7 ; если true, то переходим к метке L7
.
.
L7: …
1 |
CMPDX,00; сравниваем значение регистра DX с нулем JEL7; если true, то переходим к метке L7 . L7… |
Инструкция CMP часто используется для проверки того, достигла ли переменная-счетчик максимального количества раз выполнения цикла или нет. Например:
INC EDX
CMP EDX, 10 ; сравниваем, достиг ли счетчик значения 10 или нет
JLE LP1 ; если его значение меньше или равно 10, то тогда переходим к LP1
1 |
INCEDX CMPEDX,10; сравниваем, достиг ли счетчик значения 10 или нет JLELP1; если его значение меньше или равно 10, то тогда переходим к LP1 |
Рассмотрим команды ассемблера на практическом примере.
С использованием среды разработки TASMED или любого текстового редактора набираем код. Программа, задаст вопрос на английском языке о половой принадлежности (имеется ввиду ваш биологический пол при рождении). Если вы нажмете m (Man), будет выведено приветствие с мужчиной, если w (Woman), то с женщиной, после этого программа прекратит работу. Если будет нажата любая другая клавиша, то программа предположит, что имеет дело с гоблином, не поверит и будет задавать вам вопросы о половой принадлежности, пока вы не ответите верно.
;goblin.asm
.model tiny ; for СОМ
.code ; code segment start
org 100h ; offset in memory = 100h (for COM)
start: main proc
begin:
mov ah,09h
mov dx,offset prompt
int 21h
inpt:
mov ah,01h
int 21h
cmp al,’m’
je mode_man
cmp al,’w’
je mode_woman
call goblin
jmp begin
mode_man:
mov addrs,offset man; указатель на процедуру в addrs
jmp cont
mode_woman:
mov addrs,offset woman; указатель на процедуру в addrs
cont:
call word ptr addrs; косвенный вызов процедуры
mov ax,4c00h
int 21h
main endp
man proc
mov ah,09h
mov dx,offset mes_man
int 21h
ret
man endp
woman proc
mov ah,09h
mov dx,offset mes_womn
int 21h
ret
woman endp
goblin proc
mov ah,09h
mov dx,offset mes_gobl
int 21h
ret
goblin endp
;DATA
addrs dw 0;for procedure adress
prompt db ‘Are you Man or Woman [m/w]? : $’
mes_man db 0Dh,0Ah,»Hello, Strong Man!»,0Dh,0Ah,’$’ ; строка для вывода. Вместо ASCII смвола ‘$’ можно написать машинный код 24h
mes_womn db 0Dh,0Ah,»Hello, Beautyful Woman!»,0Dh,0Ah,’$’ ; строка для вывода
mes_gobl db 0Dh,0Ah,»Hello, Strong and Beautyful GOBLIN!»,0Dh,0Ah,24h ; строка для вывода. 24h = ‘$’ .
len = $ — mes_gobl
end start
1 |
;goblin.asm .modeltiny; for СОМ .code; code segment start org100h; offset in memory = 100h (for COM) startmainproc begin movah,09h movdx,offsetprompt int21h inpt movah,01h int21h cmpal,’m’ jemode_man cmpal,’w’ jemode_woman callgoblin jmpbegin mode_man movaddrs,offsetman; указатель на процедуру в addrs jmpcont mode_woman movaddrs,offsetwoman; указатель на процедуру в addrs cont callwordptraddrs; косвенный вызов процедуры movax,4c00h int21h mainendp manproc movah,09h movdx,offsetmes_man int21h ret manendp womanproc movah,09h movdx,offsetmes_womn int21h ret womanendp goblinproc movah,09h movdx,offsetmes_gobl int21h ret goblinendp addrsdw;for procedure adress promptdb’Are you Man or Woman [m/w]? : $’ mes_mandb0Dh,0Ah,»Hello, Strong Man!»,0Dh,0Ah,’$’; строка для вывода. Вместо ASCII смвола ‘$’ можно написать машинный код 24h mes_womndb0Dh,0Ah,»Hello, Beautyful Woman!»,0Dh,0Ah,’$’; строка для вывода mes_gobldb0Dh,0Ah,»Hello, Strong and Beautyful GOBLIN!»,0Dh,0Ah,24h; строка для вывода. 24h = ‘$’ . len=$-mes_gobl endstart |
Замена деления умножением на Ассемблере
- ; В описании используются следующие вспомогательные значения:
- ; m = множитель (multiplier)
- ; s = коэффициент смещения (shift factor)
- ;——————————————————
- ; Алгоритм №0
- ;——————————————————
- mov eax, m
- mul делитель
- shr edx, s
- ; EDX = частное
- ;——————————————————
- ; Алгоритм №1
- ;——————————————————
- mov eax, m
- mul делитель
- add eax, m
- adc edx, 0
- shr edx, s
- ; EDX = частное
3132
Code (Assembler) : Убрать нумерациюВыделить код
- ; EAX = делимое
- xor edx, edx
- cmp eax, делитель ; CF = (делимое < делитель) ? 1 : 0
- sbb edx, -1 ; частное = 0+1-CF = (делимое < делитель) ? 0 : 1
- ; EDX = частное
Code (Assembler) : Убрать нумерациюВыделить код
- ; EAX = делимое
- cmp eax, делитель ; CF = (делимое < делитель) ? 1 : 0
- mov eax, 0
- sbb eax, -1 ; частное = 0+1-CF = (делимое < делитель) ? 0 : 1
- ; EAX = частное
SHR reg, степень_двойки
Code (Assembler) : Убрать нумерациюВыделить код
- ; EAX = делимое
- shr eax,1 ; EAX / 2
- shr eax,2 ; EAX / 4
- shr eax,3 ; EAX / 8
- shr eax,4 ; EAX / 16
- ; и так далее
0
Code (Assembler) : Убрать нумерациюВыделить код
- ; EAX = делимое
- shr eax,0 ; EAX / 1
- ; EAX остался неизменным
NEG31
Code (Assembler) : Убрать нумерациюВыделить код
- ; В описании используются следующие вспомогательные значения:
- ; m = множитель (multiplier)
- ; s = коэффициент смещения (shift factor)
- ;——————————————————
- ; Алгоритм №0
- ;——————————————————
- mov eax, m
- imul делитель
- mov eax, делитель
- shr eax, 31
- sar edx, s
- add edx, eax
- ; EDX = частное
- ;——————————————————
- ; Алгоритм №1
- ;——————————————————
- mov eax, m
- imul делитель
- mov eax, делитель
- add edx, eax
- shr eax, 31
- shr eax, s
- add edx, eax
- ; EDX = частное
Code (Assembler) : Убрать нумерациюВыделить код
- ;——————————————————
- ; Знаковое деление на 2
- ;——————————————————
- ; EAX = делимое
- cmp eax, 800000000h
- sbb eax, –1
- sar eax, 1
- ; EAX = частное
- ;——————————————————
- ; Знаковое деление на 2^n
- ;——————————————————
- ; EAX = делимое
- cdq
- and edx, (2^n–1)
- add eax, edx
- sar eax, n
- ; EAX = частное
- ;——————————————————
- ; Знаковое деление на -2
- ;——————————————————
- ; EAX = делимое
- cmp eax, 800000000h
- sbb eax, –1
- sar eax, 1
- neg eax
- ; EAX = частное
- ;——————————————————
- ; Знаковое деление на -(2^n)
- ;——————————————————
- ; EAX = делимое
- cdq
- and edx, (2^n–1)
- add eax, edx
- sar eax, n
- neg eax
- ; EAX = частное
AMD Athlon Processor x86 Code Optimization GuideAMD.Athlon.Processor.x86.Code.Optimization.Guide.zip (1,047,105 bytes)
«Division by Invariant Integers using Multiplication»
Torbjorn Granlund — Division by Invariant Integers using MultiplicationTorbjorn.Granlund.Division.by.Invariant.Integers.using.Multiplication.zip (152,759 bytes)
Magic DividerMuldiv 1.0
Утилиты для расчета замены деления умножениемDivision.by.Multiplication.Utilities.zip (20,895 bytes)
Просмотров: 13017 | Комментариев: 4
3.4.2 Символьные константы
Символьная константа содержит от одного до четырех символов, заключенных в
одиночные или двойные кавычки. Тип кавычек для NASM несущественен, поэтому если
используются одинарные кавычки, двойные могут выступать в роли символа и, соответственно,
наоборот.
Символьная константа, содержащая более одного символа, будет загружаться в
обратном порядке следования байт: если вы пишете
сгенерированной константой будет не 0x61626364,
а 0x64636261, поэтому если сохранить эту константу
в память, а затем прочитать, получится снова abcd,
но никак не dcba. Это также влияет на инструкцию
CPUID Пентиумов (см. ).
Представление чисел в Ассемблере
До сих пор мы конвертировали входные данные из ASCII-формы в двоичную систему для выполнения арифметических операций, а затем результат обратно в ASCII-форму. Например:
section .text
global _start ; должно быть объявлено для использования gcc
_start: ; сообщаем линкеру входную точку
mov eax,’3′
sub eax, ‘0’
mov ebx, ‘4’
sub ebx, ‘0’
add eax, ebx
add eax, ‘0’
mov , eax
mov ecx,msg
mov edx, len
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра
mov ecx,sum
mov edx, 1
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра
mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра
section .data
msg db «The sum is:», 0xA,0xD
len equ $ — msg
segment .bss
sum resb 1
1 |
section.text global_start; должно быть объявлено для использования gcc _start; сообщаем линкеру входную точку moveax,’3′ subeax,’0′ movebx,’4′ subebx,’0′ addeax,ebx addeax,’0′ movsum,eax movecx,msg movedx,len movebx,1; файловый дескриптор (stdout) moveax,4; номер системного вызова (sys_write) int0x80; вызов ядра movecx,sum movedx,1 movebx,1; файловый дескриптор (stdout) moveax,4; номер системного вызова (sys_write) int0x80; вызов ядра moveax,1; номер системного вызова (sys_exit) int0x80; вызов ядра section.data msgdb»The sum is:»,0xA,0xD lenequ$-msg segment.bss sumresb1 |
Результат выполнения программы:
Программирование на ассемблере позволяет более эффективно обрабатывать числа в двоичном виде. Десятичные числа могут быть представлены в следующих двух формах:
ASCII-форма;
BCD-форма (или «Двоично-десятичный код»).
2.5 Константы
NASM знает четыре различных типа констант: числовые, символьные, строковые и с плавающей точкой.
2.5.1 Числовые константы
Числовая константа — это просто число. NASM позволят определять числа в различных системах счисления и различными способами: вы можете использовать суффиксы H, Q или O, и B для шестнадцатеричных, восьмеричных и двоичных чисел соответственно; можете использовать для шестнадцатеричных чисел префикс 0x в стиле С, а также префикс $ в стиле Borland Pascal. Однако имейте в виду, что префикс $ может быть также префиксом идентификаторов (см. параграф ), поэтому первой цифрой шестнадцатеричного числа при использовании этого префикса должна быть обязательно цифра, а не буква.
Некоторые примеры числовых констант:
mov ax,100 ; десятичная
mov ax,0a2h ; шестнадцатеричная
mov ax,$0a2 ; снова шестнадцатеричная: нужен 0
mov ax,0xa2 ; опять шестнадцатеричная
mov ax,777q ; восьмеричная
mov ax,777o ; снова восьмеричная
mov ax,10010011b ; двоичная
2.5.2 Символьные константы
Символьная константа содержит от одного до четырех символов, заключенных в одиночные или двойные кавычки. Тип кавычек для NASM несущественен, поэтому если используются одинарные кавычки, двойные могут выступать в роли символа и, соответственно, наоборот. Символьная константа, содержащая более одного символа, будет загружаться в обратном порядке следования байт: если вы пишете
mov eax,’abcd’
сгенерированной константой будет не 0x61626364, а 0x64636261, поэтому если сохранить эту константу в память, а затем прочитать, получится снова abcd, но никак не dcba. Это также влияет на инструкцию CPUID Пентиумов.
2.5.3 Строковые константы
Строковые константы допустимы только в некоторых псевдо-инструкциях, а именно в семействе DB и инструкции INCBIN.
Строковые константы похожи на символьные, только длиннее. Они обрабатываются как сцепленные друг с другом символьные константы. Так, например, следующие строки кода эквивалентны.
db ‘hello’ ; строковая константа
db ‘h’,’e’,’l’,’l’,’o’ ; эквивалент из символьных констант
Следующие строки также эквивалентны:
dd ‘ninechars’ ; строковая константа в двойное слово
dd ‘nine’,’char’,’s’ ; три двойных слова
db ‘ninechars’,0,0,0 ; и действительно похоже
Обратите внимание, что когда используется db, константа типа ‘ab’ обрабатывается как строковая, хотя и достаточно коротка, чтобы быть символьной, потому что иначе db ‘ab’ имело бы тот же смысл, какой и db ‘a’, что глупо. Соответственно, трех- или четырехсимвольные константы, являющиеся операндами инструкции dw, обрабатываются также как строки
2.5.4 Константы с плавающей точкой
Константы с плавающей точкой допустимы только в качестве аргументов DW, DD, DQ и DT. Выражаются они традиционно: цифры, затем точка, затем возможно цифры после точки, и наконец, необязательная E с последующей степенью. Точка обязательна, т.к. dd 1 NASM воспримет как объявление целой константы, в то время как dd 1.0 будет воспринята им правильно.
Несколько примеров:
dw -0.5 ;
dd 1.2 ; «простое» число
dq 1.e10 ; 10,000,000,000
dq 1.e+10 ; синоним 1.e10
dq 1.e-10 ; 0.000 000 000 1
dt 3.141592653589793238462 ; число pi
В процессе компиляции NASM не может проводить вычисления над константами с плавающей точкой (это сделано с целью переносимости). Несмотря на то, что NASM генерирует код для х86 процессоров, сам по себе ассемблер может работать на любой системе с ANCI C компилятором. Само собой, ассемблер не может гарантировать присутствия устройства, обрабатывающего числа с плавающей точкой в формате Intel, поэтому стало бы необходимо включить собственный полный набор подпрограмм для работы с такими числами, что неизбежно привело бы к значительному увеличению размера самого ассемблера, хотя польза от этого была бы минимальна.
Обзор группы арифметических команд и данных
Рис. 1. Классификация
арифметических команд
Группа арифметических целочисленных команд работает с двумя типами чисел:
-
целыми двоичными числами. Числа могут иметь знаковый разряд
или не иметь такового, то есть быть числами со знаком или без знака; - целыми десятичными числами.
Целые двоичные числа
Размерность целого двоичного числа может составлять 8,
16 или 32 бит. Знак двоичного числа определяется тем, как интерпретируется
старший бит в представлении числа. Это 7-й, 15-й или 31-й биты для чисел
соответствующей размерности (см. Типы данных
). При этом интересно то, что среди арифметических команд есть
всего две команды, которые действительно учитывают этот старший разряд
как знаковый, — это команды целочисленного умножения и деления imul и idiv.
В остальных случаях ответственность за действия со знаковыми числами и,
соответственно, со знаковым разрядом ложится на программиста. К этому вопросу
мы вернемся чуть позже. Диапазон значений двоичного числа зависит от его
размера и трактовки старшего бита либо как старшего значащего бита числа,
либо как бита знака числа (табл. 1).
Таблица 1. Диапазон значений двоичных чисел
Размерность поля | Целое без знака | Целое со знаком |
байт | 0…255 | –128…+127 |
слово | 0…65 535 | –32 768…+32 767 |
двойное слово | 0…4 294 967 295 | –2 147 483 648…+2 147 483 647 |
Это делается с использованием директив
описания данных. К примеру, последовательность описаний двоичных
чисел из сегмента данных листинга 1 (помните о принципе “младший байт по
младшему адресу”) будет выглядеть в памяти так, как показано на рис. 2.
Листинг 1. Числа с фиксированной точкой ;prg_8_1.asm masm model small stack 256 .data ;сегмент данных per_1 db 23 per_2 dw 9856 per_3 dd 9875645 per_4 dw 29857 .code ;сегмент кода main: ;точка входа в программу mov ax,@data ;связываем регистр dx с сегментом mov ds,ax ;данных через регистр ax exit: ;посмотрите в отладчике дамп сегмента данных mov ax,4c00h ;стандартный выход int 21h end main ;конец программы |
Рис. 2. Дамп
памяти для сегмента данных листинга 1
Десятичные числа
-
упакованном формате — в этом формате каждый байт содержит
две десятичные цифры. Десятичная цифра представляет собой двоичное значение
в диапазоне от 0 до 9 размером 4 бита. При этом код старшей цифры числа
занимает старшие 4 бита. Следовательно, диапазон представления десятичного
упакованного числа в одном байте составляет от 00 до 99; -
неупакованном формате — в этом формате каждый байт содержит
одну десятичную цифру в четырех младших битах. Старшие четыре бита имеют
нулевое значение. Это так называемая зона. Следовательно, диапазон представления
десятичного неупакованного числа в одном байте составляет от 0 до 9.
Рис. 3. Представление
BCD-чисел
Как описать двоично-десятичные числа в программе?
Для этого можно использовать только две директивы описания
и инициализации данных — db и dt. Возможность применения только этих директив
для описания BCD-чисел обусловлена тем, что к таким числам также применим
принцип “младший байт по младшему адресу”, что, как мы увидим далее, очень
удобно для их обработки. И вообще, при использовании такого типа данных
как BCD-числа, порядок описания этих чисел в программе и алгоритм их обработки
— это дело вкуса и личных пристрастий программиста. Это станет ясно после
того, как мы ниже рассмотрим основы работы с BCD-числами. К примеру, приведенная
в сегменте данных листинга 2 последовательность описаний BCD-чисел будет
выглядеть в памяти так, как показано на рис. 4.
Листинг 2. BCD-числа ;prg_8_2.asm masm model small stack 256 .data ;сегмент данных per_1 db 2,3,4,6,8,2 ;неупакованное BCD-число 286432 per_3 dt 9875645 ;упакованное BCD-число 9875645 .code ;сегмент кода main: ;точка входа в программу mov ax,@data ;связываем регистр dx с сегментом mov ds,ax ;данных через регистр ax exit: ;посмотрите в отладчике дамп сегмента данных mov ax,4c00h ;стандартный выход int 21h end main ;конец программы |
Рис. 4. Дамп
памяти для сегмента данных листинга 2
После столь подробного обсуждения объектов, с которыми
работают арифметические операции, можно приступить к рассмотрению средств
их обработки на уровне системы команд микропроцессора.
Прыжок без условия
Как мы уже говорили, безусловный прыжок выполняется инструкцией JMP, которая включает в себя имя метки, куда следует перебросить точку выполнения программы:
В следующем примере мы рассмотрим использование инструкции JMP:
MOV AX, 00 ; инициализируем регистр AX значением 0
MOV BX, 00 ; инициализируем регистр BX значением 0
MOV CX, 01 ; инициализируем регистр CX значением 1
L20:
ADD AX, 01 ; выполняем инкремент регистра AX
ADD BX, AX ; добавляем AX к BX
SHL CX, 1 ; сдвиг влево регистра CX, что, в свою очередь, удваивает его значение
JMP L20 ; повторно выполняем стейтменты
1 |
MOVAX,00; инициализируем регистр AX значением 0 MOVBX,00; инициализируем регистр BX значением 0 MOVCX,01; инициализируем регистр CX значением 1 L20 ADDAX,01; выполняем инкремент регистра AX ADDBX,AX; добавляем AX к BX SHLCX,1; сдвиг влево регистра CX, что, в свою очередь, удваивает его значение JMPL20; повторно выполняем стейтменты |
Подытожим…
Итак, приведу неполный перечень того, в каких случаях используется ассемблер.
-
Создание загрузчиков, прошивок устройств (комплектующих ПК, встраиваемых систем), элементов ядра ОС.
-
Низкоуровневая работа с железом, в т.ч. с процессором, памятью.
-
Внедрение кода в процессы (injection), как с вредоносной целью, так и с целью защиты или добавления функционала. Системный софт.
-
Блоки распаковки, защиты кода и прочего функционала (с целью изменения поведения программы, добавления новых функций, взлома лицензий), встраиваемые в исполняемые файлы (см. UPX, ASProtect и пр).
-
Оптимизация кода по скорости, в т.ч. векторизация (SSE, AVX, FMA), математические вычисления, обработка мультимедиа, копирование памяти.
-
Оптимизация кода по размеру, где нужно контролировать каждый байт. Например, в демосцене.
-
Вставки в языки высокого уровня, которые не позволяют выполнять необходимую задачу, либо позволяют делать это неоптимальным образом.
-
При создании компиляторов и трансляторов исходного кода с какого-либо языка на язык ассемблера (например, многие компиляторы C/C++ позволяют выполнять такую трансляцию). При создании отладчиков, дизассемблеров.
-
Собственно, отладка, дизассемблирование, исследование программ (reverse engineering).
-
Создание файлов данных с помощью макросов и директив генерации данных.
-
Вы не поверите, но ассемблер можно использовать и для написания обычного прикладного ПО (консольного или с графическим интерфейсом – GUI), игр, драйверов и библиотек 🙂
The ADD and SUB Instructions
The ADD and SUB instructions are used for performing simple addition/subtraction of binary data in byte, word and doubleword size, i.e., for adding or subtracting 8-bit, 16-bit or 32-bit operands, respectively.
Syntax
The ADD and SUB instructions have the following syntax −
ADD/SUB destination, source
The ADD/SUB instruction can take place between −
- Register to register
- Memory to register
- Register to memory
- Register to constant data
- Memory to constant data
However, like other instructions, memory-to-memory operations are not possible using ADD/SUB instructions. An ADD or SUB operation sets or clears the overflow and carry flags.
Example
The following example will ask two digits from the user, store the digits in the EAX and EBX register, respectively, add the values, store the result in a memory location ‘res‘ and finally display the result.
SYS_EXIT equ 1 SYS_READ equ 3 SYS_WRITE equ 4 STDIN equ 0 STDOUT equ 1 segment .data msg1 db "Enter a digit ", 0xA,0xD len1 equ $- msg1 msg2 db "Please enter a second digit", 0xA,0xD len2 equ $- msg2 msg3 db "The sum is: " len3 equ $- msg3 segment .bss num1 resb 2 num2 resb 2 res resb 1 section .text global _start ;must be declared for using gcc _start: ;tell linker entry point mov eax, SYS_WRITE mov ebx, STDOUT mov ecx, msg1 mov edx, len1 int 0x80 mov eax, SYS_READ mov ebx, STDIN mov ecx, num1 mov edx, 2 int 0x80 mov eax, SYS_WRITE mov ebx, STDOUT mov ecx, msg2 mov edx, len2 int 0x80 mov eax, SYS_READ mov ebx, STDIN mov ecx, num2 mov edx, 2 int 0x80 mov eax, SYS_WRITE mov ebx, STDOUT mov ecx, msg3 mov edx, len3 int 0x80 ; moving the first number to eax register and second number to ebx ; and subtracting ascii '0' to convert it into a decimal number mov eax, sub eax, '0' mov ebx, sub ebx, '0' ; add eax and ebx add eax, ebx ; add '0' to to convert the sum from decimal to ASCII add eax, '0' ; storing the sum in memory location res mov , eax ; print the sum mov eax, SYS_WRITE mov ebx, STDOUT mov ecx, res mov edx, 1 int 0x80 exit: mov eax, SYS_EXIT xor ebx, ebx int 0x80
When the above code is compiled and executed, it produces the following result −
Enter a digit: 3 Please enter a second digit: 4 The sum is: 7
The program with hardcoded variables −
section .text global _start ;must be declared for using gcc _start: ;tell linker entry point mov eax,'3' sub eax, '0' mov ebx, '4' sub ebx, '0' add eax, ebx add eax, '0' mov , eax mov ecx,msg mov edx, len mov ebx,1 ;file descriptor (stdout) mov eax,4 ;system call number (sys_write) int 0x80 ;call kernel mov ecx,sum mov edx, 1 mov ebx,1 ;file descriptor (stdout) mov eax,4 ;system call number (sys_write) int 0x80 ;call kernel mov eax,1 ;system call number (sys_exit) int 0x80 ;call kernel section .data msg db "The sum is:", 0xA,0xD len equ $ - msg segment .bss sum resb 1
When the above code is compiled and executed, it produces the following result −
The sum is: 7
3.4.3 Строковые константы
Строковые константы допустимы только в некоторых псевдо-инструкциях, а именно
в семействе DB и инструкции INCBIN.
Строковые константы похожи на символьные, только длиннее. Они обрабатываются
как сцепленные друг с другом символьные константы. Так, например, следующие
строки кода эквивалентны.
Следующие строки также эквивалентны:
Обратите внимание, что когда используется db,
константа типа ‘ab’ обрабатывается как строковая,
хотя и достаточно коротка, чтобы быть символьной, потому что иначе db ‘ab’
имело бы тот же смысл, какой и db ‘a’, что глупо. Соответственно, трех- или четырехсимвольные константы, являющиеся операндами
инструкции dw, обрабатываются также как строки
Логические операции
Мнемоника | Описание | Операция | Флаги |
---|---|---|---|
AND Rd, Rr | Логическое «И» двух регистров | Rd ← Rd and Rr | Z, N, V |
ANDI Rd, K | Логическое «И регистра и константы | Rd ← Rd and K | Z, N, V |
EOR Rd, Rr | Исключающее «ИЛИ» двух регистров | Rd ← Rd xor Rr | Z, N, V |
OR Rd, Rr | Логическое «ИЛИ» двух регистров | Rd ← Rd or Rr | Z, N, V |
ORI Rd, K | Логическое «ИЛИ» регистра и константы | Rd ← Rd or K | Z, N, V |
COM Rd | Перевод в обратный код | Rd ← 0xFF — Rd | Z, C, N, V |
NEG Rd | Перевод в дополнительный код | Rd ← 0 — Rd | Z, C, N, V, H |
CLR Rd | Очистка регистра | Rd ← Rd xor Rd | Z, N, V |
SER Rd | Установка всех разрядов регистра | Rd ← 0xFF | — |
TST Rd | Проверка регистра на отрицательное (нулевое) значение | Rd ← Rd and Rd | Z, N, V |
Запуск отладчика OLLYDBG
Программа скомпилировалась, а это уже хорошо, теперь нам нужно проверить как она сработала, вдруг она не сложила 2 числа. Для этого в папке BIN открываем наш отладчик(который устанавливали в 1 статье) OLLYDBG. В отладчике открываем файл программы (file > open first.exe), и видим наш код уже с другой стороны: Так как мы работаем с регистром eax, то именно его значение и будем отслеживать.(в правом окне, 1 значение регистров) Итак, чтобы пошагово прогнать нашу программу нужно нажать на 4 синию кнопку слева.(стрелка вниз с 3 точками) После 1 нажатия, значение в eax стало равно 3, после 2 нажатия — 5. Наша программа работает верно!
Итак, казалось бы простая программа сложения 2 чисел на Assembler, а сколько всего узнали! На сегодня все, оставляйте свои комментарии, если у вас есть вопросы или пожелания.