Вы здесь: HomeLinuxРазное

Макросы

Для каждой закодированной команды ассемблер генерирует  одну  команду на машинном языке. Но для каждого закодированного оператора компиляторного языка Pascal или  C  генерируется  один  или  более  (чаще  много)  команд машинного языка.  В этом отношении можно считать, что  компиляторный  язык состоит из макрооператоров.

Ассемблер  FASM ( MASM так же как и другие)  также  имеет  макросредства,  но    макросы    здесь определяются программистом.  Для этого  задается    директива MACRO ,имя  макроса, открываеться фигурная скобка, различные ассемблерные команды, которые должен генерировать  данный макрос и для завершения макроопределения - закрываеться фигурная скобка.  Затем в  любом месте  программы,  где  необходимо  выполнение определенных в макрокоманде команд,  достаточно  закодировать  имя  макроса.  В  результате  ассемблер сгенерирует необходимые команды. Использование макрокоманд позволяет:

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

 Примерами макрокоманд могут быть операции ввода-вывода,  связанные  с инициализацией регистров и выполнения прерываний  преобразования  ASCII  и двоичного форматов данных, арифметические операции  над  длинными  полями, обработка строковых данных, деление с помощью вычитания.

Пример простого макроса:

org 100h
macro endprogram
{
mov ax,4c00h
int 21h
}
mov ah,9
mov dx,msg
int 21h
endprogram
msg db "Hello World$" 

Как видим создали простой макрос который упрощает наглядно вид программы. Вместо макроса endprogram вставляется его тело(то есть его код).

Давайте создадим еще один макрос который принимает параметры:

macro endprogram
{
mov ax,4c00h
int 21h
}
macro print str {
	mov ah,9
	mov dx,str
	int 21h
}
org 100h
print msg
endprogram
msg db "Hello World$" 

Мы создали макрос который принимает переменную и выводит ее на экран. Это переменная (str) для макроса является локальной, и она видна только внутри макроса. также в макросе можно создавать свои локальные метки и переменные:

macro endprogram
{
mov ax,4c00h
int 21h
}

macro print str,kol {

local start
local cikl
local endl 
jmp start
endl db 10,13,'$'

start:

mov cx,kol
cikl:

	mov ah,9
	mov dx,str
	int 21h
	
	
	mov ah,9
	mov dx,endl
	int 21h
loop cikl
}
org 100h
print msg,8
endprogram
msg db "Hello World$" 

Так же все макросы можно написать в отдельном файле а затем подключить его с помощью директивы include. К примеру создадим два файла main.asm и my_macro.asm со следующим содержимым:

my_macro.asm:

macro endprogram
{
mov ax,4c00h
int 21h
}

macro print str,kol {

local start
local cikl
local endl 
jmp start
endl db 10,13,'$'

start:

mov cx,kol
cikl:

	mov ah,9
	mov dx,str
	int 21h
	
	
	mov ah,9
	mov dx,endl
	int 21h
loop cikl
}

main.asm:

include 'my_macro.asm'
org 100h
print msg,8
endprogram
msg db "Hello World$" 

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

OFFSET

Оператор OFFSET возвращает относительный адрес переменной  или  метки внутри сегмента данных или кода. Оператор имеет следующий формат: 

OFFSET переменная или метка

 Например, команда:

MOV  DX,OFFSET TABLEA

 устанавливает в регистре DX относительный адрес (смещение) поля  TABLEA  в сегменте данных. (Заметим, что команда LEA выполняет аналогичное действие, но без использования оператора OFFSET.)

PTR

Оператор PTR используется совместно с атрибутами типа BYTE, WORD  или DWORD для локальной отмены определенных  типов  (DB,  DW  или  DD)  или  с атрибутами NEAR или FAR для отмены значения дистанции по умолчанию. Формат оператора следующий:

тип  PTR  выражение

 В поле "тип" указывается новый атрибут,  например  BYTE.  Выражение  имеет ссылку на переменную или константу.  Приведем несколько примеров оператора PTR:

FLDB DB 22H
DB 35H
FLDW DW 2672H                 ;0бьектный код 7226
MOV       AН,BYTE PTR FLDW    ;Пересылает 1-й байт (72)
ADD       BL,BYTE PTR FLDW+1  ;Прибавляет 2-й байт (26)
MOV       BYTE PTR FLDW,05    ;Пересылает 05 в 1-й байт
MOV       AX,WORD PTR FLDB    ;3аносит в АХ байты (2235)
CALL      FAR PTR[BX]         ;Длинный вызов процедуры 

EQU

Директива EQU не определяет элемент данных, но  определяет  значение, которое  может  быть  использовано для  постановки  в  других   командах. Предположим, что в сегменте данных закодирована следующая директива EQU:

TIMES     EQU  10

Имя, в данном случае TIMES, может быть  представлено  любым  допустимым  в ассемблере  именем.  Теперь,  в  какой-бы  команде  или    директиве    не использовалось слово TIMES  ассемблер  подставит  значение  10.  Например, ассемблер преобразует директиву

FIELDA    DB   TIMES DUP (?)

в

FIELDA    DB   10 DUP (?)



Имя, связанное с некоторым значением с помощью директивы  EQU,  может использоваться в командах, например:

COUNTR    EQU  05
...
MOV  CX,COUNTR

 

 Ассемблер заменяет имя COUNTR в команде MOV на значение 05,  cоздавая операнд с непосредственным значением, как если бы было закодировано 

MOV  CX,05     ;Ассемблер подставляет 05

Здесь преимущество  директивы  EQU  заключается  в  том,  что  многие команды могут использовать значение, определенное по  имени  COUNTR.  Если это значение  должно  быть  изменено,  то  изменению  подлежит  лишь  одна директива EQU.  Естественно, что использование директивы EQU разумно  лишь там, где подстановка имеет смысл для ассемблера.  В  директиве  EQU  можно использовать символические имена:

TP   EQU  TOTALPAY


 

MPY  EQU  MUL

Первый пример предполагает, что в сегменте данных программы опpеделено имя TOTALPAY.  Для любой команды, содержащей операнд TP, ассемблер заменит его на адрес TOTALPAY.  Второй пример показывает возможность  использования  в программе слова MPY вместо обычного мнемокода MUL.

 

Linux

Данный пост я хочу посвятить тому как написать простую программу на ассемблере для Linux. И так начнем с того что все системные вызовы в линуксе осуществляются с помощью прерывания 80h. Номер системного вызова содержится в регистре ЕАХ, если системный вызов(прерывание) принимает параметры то они находятся в регистрах EBX,ECX, EDX, ESI и EDI. Результат прерывания будет находиться в регистре ЕАХ и если результат будет от fffff000h до ffffffffh то это говорит что прерывание совершилось с ошибкой.  К примеру системная функция которая отвечает за ввод и вывод в консоли находиться под номером 4, а номер под функции будет находиться в регистре EBX. Теперь рассмотрим пример кода для FASM ассемблера:

format ELF executable 3
entry start
 
segment readable executable
 
start:
	mov	eax,4
	mov	ebx,1
	mov	ecx,msg
	mov	edx,msg_size
	int	0x80
 
	mov	eax,1
	xor	ebx,ebx
	int	0x80
 
segment readable writeable
 
msg db 'Hello world!',0xA
msg_size = $-m

ассемблер linux

Теперь все по порядку:
format ELF executable 3 - строка говорит нам что будет собран  исполняем файл  UNIX-подобных системах(linux к ним относиться).

start: - метка входа, то есть начало нашей ассемблер программы. Далее идет подготовка к выводу строки:

mov eax,4        ;указываем номер функции
mov ebx,1        ;затем говорим про номер подфункции, в данной ситуации это вывод строки 
mov ecx,msg     ;указываем адрес строки которую будем выводим
mov edx,msg_size ;вносим в регистр количество символов выводимой строки
int 0x80         ;и делаем прерывание
 

Затем код ниже отвечает за  выход из программы предварительно обнулив bx, для того чтоб сообщить системе что все было успешно. 

mov eax,1
xor ebx,ebx
int 0x80

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

msg db 'Hello world!',0xA
msg_size = $-m

Теперь давайте расмотрим как вводить символы для этого предназначен системный вызов под номером 3. Пример:

format ELF executable 3
entry start
start:
mov eax,3
mov ebx,1
mov ecx,msg
mov edx,19
int 0x80
;eax - содержит количество введеных символов... 
mov edx,eax  ;указываем кол символов которое нужно вывести
mov eax,4
mov ebx,1
mov ecx,msg
int 0x80
mov eax,1
xor ebx,ebx
int 0x80
msg db 20 dup (0) 
msg_size = $-msg

По всем вопросом пишите в коментариях. 

Еще статьи...

  1. Hello world