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

Программирование на языке ассемблера

.pdf
Скачиваний:
79
Добавлен:
08.05.2015
Размер:
1.66 Mб
Скачать

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

Для обеспечения реентерабельности необходимо выполнение нескольких условий:

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

вызываемая процедура не должна сохранять информацию между вызовами;

если процедура изменяет какие-либо данные, то они должны быть уникальными для каждого пользователя;

процедура не должна возвращать указатели на объекты, общие для разных пользователей.

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