Машинный код пример программы
6. ДЕМОНСТРАЦИОННЫЕ ПРИМЕРЫ ПРОГРАММ В КОДАХ
Основная цель главы — дать читателю сведения о начальных проблемах, связанных с написанием программ в машинных кодах. Команды Z80 перечислены в главе 5, но не указывается, как ими пользоваться на SP. Настоящая глава содержит серию демонстрационных программ. В главе 8 будут даны советы, как программа машинного кода может быть написана, чтобы иметь полное представление о преимуществе цветного SP и дисплея с высоким разрешением. Имеется три точки зрения, подлежащие обсуждению.
A. Выбор области RAM.
Программист должен определить требуемый объём памяти. В SP имеется несколько областей памяти, которые могут использоваться, но демонстрация программ в этой главе будет представлена в области RAM с адреса 32000 и дальше. Программа в кодах в этой области, если требуется, может сохраняться на ленте (SAVE) или загружаться с ленты (LOAD) как блок данных.
Б. Ввод кодов программы.
В SP можно вводить коды только по команде POKE. Однако операнд этой команды может быть задан как 10-ное или 2-ное число или как выражение. Поэтому следующие строки все равносильны: 10 POKE 32000,201 10 POKE 32000,BIN 1100 1001 10 LET A=201: POKE 32000,A
Чем машинный код отличается от ассемблера
Каждая может оказаться полезной в определенном случае. Рекомендуемый метод заключается в описании команд машинного кода в их соответствующих 16-ных символах. Следующий 16-ный загрузчик будет использоваться во всей главе.
10 D=32000: REM HEX LOADER
20 DEF FN A(A$,B)=CODE A$ (В)-48-7* (CODE A$(B)-57) 30 DEF FN C(A$)=16*FN A (A$,1)+FN A(A$,2)
40 READ A$: IF A$<>»**» THEN POKE D,FN С(А$): LET D=D+1: GO TO 40
Загрузчик будет считывать данные из DATA, в котором каждая пара 16-ных символов заключена в кавычки и отделена от других пар запятыми. Пара символов «**» используется как символ конца. Пример:
50 DATA «00»,»01″,»02″,»**» RUN
Это приведет к тому, что адрес 32000 содержит 0, адрес 32001 содержит 1, адрес 32002 содержит 2.
B. Выполнение программы.
Команда Бейсика «USR число» позволяет остановить выполнение программы монитора в SP и начать выполнять программу в кодах. Важно убедиться, что операнд «USR» установлен на требуемую команду, что программа пользователя кончается командой «RETURN», если необходимо, вернуться в Бейсик, и что величина возвращенной командой «USR» обработана соответствующим образом. Примеры USR:
PRINT USR N — печатает 10-ное значение содержимое регистровой пары BC.
RANDOMIZE USR N — влияет на генератор случайных чисел.
LET A=USR N — изменяет переменную A.
Каждая из этих форм полезна при определенных условиях. Следующие программы представлены в формате ассемблера, и оператор DATA используется совместно с 16-ным загрузчиком. Команды представлены в том же порядке, как и в главе 5.
Команда «нет операции» (NOP) (см. стр. 33). Эта команда очень простая, и программа машинного кода, содержащая одну или более строк «нет операции», заканчивается «RET».
Пишу программу в машинных кодах
Следующая программа ассемблера показывает простое использование команды NOP.
Адрес Код Обозначение Комментарии
7D00 00 NOP NOP
7D01 С9 RET возврат
Следующая программа Бейсик показывает, как изменить вышеприведенную программу на ассемблере (ASM)
Программа 1. «NOPE» 50 DATA «00»,»C9″,»**» 60 LET A=USR 32000
70 PRINT «нормальное завершение NOP»
Программы для непосредственной загрузки регистров (см. стр. 33). Команда «USR число» возвращает
_ Демонстрационные примеры программ в кодах _
значение регистра BC как абсолютное 16-битовое число, и команда PRINT отображает в 10-ном виде от 0 до 65535. В следующем примере B и C регистры загружаются постоянными, используя команды из этой группы, и программа 2 Бейсик использует программу кода.
7D00 06 00 LD В,00 регистр B=0
7D02 0Е 00 LD С,+ХХ пользователь вводит различные значения
В программе 2 команды ‘LD B,+ADD’ И LD C+ADD’ обе используются. Пользователь может задавать различные значения в качестве входных операндов.
50 DATA «06»,»00″,»0Е»,»00″
60 INPUT «введите значения для регистра С (только 0-255) «;N 70 CLS
90 PRINT AT 10,0; «регистр С теперь содержит «; CHR$ 32; USR 32000 100 GO TO 60
Следующая программа показывает команду LD BC,+DDDD 7D00 01 00 00 LD BC,+DDDD пользователь вводит
7D03 С9 RET различные значения
В программе 3 можно задавать различные величины «LD BC,+DDDD». Введенная величина должна быть разбита на младший и старший байты прежде, чем она может быть заслана по POKE. Программа 3. «LD BC,+DDDD»
60 INPUT «введите значение для регистра пары BC (только 0-255)»; CHR$ 32;N 70 CLS
80 POKE 32001,N-256*INT(N/256): POKE 32002,INT(N/256)
90 PRINT AT 10,0, «Регистр BC теперь содержат»; CHR$ 32; USR 32000
Команды копирования и обмена регистров (см. стр. 33).
Команды копирования регистр-регистр могут быть показаны загрузкой регистров, отличных от регистров B и C, постоянными, которые затем копируются в B и C регистры для возврата их пользователю. 7D00 06 00 LD B,+00 регистр В=0
7D02 2Е 00 ID L,+00 вводится значение
7D04 4D LD C,L регистр С = регистр L
В программе 4 переменная заводится в регистр B и пересылается в регистр C. Программа 4. «LD C,L»
60 INPUT «введите значения для регистра L (только 0-255)»; CHR$ 32; N 70 CLS
90 PRINT AT 10,0; «С регистр теперь содержит»; CHR$ 32; USR 32000 100 GO TO 50
Нижеследующие программы воодушевят читателя.
50 DATA «06»,»00″,»OE»,»00″
51 DATA «65»,»7C»,»57″,»5F»
Команда ‘EX DE,HL’ может также быть включена в программу 4. Пример.
7D00 26 00 LD H,+00 регистр H=0
7D02 2Е00 LD L,+00 вводятся значения
7D04 ЕВ EX DE,HL пересылается в DE
7D05 42 LD B,D регистр B = регистр D
7D06 4В LD C, E регистр С = регистр E
Это задается следующим оператором DATA.
50 DATA «26»,»00″,»2E»,»00″
и может использоваться с программой 4.
Последние две программы этой группы посвящены командам ‘EX AF.’F» и ‘ЕХХ’. Пользователю можно
_ Демонстрационные примеры программ в кодах _
использовать альтернативные регистры, кроме Н’ и L’, содержащие адрес возврата в Бейсик. Читатель может включить эти команды в программу 4. Пример.
50 DATA «06»,»00″,»2E»,»00″
51 DATA «08»,»D9″,»D9″,»06″
Строка 51 переключает все основные регистры, а потом переключает их обратно, не изменяя.
Команды загрузки регистров из адресов памяти (см. стр. 35). Команды в этой группе позволяют пользователю загрузить регистр содержимым памяти. Эта адресация может быть абсолютной, косвенной и индексной.
Первая программа показывает абсолютную индексацию. Задается адрес 31999, имеющий имя STORE, и команда LD A,(ADDR) используется для выборки содержимого STORE по следующей программе. STORE в строке 1 присваивается значение 31999 (16-ное 7CFF). 7D00 06 00 LD В,+00 регистр В
7D02 3A FF 7С LD A,(STORE) выборка текущего значения
В программе 5 пользователь может вводить различные значения, сохраняемые в STORE, и вышеприведенная программа на ASM возвращает их пользователю. Программа 5. «LD A,(ADDR)»
60 INPUT «INTER A VALUE FOR LOCATION STORE(0-255 ONLY)»; CHR$ 32; N 70 CLS
90 PRINT AT 10,0; «THE LOCATION STORE NOW HOLDS»; CHR$ 32; USR 32000 100 GO TO 60
Вторая программа показывает косвенную адресацию. В ней адрес STORE загружается в регистр HL до использования команды «LD C,(HL)» STORE=7CFF. 7D00 06 00 LD B,+00 регистр B=0
7D02 21 FF 7С LD HL,+STORE HL указывает на STORE
7D05 4E LD ^(HL) выборка текущего значения
В программе 6 пользователь может вводить различные величины, сохраняемые в STORE. Программа 6. «LD C,(HL)»
52 DATA «4E»,»C9″,»**» строки 60-100 как в программе 5.
Третья программа показывает индексную адресаций. В этой программе адрес 31936 (7CC0) называется BASE, а адрес 31999 (7CFF) — STORE, BASE=7TC0 STORE=BASE+3F
7D00 06 LD B,+00 -^регистр B=0
7D02 DD 21 С0 6С LD IX,+BASE установка IX 7D06 DD 4E 3E LD ^(BASE+3F) выборка текущего значения
В программе 7 пользователь опять вводит различные значения, которые помещаются в STORE. Программа 7.
51 DATA «DD»,»21″,»C0″,»7C»
53 DATA «C9″,»**» строки 60-100 как в программе 5.
В программе используется регистр IX, но вполне возможно использование регистр IY, но при этом байты машинного кода надо заменить DD на FD. Заметьте, что маскируемое прерывание потребует блокирования, в то время, пока IY содержит новую величину.
Команды загрузки памяти константами или данными, хранящимися в регистрах (см. стр. 35). Команды этой группы позволяют пользователю загрузить адреса памяти константами или данными из регистров. Опять возможны абсолютная, косвенная и индексная адресация.
Первая программа показывает абсолютную адресацию. В программе абсолютный адрес для команды 31999, 16-ной 7CFF, названной ‘STORE’, используется команда ‘LD (ADDR),A’.
7D00 3E 00 LD A,+XX ввод различных значений
7D02 32 FF 7С LD (STORE), А ввод текущего значения в STORE
В программе 8 пользователь опять вводит различные значения в STORE. Программа в машинных кодах выполняется использованием через RANDOMIZE USR 32000, и величина STORE задается использованием РЕЕК 31999.
Программа 8. «LD (ADDR),A».
60 INPUT «INTER A VALUE FOR LOCATION STORE (0-255 ONLY) «;N 70 CLS
80 POKE 32001,N: RANDOMIZE USR 32000
90 PRINT AT 10,0;»THE LOCATION STORE NOW HOLDS»; CHR$32; PEEK 31999 100 GO TO 50
Вторая программа показывает косвенную адресацию. В этой программе регистровая пара HL указывает на STORE, а команда ‘LD (HD),E’ используется для передачи.
7D00 1E 00 LD E,+XX вводится значение
7D02 21 FF 7C LD HL,STORE HL-указывает на STORE
7D05 73 LD (HL),E пересылка
В программе 9 пользователь опять вводит величину, которая должна передаваться в STORE и обратно считываться с помощью PEEK 31999. Программа 9. «LD (HL),E»
52 DATA «73»,»C9″,»**» Строки 60-100 как в программе 8.
В 3-ей программе используется индексная адресация. В этом случае адрес 32061 (7D3D) называется BASE, и STORE поэтому рассматривается как (‘BASE-3E’). BASE=7D3D STORE=BASE-3E
7D00 3E 00 LD A,+XX вводится значение
7D02 DD 21 3D 7D LD IX,+BASE IX указывает на BASE
7D06 DD 77 C2 LD (IX-3E),A IX-3E рассматривается как IX+CR
В программе 10 пользователь опять вводит величины, которые должны быть переданы в STORE, и считывает обратно с помощью PEEK 31999.
Программа 10. «LD (IX+D),A»
Программы 5-10 все использовали 1-байтовые числа и соответствующие команды регистров.
Команды сложения (см. стр. 36). Команды этой группы позволяют складывать величины вместе (ADD) и увеличивать значение (INC) и складывать с битом переноса.
Первая программа показывает использование команды ‘ADD A,B’.
7D00 00 NOP будет использоваться позже
7D01 3E 00 LD A,+XX вводятся значения
7D03 06 00 LD B,+XX
7D05 8 0 ADD A,B суммирование
7D06 06 00 LD B,+00 регистр B=0
7D08 4F LD C,A пересылка результата
В программе 11 пользователь вводит 2 числа. Эти числа передаются в регистры А и В и складываются вместе в двоичной арифметике. Результат возвращается функцией USR. Программа 11. «ADDA.B».
60 INPUT «INTER A FIRST VALUE (0-255) «;CHR$ 32,F 70 INPUT «INTER A SECOND VALUE (0-255)»;CHR$ 32;S 80 CLS
90 POKE 32000,F: POKE 32004,S
100 PRINT AT 10,5;F;CHR$ 32;»ADD»;CHR$ 32;S;CHR$ 32;»=»;CHR$ 32;U SR 32000 110 GO TO 60
Вторая программа для этой группы использует команду INC BC. 7D00 01 00 00 LD B^+ХХХХ вводятся значения
7D03 03 INC BC значение увеличивается
В программе 12 пользователь вводит число в диапазоне 0-65535. Это число затем делится на старшую и младшую части и помещается командой POKE в адреса 7D01 и 7D02. Регистровая пара BC затем увеличивается, и значение возвращается функцией USR. Заметьте эффект ввода значения 65535. Программа 12. «INC BC».
60 INPUT «ENTER A VALUE (0-65535)»; CHR$ 32; N 70 POKE 32002,INT(N/256) 80 POKE 32001,N-256*INT(N/256) 90 CLS
100 PRINT AT 10,0; CHR$ 32;»IN CREMENTS TO GIVE»; CHR$ 32; USR 32000 110 GO TO 60
Третья программа показывает использование команды. Это та же программа, которая используется в программе 11, но изменена для включения команды ADC А,В, а не ADD A,C.
7D00 37 SCF установка флага переноса
7D01 3E 00 LD A,+XX вводятся значения
7D03 06 00 LD B,+XX
7D05 8 8 ADC А, В суммирование с переносом
7D06 06 00 LD B,+00 регистр B=0
Эта программа используется в программе 13, в которой пользователь складывает 2 числа вместе с битом переноса. Флаг переноса всегда устанавливается.
Используйте эффект замены команды SCF на AND А (16-ное А7), которая даёт сброс флага переноса. Программа 13. «ADC A,B».
60 INPUT «ENTER A FIRST VALUE (0-255) «; CHR$ 32;F 70 INPUT «ENTER A SECOND VALUE (0-255)»; CHR$ 32;S 80 CLS
90 POKE 32002,F: POKE 32004,S
100 PRINT AT 10,0;»WITH GARRY SET»; CHR$ 32;F; CHR$ 32;»ADC»; CHR$ 32;S; CHR$ 32;»=»; CHR$ 32; USR 32000
Группа 7. Команды вычитания.
Команды в этой группе позволяют вычесть одну величину из другой (SUB), декрементировать значение (DEC) и вычесть бит переноса (SBC).
Первая программа для этой группы команд использует команду ‘SUB В’. Состояние флага переноса определяется после вычитания, и величины 0 или 1 сохраняются в STORE.
будет использоваться дальше вводятся значения
вычитание регистр B=0 пересылка результата регистр A=0 сложение с переносом
передать значение флага переноса в STORE
В программе 14 вышеприведенная программа вызывается после того, как пользователь вводит значения для регистров A и B. Значение флага переноса получено использованием PEEK 31999.
Источник: zxpress.ru
Машинный код пример программы
Рустэм Галеев aka Roustem
1. Введение в машинные коды для Win32
Мир машинных кодов для процессоров Intel IA-32 захватывающий и фантастический. Он предоставляет такие богатые возможности для творчества, что будет неудивительно, если через некоторое время станут проводить чемпионаты по спортивному программированию в машинных кодах, а лучшие творения кодеров представлять на выставках, как произведения искусства. Множество интересных находок было накоплено за прошедшие годы кодокопателями, среди которых есть как законные системные программисты, так и подпольные авторы вирусов, хакеры и кракеры.
Как когда-то великие путешественники-первопроходцы открывали новые земли, кодеры исследуют бурно разросшееся виртуальное пространство информационных технологий. Несмотря на то, что ее создавали сами люди, эта матрица нашего времени стремительно развивается по каким-то своим законам. Накопились огромные пласты неосвоенных знаний. Развилась целая философия «быстрой разработки приложений» — своего рода «информационный фастфуд». Но разве может забегаловка заменить собой изысканный ресторан?
Можно сказать, информационные технологии проходят сейчас период массового производства, как когда-то автомобильная и другие виды промышленности. Конвейер штампует однотипные универсальные изделия. Но посмотрите на исторические тенденции. Сначала автомобили собирали поштучно. Потом появился конвейер.
Но сейчас самые дорогие и качественные машины опять собирают вручную! А разве механические часы исчезли с появлением электронных? Напротив, стали только качественнее и дороже. А когда их сравнивают с электронными, последние презрительно именуют «штамповкой». И как сравнить массовую бижутерию с синтетическими камнями с филигранной ювелирной работой.
Как бы то ни было, но и в компьютерной индустрии постепенно развилась особая субкультура низкоуровневого программирования. Долгое время она варилась в собственном соку, оставаясь достоянием узкого круга посвященных, интенсивно осмысливавших накопленные знания. Вероятно, был пройден некий порог, и мы вплотную приблизились к моменту, когда начинает зарождаться элитарное штучное ручное производство и в данной высокотехнологичной области. И делать это, естественно, могут лишь специалисты высочайшей квалификации, понимающие значение каждого используемого байта. Однако для дальнейшего развития в этом направлении нужно не только ознакомить более широкую аудиторию с накопленным в узких кругах опытом, но и развенчать некоторые уже устаревшие стереотипы наподобие того, что современные системы программировать на низком уровне невозможно вообще.
Вот с этой целью и появилась задумка систематически рассмотреть с уровня машинных кодов работу наиболее популярной ОС — Windows, чтобы это оказалось доступным самому широкому кругу заинтересовавшихся читателей — от простых пользователей до искушенных программистов. Это и программирование, и изучение работы ОС «изнутри», причем проводимое без всяких посредников в виде языков программирования, вспомогательных библиотек и сред разработки, напрямую, «как есть» в самой ОС. Для работы специально будут использоваться простейшие и даже примитивные инструменты, входящие в состав любой версии Windows от 95 до XP и даже 2003 Server — любой, кто захочет, сможет повторить описываемые эксперименты на самом обычном компьютере.
Хочу добавить пару слов о пользователях, никогда до этого не программировавших. Идея научить их программировать — причем сразу в машинных кодах и сразу под Windows — может, и несколько авантюрная (даже многие низкоуровневики отнеслись к ней скептически), тем не менее, мне кажется, это вполне посильная задача. Особенно если учесть, сколько сил и времени надо затратить, чтобы научиться работать в интегрированной среде разработки, скажем, в том же VisualBasic’е, не говоря уже о том, что надо еще выучить язык. А если, не приведи господи, в набранном из самоучителя тексте окажется опечатка и система выдаст кучу сообщений об ошибках — для новичка продраться через это, по моему глубокому убеждению, гораздо более нереально, чем построить собственными руками подобное же, но работоспособное приложение в машинных кодах.
Не надо бояться окунуться в джунгли машинных кодов. На самом деле, здесь уже есть и проторенные дороги, и тайные заветные тропинки — надо всего лишь их знать и уметь по ним ходить. И я хочу просто показать, как это можно сделать; а уж каждый пусть сам сравнивает, оценивает и решает, сложно это или элементарно, нужно это ему или нет — это будет осознанный выбор, основанный на его собственных знаниях и опыте, а не на чьих-то стереотипах из прошлого.
Что ж, пора перейти от вступлений к сути. Архитектура процессоров Intel IA-32 относится к CISC-модели (с усложненным набором инструкций). Одна из самых примечательных особенностей этих процессоров — формат команды с переменным размером. Команда процессора может быть от 1 до 15 байтов длиной (включая возможные префиксы).
Любители комбинаторики могут подсчитать количество возможных инструкций при такой схеме. Но и без подсчетов ясно, что число астрономическое. Команда может иметь один или несколько так называемых префиксов; собственно код операции (он называется опкодом) состоит из 1 или 2 байтов, а дальше идут байты, описывающие операнды — данные (или ссылки на данные), над которыми производится соответствующая операция. Даже если считать командой лишь байты опкода, то возможны 255 однобайтных команд и столько же двухбайтных (в двухбайтных опкодах первый байт всегда одинаков и равен 0Fh). Т.е. получаем свыше 500 команд процессора (на самом деле, не все возможные опкоды используются в настоящее время; кроме того, некоторые опкоды могут иметь дополнительные поля в байтах для операндов и т.п., но это уже тонкости, которые мы можем пока опустить).
Пугаться этого не следует. На самом деле, для программирования под Windows требуется весьма ограниченный набор инструкций, и скоро мы сможем в этом убедиться. Мы будем изучать нужные нам инструкции по мере необходимости. А сейчас кратко рассмотрим «суть» программирования в машинных кодах, а она довольно проста.
Компьютер — это машина для обработки информации. Для этой цели вся информация, которую нужно обработать, делится на более-менее элементарные «кусочки». Необходимая обработка тоже подразделяется на более-менее элементарные действия. Элементарный «кусочек» обрабатываемой информации называется операндом, а элементарное действие — командой.
Таким образом, инструкция процессора представляет собой команду и связанные с ней операнды (которые, кстати, могут подразумеваться, а не быть явно заданными в инструкции). А сама программа представляет собой набор инструкций.
Все уже знают, что информация в компьютере представлена в виде двоичных чисел. Обычно в этом месте положено рассказывать об основах двоичного и шестнадцатеричного счислений и способах перевода чисел из одной формы представления в другую, но мы этого делать не будем. Во-первых, это несколько отвлекает от нашей непосредственной темы; во-вторых, кому надо, без труда найдет соответствующие сведения; а в-третьих, все это и так запомнится при практической работе. А если на первых порах будут проблемы, в Windows есть стандартное приложение — калькулятор, который можно использовать для перевода чисел из одной формы в другую. Только в меню «Вид» калькулятора установите «Научный», и в верхнем ряду слева увидите 4 кнопки-переключателя «Hex», «Dec», «Oct», «Bin», которыми и нужно пользоваться.
Windows сильно упрощает программирование — это относится к машинным кодам в значительно большей степени, чем к любому языку программирования на высоком уровне (обстоятельство, которое упускают из виду противники низкоуровневого программирования). Для программирования под Windows нам вполне достаточно рассматривать процессор, как обычный калькулятор.
В свое время был такой программируемый калькулятор — Б3-34. Он имел 14 регистров для хранения чисел. В процессоре тоже есть набор 32-разрядных регистров общего пользования, и их всего 8. На ассемблере их обозначают как EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI. Понятное дело, в машинных кодах никаких букв нет, и регистры кодируются тремя битами (в указанном выше порядке — от 000 до 111). Но в разговоре для удобства мы будем использовать и их «названия».
Еще одна особенность интеловских процессоров — они несут на себе «печать своего детства»: когда-то регистры были 16-разрядными, и именовались соответственно как AX, CX, DX, BX, SP, BP, SI, DI (причем с такими же кодами, как для 32-разрядных регистров). Еще раньше микропроцессоры были 8-разрядными, и регистров у них было поменьше; очевидно, это тоже оставило свой след, поскольку к четырем 16-разрядным регистрам (AX, CX, DX, BX) можно обращаться побайтно, т.е. отдельно к старшему и младшему байтам. Эти отдельно взятые байты четырех общих регистров обозначаются как AL (младший байт AX), CL, DL, BL, AH (старший байт AX), CH, DH, BH; а коды их тоже соответственно от 000 до 111 (совпадают со значениями для «полных» регистров.
На рисунке показано взаимоотношение адресуемых частей для регистра EAX; регистры ECX, EDX и EBX имеют подобную же схему. Регистры ESP, EBP, ESI и EDI «включают» в свой состав лишь 16-разрядные SP, BP, SI, DI и не допускают обращения к отдельным байтам.
Как же узнать, к какой именно части регистра происходит обращение, тем более, если коды регистров одни и те же (как в случае EAX, AX и AL)? Эта информация заложена в саму инструкцию. Многие опкоды имеют так называемый бит w, который указывает на размер используемого регистра (или операнда в целом): если он равен 0, это байт, если 1, «полный» регистр.
В 16-разрядном режиме бит w обозначает размер операнда 8 или 16 бит. Но современная Windows работает в 32-разрядном режиме, и состояние бита w обозначает размер операнда 8 или 32 бита. Обращение к 16 младшим битам регистра тоже возможно, но для этого используется другая схема с применением префиксов (об этом поговорим в другой раз).
Есть еще два регистра, с которыми придется иметь дело: это регистр флагов EFLAGS и указатель инструкций EIP. Состояние регистра флагов может меняться после каждой инструкции в зависимости от полученного результата; подробнее об этом поговорим в другой раз. Регистр EIP содержит адрес начала следующей инструкции в памяти. Его значение увеличивается каждый раз, когда из памяти извлекается для исполнения очередная инструкция, на величину размера этой инструкции.
Обрабатываемые инструкцией данные могут находиться не только в регистре, но и в памяти, а также входить в состав самой инструкции. При обращении к памяти в инструкции указывается адрес, по которому расположены данные. Рассмотрим различные способы доступа к данным на примере инструкции (а вернее, группы инструкций) перемещения данных, которыми мы будем очень активно пользоваться. На ассемблере группа данных инструкций обозначается мнемоникой MOV.
Начнем с команды, которая перемещает непосредственное значение (являющееся частью самой инструкции) в регистр общего назначения. Формат команды следующий:
1011 w reg gt
В зависимости от значения бита w за опкодом следует либо 1, либо 4 байта, содержащих непосредственное значение (и это значение попадет соответственно либо в 1-байтную часть регистра, либо заполнит регистр целиком).
В архитектуре IA-32 используется так называемый «little-endian» порядок следования байтов (его называют обратным): сначала (по младшим адресам в памяти) размещаются младшие байты числа. Т.е. 16-ричное ABCDh будет представлено как байты CDh ABh, а 12345678h — как 78h 56h 34h 12h. Подробнее об этом поговорим в следующей статье, а пока пример: загрузим в регистр EAX единицу. Регистр 000, бит w=1 (полный регистр), а данные — внимание — 4 байта для одной единицы!
10111000 00000001 00000000 00000000 00000000
Или в 16-ричном виде: B8 01 00 00 00. А вот как то же значение передается в младший байт регистра EAX (т.е. AL): регистр тот же — 000, бит w=0 (1 байт), а вот данные уже — 1 байт — 01:
10110000 00000001 (B0 01)
Обратите внимание — если в регистре EAX до этого содержался 0, последняя инструкция будет равносильна первой. Но в общем случае это не так.
Теперь эту же единицу загрузим в старший байт регистра AX (2-й байт EAX): тоже один байт (w=0), но код регистра AH уже другой (100):
10110100 00000001 (B4 01)
Удовольствие составления различных инструкций с данным опкодом оставим вам для самостоятельных упражнений и перейдем к другой команде, которая перемещает данные между памятью и регистром EAX (AX, AL):
101000 d w gt
Этот опкод содержит бит w, но не содержит кодов регистров, поскольку он предполагает работу лишь с регистром EAX (или его частью). Зато есть другой характерный бит — d (direction), указывающий направление перемещения данных — из памяти в регистр (0) или из регистра в память (1).
В этом примере мы видим одну важную особенность обращения к данным в памяти: размер операнда и размер его адреса в памяти — разные вещи. В данном случае операнд находится в памяти и может занимать 1, 2 или 4 байта, тогда как адрес (входящий в состав самой инструкции) в любом случае занимает 4 байта. Составим инструкцию для перемещения в регистр EAX значения, которое хранится по адресу 1. Используется полный регистр (w=1), направление — из памяти в регистр (d=0):
10100001 00000001 00000000 00000000 00000000 (A1 01 00 00 00)
А теперь то же значение загрузим в регистр AL (w=0, d=0):
10100000 00000001 00000000 00000000 00000000 (A0 01 00 00 00)
Изменился всего один бит инструкции! Между тем результат операции будет разительно отличаться: в первом случае в регистр EAX будут скопированы четыре (!) байта, начиная с адреса 1, тогда как во втором случае — в регистр AL будет скопирован лишь один байт по тому же адресу, остальные 3 байта регистра EAX останутся без изменений.
Архитектура IA-32 предоставляет очень богатый набор способов адресации памяти. Сейчас отметим лишь, что возможна еще и косвенная адресация, когда адрес операнда в памяти находится в регистре, а инструкция ссылается на соответствующий регистр. Для работы с такими случаями, а также для перемещения данных между регистрами используется так называемый байт способа адресации (ModR/M). Этот байт следует непосредственно за опкодом, который предполагает его использование, и содержит следующие поля:
2 бита MOD — 3 бита REG — 3 бита R/M
Байт ModR/M предполагает, что имеются два операнда, причем один из них всегда находится в регистре (код которого содержится в поле REG), а второй может находиться (в зависимости от значения поля MOD) либо тоже в регистре (при MOD = 11; при этом поле R/M содержит код регистра), либо в памяти (R/M=»register or memory»). В последнем случае адрес памяти, по которому находится операнд, вычисляется следующим образом (см. табл.):
000 | [EAX] | [EAX] + 1 байт смещения | [EAX] + 4 байта смещения |
001 | [ECX] | [ECX] + 1 байт смещения | [ECX] + 4 байта смещения |
010 | [EDX] | [EDX] + 1 байт смещения | [EDX] + 4 байта смещения |
011 | [EBX] | [EBX] + 1 байт смещения | [EBX] + 4 байта смещения |
100 | SIB | SIB + 1 байт смещения | SIB + 4 байта смещения |
101 | 4 байта смещения | [EBP] + 1 байт смещения | [EBP] + 4 байта смещения |
110 | [ESI] | [ESI] + 1 байт смещения | [ESI] + 4 байта смещения |
111 | [EDI] | [EDI] + 1 байт смещения | [EDI] + 4 байта смещения |
SIB означает, что после байта ModR/M следует еще один байт способа адресации (Scale-Index-Base — SIB), который мы рассматривать не будем. При MOD=00 нужный адрес памяти находится в соответствующем регистре, кроме R/M=101, когда 4 байта адреса следуют непосредственно после опкода и байта ModR/M (как в случае команды 101000dw). В ассемблере для указания того, что в регистре содержится адрес операнда, а не его значение, регистр заключают в квадратные скобки.
Если MOD=01, за байтом ModR/M следует байт, значение которого добавляется к значению соответствующего регистра и таким образом вычисляется адрес операнда. При MOD=10 за ModR/M следуют уже 4 байта; значение этого числа тоже суммируются со значением соответствующего регистра для вычисления адреса.
Присутствие байта ModR/M обычно требует также наличия битов d и w. Рассмотрим еще одну команду:
100010 d w
При d=0 данные перемещаются из регистра, закодированного в REG, в регистр или память, определяемые по R/M. При d=1 наоборот — из R/M в REG. Составим, например, инструкцию для копирования данных из EAX в EBX. Сначала «составим» байт ModR/M: оба операнда в регистрах, поэтому MOD=11; 1-й операнд в EAX — REG=000; 2-й операнд в EBX — R/M=011; итого — 11000011 (C3). Опкод: полные регистры — w=1; копирование от REG к R/M — d=0.
Итоговая инструкция — 10001001 11000011 (89 C3).
Теперь фишка: 1-й операнд в EBX (REG=011), 2-й — в EAX (MOD=11, R/M=000), бит d установим (1). Итог: 10001011 11011000 (8B D8) — но эта инструкция делает абсолютно то же самое, что и предыдущая! На ассемблере обе инструкции записываются одинаково: MOV EBX, EAX. Аналогичные примеры можно привести с инструкциями (A1 78 56 34 12) и (8B 05 78 56 34 12), (89 D7) и (8B FA) и т.д. Проверьте!
Да и сами вы теперь сможете составить кучу таких же. А что делают инструкции (88 E4) и (8A C9)?
Это характерная особенность работы с машинными кодами. Подобные этим трюки могут использоваться для создания защит и антиотладочных приемов. Между тем даже ассемблер генерирует для подобных команд лишь один вид кода, тем самым значительно вас обкрадывая, не говоря уже о компиляторах с языков высокого уровня.
Только не надо пугаться и думать, что при программировании в машинных кодах все время придется делать выбор из сотен возможных вариантов. На самом деле в Win32-программировании постоянно будут встречаться одни и те же инструкции, так что мы их помимо своей воли выучим наизусть. Хотя в этой статье оказалось много разнообразного материала, вы можете считать его одой свободе и богатству выбора, которую несут с собой машинные коды. В будущих статьях мы непременно сможем убедиться, насколько простым может быть программирование под Windows в машинных кодах, особенно если вы сумели уловить логику построения инструкций.
Источник: citforum.ru