- •Системное программное обеспечение
- •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.2. Использование индексной адресации данных
В простейших архитектурах компьютеров удавалось ограничиться косвенно-регистровой адресацией для доступа к различным областям памяти с помощью одной и той же команды. Но для программиста удобней строить программы, используя явный индекс – номер элемента массива. Такой прием восходит к математическим средствам записи элементов массива, и он был в значительной степени реализован с помощью индексной адресации данных.
Существо индексной адресации данных – использование в записи операнда регистра, содержащего индекс – порядковый номер байта, относительно начала массива. С учетом машинных особенностей численного смещения (а не абстрактного нумерующего индекса) значения индексов в этом способе адресации изменяются, начиная с нуля (а не от единицы, как в традиционном математическом индексировании). Такой подход к индексированию должен быть известен читателю после овладения языком Си (или даже после языка Бейсик).
На ассемблере NASM операнды, задаваемые индексным способом адресации, записываются в форме [имя_массива + индексный регистр] или [смещение + индексный регистр]. Первый вариант широко используется для именованных областей, второй наблюдается при просмотре исполняемых файлов с помощью дизассемблеров и отладчиков, когда нет достаточной информации об исходных программах на языке ассемблера. В частности, индексному способу адресации отвечают обозначения операндов [tabla+ESI], [buffer+EDX] и т. п.
На ассемблерах MASM и TASM индексный способ адресации операнда позволяется и в форме имя_массива [индексный регистр], что практически делает его внешне совпадающим с традиционной формой записи индексированных переменных в алголоподобных языках.
Запишем для сравнения простейшую программу суммирования элементов массива, где каждый элемент занимает один байт (это предельно упрощенный пример, так как отдельные байты позволяют записывать числа в чрезвычайно ограниченном диапазоне от –128 до +127). Вот эта программа в двух вариантах – на языке ассемблера (в первом столбце) и на языке Паскаль (во втором столбце):
mov eax,0 sum:=0;
repeat
mov esi, 0 i:=0;
met: add eax, [tabla + esi] sum:=sum+tabla[i];
incesi i:=i+1;
cmpesi, 10 untili>= 10;
jlmet
Как видим, сходство поразительное, и отличие в основном заключается в средствах организации цикла, а не в форме записи доступа к элементу массива.
При практическом использовании индексного способа адресации следует иметь в виду, что недопустимо применение в качестве индексного 8-битного регистра. Регистры же, которые можно применять в косвенно-регистровой адресации, также хорошо подходят и для индексной адресации. (Напомним, что это регистры EAX, EBX, ECX, EDX, ESI, EDI.) В 16-битной архитектуре для индексной адресации были назначены только два регистра SI и DI, отсюда возникло и их наименование (Source Index и Destination Index). В той же 16-битной архитектуре допускалось использование в качестве индексных и регистров BX, BP. Практически же в этой исходной архитектуре все регистры были специализированы в большей или меньшей степени.
При широком использовании индексного способа адресации следует постоянно учитывать, что значение индексного регистра задает смещение в байтах относительно начала массива, а не порядковый номер элемента в этом массиве. Совпадение того и иного имеет место только в том случае, когда элементы массива однобайтовые. (Так оно и было в приведенных примерах.) Если же элементы массива представляются 16-битными словами, то вместо приращения индекса на единицу необходимо увеличивать его на два (два байта занимает каждый элемент). При размере элемента массива в N байтов следует изменять значение индексного регистра на N.
В качестве примера практического использования индексной адресации рассмотрим задачу поиска максимального в массиве чисел с размером числовых элементов, составляющим 4 байта (размер типа int для этой архитектуры). Пример такой программы приведен в листинге 4.2.1.
GLOBAL _start
SEGMENT .text
_start:
mov esi, 0 ; esi=valindex = 0
mov eax, [narray+0] ; или просто [narray]; eax – текущее значение max
met1: cmp eax, [narray+esi] ; сравнение max и narray[index]
jg next
mov eax, [narray+esi] ; поместить narray[index] в edx
next:add esi, 4 ; valindex – приходится увеличивать на 4 байта
; – размер места числа в памяти
cmp esi, 10*4 ; проверены ли все 10 элементов массива?
jl met1
; полученный max в регистре eax
mov esi,10 ; base of position digit system
mov ecx, 0 ; reset digit counter
pov: mov edx, 0 ; null into left part of devident
div esi ; divide for next digit = rest
add dl, '0'
push edx
inc ecx ; step into counter
cmp eax, 0
jne pov
mov [cnt], ecx
mov ebx, formax
izv: pop edx
mov byte [ebx],dl ; digit into array for text value
inc ebx
loop izv ; izv,ecx
;--- write(1, txtmes, lenmes+[cnt]) == <4>(ebx, ecx, edx)
mov eax,4 ; N function=write
mov ebx,1 ; N handle=1 (stdout)
mov edx,[cnt] ; number of byte
add edx, lenmes
mov ecx, txtmes ; address of txtmes (идалее formax)
int 80h
mov eax,1
int 80h ; function=exit for Linux
SEGMENT .data
narray dd 3,17,–5,27,0,–11,17,33,–1,9
txtmesdb 'Максимальное число в массиве есть '
lenmes equ $–txtmes
formax times 10 db ' '
cnt dd 0
Листинг 4.2.1. Поиск максимального числа в массиве чисел
В этой программе значение индекса (смещения индексируемого элемента массива) помещается в регистр ESI. Регистр EAX выделен для размещения в нем будущего значения максимального элемента. Значение индекса, с учетом размеров элементов массива в 4 байта, увеличивается на 4 командой ADD ESI, 4. При проверке выхода значения индекса за пределы массива – в качестве значения для сравнения – используется не число элементов массива, а его произведение на размер элементов. Это произведение явно записывается в операнде с непосредственной адресацией команды сравнения cmp esi, 10*4. Тем самым, в частности, демонстрируется еще одна замечательная возможность современных ассемблеров – использование выражений для вычисления значений операнда, которые можно записывать на месте операнда. При этом требуется, чтобы все компоненты такого выражения были определены еще на этапе компиляции программ. Выражаясь несколько менее обобщенно, компоненты таких выражений должны быть заданы явными константами, в частности определенными через директивы EQU.
Кроме данных выше основных форм записи операндов в индексном способе адресации, возможно также применение записи [имя_области + смещение + индексный_регистр], два первых члена которой на этапе компиляции превращаются в одно числовое смещение от начала сегмента данных, сводясь тем самым ко второй из записанных в начале разделов основных форм. (Внутреннее представление этого способа адресации в машинных кодах и есть машинная форма записи операнда [смещение_от_начала_сегмента + индексный_регистр].)