Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Assembler / P15

.pdf
Скачиваний:
62
Добавлен:
02.06.2015
Размер:
470.78 Кб
Скачать

1

15. Язык Ассемблера

15.1. Что такое Ассемблер.

Ассемблер — программа для перевода мнемоники команд в машинный код. Дословно "Assembler" переводится как "сборщик", т.е. Ассемблер собирает коды. Язык, на котором записываются инструкции для программы Ассемблер, называется языком Ассемблера. Поэтому не совсем корректно говорить: "программа на Ассемблере". Правильно: "программа на языке Ассемблера".

С мини-Ассемблером нам пришлось работать, когда мы создавали программы в отладчике Turbo Debugger. Напомним, какие при этом возникали основные проблемы:

Нам приходилось самостоятельно распределять память для данных и кода. При этом в памяти возникали "дыры", т.к. мы опасались перекрытия данных и кода при внесении изменений в код программы;

Приходилось корректировать команды перехода "вперед", т.к. мы не знали числового значения метки в момент ввода команды;

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

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

Таких пакетов в настоящее время существует несколько. Наибольшей популярностью пользуются MASM (Macro Assembler) фирмы Microsoft и TASM (Turbo Assembler) фирмы Borland. Мы будем составлять программы так, чтобы они проходили трансляцию и компоновку средствами обоих пакетов. На различия будем указывать особо. Основное внимание будет уделено TASM.

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

15.2. Программа на языке Ассемблера.

В любом текстовом редакторе наберем текст программы, которая выводит на экран строку "Hello!". Если мы набираем эту программу в Microsoft Word, то ее нужно сохранять как текст, а не как документ.

файл first.asm

 

 

.MODEL small

; Малая модель памяти

 

.STACK 100h

; Для стека выделяется 100h байтов

 

.DATA

 

; Начало секции (сегмента) данных

msg

DB "Hello!",0Dh,0Ah,'$' ; Строка для вывода

 

.CODE

; Начало секции (сегмента) кода

start: mov

ax,@data

; Загрузить регистр DS адресом DGROUP,

 

mov

ds,ax

; в который входят сегменты данных и стека.

 

mov

ah,9h

; Вывод на экран

 

mov

dx, OFFSET msg ; строки msg

 

int

21h

; с помощью 9-й функции 21-го прерывания.

 

mov

ax,4C00h

; Завершение работы программы

 

int

21h

; с помощью функции 4Ch.

2

END start ; Завершение текста программы

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

Директива .MODEL small указывает, что программная секция данных (она начинается с директивы .DATA) и программная секция кода (начинается с директивы .CODE) будут занимать не более одного сегмента ОЗУ. Напомним, что размер сегмента — 64 Кбайт. Для стекового сегмента явно указан его размер: 256 байт.

Впрограмме имеются метки: в секции данных метка msg, отделяемая пробелом от уже знакомой нам директивы определения данных DB. В секции кода первая команда снабжена меткой start. Метка отделена от команды двоеточием. Метка могла располагаться и в отдельной строке. Текст программы заканчивается директивой END, после которой указывается метка команды, с которой должно начаться выполнение программы.

Встроке msg байты 0Dh и 0Ah — это коды возврата каретки и перевода строки. За меткой start следуют две команды, которые обеспечивают загрузку сегментного регистра DS адресом секции данных (более точно, адресом группы

сименем DGROUP, в которую входят секции данных и стека). Этот адрес хранится во встроенной переменной @data. Команда mov ds,@data недопустима, поэтому приходится использовать промежуточный регистр AX. Операция OFFSET msg вычисляет смещение msg относительно начала секции данных. Так как это операция времени ассемблирования, набираем ее прописными буквами.

15.3. Трансляция и компоновка Трансляция программы происходит по команде

с:\tasm\bin\tasm first.asm

На диске появляется файл first.obj. Объектный файл имеет весьма сложную структуру. В частности, там хранятся машинные коды команд. Компоновка происходит по команде

с:\tasm\bin\tlink first.obj

На диске появляется файл first.exe, который можно запустить на выполнение. Одновременно создается карта загрузки first.map.

Запустим файл first.exe на выполнение. first.exe

Hello!

(Поучительно проследить, как изменяется на разных этапах подготовки программы число, обозначенное как @data.)

Транслятор tasm и компоновщик tlink имеют ключи, которые задают режимы создания объектного и исполнительного файлов. Чтобы получить листинг при трансляции введите команду: с:\tasm\bin\tasm/l first. Обратите внимание, что расширение файла можно и не указывать.

Если вы хотите включить в загрузочный файл отладочную информацию, а затем изучать программу в отладчике, то нужно ввести команды:

с:\tasm\bin\tasm/zi first с:\tasm\bin\tlink/v first с:\tasm\bin\td first

3

Ключи можно комбинировать, например:

с:\tasm\bin\tasm/zi/l first

15.4. Анализ листинга программы

Листинг программы first.lst состоит из трех частей. Проанализируем каждую часть отдельно. Для наглядности из текста программы перед трансляцией были удалены комментарии.

Turbo Assembler Version 4.1

19/04/02 00:33:35

Page 1

first.asm

 

 

 

 

1

0000

 

.MODEL small

 

2

0000

 

.STACK 100h

 

3

0000

 

.DATA

 

 

4

0000

48 65 6C 6C 6F 21 0D+ msg

DB "Hello!",0Dh,0Ah,'$'

5

 

0A 24

 

 

 

6

0009

 

.CODE

 

 

7

0000

B8 0000s start:

mov

ax,@data

 

8

0003

8E D8

mov

ds,ax

 

9

0005

B4 09

mov

ah,9h

 

10

0007

BA 0000r

mov

dx, OFFSET msg

11

000A CD 21

int 21h

 

 

12

000C B8 4C00

mov

ax,4C00h

 

13

000F CD 21

int 21h

 

 

14

 

 

END

start

 

Впервой части листинга информация расположена в четырех колонках. В первой колонке — порядковые номера строк листинга. Вторая колонка — текущее значение счетчика адреса (не путать с регистром IP — счетчиком команд). В начале секции данных (.DATA) и секции кода (.CODE) счетчик адреса сбрасывается, а затем возрастает с появлением в листинге новых данных

икоманд. В нашем случае, когда программа размещается только в одном файле, значение счетчика адреса станет в дальнейшем значением смещения (offset). В третьей колонке показаны сгенерированные коды. В секции данных мы видим ASCII коды для строки "Неllo!" и управляющих символов. Коды не уместились на одной строке (символ переноса — знак '+'). По значению счетчика адреса 0009 можно заключить, что строка занимает 9 байтов.

Всекции кода в третьей колонке размещены машинные коды команд. Обращает на себя внимание строка листинга

7 0000 B8 0000s start: mov ax,@data

Суффикс s (s — segment) в коде команды указывает, что непосредственный операнд связан с сегментным адресом. Когда мы будем изучать код программы в отладчике, то первый байт команды останется тем же — B8, а второй и третий байты изменятся, т.е. не будут соответствовать листингу. Об этом и

предупреждает суффикс s.

 

В строке

 

10 0007 BA 0000r

mov dx, OFFSET msg

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

4

секцией данных, то смещение msg от начала сегмента изменилось бы. На такую возможность и указывает r.

Вторая часть листинга содержит таблицу символов. Приведем фрагмент

этой таблицы.

 

 

 

Turbo Assembler

Version 4.1

19/04/02 00:33:35 Page 2

Symbol Table

 

 

 

Symbol Name

 

Type

Value

??DATE

 

Text

"19/04/02"

??FILENAME

 

Text

"first "

??TIME

 

Text

"00:33:35"

 

 

 

@DATA

 

Text

DGROUP

 

 

 

MSG

 

Byte

DGROUP:0000

START

 

Near

_TEXT:0000

Впервой колонке перечислены символические имена (Symbol Name), во второй колонке их тип (Type): текст, число. Для метки msg указано, что ее тип

байтовый, для метки start — что она "близкая" (это определяется выбранной моделью памяти small). В третьей колонке приведено значение (Value) символического имени. Все имена записаны прописными буквами. Нами в программе определены только два имени: msg и start (Ассемблер перевел их в верхний регистр, т.е. заменил все буквы прописными). Остальные имена являются встроенными.

Втретьей, заключительной части листинга перечислены программные сегменты (секции) и группы, в которые они объединены.

Groups & Segments

Bit Size Align

Combine Class

DGROUP

Group

 

STACK

16

0100 Para

Stack STACK

_DATA

16

0009 Word

Public DATA

_TEXT

16

0011 Word

Public CODE

Сейчас мы не будем подробно анализировать эту часть листинга. Уместно сделать это позже, когда мы будем размещать текст программы в нескольких файлах. Сейчас отметим следующее. Секция кода (CODE) получила имя _TEXT, ее размер (Size) составляет 11h байтов. Секции стека с именем STACK и секция данных с именем _DATA объединены в группу DGROUP (группа размещается в одном сегменте оперативной памяти). Теперь ясно, почему в таблице символов введенные нами символические имена msg и start имеют сегментную часть адреса DGROUP и _TEXT. Конкретные числовые значения станут известны только на этапе загрузки .exe-файла в оперативную память.

15.5. Сообщения TASM об ошибках

При вызове tasm для трансляции first.asm выдаются следующие сообщения.

D:\ >tasm first.asm

Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International Assembling file: first.asm

5

Error messages: None

Warning messages: None

Passes: 1

Remaining memory: 388k

Сначала выводится номер версии Ассемблера. В пакет TASM 5.0 входит ассемблер tasm.exe версии 4.1. Далее сообщается, какой файл ассемблируется (позднее мы увидим, что за один вызов tasm можно ассемблировать несколько файлов, поэтому вовсе не лишней является информация, какой именно файл обрабатывается). Для этого файла в следующих двух строках выдается информация, что ошибок и предупреждений нет. Далее сообщается, что по тексту программы сделан один проход. Последнее сообщение информирует, сколько оперативной памяти осталось свободной при ассемблировании.

Перечислим возможные типы ошибок и предупреждений. Для этого намеренно будем вносить в файл ошибки.

1.Удалим директиву END start (проще всего закомментировать ее, поставив перед ней точку с запятой).

Assembling file: first.asm

**Fatal** first.asm(15) Unexpected end of file encountered Error messages: 1

Выдается тип ошибки (Fatal — грубая), в скобках указан номер строки файла, где встретилась ошибка (15), далее — диагностическое сообщение: "Неожиданно встречен конец файла". При этом не создается объектный файл и не создается файл с листингом (при наличии ключа /l).

Восстановим директиву END start.

2.Удалим запятую, разделяющую операнды, в команде mov ah,9h

**Error** first.asm(8) Undefined symbol: AH9H *Warning* first.asm(8) Argument needs type override Error messages: 1

Warning messages: 1

На этот раз файл листинга есть, но объектный файл по-прежнему отсутствует. Сообщение об ошибке: "Неопределенный символ AH9H". Как видите, сообщение не проясняет происхождение ошибки. К этой же строке делается предупреждение: аргумент требует переопределения типа. Это даже комментировать не будем. Восстановите запятую.

3. Добавим перед директивой END start строку mov ax,[200h]. *Warning* first.asm(13) [Constant] assumed to mean immediate constant Error messages: None

Warning messages: 1

На этот раз создается и файл с листингом и объектный файл. Но к предупреждениям нужно относиться внимательно и настороженно. В отладчике

мы увидим строку:

 

 

1AB7:0011 B80002

mov

ax,0200

Это вовсе не соответствует нашему замыслу: поместить в регистр AX содержимое ячейки со смещением 200. Предупреждение на этот раз было точным: Константа, заключенная в квадратные скобки, предполагается константой — непосредственным операндом. Как исправить эту ошибку? Нужно использовать префикс замены сегмента: mov ax, ds:[200h]. Тогда мы

увидим в отладчике:

 

1AB7:0011 A10002

MOV AX,[0200]

6

Префикс для DS: в коде, конечно, отсутствует.

15.6. Карта памяти Файл с картой памяти возникает при вызове компоновщика.

Start

Stop

Length

Name

Class

00000H

00010H

00011H

_TEXT

CODE

00020H

00028H

00009H

_DATA

DATA

00030H

0012FH 00100H STACK

STACK

Program entry point at 0000:0000

Для каждой программной секции определен начальный адрес (Start), конечный адрес (Stop), длина в байтах (Length), имя (Name) и класс (Class). Указан стартовый адрес (Program entry point). Адреса, указанные в Start, будут приплюсованы к адресу загрузки программы. Этот адрес задает загрузчик — компонента операционной системы программа command.com

15.7. Сообщения TLINK об ошибках

Так же как и TASM компоновщик выдает сообщения об ошибках трех типов: Fatal, Error, Warning. Приведем два примера.

Изменим в программе first.asm последнюю строку:

END ; start

Трансляция пройдет без замечаний. А компоновщик выдаст сообщение об ошибке:

Turbo Link Version 7.1.30.1. Copyright (c) 1987, 1996 Borland International Fatal: No program entry point (в программе нет стартового адреса) Восстановим последнюю строку (END start) и закомментируем строку

.STACK 100h. На этот раз получим предупреждение:

Warning: No stack

15.8. Запуск программы на выполнение

При запуске exe-файла на выполнение в ОЗУ образуется два блока памяти. В первом блоке расположены переменные окружения (enviroment). Например, там находятся строки PATH из autoexec.bat.

Второй блок (в котором и находится программа) состоит из двух частей. Сначала в памяти расположен так называемый префикс программного сегмента (PSP — program segment prefix). Его размер составляет ровно 256 = 100h байт. Далее располагается код и данные самой программы.

PSP в свою очередь состоит из двух частей. В его первой половине система размещает некоторую служебную информацию, к которой можно обращаться из самой программы. Например, по определенному смещению внутри PSP находится адрес блока окружения. Вторая половина PSP начинается со смещения 80h (именно половина: 80h + 80h = 100h). Она содержит хвост командной строки. Речь об этом пойдет ниже.

После загрузки программы в ОЗУ сегментные регистры содержат фиксированные адреса. В CS находится адрес сегмента кода программы, в SS — адрес сегмента стека программы. Они уже настроены нужным образом. А вот DS и ES содержат сегментный адрес PSP. Именно поэтому первые две выполняемые команды в exe-программе такие:

7

start: mov ax, @data mov ds, ax

После этого DS содержит сегментный адрес данных (более точно: сегментный адрес группы, включающей сегмент данных и сегмент стека). ES продолжает показывать на PSP.

15.9. Директивы определения данных.

Определение байтов, слов и двойных слов. Для начала мы изучим три директивы: DB (define byte), DW (define word), DD (define doubleword). Первые две нам уже знакомы по мини-Ассемблеру. Например,

DB 15, –3, 7 (в отличие от debug можно вводить и отрицательные числа) Метки. Метка — это символическое имя адреса с данными или командой.

Метка может состоять из симолов: A-Z, a-z, _, @, $, ?, 0-9. Метка не должна начинаться символами 0-9. Длина метки может составлять 255 символов, но Ассемблер различает только первые 32 символа. От директивы объявления данных метка отделяется пробелами, от команды — двоеточием. Метка с двоеточием может стоять в отдельной строке.

Использование меток. Рассмотрим пример. num DW 12

……

mov ax, num ; поместить в регистр AX число 12

Проанализируем синтаксис команды mov ax, num. Числовое значение num

— это адрес. Допустим, адрес (смещение) равен 4. Но в AX загружается не адрес num, а содержимое слова по этому адресу! В отладчике мы увидим команду mov ax, [0004]. Поэтому хотелось бы записать на языке Ассемблера эту команду иначе: mov ax, [num]. И MASM и TASM транслируют такую команду правильно. Более того, в TASM есть режим IDEAL, который требует именно такую форму записи команды: mov ax, [num]. Встретив команду mov ax, num в режиме IDEAL ассемблер TASM выдает предупреждение: Pointer expression needs brackets (Ссылка на адрес в памяти не заключена в квадратные скобки). Но сложилась традиция опускать квадратные скобки. Будем ей следовать.

А как загрузить адрес? Это можно сделать двумя способами. Один вам уже знаком: lea si, num. Второй способ — новый: mov si, OFFSET num. OFFSET —

это функция времени ассемблирования. На этапе ассемблирования вычисляется смещение num и формируется код команды. В отладчике (на этапе выполнения) мы увидим: mov si, 0004. Директивы и функции Ассемблера рекомендуется набирать прописными буквами. Ассемблеру это безразлично, а читателю делает текст программы это позволяет сразу видеть, что делается на стадии ассемблирования, а что — на стадии выполнения.

Цепочка символов. Определить цепочку символов можно двояко:

DB 'Hello'

DB "Hello"

т.е. в качестве ограничителей строки могут быть либо одинарные, либо двойные кавычки. Но недопустимо эти кавычки смешивать: DB 'Hello".

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

DB "Hello", 0Dh, 0Ah, '$'

Если вместо 0Dh поместить Dh, то Ассемблер выдал бы сообщение об ошибке: Illegal use of register (неверное использование регистра). Dh было бы

8

воспринято как имя регистра DH. Число должно предваряться десятичной цифрой.

Резервирование памяти. Если начальное значение выделяемой области памяти несущественно, то можно вместо конкретных чисел поместить вопросительные знаки:

DW ?, 6, ?, ? ; Зарезервировано четыре слова, определено второе.

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

Оператор дублирования. Если некоторая последовательность данных многократно повторяется, то для сокращения записи можно воспользоваться оператором дублирования DUP (duplicate — дублировать). Примеры:

DB 6 DUP (1) ; эквивалентно DB 1,1,1,1,1,1

DW 2 DUP (1,2,?) ; эквивалентно DW 1,2,?,1,2,?

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

DB 2 DUP( 5, 3 DUP(1,0),2) ; эквивалентно DB 5,1,0,1,0,1,0,2,5,1,0,1,0,1,0,2

Атрибутные операторы. Как вы помните, отладчик "толковал по своем усмотрению" команду mov [300],5. Неясно, число 5 записывается в байт по адресу 300 или в слово по адресу 300? Разница существенная: байт с адресом 301 сохранит свое значение или будет обнулен? Поэтому для уточнения действия команды нужно было использовать атрибутный оператор: mov word ptr [300],5. Теперь рассмотрим пример на языке Ассемблера.

num DW 0FFFFh

……

mov num,5

Здесь атрибутный оператор не нужен. Ассемблер знает, что num — адрес слова, поэтому генерирует правильный код команды. После выполнения команды в байте с адресом num будет храниться число 5, а в байте с адресом num + 1 — нуль. Но как поступить, если нужно обратиться к байту слова? Вот здесь понадобится атрибутный оператор: mov byte ptr num+1,5. Адрес num + 1 вычисляется на этапе ассемблирования. В результате: num DW 5FFh.

В этом примере можно было обойтись без атрибутного оператора, но тогда слову num надо было дать альтернативное имя:

numb LABEL BYTE ; LABEL — метка num DW 0FFFFh

……

mov numb+1,5

Еще об атрибутном операторе. В отладчике была допустимой команда mov ah, [400]. Операнд-приемник — регистр, имеющий размер байта. Поэтому по адресу 400 выбирается байт. Теперь введем команду: mov ah, num. Последует сообщение об ошибке: Operand types do not match (Несоответствие типов операндов). Попытаемся исправить ошибку:

mov byte ptr ah, num.

Вновь последует то же самое сообщение об ошибке. Правильно так: mov ah, byte ptr num.

15.10. Подпрограммы.

Подпрограммы в программах на языке Ассемблера имеют формат

9

имя PROC

<тело подпрограммы> ret

имя ENDP

Вызов подпрограммы осуществляется командой call имя.

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

Пример. Строка заканчивается символом $. Подпрограмма преобразует прописные буквы строки в строчные. Входной параметр подпрограммы — адрес строки.

.MODEL small

.STACK 100h

.DATA

msg DB "Dogs and Cats are Friends", 0Dh, 0Ah, '$'

.CODE ;-----------------------------------------------------------------------------------------------

;Подпрограмма преобразует прописные буквы строки в строчные.

;Строка должна завершаться символом $.

;Вход: SI — адрес строки;

;Выход: SI — адрес терминатора строки;

;Используемые регистры: AL.

;------------------------------------------------------------------------------------------------

 

 

 

ToLower PROC

 

 

next:

mov al, [si]

;

Поместить очередной символ строки в AL

 

cmp al, '$'

;

Если это терминатор строки,

 

je fin

;

то завершить обработку.

 

cmp al, 'A'

; Если символ лежит в диапазоне 'A' - 'Z'

 

jb cont

 

 

 

cmp al, 'Z'

 

 

 

ja cont

 

 

 

add al, 'a' - 'A' ; то превратить его в соответствующую

 

 

 

; строчную букву.

 

mov [si], al

 

; и поместить символ обратно в строку

cont:

inc si

 

; Переместить указатель на следующий символ

 

jmp next

 

 

fin:

ret

 

 

ToLower ENDP

start:mov ax,@data mov ds,ax

;Вывод исходной строки mov ah,9

mov dx,OFFSET msg int 21h

;Преобразование строки mov si, OFFSET msg

10

call ToLower

;Вывод новой строки mov ah,9

mov dx,OFFSET msg int 21h

;Завершение программы mov ax, 4C00h

int 21h END start

Вместо je fin можно было сразу поместить команду ret.

Задача. Написать подпрограмму, которая удаляет из строки все цифры.

15.11. Командная строка.

Обычно мы запускаем программу на выполнение, набирая ее имя в командной строке и заканчивая ввод нажатием клавиши Enter. Но в командной строке можно вводить и дополнительную информацию, которая составляет так называемый "хвост командной строки". Пример: tasm.exe prim.asm. Здесь в хвосте командной строки мы указываем, какую программу нужно ассемблировать.

Хвост командной строки располагается в PSP, начиная со смещения 80h. Пример. Пусть в командной строке введено:

D:\>prim.exe ab c

Посмотрим в отладчике, как выглядит хвост командной строки в PSP Для этого вызовем отладчик так:

td prim.exe ab c

Перейдем в окно CPU (F10 → View → CPU). Перейдем в панель данных (Shift+Tab). Отобразим данные, начиная со смещения 80h (Alt+F10 → Goto → 80). Мы увидим:

ds:0080 05 20 61 62 20 63 0D 00

Сегментная часть адреса находится в DS, т.к. выполнение программы еще не началось. 05 — количество символов в командной строке: пробел, 'a', 'b', пробел, 'c'. Строка заканчивается управляющим символом 0D (в количество символов он не входит). Теперь из программы можно обратиться к этой области памяти и извлечь из нее необходимую информацию.

15.12. Пример выполнения задания D1.

15.12.1. Формулировка задания.

Вариант 0. Текст для шифрования вводится в командной строке. В тексте — только прописные латинские буквы и пробелы. Удалить пробелы и зашифровать текст. Каждый символ преобразуется по формуле 3*код+5 (mod 26). При этом считается, что код буквы 'A' равен 0, код буквы 'B' равен 1 и т.д. В программе должны быть одна или две подпрограммы для шифрования символа (или группы символов).

15.12.2. Текст программы.

файл D1v0.asm

Соседние файлы в папке Assembler