TextmetukSPO
.pdf
руют и код условия. В частности, такими командам являются команды сложения, вычитания и логические команды. Именно после арифметических команд обычно используются мнемокоды JZ, JNZ, в то время как после команды сравнения более естественным является применение команд JE, JNE.
Для выполнения не следующей по записи в программе команды, а некоторой другой команды, отмеченной некоторой меткой, предназначена команда
JMP метка
Учитывая, что она выполняется без всякого условия, ее называют безусловным переходом.
В табл. 2 приведены наиболее значимые в применениях мнемокоды команд условных переходов вместе с пояснениями, какие содержательные условия отвечают выполнению переходов этими командами.
Табл. 1. Связь мнемокодов для команд условного перехода с условиями
Мнемокод команды |
Условие перехода |
Примечание |
|
|
|
JE или JZ |
= |
(=0) |
От слов equal и zero |
|
|
JNE или JNZ |
¹ |
(¹0) |
От слов not, equal и zero |
|
|
JL или JNGE |
< |
(не ³ ) |
Сравнение со знаком; от слова |
||
|
|
|
less |
|
|
JG или JNLE |
> |
(не £ ) |
Сравнение со знаком; от great- |
||
|
|
|
er |
|
|
JLE или JNG |
£ |
(не > ) |
Сравнение со знаком |
|
|
JGE или JNL |
³ |
(не < ) |
Сравнение со знаком |
|
|
JC |
при флаге переноса |
От слова carry |
|
||
JNC |
не было переноса |
|
|
|
|
JB или JNAE |
< |
(не ³ ) |
Сравнение |
беззнаковое; |
от |
|
|
|
слова below - ниже |
|
|
JA или JNBE |
> |
(не £ ) |
Сравнение |
беззнаковое; |
от |
|
|
|
слова above - выше |
|
|
JBE или JNA |
£ |
(не > ) |
Сравнение беззнаковое |
|
|
JAE или JNB |
³ |
(не < ) |
Сравнение беззнаковое |
|
|
Следует обратить внимание, что для ряда машинных команд существует два (и более) различных мнемокодов, в различных терминах задающих семантически равносильные условия. Например, вместо "больше", задаваемого символическим кодом условия G, можно использовать равносильное условие "не меньше или не равно", задаваемое символическим кодом условия NLE. Конкретный вариант использования мнемокода выбирает программист согласно со своими текущими соображениями и удобствами.
Команды условного перехода архитектуры IA32 по причине ее происхождения от примитивных микропроцессоров в записи на ассемблере "по умолчанию" предполагают наиболее компактное формирование машинного кода команд. Этот вариант для размещения информации о величине перехода использует всего один байт в машинных кодах. Такое архаичное техническое решение приводит нередко к сообщению транслятора "ralative jump out of range", что обозначает необходи-
11
мость для программиста использование более длинного варианта машинного кода. Последний вариант требует указания на ассемблере модификатора NEAR, записываемого перед меткой в команде условного перехода и разделяемого, естественно пробелом от других частей команды.
Регистров программиста для хранения всей информации, требуемой в программе, оказывается в большинстве случаев недостаточно. Поэтому для такого хранения используется основная память компьютера, а доступ к ячейках этой памяти в простейших случаях обеспечивается с помощью прямого способа адресации. Такой подход в одном из частных вариантов имеет в программе вид
. . .
MOV [имя_области_для_хранения_значения], обозначение_регистра
. . .
MOV обозначение_регистра, [имя_области_для_хранения_значения]
Следует обратить особое внимание, что при использовании ассемблера nasm прямой способ адресации обязательно требует символов '[' и ']' в записи операнда.
При использовании в программе константных текстов, для обработки и вывода которых программисту нужно иметь значение длины этих текстов, очень удобным является следующий прием. Он основан на использовании макросимвола $. Этот макросимвол в программах на nasm обозначает относительный адрес, по которому в формируемом объектном коде размещается код, формируемый той строкой исходной программы, содержащий этот символ. Эти соображения приводят к использованию в программах записей вида
msg DB 'текст' lenmsg EQU $ - msg
Здесь конструкция $ - msg используется для поручения транслятору вычислить длину текста, описанного в предыдущей директиве (в общем случае предыдущих директивах). Такое вычисление выполняется как определение разности между относительным адресом, который соответствует месту сразу за текстом (началом строки lenmsg EQU …), и относительным адресом, по которому начинается текст, обозначенный меткой msg.
Задание. Разработать на языке ассемблера программу, которая должна делать приглашение для ввода двух строк текста, затем вводить эти строки и выполнять сравнение пятых символов из обеих строк. По результатам сравнения выдается одно из двух сообщений - равны символы или нет. Причем при вводе строки меньшей, чем из пяти символов, выдается сообщение о невозможности выполнения задачи. В качестве дополнительного задания следует реализовать вывод несовпадающих символов в отдельной строке с соответствующими пояснениями.
Контрольные вопросы.
12
1.Придумайте пример применения команд JB и JA для байтовых числовых данных, в котором использование соответствующих команд JL и JG приводит к ошибке.
2.Объясните, когда будет правильным использование команд условного перехода JE метка, а когда правильным будет использование подобных команд в форме JE NEAR метка.
3.Сколько подряд можно использовать команд условного перехода, чтобы это действительно имело смысл и решало содержательную задачу.
Лабораторная работа №3
Содержание работы. Изучение организации подпрограмм на уровне ассемблера и особенностей использования команд умножения и деления.
Предварительные сведения. В отличие от языков высокого уровня, где обращение к подпрограммам осуществляется просто указанием их по имени без ка- ких-либо служебных слов, в ассемблерах требуется явное указание команды вызова подпрограммы. В качестве мнемоники команды такого вызова практически во всех ассемблерах используется служебное слово CALL. Эта команда имеет единственный операнд, которым является имя вызываемой программы. Аргументы в собственно машинной команде указаны быть не могут, поскольку для общего случая число их может быть любым. В простейших случаях эти аргументы передаются в регистрах. Изучение более общих способов передачи аргументов будут рассматриваться в одной из следующих лабораторных работ.
Сама подпрограмма на языке nasm оформляется очень просто. Она должна начинаться с метки, обозначающей первую команду подпрограммы, и ее действия должны завершаться командой RET. Команда RET обеспечивает возвращение действий выполнения программы, традиционно называемое возвратом управления, к тому месту, откуда была вызвана подпрограмма.
Более детальное рассмотрение показывает, что команда CALL запоминает адрес команды, следующей по порядку размещения в программе (точнее, адрес следующей команды в машинных кодах сегмента команд). Это запоминание в процессорах архитектуры IA32 выполняется в аппаратно-программном стеке процессора. На вершину такого стека показывает регистр EIP. При выполнении команды RET верхнее слово этого встроенного стека снимается и используется в качестве адреса возврата, размещаемого тут же в регистре EIP. Из такого технического решение сразу же вытекает необходимость обеспечивать в любой подпрограмме работу со встроенным стеком так, чтобы все информация, которая помещается в него при выполнении подпрограммы, была обязательно извлечена из стека до момента выполнения команды RET, завершающей действия подпрограммы.
В ассемблерах TASM и MASM применяется более сложное оформление подпрограмм, хотя фактически можно и в этих ассемблерах обойтись более простым построением, подобным используемому в NASM. Указанное оформление, настоятельно рекомендуемое фирменной документацией, заключается в использовании парных директив для начала и конца подпрограммы. Эти директивы имеют вид
13
имя_подпрограммы PROC команды подпрограммы
. . .
RET
имя_подпрограммы ENDP
При использовании команд умножения и деления в архитектуре IA32 необходимо иметь в виду неявное использование подразумеваемых операндов. Явно в них указывается только один операнд. Это множитель в команде MUL и делитетель в команде DIV. Разрядностью этого явного операнда и определяется разрядность всей выполняемой операции. В табл. 3 приведены варианты использования команды MUL, зависящие от разрядности выполняемой операции
Табл. 3. Размещение неявных операндов команды MUL
Разрядность |
Результат |
Множимое |
|
8 бит |
AX |
AL |
|
16 |
бит |
DX – старшие биты, AX – младшие биты |
AX |
32 |
бита |
EDX – старшие биты, EAX – младшие биты |
EAX |
Табл. 4 содержит информацию о размещении неявных операндов команды DIV.
|
|
Табл. 4. Размещение неявных операндов команды DIV |
|
Разрядность |
Результат |
Делимое |
|
8 бит |
AH – остаток, AL – частное |
AX |
|
16 |
бит |
DX – остаток, AX – частное |
DX – старшие биты, AX – млад- |
|
|
|
шие биты |
32 |
бита |
EDX – остаток, EAX – частное |
EDX – старшие биты, EAX – |
|
|
|
младшие биты |
Следует иметь в виду, что команда DIV в качестве делителя допускает размещение операнда только в регистре или памяти, но не в виде константы. Это практически требует в большинстве случаев размещать константу, на которое производится деление в регистре, а уже потом использовать этот регистр в качестве явного операнда - делителя.
Для преобразования двоичного кода числа, помещенного в регистр EAX, в последовательность десятичных цифр этого же числа, выводимых на экран в Linux может быть использована следующая программа, приведенная в листинге 1.
mov esi,10 ; основание позиционной системы счисления в регистр mov ecx, 0 ; ECX – регистр для счетчика вычисленных цифр
pov: mov edx, 0 ; обнуление левой части делимого
div esi |
; деление для получения следующей цифры в остатке деления |
add dl, '0' |
;приведение числового значения цифры к ее коду представления |
push edx |
; сохранение цифры в стеке |
inc ecx |
; подсчет шага в регистре для счетчика |
14
cmp eax, 0 ; проверка на завершение формирования ненулевых цифр jne pov
mov [cnt], ecx ; запоминание в поле cnt числа запомненных цифр
izv: pop edx |
; извлечение очередной цифры из стека |
mov [digit],dl ; digit into digit for write ; запись ее кода в служебное поля |
|
|
; для последующего вывода |
mov eax,4 ; N function=write |
|
mov ebx,1 |
; N handle=1 (stdout) |
mov ecx, digit; address of digit |
|
mov edx, 1 ; number of byte |
|
int 80h |
; вывод цифры обращением к write write write через прерыва- |
ние
dec dword [cnt] ; учет в счетчике выведенной цифры
cmp dword [cnt],0 ; проверка по счетчику на наличие не выведенных цифр jne izv
. . .
; в секции SECTION .data digit db 0
cnt dd 0
Листинге 1. Программа преобразования двоичного кода числа, помещенного в регистр EAX, в последовательность десятичных цифр
Соответствующий алгоритм (вывода десятичного числа из двоичного кода в регистре EAX) в словесной форме можно записать следующим образом, обозначая начальное значение регистра EAX переменной N:
1)Разделить N на 10, обозначим: остаток - R, частное - M {остаток равен значению очередной цифры десятичного представления}.
2)Преобразовать остаток в ASCII код для вывода на экран.
3)Сохранить символ для последующего вывода.
4)Подсчитать номер прохождения цикла (число запоминаемых цифр).
5)Перенести в N определенное выше частное.
6)Если частное <> 0, то повторить с пункта 1.
7)Извлекать в обратной последовательности цифры результата и выводить их на экран.
Для обратного преобразования от отдельных цифр, вводимых в компьютер, к двоичному коду числа следует использовать определение числа в позиционной системе счисления
an p n +an −1 p n −1 + a2 p 2 +a1 p1 +a0
откуда по схеме Горнера получается более эффективная по простоте последующей реализации формула вычислений
(( ((an ×p +an −1 ) ×p +an −2 ) a2 ) ×p +a1 ) ×p +a0
Рекомендуется использовать последнюю формулу, сохраняя в некотором регистре результат умножения предыдущего запомненного значения промежуточного результата и сложения, увеличенного на значение очередной введенной цифры.
15
Задание. Разработать подпрограмму, которая считывает последовательно десятичные цифры со стандартного ввода, до тех пор, пока не встретится символ, отличных от цифры. Читаемая таким образом последовательность десятичных цифр должна преобразовываться в соответствующее двоичное значение, возвращаемое подпрограммой в регистре EAX в качестве результата.
Использовать подготовленную таким образом подпрограмму для ввода двух десятичных чисел, задаваемых со стандартного ввода, выполнить сложение этих чисел в основной части программы и затем, переписав основную преобразующую последовательность команд программы из лекции 3 в виде подпрограммы, обращением к последней подпрограмме вывести полученную сумму.
Контрольные вопросы.
1.Что вы думаете о возможности использования 16-ти битного делимого в процедуре перехода от двоичного кода числа к его последовательности выводимых цифр, разместив при этом исходный делитель в регистрах DX и AX, где они явно помещаются и с учетом, что основание десятичной системы счисления также хорошо помещается в 16-битный регистр.
2.Как изменить программу преобразования от двоичного кода к последовательности выводимых цифр, если требуется выводить в шестнадцатеричной системе.
3.Объясните, как измениться разработанная вами подпрограмма преобразования от последовательности вводимых цифр во внутреннее двоичное представление, если ставится требование использовать для ввода представление числа в шестнадцатеричной системе счисления.
Лабораторная работа №4
Содержание работы. Изучение способов адресации для динамического доступа к различным ячейкам памяти в фиксированном участке программы.
Предварительные сведения.
Разработка досрочно сложных программ практически невозможна без использования отладчиков. К сожалению, современные широко распространенные отладчики ориентированы на отладку программ, написанных на языках высокого уровня, и, в первую очередь, на языке Си. Такая ситуация обусловлена тем, что использование ассемблера остается к настоящему времени уделом профессионалов и даже стандартный интерфейс доступа к программным функциям современной операционной системы (API ОС) задается теперь исключительно на языке Си.
Тем ни менее, имеющиеся отладчики могут использоваться и для отладки ассемблерных программ, хотя множество наиболее мощных средств этих отладчиков нацелены на более высокоуровневые языки.
В ОС Linux стандартным отладчиком, являющимся, как и вся ОС Linux, свободно распространяемым продуктом, является отладчик с программным именем gdb (GNU debugger). Согласно документации, это так называемый символический
16
отладчик. Его возможности очень многообразны, но в данном изложении будут рассмотрены лишь простейшие из них и те, без которых трудно обойтись даже в простейшей отладке.
Запустить программу для отладки с помощью gdb чрезвычайно просто: следует в командной строки консоли набрать вызов отладчика в виде
gdb имя_исполняемой_программы
Если в разработке не указывалась соответствующими опция, что должна быть вставлена отладочная информация, то непосредственно после вызова отладчика для программы появится сообщение
(no debugging symbol found) . . .
и будет выведено приглашение (prompt) для команд отладчика в виде (gdb)
Заметим, что текущие версии NASM не позволяют формировать отладочную информацию в процессе трансляции, а стандартный ассемблер Unix'а с именем as не удобен для начального изучения предмета. Поэтому мы ограничимся изучением использования отладчика gdb при отсутствии отладочной информации от транслятора. В то же время оказывается, что минимальная и достаточная информация для отладчика поступает от соответствующего исходного файла. В частности, оказываются доступными обычные (не обязательно глобальные) метки и именованные области данных, а поэтому, фактически и имена подпрограмм. (А больше никакой символьной информации программисту и не надо.) Заметим, что отсутствие отладочной информации от транслятора делает очень неудобной отладку программ, написанных на языке высоко уровня, так как в этом случае отладчик может предоставить отладочную информацию на уровне ассемблера, а нам больше "ничего и не надо". Поэтому в нашем случае замечание об отсутствии отладочной информации не может быть предметом неудовольствия.
Отладчику gdb присуще одно специфическое свойство, кажущееся неудобным для тех, кто привык к отладке простейших программ под MS-DOS. Именно, оказывается практически невозможным остановиться перед первой исполняемой командой программы, а можно только на любой другой. Такое поведение - косвенный результат строгой ориентации отладчика на структуры программ, построенных компиляторами с языков высокого уровня. Такая структура обязательно строится как структура систематически использующая кадры (фреймы) подпрограмм, где все подпрограммы, начиная с программной функции main, создаются по этим соглашениям.
При работе отладчика gdb и его использовании широчайшим образом применяется понятие текущего кадра, вложенных кадров вызова и переключения между кадрами для доступа к локальным данных процедуры любого более высокого уровня вызова. Расширенные возможности, вытекающие из этого подхода, влекут необходимость вхождения для отладки хотя бы в какой-то кадр и, поэтому начального выполнения хотя бы одной команды. При отладке любых программ, отличных от ассемблерных, указанное ограничение никак не заметно программисту. Это объясняется тем, что все программы языков высоко уровня используют процедуру начального вызова главной подпрограммы разрабатываемой программы. Так для программы с языка Си используется (часто незаметно для малоквали-
17
фицированного программиста) вспомогательная программа запуска главной подпрограммы main.
Поэтому отладчик gdb можно использовать для ассемблерных программ только с их второй команды. Отсюда следует два практических варианта: либо на веру принять правильность предполагаемых действия первой команды (тем более, что ее результат с помощью отладчика нетрудно увидеть), либо (для наиболее сомневающихся) вставить в качестве первой команды какую-то вспомогательную, от которой не зависят дальнейшие результаты (например, команду CLD). Можно для успокоения просто считать, что приостановиться можно только после первой команды, действия которой мы можем наблюдать.
Собственно отладка начинается с выдачи отладчику команды run (выполнить). Но студенту не следует торопиться и сразу же ее использовать. Если задать команду run сразу после вызова отладчика, то все выполнения будет происходить в автоматическом режиме (без отображения и приостановок отладчиком) и результат окажется во многих случаях таким же, как и при запуске той же программы без отладчика.
Мощнейшие средства, которые предоставляет отладчик - это задаваемые приостановки в автоматическом выполнении и пошаговое выполнение. Обычно разработчик использует и то, и другое. Заметим дополнительно, что нельзя задавать пошаговое выполнение, пока не выдана команда run.
Команда отладчика "установка точки приостанова" задается именем breakpoint, которое можно сокращать до двух начальных символов. Эта команда может использоваться в одной из двух следующих основных форм
br имя_метки_в_программе
или
br *адрес
Проще всего для наших целей воспользоваться этой командой для начальной приостановки автоматического выполнения, если в исходной программе на ассемблере поставить метку у второй ее команды и эту метку использовать в команде приостановки, выдаваемой программистом сразу после команды запуска отладчика с отлаживаемой программой.
Для удобства демонстрировать конкретные средства отладки возьмем в качестве примера программу, исходный текст которой приведен в листинге 2.
GLOBAL _start
SECTION .text
_start: |
mov eax, 1000h |
|
beg: |
mov esi,10 ; base of position digit system |
|
mov ecx, 0 ; reset digit counter |
||
pov: |
mov edx, 0 ; null into left part of devident |
|
div esi |
; divide for next digit = rest |
|
add dl, '0' |
|
|
push dx |
|
|
inc ecx |
; step into counter |
|
cmp eax, 0 |
|
|
jne pov |
|
|
18
mov [cnt], ecx mov ebx, numtxt
izv: |
pop dx |
mov byte [ebx],dl ; digit into array for text value |
|
inc ebx |
|
loop izv ; izv,ecx call outa
mov eax, 1 ; N function = exit int 80h ;
outa: mov eax,4 ; N function=write mov ebx,1 ; N handle=1 (stdout) mov ecx, numtxt ; address of text mov edx,[cnt] ; number of byte int 80h
ret SECTION .data
numtxt times 10 db 0 cnt dd 0
Листинг 2. Пример программы для отладки
Для нашего примера пусть исполняемый файл формата ELF, который получается из программы с листинга 1, называется primer.exe. Тогда начало использования отладчика дают следующие строки, вводимые с консоли и выводимые на нее.
gdb primer.exe
(no debugging symbol found) . . .
(gdb) br beg
Breakpoint 1 at 0x8048085 (gdb) run
Starting program /home/student/primer.exe Breakpoint 1, 0x8048085 in beg ()
(gdb)
Здесь отладчик выводит всю изображенную информацию, кроме находящейся в строках, которые начинаются с приглашения (gdb). Текст в строках после этого приглашения всегда вводиться пользователем (и завершается нажатием клавиши Enter). В данном примере предполагается, что программа primer.exe была помещена в базовом каталоге пользователя student. В общем случае после слов "Starting program" идет полное имя исполняемого файла, запущенного "под отладчиком".
Далее следует решить, какой вариант отображения команд желает использовать пользователь. Дело в том, что по умолчанию отображаются команды в форме универсального ассемблера ATT, которая для пользователей, привыкших к соглашениям Intel и Microsoft, может показаться непривычной. Краткая информация об этой форме записи команд в указанном ассемблере приведена в приложении 2. Здесь же следует обратить внимание, в этом ассемблере все операнды получатели записываются справа, а операнды исходной информации - слева (обратно поряд-
19
ку, принятому в Intel). Кроме того, обозначения всех регистров предваряются служебным символом %, а константы - служебным символом $.
Если пользователю кажется неудобной такая форма обозначения команд, то перейти к более привычной он может с помощью вспомогательной команды
set disassembly-flavor intel
Обратный переход осуществляется командой set disassembly-flavor intel
Заметим, что даже в режиме отображения Intel обозначениям регистров при выводе обязательно предшествуют служебные символы %.
Теперь (как и в любой последующий момент) мы можем воспользоваться командой disassemble, которую проще сокращать до пяти первых букв. Эта команда без аргументов выдает ассемблерную запись команд текущего кадра.
Для ассемблерных программ при отсутствии более детальной информации отладчик считает кадром последовательность команд от одной метки до другой. Поэтому задание команды disas отладчику в нашем примере (после приостановки на первой точке приостановки) даст следующий вывод.
0x8048085 <beg>: mov %esi,0xa 0x804808a <beg+5>: mov %ecx,0x0 (gdb)
Для ассемблерных программ эту команду удобней всего использовать с двумя аргументами, задающими начало и конец отображаемой области команд. Причем эти аргументы можно задавать именами меток в программе. Например, задав отладчику команду в виде
disas _start izv
мы получим в качестве отображаемого на экране следующий текст рассматриваемого примера, приведенный на рис. 1.
Dump of assembler code from 0x8048080 to 0x80480ac:
0x8048080 |
<_start>: |
mov |
%eax,0x1000 |
0x8048085 |
<beg>: |
mov |
%esi,0xa |
0x804808a |
<beg+5>: |
mov |
%ecx,0x0 |
0x804808f<pov>: |
mov %edx,0x0 |
|
|
0x8048094 |
<pov+5>: |
div |
%eax,%esi |
0x8048096 |
<pov+7>: |
add |
%dl,0x30 |
0x8048099 |
<pov+10>: |
push %edx |
|
0x804809b |
<pov+12>: |
inc |
%ecx |
0x804809c |
<pov+13>: |
cmp |
%eax,0x0 |
0x80480a1 |
<pov+18>: |
jne |
0x804808f <pov> |
0x80480a3 |
<pov+20>: |
mov |
%ds: 0x80490de, %ecx |
0x80480a9 |
<pov+26>: |
mov |
%ebx,0x80490d4 |
End of assembler dump. (gdb)
Рис. 1. Содержимое экрана, отображаемое отладчиком gdb
20
