- •Системное программное обеспечение
- •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.3. Базовая и индексно-базовая адресации
Еще одним способом адресации является базовая адресация. Ее основное назначение – предоставлять на уровне архитектуры средства эффективного доступа к полям структур данных. Напомним, что на языке Си структуры данных описываются с помощью служебного слова struct, а доступ к отдельным полям экземпляра структуры данных записывается с помощью обозначения экземпляра структуры и через служебный символ точки, за которым следует обозначение поля.
Для пояснения существа использования базовой адресации будем исходить из следующего описания на языке Си:
struct doca {
char sex;
int age;
charname[10];
};
struct doca persons[20];
которое в упрощенном варианте подготавливает массив структур данных для учетной информации о некоторых людях (пол, возраст и имя).
На ассемблере соответствующие данные должны 20 раз резервировать структурированную группу полей, первое из которых имеет размер в один байт, второе – двойное слово, а третье – последовательность из 10 байт. Непосредственное использование именованных обозначений полей для всех 20 экземпляров привело бы к многократному появлению одних и тех же имен, что нарушает принципиальное требование обозначения с помощью имени конкретного места в памяти.
Если отдельно описать строение данной структуры на ассемблере, мы получим следующее:
; struct doca
sex DB 0
age DD 0
name TIMES 10 DB 0
Простейшее решение заключается в использовании численных смещений от начала структуры. Непосредственно видно (в том числе из листинга соответствующей программы), что поле sex находится с нулевым смещением от начала экземпляра структуры, поле age – на расстоянии одного байта от начала экземпляра структуры, а поле name, соответственно, на расстоянии пяти байт от начала структуры (считая, что данные типа int требуют для записи в 32-битной архитектуры четыре байта).
Технической и в некоторой степени языковой проблемой для ассемблера является то, что эти поля нужно обозначать и использовать для любого из экземпляров в массиве структур. Базовый способ адресации и позволяет тут же решить эту задачу. Предварительно в некоторый регистр, называемый далее базовым в операнде с рассматриваемой адресацией, заносится значение адреса экземпляра структуры. Тогда обозначение [базовый_регистр+0] задает в операнде команды поле sex, обозначение [базовый_регистр+1] задает поле age, а обозначение [базовый_регистр+5] задает поле name. (С помощью последнего обозначения можно добраться до одного, двух или четырех начальных байтов поля name, используя, соответственно, модификаторы byte, word и dword.) Меняя значение в регистре базовый_регистр, можно с помощью одной и той же команды добираться до одноименного поля в различных экземплярах структуры.
При выполнении команды с базовым способом адресации аппаратура складывает содержимое базового регистра со значением смещения, указанного в команде, получая тем самым действительное смещение требуемого программистом поля структуры данных. Это вычисленное аппаратурой значение используется ей же для получения доступа к этому полю: для чтения значения из него или занесения в него новых данных.
Например, для вычисления суммы значений полей age в предложенном выше примере (для вычисления среднего возраста или чего-то подобного) можно использовать следующий фрагмент программы на ассемблере
mov ebx, persons ; адрес массива структур заносится в ebx
mov ecx, 20
mov eax, 0; регистр EAX предназначен для вычисляемой суммы
povt: add eax, [ebx+1] ; прибавление значения поля age
add ebx, 15 ; увеличение регистра ebx на размер структуры doca
loop povt
Здесь в начале значение регистра ebx устанавливается, чтобы указывать на начало области массива структур, а затем внутри цикла каждый раз увеличивается на длину экземпляра структуры, выраженную в байтах, тем самым указывая далее на начало следующего элемента структуры в массиве. Операнд [ebx+1] в команде сложения всегда поэтому задает поле age описанной выше структуры данных.
Для описания всего массива данных может быть использована «очень слепая» форма записи в виде
persons TIMES 20*15 db 0
или же более развернутая для программиста форма, применяющая директиву повторения REP
persons
%rep 20
DB 0
DD 0
TIMES 10 DB 0
%endrep
Эта директива относится к средствам предпроцессора (предварительной до собственно компилятора обработки исходного текста). Она с помощью начальной части конструкции %rep задает число повторений идущего далее текста – до строки с ключевым словом %endrep. В данном случае 20 раз повторяется описание полей для структуры.
Более мощным средством являются предпроцессорные директивы описания структуры данных. Они задаются ключевыми словами STRUC и ENDSTRUC, указывающими начало и конец описания структуры, причем имя структуры задается как единственный параметр в директиве STRUC. Само описание при этом должно задаваться исключительно резервированием памяти. В нашем примере мы получаем
struc doca
sex resb 1
age resd 1
name resb 10
endstruc
Такое введение в использование имен полей sex, age, name равносильно явному заданию для каждого из них числового значения, равного смещению соответствующего поля относительно начала структуры. В данном случае неявно для поля sex задается значение 0, для поля age – значение 1, для поля name – значение 5. После этого можно в операндах, указывающих доступ к этим полям, записывать соответственно[ebx+sex], [ebx+age], [ebx+name].
При использовании в различных структурах данных одноименных полей применяют так называемые локальные обозначения, задаваемые в ассемблере NASM с помощью служебного символа точки в начале имени. Если определить строение структуры doca с помощью конструкции
struc doca
.sex resb 1
.age resd 1
.name resb 10
endstruc
то имена .sex, .age, .name будут относиться только к структуре doca. Определенные таким образом имена полей должны использоваться вместе с именем структуры в виде имя_структуры.имя_поля. Все это сделано, чтобы можно было применять одноименные поля в различных структурах данных, которые различаются именно по квалификатору имени структуры, стоящему перед именем поля.
В нашем последнем примере обозначение поля sex в операнде, который в качестве базового использует регистр ebx, должно записываться в виде [ebx+doca.sex], использование поля age – в виде [ebx+doca.age], а поля name – в виде [ebx+doca.name]. Еще раз подчеркнем, что вместо таких достаточно наглядных, но более длинных обозначений программист может использовать просто числовое значение смещения.
Еще более сложным способом адресации (и наиболее сложным в базовой 16-битной архитектуре процессоров Intel) является индексно-базо-вый способ. В нем используются указания двух регистров: один содержит базовый адрес какой-то структуры, а другой используется как индексный для доступа к различным элементам массива в составе структуры. В общей форме этот способ записывается для операнда в виде
[базовый_регистр+индексный_регистр+смещение]
Компонент смещение в этой форме, равно как и в более простой форме базового способа адресации, может отсутствовать (опущен при записи на ассемблере). При выполнении команды с операндом в индексно- базовом способе адресации процессор вычисляет сумму содержимого базового и индексного регистра, а также смещения. Полученное значение используется в качестве адреса для доступа к операнду в сегменте данных.
Практическое значение этого способа заключается в обработке массивов данных внутри экземпляров структур. Например, в следующем фрагменте последовательный доступ к байтам поля name может быть организован с помощью индексно-базового способа следующим образом:
mov ebx, persons ; адрес массива структур заносится в ebx
mov ecx, 20
povt: mov esi, 0 ; нулевой индекс в esi для доступа к нач. байту области
iname: . . .использование байтов области name
; . . . с помощью операнда в виде [ebx+esi+5]
inc esi ; индекс в esi переустановить на след.элемент в массиве name
cmp esi, 10
jl iname
add ebx, 15 ; увеличение регистра ebx на размер структуры doca
loop povt
Здесь, как уже указывалось выше, обозначение доступа к байту в массиве name может быть записано в виде[ebx+esi+doca.name], но только если в программе директивой предпроцессора определено строение структуры doca.
Заметим, что и базовый и индексно-базовый способ доступа могут быть использованы шире, чем только для явно определенных структур. Практически их использование в командах является мыслимой программистом структурой данных, которая систематически может быть не записана где-то в программе. Эти способы дают возможность, отталкиваясь от некоторого адреса, который занесен в базовый регистр, указывать доступ командам к любым данным, находящимся на фиксированном расстоянии (в пространстве адресов памяти компьютера).