Переделать программу, которая выводит на экран содержимое памяти по 16 байт в одной строке. Вывод программы сохранить в файл с именем "ФИО.txt" в текущей директории.
Пример:
10 11 12 13 14 15 16 17 18 19 1А 1В 1С 1D 1E 1F
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
В кратце, идея решения. Сначала создаётся и открывается файл, с нужным названием. Далее программа пробегает по внешнему и вложенному циклу, формируя таблицу.
На каждой итерации вложенного цикла мы записываем в регистры AH
и AL
ASCII коды символов, прочитанных из памяти. Их мы записываем в буффер и с его помощью записываем в файл информацию.
format MZ
entry code_segment:main
stack 400h
;---------------------------
segment data_seg
file_name db "SamsonovEM.txt$"
buff db 2 dup(?)
Здесь стоит обратить внимание на две вещи. Первое, название файла с дампом задаётся через определение байта. Второе, буффер на 2 байта, в который будет записываться байт для дампа.
segment code_segment
main:
mov ax, data_seg ;Записываем в DS сегмент с данными
mov ds, ax
mov dx, file_name
call create_and_open_file ;Создаём и открываем файл
mov bx, ax ;Кладём логический номер файла в BX. Нужно для вывода
mov ax, data_seg ;Записываем в DS сегмент с данными
mov ds, ax
mov ax, 0xb800 ;Записываем сегмент для вывода дампа
mov es, ax
mov di, 512 ;Записываем смещение для вывода дампа
mov cx, 16 ;Цикл вывода дампа в файл
row_run:
push cx
mov cx, 16
column_run:
call get_ahal_byte
call write_block_dump
call write_space
loop column_run
call write_endl
pop cx
loop row_run
jmp end_running
Рассмотрим подробнее. Первые три строки блока main
это подготовка к работе с сегментом данных (в программе .com взаимодействие происходит иначе). Далее идёт вызов функции create_and_open_file
, она создаёт и открывает файл, а также записывает
в AX
логический номер файла, чтобы в последствии можно было в него записать данные. Поскольку регистр AX
часто используется, сохраним номер файла в BX
.
mov ax, 0xb800 ;Записываем сегмент для вывода дампа
mov es, ax
mov di, 512 ;Записываем смещение для вывода дампа
Здесь мы задаём сегмент и смещение памяти, дамп которой мы будем записывать в файл.
mov cx, 16 ;Цикл вывода дампа в файл
row_run:
push cx
mov cx, 16
column_run:
call get_ahal_byte
call write_block_dump
call write_space
loop column_run
call write_endl
pop cx
loop row_run
Это основной цикл работы программы. Здесь мы пробегаем по строкам и столбцам. Для каждой ячейки таблицы мы читаем из памяти данные и записываем их в файл с помощью функций, которые мы рассмотрим далее.
Функция читает данные по адресу ES:DI
и запиывает символы ASCII в AH
и AL
. Также увеличивает DI
на 1, чтобы потом продолжить чтение следующего участка памяти.
;Записывает в AX байт из ES:DI, увеличивает DI на 1.
;В AH код первого символа в AL код второго
get_ahal_byte:
push bx
xor ax, ax ;0 в АХ
mov al, byte[es:di] ;Прочитали память в AX
push ax ;Сохранили данные на вывод в стек
shr al, 4 ;Смещаем значение вправо, чтобы оставить один символ
and al, 0xf ;Через маску обнуляем всё остальное
cmp al, 0x9 ;Проверяем цифра это или буква
ja symb_ah
;---Блок с цифрой--- ;Запись ASCII кода цифры
add al, 0x30
mov ah, al
jmp part_two
;-------------------
symb_ah: ;Запись ASCII кода буквы
add al, 0x37
mov ah, al
part_two:
mov bh, ah ;Сохраняем найденный код в BH
pop ax
and al, 0xf ;Оставляем крайний символ
cmp al, 0x9 ;Проверяем цифра это или буква
ja symb_al
;---Блок с цифрой--- ;Запись ASCII кода цифры
add al, 0x30
jmp part_three
;-------------------
symb_al: ;Запись ASCII кода символа
add al, 0x37
part_three: ;Настройка значений
add di, 1
mov ah, bh
pop bx
ret
Комментарии есть в самом коде, так что пробежимся в целом по концепции. В начале обнуляем регистр AX
, чтобы предотвратить какие-либо ошибки при чтении и читаем нужный нам участок памяти в регистр AL
.
Дальше мы делаем сдвиг вправо, чтобы нужная нам часть осталась с краю. С помощью маски и инструкции AND
мы точно оставляем нужную нам часть, а после определяем ASCII код символа. Проделываем то же самое
со вторым символом, только там уже не понадобится сдвиг, так как мы изначально читаем крайний символ. В конце мы уточняем значения регистров и возвращаемся из функции.
;В AH и AL должны лежать два символа на вывод
write_block_dump: ;В BX должен лежать логический номер файла, в СХ кол-во байт на запись - для записи в файл
pusha
push ax ;Запомнили значение
mov byte[ds:buff], ah ;Записываем в буфер значение на вывод в файл
pop ax ;Восстановили значение
mov byte[ds:buff+1], al ;Записываем в буфер значение на вывод в файл
mov cx, 2 ;Указываем, что выведем 1 символ
mov dx, buff ;Для работы функции прерывания, указываем смещение. Строка вывода: DS:DX
mov ah, 0x40 ;Функция записи в файл
int 0x21 ;Вызываем прерывание
popa
ret
write_space: ;Блок по выводу пробела в файл
pusha
mov byte[ds:buff], " "
mov cx, 1
mov dx, buff
mov ah, 0x40
int 0x21
popa
ret
write_endl: ;Блок по выводу конца строки в файл
pusha
mov byte[ds:buff], 0xA
mov cx, 1
mov dx, buff
mov ah, 0x40
int 0x21
popa
ret
Все эти блоки работают по одному принципу, поэтому рассмотрим их совместно. Последовательность действий для запись данных в файл:
- Записали по адресу
DS:DX
данные, которые необходимо записать - В
BX
должен лежать логический номер файла. Он кладётся вAX
при создании файла - В
CX
указываем кол-во вывода. Если положить туда 2, то данные изDS:DX
запишутся в файл дважды - В
AH
кладём 0х40 - функция записи в файл прерывания 0х21 - Собственно вызываем прерывание
0x21
create_and_open_file: ;В ds:dx имя файла. В AX запишется логический номер файла
push cx
xor cx,cx ;Обнуляем СХ, чтобы создался обычный файл(без флагов)
mov ah, 0x3C ;Функция создания файла
int 0x21 ;Вызываем прерывание
pop cx
ret
Для создания обычного файла мы должны сделать следующее:
- В память
DS:DX
нужно записать название создаваемого файла. - В
CX
должен лежать 0 для создания обычного файла. - Вызвать прерывание
0x21
с функцией0x3C
.
Если вы захотите что-то спросить, узнать, дополнить, уточнить, смело открывайте issue.