- •Системное программное обеспечение
- •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
- •Контрольные вопросы
- •Заключение
- •Библиографический список
3.5. Неарифметические операции над кодами
К настоящему моменту мы познакомились с командами преобразования информации, которые выполняют сложение, вычитание, умножение и деление. Кроме этих, очевидным образом необходимых команд, в архитектуру процессора входят еще некоторые команды, выполняющие неарифметические операции. В составе таких команд можно выделить две основные группы, которые предназначены для поразрядных логических операций и операций сдвига двоичных кодов.
Для выполнения поразрядной логической операции И служит команда с мнемокодом AND, имеющая общий вид
AND операнд1, операнд2
Основное практическое применение эта команда имеет с константным вторым операндом, который в этом случае называют маской логической операции. Команда AND с маской применяется для выборочного сброса в нуль отдельных битов двоичного кода первого операнда. (Такое действие основано на обычных, хорошо известных свойствах логической операции И: x&0=0, x&1=x.) Например, команда
AND al, 3Fh
сбрасывает в нуль два старших бита двоичного кода в регистре AL.
Для выполнения поразрядной логической операции ИЛИ служит команда с мнемокодом OR, имеющая общий вид
OR операнд1, операнд2
Основное практическое применение эта команда имеет также с константным вторым операндом – маской логической операции ИЛИ. Команда OR с маской применяется для выборочной установки (в единицу) отдельных битов двоичного кода первого операнда. (Такое действие основано на обычных, хорошо известных свойствах логической операции И: xv0=x, xv1=1.) Например, команда
OR dx, 8001h
устанавливает в единицу старший и младший биты двоичного кода в регистре DX.
Менее употребительной является команда исключающего ИЛИ, задаваемая мнемокодом XOR и употребляемая обычно также с константой – маской в качестве второго операнда. В общем случае она имеет вид
XOR операнд1, операнд2
и служит для обращения (инверсии) тех двоичных разрядов первого операнда, которым соответствуют единичные биты во втором операнде.
Последняя из логических команд выполняет операции поразрядного инвертирования двоичного кода единственного ее операнда и имеет вид
NOT операнд
Команды сдвига представлены множеством модификаций. Основными в большинстве применений являются команды с мнемокодами SHL и SHR, наименования которых восходят к английским словам Shift, Left и Right. Эти команды задают сдвиг двоичного кода в первом операнде на число разрядов, заданное во втором операнде. В настоящее время основные формы этих команд задаются следующими схемами:
SHL операнд1, число
SHR операнд1, число
Биты, вдвигаемые в этих командах, всегда нулевые. Кроме перечисленных команд, имеются еще команды арифметического сдвига SAL, SAR и циклического сдвига ROL, ROR, RCL, RCR. Из-за редкого их использования они не будут рассматриваться в данном пособии.
Далее в листинге 3.5.1 приведена программа, демонстрирующая изложенный выше материал. В этой программе содержатся подпрограммы вывода на экран содержимого регистров AL, AX и EAX в виде шестнадцатеричных представлений. Причем процедура call wrhex_hal отображает на экране значение только младшей половины регистра AL, выдавая его всегда в виде одной шестнадцатеричной цифры. Процедура wrhex_al выдает содержимое регистра AL в виде двух шестнадцатеричных цифр, процедура wrhex_ax выдает содержимое регистра AX в виде четырех шестнадцатеричных цифр, а процедура wrhex_eax выдает содержимое регистра EAX в виде восьми шестнадцатеричных цифр.
; Использование процедур.
GLOBAL _start
SEGMENT .text
_start: mov al, 0d5h
call wrhex_hal
call wrcrlf
mov al, 0fah
call wrhex_al
call wrcrlf
mov ax, 05e7bh
call wrhex_ax
call wrcrlf
mov eax, 03fec5e7bh
call wrhex_eax
call wrcrlf
mov eax,1
int 80h ; function=exit for Linux
; procedure wrcrlf
wrcrlf:
pusha
mov eax,4 ; N function=write
mov ebx,1; N handle=1 (stdout)
mov edx,1; number of byte
mov ecx, crlf ; address of crlf
int 80h
popa
ret
; end procedure wrcrlf
; procedure wrhex_hal
wrhex_hal: push eax
push ebx
push ecx
push edx
and al, 0Fh
cmp al, 10
jge hexa
add al, '0'
jmp wrchar
hexa: add al,'A'-10 ; из AL вычесть 10 и прибавить значение 'A'
wrchar:
mov [cha],al
mov eax,4 ; N function=write
mov ebx,1; N handle=1 (stdout)
mov edx,1; number of byte
mov ecx, cha ; address of cha
int 80h
pop edx
pop ecx
pop ebx
pop eax
ret
; end procedure wrhex_hal
; procedure wrhex_al
wrhex_al: push eax
shr al,4
call wrhex_hal
pop eax
call wrhex_hal
ret
; end procedure wrhex_al
; procedure wrhex_ax
wrhex_ax: push eax
shr ax,8
call wrhex_al
pop eax
call wrhex_al
ret
; end procedure wrhex_ax
; procedure wrhex_eax
wrhex_eax: push eax
shr eax,16
call wrhex_ax
pop eax
call wrhex_ax
ret
; end procedure wrhex_eax
SEGMENT .data
crlf db 10
cnt dd 0
cha db 0
Листинг 3.5.1. Вывод шестнадцатеричного значения из регистров AL, AX, EAX
Кроме того, в качестве вспомогательной использована процедура wrcrlf, вызов которой выводит управляющий символ перевода на следующую строку, поэтому, когда в основной программе нужно задать такой переход вывода на следующую строку экрана, требуется лишь запись команды CALL wrcrlf.
Во всех приведенных процедурах используется сохранение регистров и восстановление их из стека. Причем в процедуре wrcrlf для этого применены универсальные команды PUSHA и POPA, а в остальных процедурах сохраняются и восстанавливаются только действительно изменяемые в них регистры.
Основную работу для всех процедур выполняет подпрограмма wrhex_hal, предназначенная для вывода значения младшей половины регистра AL в виде одной шестнадцатеричной цифры. В ее начале содержимое регистра AL изменяется обнулением битов в старшей половине регистра. Для этих целей использована команда логического поразрядного умножения на константу 0fh. После этого выполняется сравнение промежуточного результата (значения младшей половины) с константой 10.
Если рассматриваемое значение меньше 10, то шестнадцатеричная цифра представляется обычной десятичной цифрой, и в этом случае используется уже изученная выше технология получения выводимой цифры путем прибавления к ее значению кода цифры '0'. В противном случае следует вычесть из промежуточного результата значение 10, получив тем самым числовое смещение требуемого кода от самой меньшей по значению шестнадцатеричной цифры, изображаемой буквой 'A'. Затем добавить к очередному результату значение кода буквы 'A' (что и порождает код шестнадцатеричной цифры, изображаемой соответствующей буквой). Две последние операции оказалось возможным объединить в одну с помощью команды
add al,'A'-10
которая прибавляет к промежуточному значению в регистре AL значение константы, сформированной из разности кода буквы 'A' и числа 10.
Далее подготовленное в регистре AL значение любой возможной шестнадцатеричной цифры выводится с помощью традиционной последовательности команд, обеспечивающих вызов системной функции write. Причем, соответственно с требованиями организации обращения выводимое значение переносится из регистра AL в однобайтовое служебное поле данных с именем cha.
Процедура wrhex_al строится уже проще на основе использования процедуры wrhex_hal. В начале из аргумента – регистра AL – сдвигом на 4 бита в младшую половину регистра помещается его бывшая старшая половина. Затем обращением call wrhex_hal на экран выводится шестнадцатеричная цифра, отвечающая значению этой старшей половины. Потом в регистре AL восстанавливается первоначальное значение, бывшее в нем при обращении к данной подпрограмме call wrhex_hal. С этой целью ранее запомненное для сохранения значение всего регистра EAX восстанавливается командой pop eax. Далее опять вызывается подпрограмма wrhex_hal, которая выводит в виде шестнадцатеричной цифры значение младшей половины регистра AL. Тем самым отображаются обе шестнадцатеричные цифры значения всего регистра AL, причем в правильном порядке. Заметим, что какое-либо восстановление регистров после последнего вызова уже не требуется, так как после восстановления значения регистра EAX никаких изменений регистров в текущей процедуре wrhex_al не происходит. Это, в частности, демонстрирует, что общее правило восстановления регистров может в конкретных ситуациях видоизменяться.
Процедуры wrhex_ax и wrhex_eax построены очень похоже на процедуру wrhex_al, они отличаются только тем, что на первом этапе содержимое регистров AX и EAX сдвигается на 8 и 16 бит. Тем самым вначале для промежуточного вывода задается старшая половина соответствующего регистра, а затем, после восстановления значения регистра eax, используется отображение содержимого младшей половины этого регистра. Отображения же указанных половин выполняется вызовом процедур wrhex_al и wrhex_ax.
В основной части программы после занесения в регистры AL, AX и EAX констант 0fah, 05e7bh и 03fec5e7bh осуществляются вызовы процедур wrhex_al, wrhex_ax и wrhex_eax соответственно, что обеспечивает демонстрацию использования этих процедур.