Урок 7

Стейтменты

В ассемблере есть три вида стейтментов:

   Выполняемые инструкции (или просто «инструкции») — сообщают процессору, что нужно делать. Каждая инструкция хранит в себе код операции (или «опкод») и генерирует одну инструкцию на машинном языке.

   Директивы ассемблера — сообщают программе об аспектах компиляции. Они не генерируют инструкции на машинном языке.

   Макросы — являются простым механизмом вставки кода.

В ассемблере на одну строку приходится один стейтмент, который должен соответствовать следующему формату:

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
2
3
4
5
6
7
8
9
10

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
2

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

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
2
3
4
5

CMPDX,00; сравниваем значение регистра DX с нулем

JEL7; если true, то переходим к метке L7

.
.

L7…

Инструкция CMP часто используется для проверки того, достигла ли переменная-счетчик максимального количества раз выполнения цикла или нет. Например:

INC EDX
CMP EDX, 10 ; сравниваем, достиг ли счетчик значения 10 или нет
JLE LP1 ; если его значение меньше или равно 10, то тогда переходим к LP1

1
2
3

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

;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

 
;DATA

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

Замена деления умножением на Ассемблере

  1. ; В описании используются следующие вспомогательные значения:
  2. ; m = множитель (multiplier)
  3. ; s = коэффициент смещения (shift factor)
  4. ;——————————————————
  5. ; Алгоритм №0
  6. ;——————————————————
  7.         mov     eax, m
  8.         mul     делитель
  9.         shr     edx, s
  10.         ; EDX = частное
  11. ;——————————————————
  12. ; Алгоритм №1
  13. ;——————————————————
  14.         mov     eax, m
  15.         mul     делитель
  16.         add     eax, m
  17.         adc     edx, 0
  18.         shr     edx, s
  19.         ; EDX = частное

3132
Code (Assembler) : Убрать нумерациюВыделить код

  1.         ; EAX = делимое
  2.         xor     edxedx
  3.         cmp     eax, делитель ; CF = (делимое < делитель) ? 1 : 0
  4.         sbb     edx, -1 ; частное = 0+1-CF = (делимое < делитель) ? 0 : 1
  5.         ; EDX = частное

Code (Assembler) : Убрать нумерациюВыделить код

  1.         ; EAX = делимое
  2.         cmp     eax, делитель ; CF = (делимое < делитель) ? 1 : 0
  3.         mov     eax, 0
  4.         sbb     eax, -1 ; частное = 0+1-CF = (делимое < делитель) ? 0 : 1
  5.         ; EAX = частное

SHR reg, степень_двойки
Code (Assembler) : Убрать нумерациюВыделить код

  1.         ; EAX = делимое
  2.         shr     eax,1    ; EAX / 2
  3.         shr     eax,2    ; EAX / 4
  4.         shr     eax,3    ; EAX / 8
  5.         shr     eax,4    ; EAX / 16
  6.         ; и так далее

0
Code (Assembler) : Убрать нумерациюВыделить код

  1.         ; EAX = делимое
  2.         shr     eax,0    ; EAX / 1
  3.         ; EAX остался неизменным

NEG31
Code (Assembler) : Убрать нумерациюВыделить код

  1. ; В описании используются следующие вспомогательные значения:
  2. ; m = множитель (multiplier)
  3. ; s = коэффициент смещения (shift factor)
  4. ;——————————————————
  5. ; Алгоритм №0
  6. ;——————————————————
  7.         mov     eax, m
  8.         imul    делитель
  9.         mov     eax, делитель
  10.         shr     eax, 31
  11.         sar     edx, s
  12.         add     edxeax
  13.         ; EDX = частное
  14. ;——————————————————
  15. ; Алгоритм №1
  16. ;——————————————————
  17.         mov     eax, m
  18.         imul    делитель
  19.         mov     eax, делитель
  20.         add     edxeax
  21.         shr     eax, 31
  22.         shr     eax, s
  23.         add     edxeax
  24.         ; EDX = частное

Code (Assembler) : Убрать нумерациюВыделить код

  1. ;——————————————————
  2. ; Знаковое деление на 2
  3. ;——————————————————
  4.         ; EAX = делимое
  5.         cmp     eax, 800000000h
  6.         sbb     eax, –1
  7.         sar     eax, 1
  8.         ; EAX = частное
  9. ;——————————————————
  10. ; Знаковое деление на 2^n
  11. ;——————————————————
  12.         ; EAX = делимое
  13.         cdq
  14.         and     edx, (2^n–1)
  15.         add     eaxedx
  16.         sar     eax, n
  17.         ; EAX = частное
  18. ;——————————————————
  19. ; Знаковое деление на -2
  20. ;——————————————————
  21.         ; EAX = делимое
  22.         cmp     eax, 800000000h
  23.         sbb     eax, –1
  24.         sar     eax, 1
  25.         neg     eax
  26.         ; EAX = частное
  27. ;——————————————————
  28. ; Знаковое деление на -(2^n)
  29. ;——————————————————
  30.         ; EAX = делимое
  31.         cdq
  32.         and     edx, (2^n–1)
  33.         add     eaxedx
  34.         sar     eax, n
  35.         neg     eax
  36.         ; 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

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
2
3
4
5
6
7
8

MOVAX,00; инициализируем регистр AX значением 0

MOVBX,00; инициализируем регистр BX значением 0

MOVCX,01; инициализируем регистр CX значением 1

L20

ADDAX,01; выполняем инкремент регистра AX

ADDBX,AX; добавляем AX к BX

SHLCX,1; сдвиг влево регистра CX, что, в свою очередь, удваивает его значение

JMPL20; повторно выполняем стейтменты

Подытожим…

Итак, приведу неполный перечень того, в каких случаях используется ассемблер.

  1. Создание загрузчиков, прошивок устройств (комплектующих ПК, встраиваемых систем), элементов ядра ОС.

  2. Низкоуровневая работа с железом, в т.ч. с процессором, памятью.

  3. Внедрение кода в процессы (injection), как с вредоносной целью, так и с целью защиты или добавления функционала. Системный софт.

  4. Блоки распаковки, защиты кода и прочего функционала (с целью изменения поведения программы, добавления новых функций, взлома лицензий), встраиваемые в исполняемые файлы (см. UPX, ASProtect и пр).

  5. Оптимизация кода по скорости, в т.ч. векторизация (SSE, AVX, FMA), математические вычисления, обработка мультимедиа, копирование памяти.

  6. Оптимизация кода по размеру, где нужно контролировать каждый байт. Например, в демосцене.

  7. Вставки в языки высокого уровня, которые не позволяют выполнять необходимую задачу, либо позволяют делать это неоптимальным образом.

  8. При создании компиляторов и трансляторов исходного кода с какого-либо языка на язык ассемблера (например, многие компиляторы C/C++ позволяют выполнять такую трансляцию). При создании отладчиков, дизассемблеров.

  9. Собственно, отладка, дизассемблирование, исследование программ (reverse engineering).

  10. Создание файлов данных с помощью макросов и директив генерации данных.

  11. Вы не поверите, но ассемблер можно использовать и для написания обычного прикладного ПО (консольного или с графическим интерфейсом – 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, а сколько всего узнали! На сегодня все, оставляйте свои комментарии, если у вас есть вопросы или пожелания.