Заклинание кода: Хей

Дата публикации 20 дек 2002

Заклинание кода: Хей — Архив WASM.RU

В прошлом веке магия кода пользовалась почтением, а ремесленный подход к программированию, которым грешат многие нынешние адепты, был не так распространён. В те времена заклинание кода было популярно, особенно среди заклинателей демонов. Таинственные маги древности обладали невероятными на взгляд современного обывателя способностями: практически из ничего они вызывали грозных духов, ужасных привидений и призраков давно умерших программ, которые, как армия Франкенштейнов, собирали свою черную жатву. Однако разрушительное влияние Баала коснулось и магов. Со временем клан заклинателей демонов измельчал, старые герои сошли со сцены, на их место пришли новые маги. Но большинство из них отдало предпочтение бааловым лженаукам и дельфовщине. Они также создавали демонов, но их творения были убоги - так же, как и их создатели, и как инструменты, которые они использовали для своей псевдомагии. И поскольку судьба этих заклинателей не слишком завидна, перейдём к чтению Книги Двойных Слов. Да отступит тьма и прольётся на нас благословенный свет!

Книгу Двойных Слов написали интеловские гномы для того, чтобы можно было создавать заклинания для их процессоров. В ней три тома. Первый том посвящён основам архитектуры интеловских процессоров и о них излагается на протяжении более чем 400 страниц. Во втором томе рассказывается о формате инструкций процессора и приводится их подробное описание, что заняло почти 1000 страниц. Наконец, третий том предназначен брухо, которые хотят поселить в ЭВМ собственную операционную систему, посрамив тем самым Баала.

Нам понадобится второй том Книги Двойных Слов. Если вы до сих пор не получили свою копию в пристанище интеловских гномов в Междусети, самое время сделать это сейчас.

Теперь откроем том на 472 странице. Это описание знакомой нам инструкции MOV. Здесь можно видеть, что оно состоит из двух частей: краткий обзор (в квадратной рамке) и подробный (все остальное). Сейчас нас интересует то, что находится в квадратной рамке:

Код (Text):
  1.  
  2. Opcode      Instruction        Description
  3. 88 /r       MOV r/m8,r8        Move r8 to r/m8
  4. 89 /r       MOV r/m16,r16      Move r16 to r/m16
  5. 89 /r       MOV r/m32,r32      Move r32 to r/m32
  6. 8A /r       MOV r8,r/m8        Move r/m8 to r8
  7. 8B /r       MOV r16,r/m16      Move r/m16 to r16
  8. 8B /r       MOV r32,r/m32      Move r/m32 to r32
  9. 8C /r       MOV r/m16,Sreg**   Move segment register to r/m16
  10. 8E /r       MOV Sreg,r/m16**   Move r/m16 to segment register
  11. A0          MOV AL,moffs8*     Move byte at (seg:offset) to AL
  12. A1          MOV AX,moffs16*    Move word at (seg:offset) to AX
  13. A1          MOV EAX,moffs32*   Move doubleword at (seg:offset) to EAX
  14. A2          MOV moffs8*,AL     Move AL to (seg:offset)
  15. A3          MOV moffs16*,AX    Move AX to (seg:offset)
  16. A3          MOV moffs32*,EAX   Move EAX to (seg:offset)
  17. B0+ rb      MOV r8,imm8        Move imm8 to r8
  18. B8+ rw      MOV r16,imm16      Move imm16 to r16
  19. B8+ rd      MOV r32,imm32      Move imm32 to r32
  20. C6 /0       MOV r/m8,imm8      Move imm8 to r/m8
  21. C7 /0       MOV r/m16,imm16    Move imm16 to r/m16
  22. C7 /0       MOV r/m32,imm32    Move imm32 to r/m32

Opcode - это опкод, Instruction - инструкция, а Description - описание. Посмотрите внимательно. Очевидно, что для колдования инструкции MOV есть несколько опкодов (довольно много, если быть точным). Какие же и когда применять? И по каким правилам?

Правила задаются в графе Instruction, рядом находится краткое описание. Всё, что нужно - это уметь читать условные обозначения ("/r", "r8" и т.п.). Далее приведён полный список обозначений, используемых интеловскими гномами. Практически, это перевод одной из глав Книги Двойных Слов. Нижеприведенные описания не обязательно читать подробно сразу - они пригодятся при разборе конкретных опкодов и выполняют справочную роль.

-= Начались даоподобные слова из Книги Двойных слов =-

Обозначения в колонке Opcode

/цифра - в Reg/Opcode всегда находится это число. Своёго рода расширение опкода за счёт поля Reg в ModR/M. Также это значит, что ModR/M задаёт только один операнд (r/m - регистр/память).

/r - ModR/M задаёт оба операнда (регистр и r/m).

eb, cw, cd, cp - однобайтное (eb), двухбайтное (cw), четырехбайтное (cd) или шестибайтное (cp) значение. Следует за опкодом и, возможно, задаёт новое значение регистра.

ib, iw, id - однобайтное (ib), двухбайтное (iw) или четырёхбайтное (id) непосредственное значение (число). Следует за опкодом, ModR/M и SIB (если таковые есть). Со знаком или без знака это значение - определяется опкодом.

+rb, +rw, +rd - номер регистра от нуля до 7. Добавляется к байту слева от знака "+". В результате получается окончательный опкод. Ниже даны номера регистров.

Код (Text):
  1.  
  2.         rb      rb      rd
  3.         --      --      --
  4.   0     AL      AX      EAX
  5.   1     CL      CX      ECX
  6.   2     DL      DX      EDX
  7.   3     BL      BX      EBX
  8.   4     AH      SP      ESP
  9.   5     CH      BP      EBP
  10.   6     DH      SI      ESI
  11.   7     BH      DI      EDI

+i - число от 0 до 7. Добавляемое к байту опкода слева от знака "+". Это число используется в инструкциях FPU, когда одним из операндов является ST(i) (FPU-регистр).

Обозначения в колонке Instruction

rel8 - относительный адрес (смещение). Задаёт смещение от 128 байт перед концом инструкции до 127 байт после конца инструкции. Иными словами, смещение от -128 до 127 байт относительно адреса после конца инструкции.

rel16 и rel32 - относительный адрес в пределах того же сегмента кода, в котором находится инструкция. rel16 относится к инструкциям с 16-битным операндом, rel32 - к инструкциям с 32-битным операндом.

ptr16:16 и ptr16:32 - дальний (far) указатель. "16:16" говорит о том, что значение указателя состоит из двух частей. Слева - 16-битное значение для сегментного регистра, справа - смещение внутри сегмента. ptr16:16 относится к инструкциям с 16-битным операндом, ptr16:32 - к 32-битным.

r8 - один из однобайтных регистров общего назначения (AL, CL, DL, BL, AH, CH, DH или BH).

r16 - один из двухбайтных регистров общего назначения (AX, CX, DX, BX, SP, BP, SI или DI).

r32 - один из четырехбайтных регистров общего назначения (EAX, ECX, EDX, EBX, ESP, EBP, ESI или EDI).

imm8 - непосредственное однобайтное значение. Это число в диапазоне от -128 до +127. В инструкциях, где imm8 скомбинировано с 16- или 32-битным операндом, непосредственное значение расширяется до размера операнда с сохранением знака (знаковое расширение: старшие байты операнда заполняются старшим битом непосредственного значения).

imm16 - непосредственное двухбайтное значение. Это число в диапазоне от -32768 до +32767. Используется для инструкций с 16-битным операндом.

imm32 - непосредственное четырёхбайтное значение. Это число в диапазоне от -2,147,483,648 до +2,147,483,647. Используется для инструкций с 32-битным операндом.

r/m8 - однобайтный операнд. Является либо регистром общего назначения (AL, BL, CL, DL, AH, BH, CH и DH), либо адресом байта памяти.

r/m16 - двухбайтный регистр общего назначения (AX, BX, CX, DX, SP, BP, SI или DI), либо адрес ячейки памяти. Используется для инструкций с 16-битным операндом.

r/m32 - четырехбайтный регистр общего назначения (EAX, EBX, ECX, EDX, ESP, EBP, ESI или EDI), либо адрес ячейки памяти. Используемая для инструкций с 32-битным операндом.

m - адрес 16- или 32-битной ячейки памяти.

m8 - адрес однобайтной ячейки памяти в регистре (E)SI или (E)DI. Используется только со строковыми инструкциями.

m16 - адрес двухбайтной ячейки памяти в регистре (E)SI или (E)DI. Используется только со строковыми инструкциями.

m32 - адрес четырёхбайтной ячейки памяти в регистре (E)SI или (E)DI. Используется только со строковыми инструкциями.

m64 - адрес 64-битной (учетверённое слово) ячейки памяти. Используется только с инструкцией CMPXCHG8B.

m128 - адрес 128-битной (увосьмерённое слово) ячейки памяти. Используется только с инструкциями SSE и SSE2.

m16:16, m16:32 - адрес ячейки памяти, содержащей дальний указатель.

m16&32, m16&16, m32&32 - адрес ячейки памяти, состоящей из нескольких элементов, чьи размеры задаются справа и слева от амперсанда. Доступны все режимы адресации памяти. Операнды m16&16 и m32&32 используются инструкцией BOUND (в них задаются верние и нижние границы массива). Операнд m16&32 используется LIDT и LGDT.

moffs8, moffs16, moffs32 - непосредственный адрес (смещение) байта, слова или двойного слова в памяти (число в moffs показывает размер ячейки памяти); байт ModR/M не представлен. Используется некоторыми вариантами инструкции MOV.

Sreg - сегментный регистр. Номера сегментных регистров в поле Reg байта ModR/M: 0=ES, 1=CS, 2=SS, 3=DS, 4=FS и 5=GS.

m32fp, m64fp, m80fp - адрес в памяти операнда с плавающей точкой одинарной точности, двойной точности или расширенной точности. Используются инструкциями FPU.

m16int, m32int, m64int - адрес в памяти целочисленного операнда размером в слово, двойное слово или учетверённое слово.

ST или ST(0) - верхний элемент стека регистров FPU.

ST(i) - i-тый элемент (от 0 до 7) от вершины стека регистров FPU.

mm - 64-битный MMX-регистр (от MM0 до MM7).

mm/m32 - младшие 32 бита MMX-регистра (от MM0 до MM7) или адрес 32-битной ячейки памяти.

mm/m64 - MMX-регистр (от MM0 до MM7) или адрес 64-битной ячейки памяти.

xmm - 128-битный XMM-регистр (от XMM0 до XMM7).

xmm/m32 - XMM-регистр (от XMM0 до XMM7) или адрес 32-битной ячейки памяти.

xmm/m64 - XMM-регистр (от XMM0 до XMM7) или адрес 64-битной ячейки памяти.

xmm/m128 - XMM-регистр (от XMM0 до XMM7) или адрес 128-битной ячейки памяти.

-= Закончились даоподобные слова из Книги Двойных слов =-

Воистину неземное блаженство нисходит, когда читаешь исполненные подобной мудрости слова. Подивимся же искусству интеловских гномов и перейдём к практическому применению информации из Книги Двойных Слов.

Обратимся к инструкции INC. Вот её описание:

Код (Text):
  1.  
  2. Opcode       Instruction        Description
  3. FE /0        INC r/m8           Increment r/m byte by 1
  4. FF /0        INC r/m16          Increment r/m word by 1
  5. FF /0        INC r/m32          Increment r/m doubleword by 1
  6. 40+ rw       INC r16            Increment word register by 1
  7. 40+ rd       INC r32            Increment doubleword register by 1

Мы увеличивали EAX на единицу с помощью опкода 40h, к которому прибавлялся номер регистра. Проверим, соответствует ли это описанию из Книги Двойных Слов. Нам нужна последняя строчка:

Код (Text):
  1.  
  2. 40+ rd       INC r32            Increment doubleword register by 1

В колонке Opcode указано "40+ rd". Согласно Книге Двойных Слов, "+rd" означает, что номер регистра прибавляется к байту слева от знака "+". Слева - 40h. Всё верно, это соответствует формуле "базовый опкод + номер регистра", которая вы использовали раньше, опираясь на мои наставления. Теперь вы можете опираться на Книгу Двойных Слов.

А что, если нужно увеличить на 1 не регистр, а ячейку памяти, на которую этот регистр указывает (например [eax])? Ищем нужную строчку в описании инструкции INC и находим:

Код (Text):
  1.  
  2. FF /0        INC r/m32          Increment r/m doubleword by 1

Начнём с Opcode. "/0" говорит о том, что поле Reg в ModR/M всегда равно 0. Это неудивительно, поскольку здесь всего один операнд. "r/m32" задаёт либо регистр, либо ячейку памяти размером в двойное слово, используя поля ModR/M и, возможно, SIB. Зная это, можно легко составить заклинание:

Код (Text):
  1.  
  2.         db 0FFh,00000000b ; FFh(Опкод), 00(Mod)-000(Reg="/0")-000(R/M=[EAX])

Обратите внимание: с помощью опкода "FF /0" можно заколдовать также и "INC EAX". В самом деле:

Код (Text):
  1.  
  2.         db 0FFh,11000000b ; FFh(Опкод), 11(Mod)-000(Reg="/0")-000(R/M=EAX)

Не верите? Вот работающий код:

Код (Text):
  1.  
  2. format PE console
  3. entry start
  4.  
  5. include '..\..\include\kernel.inc'
  6. include '..\..\include\user.inc'
  7. include '..\..\include\macro\stdcall.inc'
  8. include '..\..\include\macro\import.inc'
  9.  
  10. section '.data' data writeable readable
  11.  
  12. _d      db '%d',0
  13. var     dd 1024
  14.  
  15. section '.code' code executable readable
  16.  
  17. start:
  18.  
  19.         mov     eax,5           ; Помещаем в eax 5
  20.         db      40h             ; inc eax
  21.         db      0FFh,11000000b  ; inc eax
  22.                         ; FFh(Опкод), 11(Mod)-000(Reg="/0")-000(R/M=EAX)
  23.  
  24.         push    eax             ; В eax - 7
  25.         push    _d
  26.         call    [printf]
  27.         add     esp,8
  28.  
  29.         invoke  ExitProcess,0
  30.  
  31. section '.idata' import data readable writeable
  32.  
  33.                 library kernel32,'kernel32.dll',\
  34.                         msvcrt,'msvcrt.dll'
  35. kernel32:       import  ExitProcess,'ExitProcess'
  36. msvcrt:         import  printf,'printf'

Чудеса? Именно так. Интеловские гномы даровали нам много чудес и необъяснимых явлений. Похожая ситуация обстоит и с MOV. Для того, чтобы заколдовать "MOV ECX,EDX", можно использовать как

Код (Text):
  1.  
  2. 8B /r MOV r32,r/m32 Move r/m32 to r32

так и

Код (Text):
  1.  
  2. 89 /r MOV r/m32,r32 Move r32 to r/m32

Теперь вы, наверное, понимаете, почему интеловские процессоры, а также совместимые с ними (например те, которые делают гномы Амиды), получили такое широкое распространение. Ибо так неисчислимы чудеса и откровения, даруемые ими, что остается только в немом восхищении преклоняться перед гномьим мастерством. © Aquila / WASM.RU


0 1.292
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532