- •Системное программное обеспечение
- •Isbn 978-5-8149-2441-4
- •Введение
- •1. Основы программирования на ассемблере
- •1.1. Принципы построения ассемблерных программ
- •1.2. Понятие архитектуры компьютера
- •1.3. Регистры программиста в ia32
- •1.4. Описание сегментной структуры программы
- •2. Простейшие средства ассемблера
- •2.1. Средства описания данных
- •2.2. Обращения к функциям операционной системы посредством прерываний
- •2.3. Средства преобразования в исполняемый файл
- •2.4. Управление строками при выводе и вводе данных
- •2.5. Простейшие способы адресации
- •3. Архитектурные элементы для построения программ
- •3.1. Организация условных переходов
- •Команды условных переходов
- •3.2. Средства организации циклов
- •3.3. Особенности команд умножения и деления
- •3.4. Организация процедур
- •3.5. Неарифметические операции над кодами
- •3.6. Архитектура amd64 процессоров в ассемблерных Linux программах
- •4. Использование неэлементарных способов адресации
- •4.1. Косвенно-регистровая адресация и ее использование
- •4.2. Использование индексной адресации данных
- •4.3. Базовая и индексно-базовая адресации
- •5. Взаимодействие программных компонентов
- •5.1. Многомодульная разработка программ
- •5.2. Организация стекового кадра подпрограммы
- •5.3. Программный доступ к системным функциям Win32
- •5.4. Использование свободно распространяемых утилит для Win32
- •5.5. Вызов функций из стандартных библиотек Linux
- •6. Библиотеки объектных модулей
- •6.1. Использование библиотек объектных модулей в Linux
- •6.2. Использование библиотек объектных модулей в Win32
- •7. Разделяемые библиотеки выполняемых программ
- •7.1. Понятие о статической и динамической компоновке
- •7.2. Конструкция библиотеки динамической компоновки
- •7.3. Компоновка времени загрузки с использованием GoLink
- •Контрольные вопросы
- •Заключение
- •Библиографический список
4. Использование неэлементарных способов адресации
4.1. Косвенно-регистровая адресация и ее использование
Наиболее элементарные из способов адресации, рассмотренные в третьей главе, не дают возможности обрабатывать массивы данных, не позволяют одной и той же команде добираться до различных позиций в памяти компьютера.
Для доступа к памяти до сих пор мы использовали только прямую адресацию. Она позволяет указывать в качестве операнда фиксированное место в памяти. Для этого мы задавали в качестве операнда ограниченное квадратными скобками имя области памяти либо в качестве ассемблерных расширений исходной возможности записывали имя области с прибавлением к нему в записи операнда фиксированной числовой величины. На самом деле за этими записями на ассемблере стояли внутренние (в машинных кодах) числовые указания, которые на ассемблере можно записать в виде [числовое смещение].
Таких средств явно недостаточно для полноценного программирования. Поэтому с самого начала компьютерной эры в архитектуре процессоров используется хотя бы еще один способ адресации, называемый косвенно-регистровой адресацией. Существо этого способа в том, что регистр, заданный для доступа к значению операнда, содержит не само значение операнда, а его адрес (фактически смещение относительно начала сегмента, называемое также относительным адресом).
С получением адреса именованной области данных мы уже встречались при изучении средств обращения к системным функциям. Именно запись на NASM команды
MOV регистр, имя_области_данных
приводила при выполнении команды к помещению адреса области данных, заданной именем во втором операнде, в регистр, заданный первым операндом. Тогда не объяснялось, как такое значение адреса используется далее в программе, потому что подобное использование осуществляли команды самой операционной системы.
Косвенно-регистровый способ адресации задается на ассемблере записью операнда в виде [имя_регистра]. В качестве регистра, используемого в такой записи, можно применять любые из перечисленных EAX, EBX, ECX, EDX, ESI, EDI. Для специальных целей в косвенно-регистровом способе адресации можно применять и регистры ESP, EBP, но делать это вне узкой области специализированного применения не рекомендуется. Следует заметить, что в 16-битной архитектуре применение регистров для косвенно-регистровой адресации было существенно ограниченным, нельзя было с этой целью использовать регистры AX, CX и DX. Приходилось практически ограничиваться лишь регистрами BX, SI и DI, что было очень неудобно.
Рассмотрим, как косвенно-регистровый способ адресации применить для доступа к элементам массива. Пусть имеется (для простоты) массив из байтов, описанный с именем tabla, и мы хотим в цикле перебирать все его элементы для какого-то анализа или вычислений. Это можно сделать следующим фрагментом программы:
MOV ecx, число_элементов_в_массиве
MOV edx, tabla
povt: moval, [edx]
анализ или обработка очередного байта, находящегося
сейчас в регистре AL
INC edx
LOOP povt
Первая и последняя команды в этом фрагменте вспомогательные для демонстрации возможностей рассматриваемого способа адресации (цикл может быть организован и как-то иначе). Собственно косвенно-регист-ровый способ адресации использован здесь во втором операнде третьей команды. Именно в команде пересылки местом источника данных задается байт памяти, адрес которого содержит регистр edx. На первом шаге выполнения цикла в этом регистре содержится адрес начала области tabla, занесенный предыдущей командой, о которой речь уже шла раньше. Поэтому на этом шаге выполнения цикла в регистр AL пересылается значение первого байта из области tabla. Перед концом цикла используется команда инкремента регистра edx, благодаря которой перед вторым шагом значение адреса в регистре edx увеличивается на единицу, показывая теперь на следующий байт этой области. Эти же действия выполняются в цикле многократно. После доступа к некоторому байту внутри области tabla команда инкремента увеличивает значение адреса в регистре edx, заставляя его в дальнейшем указывать на следующий очередной байт.
Заметим, что нет крайней необходимости увеличивать адрес в регистре, служащий для косвенно-регистровой адресации, обязательно на единицу. Можно уменьшать это значение в регистре, обеспечивая перемещение доступа к элементам области от более дальних элементов к более близким, в частности обеспечивая просмотр массива от последнего элемента к начальному. Можно перескакивать через элемент или через любое число элементов.
Косвенно-регистровый способ адресации можно было применить в рассмотренном выше примере программы (листинг 3.3.1). В той программе, вынужденно пользуясь минимальными изученными средствами, осуществлялся вывод цифр результата путем отдельного обращения к системной функции вывода для каждой цифры. Для этого целесообразно сначала сформировать текст для вывода, состоящий из последовательности полученных цифр, и только затем обратиться к системной функции вывода, причем единственный раз для всех цифр результата. Но на этом пути следует размещать цифры результата в различных позициях строки для вывода.
Новое решение этой проблемы, основанное на косвенно-регистровой адресации, приведено далее в листинге 4.1.1.
. . . получение цифр результата и запоминание их в стеке как и ранее
mov [cnt], ecx
mov ebx, numtxt
izv: pop edx
mov byte [ebx],dl ; digit into array for text value
inc ebx
loop izv
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
mov eax, 1 ; N function = exit
int 80h ;
SEGMENT .data
cnt dd 0
SEGMENT .bss
numtxt resb 10
Листинг 4.1.1. Фрагмент вывода цифр результата в Linux
Здесь вместо однобайтовой области digit для единственного выводимого символа цифры сразу заведено десять байтов области numtxt, в которых должны разместиться все цифры числа. Адрес этой области заносится в регистр edx второй командой фрагмента. После извлечения очередной цифры из стека выполняется команда пересылки mov byte [ebx],dl. Эта команда переносит содержимое регистра DL (очередную цифру) в байт области numtxt, заданный косвенно-регистровой адресацией через регистр edx. Далее тут же командой инкремента увеличивается значение адреса в регистре edx, заставляя его указывать для следующего шага на дальнейший байт служебной области numtxt. Пока из стека не извлечены все символы цифр, цикл повторяется командой LOOP, число выполнения которой обусловлено начальным значением регистра ecx, где находится число запомненных в стеке цифр. Лишь по завершении этого цикла задается обращение к системной функции вывода, причем адрес области вывода и число символов в ней формируются командами
mov ecx, numtxt
mov edx,[cnt]
которые задают адрес области numtxt и число символов, запомненное ранее в переменной cnt.