Семинары по курсу «Архитектура ЭВМ и язык ассемблера» учебно-методическое пособие. Часть 2. - Е.А. Кузьменкова_ В.А. Падарян_ М.А. Соловьев, страница 7
Описание файла
PDF-файл из архива "Семинары по курсу «Архитектура ЭВМ и язык ассемблера» учебно-методическое пособие. Часть 2. - Е.А. Кузьменкова_ В.А. Падарян_ М.А. Соловьев", который расположен в категории "". Всё это находится в предмете "архитектура эвм" из 2 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Просмотр PDF-файла онлайн
Текст 7 страницы из PDF
Первый параметр – число идущихследом целых чисел. Для этих чисел выделяется динамическая память, куда оникопируются. Возвращаемое значение – адрес выделенной динамической памяти.43Если параметр n равен 0 возвращается NULL. Считать, что при вызове функции dynamoстек уже выровнен на границе в 16 байтов.РешениеПервый параметр вызванной функции размещен по адресу EBP+8. Адреса следующих параметров будут идти следом за ним с шагом в 4 байта:EBP+8+4*(i-1), где i – номер параметра, при нумерации параметров функции начиная с 1.Особенностью кода будет то, что вызов функции malloc необходимо будет выполнять на выровненном фрейме. Для остальных действий будет достаточно трех регистров, поэтому закажем на стеке 8 байтов, чтобы суммарный объем стека составил 16 байтов. Из этих 8 байтов 4 байта на верхушке стека потребуются для вызовафункции malloc, другие 4 байта в вычислениях не принимают участия и используются исключительно для выравнивания стека.
Границы выровненного 16-байтногофрейма показаны на рисунке жирными линиями.44section .textdynamo:pushebpmovebp, espmoveax, dword [ebp+8]testeax, eaxjz.endsubesp, 8saleax, 2movdword [esp], eaxcallmallocmovecx, dword [ebp+8]leaesp, [ebp+12]xor.copy:popincloopmov.end:popretedx, edxdword [eax+4*edx]edx.copyesp, ebpebp; Пролог; Проверяем не нулевое ли количество элементов; было передано;;;;;;;;;;;Выделяем 8 байт во фреймеПереводим элементы в байтыКладем нужное число байт на стеки вызываем malloc.
Теперь eax - адрес буфераСнова берем число элементов. Оно не нулевое.Временно переставляем указатель стека напоследовательность чисел-параметровn раз выталкиваем в буфер содержимое со стекаЯвно сдвигать надо только edx; Возвращаем указатель стека на место; ЭпилогЗамечание. Показанный прием следует рассматривать как «hack» в изначальномсмысле этого термина, но более уместен здесь термин «kludge»: проблема решается с нарушением правил, но, тем не менее, код работает и, возможно, имеет какие-то преимущества, например, занимает меньше места, быстрее выполняется. Вданном случае особенностью является то, что не используются регистры, требующие сохранения (EBX, ESI, EDI) и тело цикла занимает всего 6 байтов: 8f 04 90 42 e2 fa.Важно понимать, что приведенный код нарушает принятый порядок работы с регистром ESP.
Перестановка указателя стека на произвольные пользовательскиеданные может вызывать проблемы при работе отладчика. В случае возникновенияошибки нет никакой гарантии, что отладчик будет давать корректную диагностику.Пример 3-10 Стандартный ввод/вывод своими силамиРеализовать на языке ассемблера заданную Си-функцию f.void f(void) {int x;scanf("%d", &x);printf("%d @ %p\n", x, &x);}Считать, что при вызове функции f стек уже выровнен на границе в 16 байтов.Пользоваться командами ввода/вывода из библиотеки io.inc запрещено.45РешениеОсновная сложность данной задачи заключается в правильном распределении памяти во фрейме функции f. Это необходимо для того, чтоб можно было корректновызвать библиотечные функции scanf и printf (их использование обусловлено ограничением, заданном в условии).
Фрейм функции f должен содержать автоматическую локальную переменную x (4 байта), сохраненное значение регистра EBP (4байта), фактические аргументы вызываемых функций, а также адрес возврата (4байта). Помимо того, значение &x используется в Си-функции дважды, вычислим иразместим его в регистре EBX (значение должно «пережить» вызов scanf), а для сохранения прежнего значения регистра выделим еще 4 байта.Пространство аргументов будет совместно использоваться функциями scanf иprintf. При вызове первой потребуется два двойных слова: адрес форматной строки и адрес переменной, в которой будет размещено введенное число. Для вызоваprintf потребуется три двойных слова: адрес форматной строки, значение х и адрес, по которому эта переменная размещена. Таким образом, под пространствоаргументов потребуется 12 байтов.Суммарный объем размещенных во фрейме данных составит 28 байтов, что некратно 16.
Необходимо дополнительно выделить 4 байта для выравнивания, тогдасуммарный объем фрейма достигнет 32 байтов, что уже кратно 16. КомпиляторGCC традиционно размещает выравнивающие байты непосредственно выше пространства аргументов. Границы блоков памяти, выровненных по 16 байтов, показаны жирной чертой.46section .rodata.LC0 db "%d", 0; Форматная строка для scanf.LC1 db `%d @ %p\n`, 0 ; Форматная строка для printfsection .textf:pushebpmovebp, esppushebxsubesp, 20leaebx, [ebp-8]movdword [esp+4], ebxmovdword [esp], .LC0callscanfmovdword [esp+8], ebxmoveax, dword [ebp-8]movdword [esp+4], eaxmovdword [esp], .LC1callprintfmovebx, dword [ebp-4]leaveretПример 3-11 Дамп файлаРеализуйте на языке ассемблера функцию hdump, которая печатает на стандартныйвывод содержимое файла fname в шестнадцатеричном виде. Печатать следует поодному байту, разделяя их пробелами, не более 16 байтов в одной строке.void hdump(const char * fname);Считать, что при вызове функции hdump стек уже выровнен на границе в 16 байтов.Пользоваться командами ввода/вывода из библиотеки io.inc запрещено.РешениеВ функции hdump будут вызываться fopen, getc, printf, fclose.
printf объявлять нетребуется, объявление остальных функций обязательно. Для работы потребуютсятри переменные: описатель открытого файла, значение текущего байта, номер текущей позиции в строке. Значение байта будет передаваться из getc в printf, сохранение во фрейме не требуется. В свою очередь описатель и номер позиции необходимо сохранять между вызовами функций, для этих переменных будут выделены регистры ebx и edi соответственно.47%include 'io.inc'CEXTERN fopenCEXTERN fcloseCEXTERN getcsection .rodataLC0 db "rb", 0LC1 db "%02x ", 0LC2 db `\n`, 0; параметр fopen – открыть двоичный файл на чтение; параметр printf для печати считанного байта; параметр printf для перевода строкиДля работы библиотечных функций потребуются три строки, которые заданы всекции .rodata.В функции hdump присутствуют типовые пролог и эпилог.
По смещениям EBP-4 и EBP8 сохраняются регистры EBX и EDI соответственно.48hdump:pushmovsubmovmovebpebp, espesp, 24dword [ebp-4], ebxdword [ebp-8], edi; Прологmovmovmovcallcmpjemoveax, dword [ebp+8]dword [esp], eaxdword [esp+4], LC0fopeneax, 0.endebx, eax; Открываем файл fname на чтениеmovedi, 0; Обнуляем номер позиции на строкеdword [esp], ebxgetceax, -1.le; Считываем один байтedidword [esp], LC1dword [esp+4], eaxprintf; Увеличиваем счетчик позиции на строке; Печатаем байт.
Обязательно выводим два символа.loop:movcallcmpjeincmovmovcall; В случае неудачи – сразу выходим; Сохраняем описатель файла в ebx; -1 – признак конца файлаand edi, 0xfjnz .currLinemov dword [esp], LC2call printf.currLine:jmp .loop.le:; Если достигли 16 позиции – сбрасываем счетчик; в ноль и печатаем функцией printf перевод; строкиtestjzmovcall.nl:; Если номер позиции не ноль – надо перевести; не до конца оконченную строкуedi, edi.nldword [esp], LC2printfmov dword [esp], ebxcall fclose; Переходим на считывание очередного байта; Не забываем закрыть файл.end:mov edi, dword [ebp-8]mov ebx, dword [ebp-4]leaveretУстройство фрейма функцииФрейм содержит как пользовательские, так и служебные данные.
Помимо автоматических локальных переменных в нем сохраняются регистры и размещен адресвозврата, переход по которому происходит при завершении функции. Язык Си непредусматривает контроля границ памяти, выделенной переменным. Ошибки в49индексации массивов и адресной арифметике могут привести не только к порчедругих переменных пользователя, но и, что гораздо хуже, к порче служебных данных. При определенных условиях ошибка работы с памятью становится уязвимостью: специально подобранные входные данные (их принято называть эксплойтом) вызывают срабатывание ошибки в таком виде, что поведение программыпринципиально меняется.
Например, до того, как программа аварийно завершится, происходит целенаправленная порча определенных данных или отправка данных на вывод. Наихудшая ситуация – когда программа в результате срабатыванияуязвимости начинает выполнять заданный извне код.Задачей разработчика является не только обеспечение высокой производительности программы, но и ее безопасность – минимальное число ошибок. В тех случаях,когда происходит порча данных в памяти предпочтительно аварийно завершатьвыполнение как только всплывает факт срабатывания ошибки.
Определенную помощь разработчику оказывает современный компилятор, встраивающий в программу проверки в наиболее опасных с точки зрения безопасности местах.Пример 3-12 Карта фреймаДля данной Си-функции компилятор построил следующий ассемблерный код.#include <stdio.h>int f(int i) {int array[4];scanf("%x", (unsigned*)&array[i]);return array[i];section .rodata}LC0 db "%x", 0sectionf:pushmovpushsubmovmovleamovcallmovaddpoppopret.textebpebp, espebxesp, 36ebx, dword [ebp+8]dword [esp], LC0eax, [ebp-24+ebx*4]dword [esp+4], eaxscanfeax, dword [ebp-24+ebx*4]esp, 36ebxebp;;;;;;;;;;;;;;(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)В момент вызова функции (перед выполнением ассемблерной инструкции №1) регистры имели следующие значения: ESP = 0xbfffff1c, EBP = 0xbfffff50.50При вызове функции scanf co стандартного потока ввода было считано число 0x23.Строка LC0 размещена в памяти по адресу 0x25207825.1.Какое значение регистр EBP получит после выполнения инструкции №2?2.Какое значение регистр ESP получит после выполнения инструкции №4?3.По какому адресу размещена переменная array?4.Нарисуйте карту фрейма, указав на рисунке: границы фрейма, аргументы вызова, сохраненные регистры, автоматические локальные переменные, пространство аргументов, неиспользуемую память.
Укажите содержимое ячеекпамяти, когда их значение гарантированно известно.РешениеНачнем решение с того, что определим, как происходило распределение памятиво фрейме функции.Границы выровненных блоков показаны на рисунке жирными линиями. Командами PUSH были сохранены регистры EBP и EBX, после чего на стеке было выделено 9двойных слов.
Общий размер фрейма – 48 байтов, два нижних двойных слова –пространство аргументов для вызова функции scanf. Как видно по командам №7 и№10, массив array размещен во фрейме по адресу EBP-24 и занимает целиком выровненный блок из 16 байтов.51Однозначно можно утверждать, что сохраненный EBP и первый аргумент вызоваscanf (адрес форматной строки) будут иметь значения 0xbfffff50 и 0x25207825 соответственно.