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

ForSPOmetuk

.pdf
Скачиваний:
9
Добавлен:
06.02.2018
Размер:
199.58 Кб
Скачать

Более детальное рассмотрение показывает, что команда CALL запоминает адрес команды, следующей по порядку размещения в программе (точнее, адрес следующей команды в машинных кодах сегмента команд). Это запоминание в процессорах архитектуры IA32 выполняется в аппаратно-программном стеке процессора. На вершину такого стека показывает регистр EIP. При выполнении команды RET верхнее слово этого встроенного стека снимается и используется в качестве адреса возврата, размещаемого тут же в регистре EIP. Из такого технического решение сразу же вытекает необходимость обеспечивать в любой подпрограмме работу со встроенным стеком так, чтобы все информация, которая помещается в него при выполнении подпрограммы, была обязательно извлечена из стека до момента выполнения команды RET, завершающей действия подпрограммы.

В ассемблерах TASM и MASM применяется более сложное оформление подпрограмм, хотя фактически можно и в этих ассемблерах обойтись более простым построением, подобным используемому в NASM. Указанное оформление, настоятельно рекомендуемое фирменной документацией, заключается в использовании парных директив для начала и конца подпрограммы.

При использовании команд умножения и деления в архитектуре IA32 необходимо иметь в виду неявное использование подразумеваемых операндов. Явно в них указывается только один операнд. Это множитель в команде MUL и делитетель в команде DIV. Разрядностью этого явного операнда и определяется разрядность всей выполняемой операции [5].

Следует иметь в виду, что команда DIV в качестве делителя допускает размещение операнда только в регистре или памяти, но не в виде константы. Это практически требует в большинстве случаев размещать константу, на которое производится деление в регистре, а уже потом использовать этот регистр в качестве явного операнда - делителя.

Для преобразования двоичного кода числа, помещенного в регистр EAX, в последовательность десятичных цифр этого же числа, выводимых на экран в Linux может быть использована следующая программа [5], приведенная в листинге 1.

mov esi,10 ; основание позиционной системы счисления в регистр mov ecx, 0 ; ECX – регистр для счетчика вычисленных цифр

pov: mov edx, 0 ; обнуление левой части делимого

div esi

; деление для получения следующей цифры в остатке деления

add dl, '0'

;приведение числового значения цифры к ее коду представления

push edx

; сохранение цифры в стеке

inc ecx

; подсчет шага в регистре для счетчика

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

11

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 pn + an1 pn1 + a2 p2 + a1 p1 + a0

откуда по схеме Горнера получается более эффективная по простоте последующей реализации формула вычислений

(( ((an × p + an1 ) × p + an2 ) a2 ) × p + a1 ) × p + a0

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

Задание. Разработать подпрограмму, которая считывает последовательно десятичные цифры со стандартного ввода, до тех пор, пока не встретится символ, отличных от цифры. Читаемая таким образом последовательность десятичных цифр должна преобразовываться в соответствующее двоичное значение, возвращаемое подпрограммой в регистре EAX в качестве результата.

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

12

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

Контрольные вопросы.

1.Что вы думаете о возможности использования 16-ти битного делимого в процедуре перехода от двоичного кода числа к его последовательности выводимых цифр, разместив при этом исходный делитель в регистрах DX и AX, где они явно помещаются и с учетом, что основание десятичной системы счисления также хорошо помещается в 16-битный регистр.

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

3.Объясните, как измениться разработанная вами подпрограмма преобразования от последовательности вводимых цифр во внутреннее двоичное представление, если ставится требование использовать для ввода представление числа в шестнадцатеричной системе счисления.

Лабораторная работа №4

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

Предварительные сведения.

Разработка досрочно сложных программ практически невозможна без использования отладчиков. К сожалению, современные широко распространенные отладчики ориентированы на отладку программ, написанных на языках высокого уровня, и, в первую очередь, на языке Си. Такая ситуация обусловлена тем, что использование ассемблера остается к настоящему времени уделом профессионалов и даже стандартный интерфейс доступа к программным функциям современной операционной системы (API ОС) задается теперь исключительно на языке Си.

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

В ОС Linux стандартным отладчиком, являющимся, как и вся ОС Linux, свободно распространяемым продуктом, является отладчик с программным именем gdb (GNU debugger). Согласно документации, это так называемый символический отладчик. Его возможности очень многообразны, но в данном изложении будут рассмотрены лишь простейшие из них и те, без которых трудно обойтись даже в простейшей отладке.

Запустить программу для отладки с помощью gdb чрезвычайно просто: следует в командной строки консоли набрать вызов отладчика в виде

gdb имя_исполняемой_программы

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

(no debugging symbol found) . . .

13

и будет выведено приглашение (prompt) для команд отладчика в виде (gdb)

Отладчику gdb присуще одно специфическое свойство, кажущееся неудобным для тех, кто привык к отладке простейших программ под MS-DOS. Именно, оказывается практически невозможным остановиться перед первой исполняемой командой программы, а можно только на любой другой. Такое поведение - косвенный результат строгой ориентации отладчика на структуры программ, построенных компиляторами с языков высокого уровня. Такая структура обязательно строится как структура систематически использующая кадры (фреймы) подпрограмм, где все подпрограммы, начиная с программной функции main, создаются по этим соглашениям.

При работе отладчика gdb и его использовании широчайшим образом применяется понятие текущего кадра, вложенных кадров вызова и переключения между кадрами для доступа к локальным данных процедуры любого более высокого уровня вызова. Расширенные возможности, вытекающие из этого подхода, влекут необходимость вхождения для отладки хотя бы в какой-то кадр и, поэтому начального выполнения хотя бы одной команды. При отладке любых программ, отличных от ассемблерных, указанное ограничение никак не заметно программисту. Это объясняется тем, что все программы языков высоко уровня используют процедуру начального вызова главной подпрограммы разрабатываемой программы. Так для программы с языка Си используется (часто незаметно для малоквалифицированного программиста) вспомогательная программа запуска главной подпрограммы main.

Поэтому отладчик gdb можно использовать для ассемблерных программ только с их второй команды. Отсюда следует два практических варианта: либо на веру принять правильность предполагаемых действия первой команды (тем более, что ее результат с помощью отладчика нетрудно увидеть), либо (для наиболее сомневающихся) вставить в качестве первой команды какую-то вспомогательную, от которой не зависят дальнейшие результаты (например, команду CLD). Можно для успокоения просто считать, что приостановиться можно только после первой команды, действия которой мы можем наблюдать.

Собственно отладка начинается с выдачи отладчику команды run (выполнить). Но студенту не следует торопиться и сразу же ее использовать. Если задать команду run сразу после вызова отладчика, то все выполнения будет происходить в автоматическом режиме (без отображения и приостановок отладчиком) и результат окажется во многих случаях таким же, как и при запуске той же программы без отладчика.

Мощнейшие средства, которые предоставляет отладчик - это задаваемые приостановки в автоматическом выполнении и пошаговое выполнение. Обычно разработчик использует и то, и другое. Заметим дополнительно, что нельзя задавать пошаговое выполнение, пока не выдана команда run.

Команда отладчика "установка точки приостанова" задается именем breakpoint, которое можно сокращать до двух начальных символов. Эта команда может использоваться в одной из двух следующих основных форм

br имя_метки_в_программе

или

14

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

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) . . .

15

(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. Здесь же следует обратить внимание, в этом ассемблере все операнды получатели записываются справа, а операнды исходной информации - слева (обратно порядку, принятому в 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.

16

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

Этот вариант появится, если было произведено переключение на ассемблер типа intel.

По умолчанию стандартный режим отладчика gdb не отображает информацию о текущей команде в пошаговом режиме этого отладчика. (Это вполне целесообразно, так как он обычно используется для отладки программ, написанных на языке высокого уровня.) Для установки режима автоматического отображения следующей команды следует передать отладчику приказ в виде директивы

display/i $eip

после чего в нашем примере автоматически выдается (с учетом предыдущей остановки на первой точки приостановки)

1: x/i $eip 0x8048085 <beg> mov %esi, 0xa

(если текущим режимом отображения команд будет режим intel).

Само приказание на выполнение одной следующий команды задается в виде команды отладчика

stepi

В частности, в нашем примере после полученной выше остановки на первой точке приостановки, выдача команды stepi приводит к следующей реакции отладчика в виде

(gdb) stepi 0x804808a in beg()

1: x/i $eip 0x804808a <beg+5> mov %ecx, 0x0

Дальнейшие выдачи команды stepi (в частности, после извлечения ее в командную строку с помощью клавиши "стрелка вверх") будут давать следующие выво-

ды

 

(gdb) stepi

 

0x804808f in pov()

 

1: x/i $eip 0x804808f <pov>:

mov %edx,0x0

(gdb) stepi

 

17

0x804808a in pov()

 

 

1: x/i $eip 0x8048094 <pov+5>:

div

%esi, %eax

(gdb) stepi

 

 

0x804808a in pov()

 

 

1: x/i $eip 0x8048096 <pov+7>:

add

$0x30, %dl

Для отображения информации служит команда с именем, восходящим к слову print, но задаваемая обычно единственным начальным символом p. В частности, выдача содержимого регистра в виде десятичного числа требует задания команды в виде p $имя_регистра, для вывода в виде шестнадцатеричного числа - команда задается в виде p/x $имя_регистра. Причем имя регистра в этой команде допускается только для 32-битных регистров (регистры меньшего размера все равно являются частью некоторого 32-битного регистра).

При отладки рассматриваемой программы-примера в качестве первой цифры должен появиться символ '6', и, действительно, в ситуации остановки перед строкой <pov+7>, т.е. после вывода последней из изображенных выше строк информации от отладчика, запрос

p $edx

приводит к выдачи информации в виде $1 = 0x6

а запрос в виде p/x $eax

к выводу результата $2 = 0x199

Аналогичный запрос в этом же месте отлаживаемой программы в виде p/x $eax

дает результат $3 = 409

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

Program exited with code число

где параметр число есть значение, переданное в регистре ebx при вызове первой функции (функции exit) Linux..

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

br имя_метки

Не нужные уже метки можно удалить командой clear в виде clear имя_метки

Можно временно выключать (выводить из использования) точки приостановки с помощью команд

disable номер_точки_приостанова

а повторно (или в очередной раз) включить ее в использование с помощью команды

enable номер_точки_приостанова

18

Команда print (в сокращенной форме задаваемая одной буквой p) может использовать кроме формата, задаваемого символом x (вывод в шестнадцатеричной системе), еще и другие форматы типа. Они задаются символами d, u, o, t и a. Формат d выводит значение как целое со знаком в десятичной системе, формат u задает целое как беззнаковое десятичное, формат o выводит целое в восьмеричной системе счисления, а формат t - в двоичной системе. Для указания вывода в виде отдельного символа служит формат c.

Собственно аргументом команды print могут быть не только регистры, но и имена областей данных, а также адреса, задаваемые в виде шестнадцатеричных констант.

Кроме того, имеется еще более мощная возможность отображения информации, но применимая только к памяти (но не к регистрам). Это команда examine, которая обычно задается единственным начальным символом сокращенного имени - символом x.

Общая форма этой команды имеет вид x/NFU ADDR

где параметр ADDR обозначает адрес места в памяти, параметр N задает число отображаемых элементов, параметр F заказывает формат вывода, а параметр U дает размер информационной единицы. Любой из параметров N, F, U может отсутствовать, тогда берется значение, ранее использованное перед этим или значение по умолчанию (если команда еще не использовалась).

Формат задается одним символом, который либо совпадает с символами формата для команды print, либо дает один из дополнительных форматов s или i. Формат i задает вывод в виде машинных команд, изображаемых ассемблерными мнемокодами, а формат s соответствует ASCIIZ, т.е. символьной строке, заканчивающейся нулевым кодом.

В качестве символов размера информационного элемента допустимо использовать символы b, h и w. Символ b обозначает байты, h обозначает полуслова (два байта), а w - слова (четыре байта) - это и есть начальное значение по умолчанию.

Для продолжения автоматического выполнения команд с текущей точки приостановки (и, обычно, до следующей встречающейся точки приостановки) служит команда отладчика continue, задаваемая обычно единственным символом c.

Для завершения работы отладчика служит команда quit, которую можно задавать единственным символом q.

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

Задание. В программе описывается массив из 30 целых чисел с заданными программистом значениями (при демонстрации программы преподавателю эти числа могут быть изменены последним). Программа запрашивает и вводит число, которое далее служит в качестве верхней границы использования индекса для действий с элементами массива. (Индекс в данном контексте понимается в математическом смысле как порядковый номер элемента.) Оно должно быть не более 30 и программе следует выполнять проверку на допустимость введенного значения.

19

Программа должна находить минимальное число в начальной части массива, используя только те его элементы, индекс которых в массив ограничен введенным числом, и номер места (индекс) минимального числа в использованной части массива. Результаты в виде числовых значений с поясняющей информацией вывести на экран.

Контрольные вопросы.

1.Каким минимальным может быть значение индексного регистра при доступе

кмассиву.

2.Какое минимальное число может быть записано в массив, элементы которого определены директивой DD?

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

Лабораторная работа №5

Содержание работы. Изучение методов и средств использования библиотек объектных модулей в ОС Linux.

Предварительные сведения. При многомодульной разработке программ части этих программ разбиваются на раздельно транслируемые модули. Когда таких модулей становится много, более эффективных техническим решением оказывается их хранение в специальных совокупностях, называемых библиотеками или архивами объектных файлов. Все современные средства компоновки программ позволяют в качестве исходной для них информации применять не только обычные объектные модули, но и библиотеки объектных модулей. Основное преимущество использования таких библиотек заключается в том, что они могут содержать значительно больше модулей, чем необходимо для компоновки конкретной программы, но "подключение" только нужных из них выполняет компоновщик. При этом на программиста не возлагается указание перечня или списка требуемых модулей, вся работа выполняется автоматически.

При наличии уже готовых библиотек объектных модулей их использование на этапе компоновки требует лишь указания этих библиотек при вызове компоновщика, что производится в соответствии с синтаксисом командного вызова такого компоновщика. В операционных системах Unix и Linux традиционно принято строить вызов компоновщиков таким образом, чтобы библиотеки объектных файлов указывались просто в перечне файлов, на основе которых строится исполняемый файл. В других операционных системах, задание этих библиотек может быть позиционным, когда они должны указываться после какой-то запятой, разделяющей текстовые части командной строки. Кроме того, в зависимости от конкретного разработчика компоновщика могут использоваться ключевые параметры задания библиотек. Эти возможности будут использоваться в следующей лабораторной работе.

20

Соседние файлы в предмете Операционные системы