Программирование на языке ассемблера
.pdfcall WriteConsole
pop |
esi |
|
|
mov |
esp, ebp |
; Освобождаем стек |
|
pop |
ebp |
; |
Восстанавливаем |
значение регистра EBP |
|
|
|
ret |
4 |
; Удаляем |
из стека переданный |
параметр и возвращаемся |
|
|
OutputNumber endp
5. Функция, находящая в одномерном массиве x сумму значений f(x[i]), где f – некоторая функция одного целочисленного аргумента, адрес которой передаётся через параметры. Функции используют соглашение о вызовах cdecl.
Sum proc
push ebp
mov ebp, esp
push esi
push edi
mov |
ecx, |
[ebp + 8] |
; Заносим в ECX первый |
||||
параметр – количество элементов массива |
|
|
|
|
|||
mov |
esi, |
[ebp + 12] |
; |
Заносим |
в |
ESI |
второй |
параметр – адрес |
начала массива |
|
|
|
|
|
|
mov |
edi, |
[ebp + 16] |
; |
Заносим |
в |
EDI |
третий |
параметр – адрес |
функции |
|
|
|
|
|
|
xor |
edx, |
edx |
; Обнуляем регистр EDX |
||||
L: push |
[esi] |
|
; Кладём в |
стек |
элемент |
||
массива |
|
|
|
|
|
|
|
call |
edi |
|
; |
Вызываем |
функцию, адрес |
||
которой находится в регистре EDI |
|
|
|
|
|
||
add |
esp, |
4 |
; Освобождаем стек |
|
|
add |
edx, eax |
|
; |
Прибавляем |
результат |
|
функции к |
общей сумме |
|
|
|
|
|
add |
esi, 4 |
; |
Переходим |
к |
следующему |
|
элементу массива |
|
|
|
|
|
|
dec |
ecx |
|
; |
Уменьшаем |
значение |
|
регистра ECX на 1 |
|
|
|
|
|
|
jnz |
L |
; |
Если |
ECX |
не |
равно 0, |
продолжаем цикл |
|
|
|
|
|
|
mov |
eax, edx |
|
; |
Записываем |
полученную |
|
сумму в регистр EAX, |
|
|
|
|
|
|
|
|
; через |
который должен возвращаться |
|||
результат функции |
|
|
|
|
|
|
pop |
edi |
|
|
|
|
|
pop |
esi |
|
|
|
|
|
mov |
esp, ebp |
|
|
|
|
|
pop |
ebp |
|
|
|
|
|
ret |
|
|
|
|
|
|
Sum endp |
|
|
|
|
|
|
Sqr proc
mov eax, [esp + 4]
imul eax
ret
Sqr endp
Negation proc
mov eax, [esp + 4]
neg eax
ret
Negation endp
Для |
вызова функции Sum будет использовать следующая |
последовательность команд. |
|
push |
Sqr |
push |
offset a |
push |
na |
call |
Sum |
add |
esp, 12 |
mov |
sa, eax |
push Negation push offset a push na
call Sum
add esp, 12 mov sa, eax
6.Процедура, проверяющая сбалансированность круглых и квадратных скобок в строке. Строка должна заканчиваться нулём. Для проверки сбалансированности открывающие скобки будем класть в стек, а при нахождении в строке закрывающей скобки будем извлекать из стека последнюю положенную туда открывающую скобку и проверять, что она соответствует закрывающей скобке. Будем считать, что скобок в тексте меньше, чем других символов, поэтому после сравнения делаем переход «если равно», считая, что это событие менее вероятно. При любом выходе из процедуры нужно очистить стек. Поскольку мы не можем заранее знать, сколько скобок будет туда положено и сколько извлечено, восстановление значения регистра ESP можно сделать только с помощью регистра EBP. Процедура возвращает значение через регистр EAX: если скобки сбалансированы, регистр EAX будет содержать значение истина (-1), в противном случае регистр EAX будет содержать значение ложь (0).
Brackets proc
push ebx |
; Сохраняем регистры |
push |
ebp |
|
mov |
ebp, esp |
; Сохраняем начальное значение |
регистра ESP |
|
|
mov |
ebx, [ebp + 12] |
|
; |
Заносим |
в |
регистр |
EBX адрес |
|||
начала строки |
|
|
|
|
|
|
|
|
|
|
mov |
eax, -1 |
; |
Заносим |
в |
|
регистр |
EAX |
|||
предварительное значение результата |
|
|
|
|
|
|
|
|||
xor |
edx, edx |
|
; Обнуляем регистр EDX |
|
||||||
L1: mov |
dl, [ebx] |
|
; |
Заносим |
в |
|
регистр |
DL |
||
очередной символ |
|
|
|
|
|
|
|
|
|
|
test |
edx, edx |
|
; |
Проверяем |
значение |
в |
||||
регистре EDX |
|
|
|
|
|
|
|
|
|
|
jz |
E1 |
; Если EDX = 0, выходим из цикла |
|
|||||||
inc |
ebx |
|
; Меняем адрес символа |
|
||||||
cmp |
dl, '(' |
; |
Сравниваем |
символ |
с |
открывающей |
||||
круглой скобкой |
|
|
|
|
|
|
|
|
|
|
je |
L2 |
; Если равно, ... |
|
|
|
|
|
|||
cmp |
dl, '[' |
; |
Сравниваем |
символ |
с |
открывающей |
||||
квадратной скобкой |
|
|
|
|
|
|
|
|
|
|
je |
L2 |
; Если равно, ... |
|
|
|
|
|
|||
cmp |
dl, ')' |
; |
Сравниваем |
символ |
с |
закрывающей |
||||
круглой скобкой |
|
|
|
|
|
|
|
|
|
|
je |
L3 |
; |
Если |
равно, |
переходим |
к |
сравнению |
|||
со скобкой из стека |
|
|
|
|
|
|
|
|
|
|
cmp |
dl, ']' |
; |
Сравниваем |
символ |
с |
закрывающей |
||||
квадратной скобкой |
|
|
|
|
|
|
|
|
|
|
je |
L4 |
; |
Если |
равно, |
переходим |
к |
сравнению |
|||
с другой скобкой из стека |
|
|
|
|
|
|
|
|
|
|
jmp |
L1 |
; |
Если символ |
– |
|
не |
скобка, |
|||
возвращаемся к началу цикла |
|
|
|
|
|
|
|
|
|
|
L2: push |
dx |
; ... заносим открывающую скобку в |
||||||||
стек (один байт записать в стек нельзя) |
|
|
|
|
|
|
jmp |
L1 |
; Возвращаемся к началу цикла |
|||
L3: cmp |
ebp, esp |
; |
Если |
была |
закрывающая |
скобка, прежде всего проверяем, есть ли скобки в стеке –
;если мы положили что-то в стек,
значение регистра ESP будет отличаться от регистра EBP
je |
E2 |
; |
Если |
значения |
регистров |
равны, |
||
выходим из процедуры |
|
|
|
|
|
|
|
|
pop |
cx |
; |
Извлекаем |
из |
стека |
последнюю |
||
открывающую |
скобку |
|
|
|
|
|
|
|
cmp |
cl, '(' |
; Сравниваем |
|
|
|
|
||
jne |
E2 |
; |
Если |
скобки |
не |
равны, |
выходим из |
|
процедуры |
|
|
|
|
|
|
|
|
jmp |
L1 |
; Иначе возвращаемся к началу цикла |
||||||
L4: cmp |
ebp, esp |
|
; |
При нахождении |
закрывающей |
|||
квадратной скобки, |
|
|
|
|
|
|
|
|
je |
E2 |
; выполняем те же действия, что и |
||||||
при нахождении закрывающей круглой скобки, |
|
|
|
|
||||
pop |
cx |
; |
только скобку из стека |
сравниваем |
||||
с другим значением |
|
|
|
|
|
|
|
|
cmp |
cl, '[' |
; |
Дублирование |
сделано |
для |
того, |
||
чтобы уменьшить |
|
|
|
|
|
|
|
|
jne |
E2 |
; количество переходов |
|
|
||||
jmp |
L1 |
|
|
|
|
|
|
|
E1: cmp |
ebp, esp |
; При достижении конца |
строки, |
|
сравниваем регистры ESP и EBP |
|
|
|
|
je |
E3 |
; Если |
значения равны, |
обходим |
обнуление регистра EAX |
|
|
|
|
E2: xor |
eax, eax |
; |
Если |
была |
несбалансированность, обнуляем регистр EAX |
|
|||
E3: mov |
esp, ebp |
; |
Восстанавливаем |
значение |
регистра ESP |
|
|
|
|
pop |
ebp |
|
|
|
pop ebx
ret
Brackets endp
1 В защищённом режиме программе выделяется один сегмент размером 4 Гб для кода и один сегмент размером 4 Гб для данных (физически они обычно совпадают). Виртуальный адрес состоит из 16-битного значения, хранящегося в сегментном регистре, и 32-битного смещения. Однако преобразование виртуального адреса в физический осуществляется не путём сложения, а по более сложной схеме. Сначала процессор преобразует виртуальный адрес в линейный. При этом он обращается к таблицам дескрипторов, которые заранее строятся операционной системой. На втором этапе по линейному адресу определяется физический. В этом преобразовании участвует другой набор системных таблиц – таблицы страничной трансляции, которые также составляются операционной системой. Оба набора таблиц могут динамически меняться, обеспечивая максимальное использование оперативной памяти.
Всегментные регистры записываются не адреса сегментов, а селекторы, которые представляют собой номера ячеек специальной таблицы, содержащей дескрипторы сегментов программ. Каждый дескриптор хранит все характеристики, необходимые для обслуживания сегмента: базовый линейный адрес сегмента, границу сегмента (номер последнего байта), а также атрибуты сегмента, определяющие его свойства. Процессор с помощью селектора определяет индекс дескриптора адресуемого сегмента, извлекает из него базовый линейный 32-битный адрес сегмента и, сложив его с 32-битным смещением, получает линейный адрес адресуемой ячейки памяти. Получив линейный адрес адресуемого байта, процессор с помощью таблиц трансляции преобразует его в 32-битный физический адрес. Этот адрес зависит от объёма оперативной памяти, установленной на компьютере.
В32-битной модели Windows предоставляет всем запущенным приложениям один и тот же селектор для сегмента кода и один и тот же селектор для сегмента данных. Базы обоих сегментов равны 0, а границы – FFFFFFFF. Другими слова, каждому приложению как бы предоставляется всё линейное пространство. Поскольку базовые линейные адреса сегментов программы равны 0, виртуальные смещения, с которыми работают приложения, совпадают с линейными адресами. Другими словами, плоское виртуальное адресное пространство программы совпадает с плоским линейным адресным пространством. При этом все приложения используют один и тот же диапазон линейных адресов. Для того чтобы при одинаковых линейных адресах приложения занимали различные участки физической памяти и не затирали друг друга, Windows при смене приложения изменяет таблицы страничной трансляции, с помощью которых как раз и происходит преобразование линейных адресов в физические.
2 Если говорить точнее, то относительный адрес перехода отсчитывается не от самой команды перехода, а от следующей за ней команды. Дело в том, что выполнение любой команды начинается с засылки в регистр EIP адреса следующей по порядку команды и только затем выполняется собственно команда. Поэтому в команде перехода относительный адрес будет прибавляться к значению регистра EIP, которое уже указывает на следующую команду, а потому от этой следующей команды и приходится отсчитывать относительный адрес перехода. Однако, в любом случае, программисту нет необходимости самому высчитывать относительный адрес перехода, это делает компилятор языка ассемблера.
3 Компьютерная программа в целом или её отдельная процедура называется реентерабельной (от англ. reentrant – повторно входимый), если она разработана таким образом, что одна и та же копия инструкций программы в памяти может быть совместно использована несколькими пользователями или процессами. При этом второй пользователь может вызвать реентерабельный код до того, как с ним завершит работу первый пользователь и это как минимум не должно привести к ошибке, а в лучшем случае не должно вызвать потери вычислений (то есть не должно появиться необходимости выполнять уже выполненные фрагменты кода).
Для обеспечения реентерабельности необходимо выполнение нескольких условий:
никакая часть вызываемого кода не должна модифицироваться;
вызываемая процедура не должна сохранять информацию между вызовами;
если процедура изменяет какие-либо данные, то они должны быть уникальными для каждого пользователя;
процедура не должна возвращать указатели на объекты, общие для разных пользователей.
Вобщем случае, для обеспечения реентерабельности необходимо, чтобы вызывающий процесс или функция каждый раз передавал вызываемому процессу все необходимые данные. Таким образом, функция, которая зависит только от своих параметров, не использует глобальные и статические переменные и вызывает только реентерабельные функции, будет реентерабельной. Если функция использует глобальные или статические переменные, необходимо обеспечить, чтобы каждый пользователь хранил свою локальную копию этих переменных.