Skip to content

Latest commit

 

History

History
208 lines (140 loc) · 21.8 KB

lab05_edb.textile

File metadata and controls

208 lines (140 loc) · 21.8 KB

Лабораторная работа №5. Визуальная отладка; подпрограммы

Краткие теоретические сведения

Основные возможности отладчика EDB

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

edb [ --attach <ID процесса>] [--run <имя_файла> (аргументы)]

После запуска появляется графическое окно EDB, разделенное на четыре основные части: дизассемблер, стек, дамп памяти с вкладками, содержимое регистров.

Всю информацию и данные Evan’s Debugger отображает в меню и в окнах. Используются различные виды окон в зависимости от того, какого типа информация в них отображается. Все окна открываются и закрываются с помощью команд меню (или активных клавиш, соответствующих этим командам).

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

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

Окно стека (Stack) отображает текущее состояние стека, причем область первой вызванной функции будет находиться на дне стека, а всех последующих вызванных функций — в направлении вершины стека в последовательности их вызова. Во всплывающем меню имеются команды, которые позволяют модифицировать отображаемые данные, менять формат их отображения на экране, переходить по указанному адресу, по адресу из регистров ebp или esp.

Дизассемблирование программы

Если есть файл с исходным текстом программы, а в исполняемый файл включена информация о номерах строк исходного кода, программу можно отлаживать, работая в отладчике непосредственно с ее исходным текстом. Чтобы программу можно было отлаживать на уровне строк исходного кода, она должна быть откомпилирована с ключом -g.

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

Напомним, что существуют два режима отображения синтаксиса машинных команд: режим Intel, используемый в т.ч. в NASM, и режим ATT. По умолчанию в дизассемблере EDB принят режим Intel.

Точки останова

Установить точку останова можно путем двойного клика мышкой на нужной инструкции. Если точка останова установилась, напротив инструкции появится красная отметка:

Информацию о всех установленных точках останова можно посмотреть с помощью Breakpoint Manager, нажав Ctrl+M или открыв меню Plugins \rightarrow BreakpointManager \rightarrow Breakpoints:

Возобновление выполнения, пошаговая отладка

Команда Run (F9) продолжает выполнение остановленной программы. Выполнение будет происходить, пока не встретится точка останова или программа не выполнится полностью.

Команда Step Into (F7) приводит к выполнению программы до тех пор, пока не будет достигнута следующая строка ее кода. Вызов процедуры трактуется отладчиком не как одна инструкция, а как передача управления на еще один блок ассемблерного кода, который тоже должен быть пройден по шагам.

Команда Step Over (F8) приводит к выполнению программы до тех пор, пока не будет достигнута следующая строка ее кода. В отличие от Step Into, вызов процедуры считается единой инструкцией.

Элементы программирования

Понятие подпрограммы

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

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

Инструкция call

Основные моменты выполнения подпрограммы иллюстрируются на рис. [pic:l54]. В вызывающей подпрограмму программе выполняется инструкция call, которая заносит адрес следующей инструкции в стек и загружает в регистр eip адрес соответствующей подпрограммы, осуществляя таким образом переход. После этого подпрограмма выполняется, как любой другой код. В подпрограммах могут (часто это так и бывает) содержаться инструкции вызовов других подпрограмм.

Когда подпрограмма заканчивает работу, она вызывает инструкцию ret, которая извлекает из стека адрес, занесенный туда соответствующей инструкцией call, и заносит его в eip. Это приводит к тому, что вызывающая программа возобновит выполнение с инструкции, следующей за инструкцией call.

Например, следующая программа выводит на экран строку «Enter string:», ждёт ввода строки (например, «HELLO») и выводит на экран строку «Result:» и введённую строку (т. е. «Result: HELLO»). Для вывода строк вызывается подпрограмма PrintString:

SECTION .data           ; Константы
ask1:       DB  'Enter string: ' , 10
ask1_len:       EQU     $-ask1
result:     DB  'Result: '
result_len: EQU $-result  

SECTION  .bss
buf1:       RESB 80 
      
SECTION .text           ; Код программы
    GLOBAL _start       ; Начало программы   
;------------------------------------------------------------------------    
; Подпрограмма вывода на экран строки
; Входные данные:
;  ecx - указатель на выводимую строку.
; Нарушаемые регистры: eax,ebx;
;------------------------------------------------------------------------
 Print_string:
    mov eax, 4
    mov ebx, 1
    int 80h
    ret             ; возврат в вызывающую программу

;------------------------------------------------------------------------
; Подпрограмма ввода строки с клавиатуры
; Входные данные:
;    ecx - указатель на буфер для входной строки.
; Нарушаемые регистры: eax,ebx;
;------------------------------------------------------------------------
Enter_string:
    mov     eax, 3
    mov     ebx, 0
    int     80h
    ret             ; возврат в вызывающую программу

_start:
    mov     ecx, ask1
    mov edx, ask1_len
    call Print_string

    mov     ecx, buf1
    mov     edx, 6
    call Enter_string

    mov     ecx, result
    mov     edx, result_len
    call Print_string

    mov     ecx, buf1
    mov     edx, 6
    call Print_string

    mov eax,1       ; Системный вызов для выхода (sys\_exit)
    mov     ebx,0       ; Выход с кодом возврата 0 (без ошибок)
    int     80h         ; Вывзов ядра

Подпрограмма PrintString не настроена жестко на печать определенной строки. Она может печатать любую строку, на которую укажет регистр ecx.

Инструкция ret

Инструкция ret возвращает управление вызывающей программе. Для этого она извлекает из вершины стека четыре байта и заносит их в регистр счётчик команд eip. После этого значение регистра esp увеличится на 4. Если в процедуре занести что-то в стек и не извлечь, то на вершине стека окажется не адрес возврата, и это приведёт к ошибке выхода из процедуры.

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

Способ перевода числа в десятичную символьную запись

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

Любое число X в позиционной системе счисления представляется в виде суммы произведений:

Здесь X — это число в системе с основанием p, имеющее n+1 цифру в целой части.

Так, при переводе кода введённого символа в десятичную систему (например, чтобы вывести на экран не символ, а численное значение его кода) надо разложить число на слагаемые, содержащие степени числа 10. Перевод кода символа производится путем последовательного деления на основание 10 с выделением остатков от деления до тех пор, пока частное не станет меньше делителя. Выписывая остатки от деления справа налево, получаем 10-чную запись числа.

Команды деления

Сначала рассмотрим деление 16-битового значения на 8-битовое. При беззнаковом делении 16-битового значения на 8-битовое, делимое должно быть записано в регистре ax. 8-битовый делитель может храниться в любом 8-битовом общем регистре или переменной в памяти соответствующего размера. Инструкция div всегда записывает 8-битовое частное в регистр al, а 8-битовый остаток — в ah. Например, после выполнения инструкций

. . .
           mov   ax,51
           mov   dl,10
           div   dl
           . . .

результат 5 (51/10) будет записан в регистр al, а остаток 1 (остаток от деления 51/10) — в регистр ah.

Заметим, что частное представляет собой 8-битовое значение. Это означает, что результат деления 16-битового операнда на 8-битовый операнд не должен превышать 255. Если частное слишком велико, то генерируется прерывание 0 («деление на 0»). Инструкции

. . .
           mov   ax,0fffh
           mov   bl,1
           div   bl
           . . .
       

генерируют прерывание по делению на 0 (как можно ожидать, прерывание по делению на 0 генерируется также, если 0 используется в качестве делителя).

При делении 32-битового операнда на 16-битовый операнд делимое должно записываться в регистрах dx:ax. 16-битовый делитель может находиться в любом из 16-битовых регистров общего назначения или в переменной в памяти соответствующего размера. Например, в результате выполнения инструкций

. . .
           mov   ax,2
           mov   dx,1       ; загрузить в регистровую
           mov   bx,10h         ; пару \verb!dx:ax! 10002h
           div   bx
       . . .
       

частное 1000h (результат деления 10002h на 10h) будет записано в регистре ax, а 2 (остаток от деления) — в регистре dx.

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

Задание для выполнения

  1. Написать программу со следующим алгоритмом:

    • ввести символ с клавиатуры;

    • преобразовать полученный код в десятичную символьную запись;

    • вывести символ и его код.

    Перевод числа в десятичную символьную запись оформить в виде подпрограммы.

  2. Загрузить программу в отладчик. Это можно сделать двумя способами: написать в командной строке edb --run имя_программы или запустить edb и выбрать программу через пункт Open меню File.

  3. Выполнить программу по шагам, нажимая кнопку Step Over панели инструментов или клавишу F7 (находясь в основном окне отладчика), до конца.

  4. Поместить в программу точку останова на инструкции, следующей после ввода символа с клавиатуры — щелкнув правой кнопкой по нужной строке дизассемблированного кода и выбрав пункт Add Breakpoint всплывающего меню. Выполнить программу до точки останова, нажав клавишу F9 или кнопку Run панели инструментов. Иметь в виду, что ввод текста с клавиатуры в выполняемую программу осуществляется в отдельном окне EDB Output, а не в основном окне отладчика.

  5. Вывести в окне дампа памяти содержимое входного буфера, щелкнув в подокне Data Dump правой кнопкой мыши и выбрав пункт Goto Address всплывающего меню. Адрес вводить в шестрадцатиричной нотации Си (начиная с символов 0x).

  6. Зайти в процедуру перевода числа в десятичную запись. Выполнить 2 прохода цикла по F7 (Step Into), контролируя значения регистров. Какие регистры изменяются в цикле?

  7. Остальные проходы цикла выполнить по F8 (Step Over). В чем разница?

  8. Определить физический адрес выходного буфера в ОЗУ .

  9. Вывести ячейки памяти, соответствующие выходному буферу, в подокне Data Dump в шестнадцатиричном и в символьном виде.

  10. Установить точку останова на инструкцию div. Выполнить программу, несколько раз нажав на F8 и наблюдая за изменением содержимого выходного буфера в подокне Data Dump. Каков результат? (Перевод чисел между шестнадцатиричной и десятичной системами счисления можно упростить, воспользовавшись программой Калькулятор, выбрав в ней пункт меню Вид \rightarrow

    Программирование).


  11. Изменить содержимое входного буфера и проверить, как это отражается на выполнении программы.