- •Системное программное обеспечение
- •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
- •Контрольные вопросы
- •Заключение
- •Библиографический список
2.5. Простейшие способы адресации
При рассмотрении команд, которые мы встречали, уже говорилось, как можно задавать операнды команды. В простейших случаях для этого записывались обозначения регистров или числовые константы. В общем случае информация для аппаратуры процессора – как добраться до содержимого операнда – находится в части машинного кода команды, называемой полем операнда, а ее истолкование (в частности, интерпретация аппаратурой процессора) называется способом адресации операнда.
Наиболее употребительный способ адресации называется регистровым. В мнемокодах команд операнды, задаваемые этим способом, записываются просто как обозначения регистров. Если этим способом адресации задан операнд исходных данных для команды, то содержимое регистра используется как исходное значение операнда. Если же этим способом задается место размещения результата команды, то в указанном таким образом регистре запоминается значение результата. Внешне такое использование очень похоже на использование переменных в операторах программы на языке высокого уровня. (Тогда, если переменная встречается в составе выражения, то используется значение этой переменной, а если переменная записана в левой части оператора присваивания, то она используется для размещения в ней результата.) Примеры использования регистровой адресации встречались нам постоянно. В команде
MOV EDX, EAX
оба ее операнда заданы регистровым способом адресации, а в команде
MOV CL, 654
левый операнд задан регистровым способом адресации, а правый – другим способом.
Вторым из простейших способов адресации является способ непосредственной адресации. В поле операнда – при применении этого способа – записывается значение константы, рассматриваемое при выполнении как соответствующий операнд. Для более глубокого понимания непосредственной адресации нужно учитывать, что константа, определяющая непосредственный операнд, записывается внутри машинного кода команды.
При использовании для задания данных прямого способа адресации в соответствующей части мнемокода записывается имя области данных, помещенное для ассемблера NASM в квадратные скобки. В общем случае, например, при рассматривании дизассемблированного кода, в квадратные скобки помещается числовое значение относительного адреса области данных. Прямой способ адресации позволяет задавать данные в памяти, аналогично использованию переменных в языках высокого уровня. Пусть, например, именованные области данных определены описаниями
x dd 56
y dd –37
z dd 0
тогда команды
MOV eax, [x]
ADDeax, [y]
MOV [z], eax
обеспечивают сложение значений из 4-байтовых полей данных с именами x и y с последующим помещением результата в двойное слово области z. Практически эти команды реализуют оператор Си
z = x + y
(в первых двух командах этого примера прямой способ адресации использован во втором операнде, а в третьей – прямой способ применен в первом операнде; остальные операнды используют регистровый способ адресации). После преобразования данных фрагментов программы в исполняемый файл и просмотра его выполнения с помощью отладчика либо же просмотра содержимого с помощью дизассемблера результаты могут быть выданы в виде следующих строк:
MOV eax, [00000020]
ADDeax, [00000024]
MOV [00000028], eax
Они отражают тот факт, что именованные в исходном файле области x, y, z в сегменте данных исполняемого файла будут находиться со смещениями 20, 24 и 28 от начала этого сегмента. Именно поэтому для обозначения на ассемблере данных в памяти компьютера используются вспомогательные синтаксические элементы – квадратные скобки. Убрав их из последнего фрагмента команд, мы бы получили, что первая команда приказывает занести числовую константу 20 в регистр eax, а вторая команда приказывает прибавить к содержимому регистра eax значение числовой константы 24. Последние приказания отличны от задаваемых в исходном файле действий, где область данных, названная x, содержит число 56, а область данных, названная y, содержит число –37. Другое дело, что область, ранее обозначенная x, в машинном коде обозначается просто как лежащая со смещением 20 от начала сегмента данных, а область, ранее обозначенная как y, лежит со смещением 24. (Заметим, что в языках высокого уровня никогда не появляются действительные обозначения переменных числовыми смещениями от начала сегмента и, поэтому, нет никакой необходимости вводить специальные символы, подчеркивающие, что имя обозначает место в памяти, а не само значение, как бывает для констант, размещаемых внутри кода команды.)
С точки поверхностного программирования действие команды
MOV edx, 127
равносильно действию команды
MOV edx, [kkk]
где имя kkk определено в сегменте данных как
kkk dd 127
но с формальной точки зрения в первом случае во втором операнде использован непосредственный способ адресации, а во втором случае во втором операнде применен прямой способ адресации. Второй вариант требует больше суммарной памяти для записи желаемого действия (кроме места для команды, еще и места под соответствующую константу в сегменте данных). Но, главное, константа в первом варианте реализации уже не может быть изменена другими командами, а значение в именованной области kkk может легко быть изменено другой командой. Последнее не всегда является недостатком и позволяет заносить не только константное значение, но и любое значение, сформированное на этот момент в области (переменной) kkk.
Поскольку прямой способ адресации отвечает (по своему содержанию) действиям использования переменных в языках высокого уровня, без использования этого способа адресации редко обходятся более-менее протяженные фрагменты программ.
Для частичной демонстрации рассмотренных способов адресации обратимся к более содержательному примеру, представленному программой в листинге 2.5.1. Эта программа вводит любой текст, набираемый пользователем (но не более 80 символов), и затем заменяет в нем внутри программы второй из введенных символов на символ! (восклицательный знак). Преобразованный таким образом текст выводится на экран.
; Ввод строки текста, изменение в ней второго из введенных на символ ! и
; вывод полученной строки
GLOBAL _start
SEGMENT .text
_start:
;---read(1, buf, 80 == <3>(ebx, ecx, edx)
mov eax,3 ; N function=read
mov ebx,0 ; 0 handle=0 (stdin)
mov ecx, buf ; address of buf
mov edx,80 ; number of byte
int 80h
mov [len],eax
mov byte [buf+1],'!'
;---write(1, buf, [len]) == <4>(ebx, ecx, edx)
mov eax,4h ; N function=write
mov ebx,1 ; N handle=1 (stdout)
mov ecx, buf ; address of buf
mov edx,[len] ; number of byte
int 80h
mov eax,1
int 80h ; function=exit
SEGMENT .data
buf times 80 db 0 ; или resb 80; но тогда будет предупреждение
len dd 0
Листинг 2.5.1. Простейшее использование прямой адресации
В этой программе использованы две области с именами buf и len, вторая из них предназначена для временного хранения длины введенного текста, а первая служит буфером ввода и вывода. Обращение к области (переменной) len в командах программы использует запись соответствующего операнда в виде [len], так что для доступа к этой области применяется исключительно прямой способ адресации.
Для доступа ко второму байту (считая, что начальный байт области называется первым) используется также прямой способ адресации, записываемый в операндах в виде
[buf+1]
Если мы запишем в операнде [buf+0], то попадем на самое начало области, что равносильно записывается как [buf], для обозначения 10-го байта в этой области достаточно записать операнд в виде [buf+9]. Заметим, что в профессиональном программировании целесообразно нумеровать элементы массива и, в частности, байты области данных, начиная с нуля. Так что n-й байт области данных с именем obl обозначится в операнде команды как [obl+n], где элемент n должен быть записан числом, или путем использования рассмотренной выше и записанной где-то в программе директивы
neq u число
Иногда при построении ассемблерных программ с операндами в памяти возникают неоднозначные ситуации. Например, приказ компьютеру в виде ассемблерной команды
MOV [xxx], –127
не может быть преобразован компилятором ассемблера NASM в машинный код. Это происходит даже при правильно определенной области данных xxx (например, с помощью директивы xxx DD 0), потому что непонятно, сколько байтов двоичного кода требуется поместить в качестве значения константы –127: 8 бит, 16 бит или все 32 бита. (Если традиционные записи чисел не используют нулевых цифр перед первой значащей цифрой, то в машинных структурах совсем наоборот. Более того, машинные способы записи отрицательных чисел в первом случае требуют запомнить шестнадцатеричный код 7F, во втором – FF7F, а в третьем – FFFFFF7F.) Для решения проблемы однозначной определенности в ассемблеры введены дополнительные средства. В ассемблере NASM они представляют собой модификаторы разрядности, задаваемые служебными словами BYTE, WORD и DWORD. Эти модификаторы ставятся перед теми операндами, разрядность которых необходимо уточнить. В частности, вместо недоопределенной выше команды имеется возможность для указанных вариантов задать одну из следующих команд
MOV BYTE [xxx], –127
MOV WORD [xxx], –127
MOV DWORD [xxx], –127
В ассемблерах MASM и TASM принята другая методология использования имен данных. Согласно принципам, заложенным в эти ассемблеры, с именами связываются не только значения, задаваемые смещением начала именованных данных относительно начала сегмента, но и атрибуты. К этим атрибутам относят имя сегмента, в котором данные определены, и характеристику размера. Эти характеристики обозначаются служебными словами BYTE, WORD и DWORD (и рядом других). Поэтому в этих ассемблерах запись команды в виде
MOV [xxx], –127
интерпретируется по-разному в зависимости от определения имени xxx. Это далеко не идеальное решение. Если имя области данных именует область неоднородной – с точки зрения программиста – структуры, то использование обозначения вида [имя_области + число] естественно для доступа к части фактической структуры, но автоматически приобретает атрибут начального поля в такой структуре.
Тремя рассмотренными способами адресации не исчерпываются возможности задания операндов в памяти компьютера. Более сложные способы адресации будут рассмотрены позднее, когда будет достаточно изученного материала, что составлять программы, содержательно использующие эти сложные способы.