Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Уроки ассемблеру. Быстро и просто. Урок 15


Вот что подумал, друзья. А давайте изменим нашу программу из 14 урока так, чтобы наш файл был загружен в память и там изменён. Помните, мы это проделывали в 11-м уроке? Для самых любопытных опубликую листинг программы ниже.

Листинг программы, читающей файлы в память:

;Всё, что следует за значком ";" - это комментарий.

.286 ;Разрешает ассемблирование непривилегированных инструкций
;процессора 80286 (реальный режим) и инструкций арифметического
;сопроцессора 80287.

CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный
;сегмент в памяти программ по определённому адресу.
;Имя нашего сегмента будет CSEG.

assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для
;вычисления действующего адреса для всех меток и переменных, опре-
;делённых для сегмента или группы сегментов с указанным именем.
;У нас их четыре, - CS, DS, ES, SS и они будут указывать на наш
;единственный сегмент (мы его назвали CSEG).

org 100h ;Устанавливаем счётчик инструкций в текущем сегменте в соот-
;ветствии с адресом, задаваемым "выражением".
;Сейчас этот счётчик равен 100h - используется для всех программ
;типа .com

begin: ;Метка начала программы.

mov ah,1Ah ;Установим PSP в конец файла.
mov dx,offset Finish
int 21h

call Find_first ;Ищем первый файл.
jc Error_file_metka ;Нет txt-файлов - на выход.

goto_cikl: ;Начало цикла.

mov ax,3D02h ;Загружаем в регистр ah число 3Dh (функция открытия
;файла с записью), а в al число 02h (пишем в конец).
;Можно было записать и так - mov ah,3Dh
;mov al,02h

mov dx,offset Finish ;Указываем адрес файла в DTA (по умолчанию он 80h от начала PSP,
add dx,1Eh ;+1Eh - наше имя файла.
int 21h ;Выполняем функцию.

mov Handle,ax ;При открытии файлу будет присвоен номер, его и
;сохраняем для дальнейших действий,
mov bx,ax ;а заодно и сохраняем его в bx.

mov ah,3Fh ;Читаем файл
mov cx,1024 ;с длиной 1Кб.
mov dx,offset Finish
add dx,100h ;DX устанавливаем за PSP.
int 21h ;Выполняем функцию.

push ax ;Запомним длину файла в стек.

mov dx,offset Finish
add dx,100h
add dx,ax ;В ax-число действительно прочитанных байт.

mov si,offset one ;Устанавливаем si на символ, который мы будем добавлять.
mov di,dx ;Устанавливаем di туда, куда мы будем осуществлять перенос.
mov cx,1 ;Переносим 1 байт.
rep movsb ;Переносим!

mov ax,4200h ;Используем функцию установки указателя.
;al=00h - устанавливаем в начало.
mov cx,0 ;Нам надо записать прямо в начало файла, поэтому
mov dx,0 ;обнулим cx и dx (иначе будет писать далее на
;значение (CX * 65536) + DX
int 21h ;Выполняем функцию.

mov ah,40h ;Используем функцию записи в файл.
mov dx,offset Finish
add dx,100h ;В dx - адрес начала считанного файла.
pop cx ;Число записываемых байт.
inc cx ;На один больше.
int 21h ;Выполняем функцию.

mov ah,3Eh ;Используем функцию закрытия файла.
mov bx,Handle ;Для закрытия обязательно "вспоминаем" его номер,
;номер у нас был сохранён в Handle.
int 21h ;Выполняем функцию.

call Find_next ;Ищем следующий файл.
jnc goto_cikl ;Нашли ещё один файл; прыгаем на метку.

mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset Vse_ok ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.

mov ah,4Ch ;Используем для выхода из программы.
int 21h

Error_file_metka: ;Вывод сообщения об ошибке.

mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset Error_file ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.

mov ah,4Ch ;Используем для выхода из программы.
int 21h

Find_first proc ;Подпрограмма поиска первого файла.
mov ah,4Eh ;Ищем первый файл по маске (функция 4Eh).
xor cx,cx ;Атрибуты обычные. Смотрим, что в CX.
mov dx,offset File_name ;Адрес маски в DS:DX
int 21h ;В DTA заносится имя найденного файла.
ret
Find_first endp
;Подпрограмма поиска следующего файла.
Find_next proc
mov dx,offset Finish ;DS:DX указывают на DTA.
xor cx,cx ;CX=0.
mov ah,4Fh ;4Fh - поиск следующего файла.
int 21h ;В DTA заносится имя найденного файла.
ret
Find_next endp

File_name db '*.txt',0 ;Маска файла.

Handle dw 0FFFFh ;Определяем переменную Handle, которую используем для
;хранения номера файла. По умолчанию она равна 0FFFFh.

one db '1' ;Определяем переменную one, содержащую символ "1",
;которую мы будем приписывать в конец файла.
Vse_ok db 'Файлы найдены и обновлены. Всем спасибо.$'

Error_file db 'Файлы не найдены. Поместите файлы *.txt в каталог с программой.$'

Finish equ $ ;Метка конца программы.

CSEG ends
end begin

Пожалуйста, помните, что эта программа будет корректно работать с файлами длиной не более 1Кб. А вообще, максимальная длина программы типа .СОМ составляет 65536 байт минус длина префикса (256 байт) и обязательное слово стека (2 байта). Когда управление передается программе типа .СОМ, все регистры указывают на префикс. В указатель стека SР, если позволяет память, помещается число 0FFFFН, в противном случае - максимальный адрес памяти минус 2 байта. (DOS при входе в программу помещает в стек нулевое слово).

Друзья, чтобы продолжить изучение нами ассемблера, нам нужно разобраться в форматах хранения данных и организации массивов. С массивами на самом деле всё проще, чем в языках высокого уровня.

Массив - структурированный тип данных, состоящий из некоторого числа элементов одного типа.

Описание и инициализация массива в программе

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

1) Перечислением элементов массива в поле операндов одной из директив описания данных. При перечислении элементы разделяют запятыми. К примеру:

;массив из 5 элементов. Размер каждого элемента 4 байта:
mas dd 1,2,3,4,5

2) Используя оператор повторения dup. К примеру:

;массив из 5 нулевых элементов.
;Размер каждого элемента 2 байта:
mas dw 5 dup (0)

Такой способ определения используется для резервирования памяти с целью размещения и инициализации элементов массива.

Также существуют способы с использованием директив label и rept, а также цикла для инициализации значениями области памяти, которую можно будет впоследствии трактовать как массив.

Доступ к элементам массива

При работе с массивами необходимо чётко представлять себе, что все элементы массива располагаются в памяти компьютера последовательно.
Само по себе такое расположение ничего не говорит о назначении и порядке использования этих элементов. И только лишь программист с помощью составленного им алгоритма обработки определяет, как нужно трактовать эту последовательность байт, составляющих массив. Так, одну и ту же область памяти можно трактовать как одномерный массив, и одновременно те же самые данные можно трактовать как двумерный массив. Всё зависит только от алгоритма обработки этих данных в конкретной программе. Сами по себе данные не несут никакой информации о своём “смысловом”, или логическом, типе. Помните об этом принципиальном моменте.
Эти же соображения можно распространить и на индексы элементов массива. Ассемблер не подозревает об их существовании и ему абсолютно всё равно, каковы их численные смысловые значения.
Для того чтобы локализовать определённый элемент массива, к его имени нужно добавить индекс. Так как мы моделируем массив, то должны позаботиться и о моделировании индекса. В языке ассемблера индексы массивов — это обычные адреса, но с ними работают особым образом. Другими словами, когда при программировании на ассемблере мы говорим об индексе, то скорее подразумеваем под этим не номер элемента в массиве, а некоторый адрес.

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

dim dw 0011h,2233h,4455h,6677h,8899h
Пусть эта последовательность чисел трактуется как одномерный массив. Размерность каждого элемента определяется директивой dw, то есть она равна 2 байта. Чтобы получить доступ к числу 6677h, нужно к адресу массива прибавить 6. Нумерация элементов массива в ассемблере начинается с нуля.
В общем случае для получения адреса элемента в массиве необходимо начальный (базовый) адрес массива сложить с произведением индекса этого элемента на размер элемента массива:

база + (индекс*размер элемента)

Архитектура микропроцессора предоставляет достаточно удобные программно-аппаратные средства для работы с массивами. К ним относятся базовые и индексные регистры, позволяющие реализовать несколько режимов адресации данных. Используя данные режимы адресации, можно организовать эффективную работу с массивами в памяти.

Выше мы рассматривали программу по изменению файла в памяти. А почему бы для этих целей нам не использовать массив? Вот как будет выглядеть программа после такого нововведения:

;Всё, что следует за значком ";" - это комментарий.

.286 ;Разрешает ассемблирование непривилегированных инструкций
;процессора 80286 (реальный режим) и инструкций арифметического
;сопроцессора 80287.

CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный
;сегмент в памяти программ по определённому адресу.
;Имя нашего сегмента будет CSEG.

assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для
;вычисления действующего адреса для всех меток и переменных, опре-
;делённых для сегмента или группы сегментов с указанным именем.
;У нас их четыре, - CS, DS, ES, SS и они будут указывать на наш
;единственный сегмент (мы его назвали CSEG).

org 100h ;Устанавливаем счётчик инструкций в текущем сегменте в соот-
;ветствии с адресом, задаваемым "выражением".
;Сейчас этот счётчик равен 100h - используется для всех программ
;типа .com

begin: ;Метка начала программы.

mov ah,1Ah ;Установим PSP в конец файла.
mov dx,offset Finish
int 21h

call Find_first ;Ищем первый файл.
jc Error_file_metka ;Нет txt-файлов - на выход.

goto_cikl: ;Начало цикла.

mov ax,3D02h ;Загружаем в регистр ah число 3Dh (функция открытия
;файла с записью), а в al число 02h (пишем в конец).
;Можно было записать и так - mov ah,3Dh
;mov al,02h

mov dx,offset Finish ;Указываем адрес файла в DTA (по умолчанию он 80h от начала PSP,
add dx,1Eh ;+1Eh - наше имя файла.
int 21h ;Выполняем функцию.

mov Handle,ax ;При открытии файлу будет присвоен номер, его и
;сохраняем для дальнейших действий,
mov bx,ax ;а заодно и сохраняем его в bx.

mov ah,3Fh ;Читаем файл
mov cx,1024 ;с длиной 1Кб.
mov dx,offset massive
int 21h ;Выполняем функцию.

push ax ;Запомним длину файла в стек.

mov dx,offset massive ;В ax-число действительно прочитанных байт.
add dx,ax ;В dx - конец прочитанного файла.

mov si,offset one ;Устанавливаем si на символ, который мы будем добавлять.
mov di,dx ;Устанавливаем di туда, куда мы будем осуществлять перенос.
mov cx,1 ;Переносим 1 байт.
rep movsb ;Переносим!

mov ax,4200h ;Используем функцию установки указателя.
;al=00h - устанавливаем в начало.
mov cx,0 ;Нам надо записать прямо в начало файла, поэтому
mov dx,0 ;обнулим cx и dx (иначе будет писать далее на
;значение (CX * 65536) + DX
int 21h ;Выполняем функцию.

mov ah,40h ;Используем функцию записи в файл.
mov dx,offset massive ;В dx - адрес начала считанного файла.
pop cx ;Число записываемых байт.
inc cx ;На один больше.
int 21h ;Выполняем функцию.

mov ah,3Eh ;Используем функцию закрытия файла.
mov bx,Handle ;Для закрытия обязательно "вспоминаем" его номер,
;номер у нас был сохранён в Handle.
int 21h ;Выполняем функцию.

call Find_next ;Ищем следующий файл.
jnc goto_cikl ;Нашли ещё один файл; прыгаем на метку.

mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset Vse_ok ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.

mov ah,4Ch ;Используем для выхода из программы.
int 21h

Error_file_metka: ;Вывод сообщения об ошибке.

mov ah,9 ;Загружаем в регистр ah число 9 (указываем функцию).
mov dx,offset Error_file ;Указываем, что за фразу мы будем выводить.
int 21h ;Выводим фразу.

mov ah,4Ch ;Используем для выхода из программы.
int 21h

Find_first proc ;Подпрограмма поиска первого файла.
mov ah,4Eh ;Ищем первый файл по маске (функция 4Eh).
xor cx,cx ;Атрибуты обычные. Смотрим, что в CX.
mov dx,offset File_name ;Адрес маски в DS:DX
int 21h ;В DTA заносится имя найденного файла.
ret
Find_first endp
;Подпрограмма поиска следующего файла.
Find_next proc
mov dx,offset Finish ;DS:DX указывают на DTA.
xor cx,cx ;CX=0.
mov ah,4Fh ;4Fh - поиск следующего файла.
int 21h ;В DTA заносится имя найденного файла.
ret
Find_next endp

File_name db '*.txt',0 ;Маска файла.

Handle dw 0FFFFh ;Определяем переменную Handle, которую используем для
;хранения номера файла. По умолчанию она равна 0FFFFh.

one db '1' ;Определяем переменную one, содержащую символ "1",
;которую мы будем приписывать в конец файла.

massive db 1024 dup(0) ;Массив massive длиной 1 Кб.

Vse_ok db 'Файлы найдены и обновлены. Всем спасибо.$'

Error_file db 'Файлы не найдены. Поместите файлы *.txt в каталог с программой.$'

Finish equ $ ;Метка конца программы.

CSEG ends
end begin

 


В избранное