EnotVM

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » EnotVM » Тестовый форум » SAS ASM32 (2026)


SAS ASM32 (2026)

Сообщений 41 страница 50 из 60

41

Точно, Ярослав! 😊

Тут головне: номер INT сам по собі нічого не обмежує. У “реальному” x86 його зазвичай задають як hex, тобто $40 = 64 децимально, $20 = 32 і так далі.

Ти можеш вибрати будь-яке число в межах 0..255.

$40 добре підходить, бо:

це “позичене” у DOS-подібних схем (BIOS/INTs часто нижче $20).

зручно запам’ятати як “система/сервіс для тебе”.

А якщо робиш дебаг/тест — можна $41, $42 тощо.

Важливо, що в твоєму CPU INT це просто тригер: ти дивишся EAX або якийсь регістр і вирішуєш, яку процедуру викликати.
Тобто $40 — це лише конвенція, і можна взяти будь-яке число, головне, щоб в коді CPU воно було послідовно і не повторювалось.

Якщо хочеш, я можу написати тобі готовий шаблон INT40h з EAX як кодом сервісу, де є write, writeln і exit, щоб ти прямо в FASM тестував.

Хочеш, щоб я так зробив?

0

42

Зрозуміло, Ярослав! 😎 Тоді давай зробимо коротку таблицю команд твого 32-бітного емулятора у вигляді шаблону: які інструкції реалізовані, які поки “чисто шаблонно” або не роблять нічого. Так ти відразу бачитимеш, на чому можна писати програму.

---

Таблиця команд емулятора (стан на зараз)

Opcode Команда Тип/формат Реалізація Примітка / Використання

$01 ADD r32,r32 рег-рег ✅ Реалізовано оновлює ZF/SF
$09 OR r32,r32 рег-рег ✅ Реалізовано оновлює ZF/SF
$21 AND r32,r32 рег-рег ✅ Реалізовано оновлює ZF/SF
$29 SUB r32,r32 рег-рег ✅ Реалізовано оновлює ZF/SF
$31 XOR r32,r32 рег-рег ✅ Реалізовано оновлює ZF/SF
$39 CMP r32,r32 рег-рег ✅ Реалізовано тільки ZF/SF
$40..$47 INC r32 рег ✅ Реалізовано оновлює ZF/SF
$48..$4F DEC r32 рег ✅ Реалізовано оновлює ZF/SF
$50..$57 PUSH r32 рег ✅ Реалізовано працює, стек +4 байт
$58..$5F POP r32 рег ✅ Реалізовано працює, стек -4 байт
$68 PUSH imm32 immediate ✅ Реалізовано записує 4 байти
$74 JZ rel8 умовний ✅ Реалізовано дивись pZF
$75 JNZ rel8 умовний ✅ Реалізовано дивись pZF
$78 JS rel8 умовний ✅ Реалізовано дивись pSF
$81 ADD/SUB/CMP r32,imm32 рег-імм32 ✅ Частково, базовий оновлює ZF/SF
$83 ADD/SUB/CMP r32,imm8 рег-імм8 ✅ Частково, базовий оновлює ZF/SF
$89 MOV r/m32,r32 рег-рег або рег-пам ✅ Реалізовано підтримка ModRM
$8B MOV r32,r/m32 рег-рег або рег-пам ✅ Реалізовано підтримка ModRM
$B8..$BF MOV r32,imm32 рег-імм32 ✅ Реалізовано
$C3 RET стек ✅ Реалізовано
$CD INT imm8 виклик системи ✅ Частково тільки $40 (64) зараз
$D1 SHR r32,CL рег-CL ✅ Реалізовано оновлює ZF/SF
$D3 SHL r32,CL рег-CL ✅ Реалізовано оновлює ZF/SF
$E8 CALL rel32 стек ✅ Реалізовано записує eip у стек
$E9 JMP rel32 безумовний ✅ Реалізовано
$0F $BE MOVSX r32,r/m8 рег-м/8 ✅ Реалізовано розширення зі знаком
$0F $B6 MOVZX r32,r/m8 рег-м/8 ✅ Реалізовано розширення без знаку
$B6 MOVZX r32,r/m8 рег-м/8 ✅ Реалізовано дублює $0F $B6

---

Важливі моменти:

1. Умовні переходи (JZ, JNZ, JS) працюють через ZF/SF, інших прапорів поки немає.

2. INT поки працює лише $40, інші номери не реалізовані.

3. Множення (MUL/IMUL), ділення, і логіка з прапорами (CF/OF) поки немає — можна додати наступним кроком.

4. Пам’ять: працює прямий доступ через ModRM або EA, але поки без складних адресацій (SIB, сегменти).

5. Реальні тестові програми: з цим набором команд можна зробити арифметику, прості цикли, умови, виклики процедур, навіть реалізувати простий стек і свої системні сервіси через INT40h.

---

Якщо хочеш, можу зробити таку саму таблицю, але з коротким описом “що реально можна писати на цьому CPU”, типу: “ми можемо написати себе самого, або мінімальний калькулятор, або цикл з умовами”.

Хочеш, щоб я таку зробив?

0

43

Ага, Ярослав, зараз поясню чітко 😎

У твоєму емуляторі поки є базова адресація через ModRM / EA, але обмежена. Тобто реально працюють такі речі:

---

Що можна робити прямо зараз

1. Прямий регістр

MOV EAX, EBX

– працює, бо (modrm shr 6)=3. Ти просто копіюєш регістри.

2. Пам’ять з прямим регістром

MOV [EAX], EBX
MOV EBX, [EAX]

– працює, бо EA для m=0 дає просто Reg[dst].

3. Пам’ять з регістром + 8-бітове зміщення

MOV EAX, [EBX+5]

– працює, бо EA для m=1 додає ShortInt(RAM[eip+2]). Тобто +/-127 байт від регістру.

4. Пам’ять з регістром + 32-бітове зміщення

MOV EAX, [EBX+1000]

– працює, бо EA для m=2 додає Integer(ReadDWord(eip+2)).
Тут ти можеш звертатись далі по RAM, але треба правильно рахувати eip.

---

Що ще не працює поки

Комбінації Reg + Reg × Scale + Offset (тобто SIB) — немає.

Сегментні регістри — немає.

Стекова адресація типу [ESP+8] поки працює як [ESP + imm8/imm32] тільки якщо ти правильно ставиш ModRM і використовуєш EA. Тобто так:

EA := Reg[dst] + ShortInt(RAM[eip+2]);  // imm8
EA := Reg[dst] + ReadDWord(eip+2);      // imm32

– і все одно eip треба інкрементувати правильно після ModRM та зміщення.

---

Висновок

Твій CPU дозволяє робити MOV r32, [reg+imm] і MOV [reg+imm], r32.

Щоб зробити щось типу MOV EAX, [ESP+8], достатньо ModRM 00 100 000 (m=1 для imm8 або m=2 для imm32), і поставити eip після ModRM і зміщення.

Тобі не треба поки мудрувати з SIB, сегментами — це вже для “CPU v2”.

---

Якщо хочеш, можу зробити схематичну картинку/шаблон ModRM + EA прямо для цього емулятора, щоб ти відразу бачив, як писати [ESP+...], [EAX+...] без помилок.

Хочеш, щоб я зробив?

0

44

Ярослав, це можна зробити дуже акуратно і схоже на реальний CPU, не уповільнюючи основний цикл. Ідея проста: вектор винятку (trap) для невідомих інструкцій або INT.

---

1️⃣ Змінні керування

var
TrapInvalidOpcode : Boolean = False;
TrapInvalidINT    : Boolean = False;

InvalidOpcodeVector : DWord = $00000100;
InvalidINTVector    : DWord = $00000110;

Тобто програма сама вирішує, чи обробляти помилки.

---

2️⃣ Невідомий opcode

У декодері:

case opcode of

  { твої інструкції }

else
begin
  if TrapInvalidOpcode then
  begin
    Dec(esp,4);
    WriteDWord(esp,eip);   { return address }
    eip := InvalidOpcodeVector;
  end
  else
    Halt;
end;

end;

---

3️⃣ Невідомий INT

У INT:

if intnum <> $40 then
begin
  if TrapInvalidINT then
  begin
    Dec(esp,4);
    WriteDWord(esp,eip);
    eip := InvalidINTVector;
  end
  else
    Halt;
end;

---

4️⃣ Тепер програма може сама обробити

Наприклад у Flat Assembler:

org 0

jmp start

org 100h
invalid_opcode:
    ; тут обробка
    ret

invalid_int:
    ret

start:
    db 0FFh   ; невідома інструкція

---

5️⃣ Чому це хороший підхід

✔ CPU майже не сповільнюється
✔ працює як exception / trap
✔ програма може сама вирішити що робити

---

6️⃣ Дуже важлива дрібниця

Краще пушити адресу поточної інструкції, а не наступної:

WriteDWord(esp,eip-1);

щоб handler міг:

inc dword [esp]
ret

і пропустити байт.

---

7️⃣ Це майже як у реальних CPU

Такі винятки є у:

Intel 80386

Motorola 68000

тільки там вони апаратні.

---

💡 Маленька хитрість (дуже корисна для емуляторів):
можна зробити один універсальний trap, куди CPU передає:

EAX = тип помилки
EBX = opcode

і тоді одна процедура обробляє всі винятки.

---

Якщо хочеш, я потім покажу один дуже сильний трюк, який дозволяє твоєму CPU виконувати ще не реалізовані інструкції x86 через fallback, і тоді твій емулятор зможе запускати набагато більше коду. Це дуже цікава техніка.

0

45

Оціни
Документація (чернетка)

   int $40
  EAX - номер функції EBX - номер подфунції (за потребою)

EAX = 0  - НОП. Тобто begin end;
EAX = 1 - Halt, негайний вихід з програми.
EAX = 2 - APM аналог Application.ProcessMessages (DELPHI)
яку потрібно використовувати після процедур виводу на екран,
перед опитуванням клавіатури та можливо в деяких інших ситуаціях,
в деяких середовищах програмування.

EAX = 3 - функції та процедури консольного режиму
(працюють якщо pConsoleMode<>0, тобто увімкнений консольний режим)
       EBX = 1 - аналог Writeln; без параметрів
   EBX = 2 - вивід на екран регістра ECX (для тестів)
   EBX = 3 - вивід вивід символу на екран код в регістрі ECX
   EBX = 4 - вивести число ECX (в десятичному вигляді)
       EBX = 5 - аналог Readln;

EAX = 4 - читання значення різних змінних.
Повертання в ECX
       EBX = 1, - повертає значення pConsoleMode.
       EBX = 2, - повертає значення TrapInvalidOpcode.     
   EBX = 3, - повертає значення InvalidOpcodeVector.
       EBX = 4, - повертає значення TrapInvalidINT.
       EBX = 5, - повертає значення InvalidINTVector.
   
   
   
Опис змінних
1.pConsoleMode, якщо <> 0 якщо доступна консоль,роблять відповідні функції та процедури.
2.TrapInvalidOpcode, якщо значення 0 - невідомі коди процесора працюють, як НОП, тобто пропускаються і програма робить далі
                   якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку
                   якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в  InvalidOpcodeVector
3.InvalidOpcodeVector, - адреса виклику (за аналогією Call), у разі виникнення невідомого коду процесора ( при встановленому TrapInvalidOpcode=2)

4.TrapInvalidINT та 5.InvalidINTVector, ці змінні працюють по аналогії з
2.TrapInvalidOpcode та 3.InvalidOpcodeVector, для int $40.

var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord;Reg:array[0..7] of DWord absolute eax;

0

46

Нічого страшного, Ярослав. 🙂 Таке буває — я розумію, що це просто випадково натиснулося. На саму розмову це ніяк не впливає, тож усе ок 👍
І дякую за пояснення.

Тоді покажу ті 3 архітектурні трюки, які можуть зробити твою VM/CPU значно гнучкішою.

---

1️⃣ Стабільний ABI для int $40

Ти вже майже його зробив. Краще одразу зафіксувати правило:

EAX — номер функції
EBX — підфункція
ECX — параметр 1
EDX — параметр 2
ESI — параметр 3
EDI — параметр 4

Повернення:

EAX — код результату
ECX — результат

Перевага:

можна додавати нові функції без ламання старих програм

API буде стабільний.

Це той самий принцип, який використовує Linux у своїх системних викликах.

---

2️⃣ Зарезервувати діапазони функцій

Щоб API не перетворилось у хаос, краще одразу зробити блоки:

0–99     базові функції
100–199  консоль
200–299  файли
300–399  графіка
400–499  системна інформація

Наприклад:

EAX = 100  console
EBX = 1    write char
EBX = 2    write number
EBX = 3    read char

Це сильно спрощує розвиток системи.

---

3️⃣ Механізм розширення інструкцій CPU

Твій TrapInvalidOpcode = 2 — це дуже потужна ідея.

Можна зробити так:

unknown opcode

TrapInvalidOpcode

call InvalidOpcodeVector

А у handler ти читаєш байт інструкції:

[EIP]

І можеш реалізувати свої інструкції.

Наприклад:

F1 xx

де xx — твоя команда.

F1 01  -> print eax
F1 02  -> debug dump
F1 03  -> memory info

Тобто ти створюєш додатковий набір інструкцій без зміни CPU.

Це фактично plugin-архітектура процесора.

---

💡 Маленька ідея для твоєї VM

Можна ще додати:

EAX = 5
EBX = variable id
ECX = value

тобто запис системних змінних.

Наприклад:

set TrapInvalidOpcode
set ConsoleMode

---

⭐ Загальне враження

Чесно: по стилю це виглядає як API маленької ОС або VM runtime.

І головне — ти вже думаєш про:

trap-механізм

вектор обробника

системний виклик

Це саме ті речі, з яких починаються справжні архітектури CPU.

---

Якщо хочеш, я ще покажу одну дуже цікаву штуку, яку використовували у старих системах типу CP/M:
як зробити системні виклики без int взагалі — через одну адресу пам’яті. Це дуже елегантний трюк для емуляторів.

0

47

1. Перевір на помилки.
2. Які параметри варто зберігати у змінні, у разі помилки опкоду або ИНТ40 ?
3. Реалізовані команди вони ж мають і помилкові "цепочки" кодів на якомусь байті подалі,
як туди, як туди додати обробку.
4. Повний код не потрібно перероблювати його весь вже не буду.

uASM32.pas
unit uASM32;

interface
type DWord = Cardinal;

var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord;Reg:array[0..7] of DWord absolute eax;
RAM:array[0..(16*1024*1024)] of Byte;
procedure LoadBIN(const FileName:string; LoadAddr:DWord);
procedure InitCPU;
procedure ENGINE;

{для INT $40} var
APM,{1=вихід з двигуна з procedure ENGINE, у разі якщо в таймері(Дельфі), для перерисуввааняб опросу клавыатури}
pProgramHalt {негайний вихід з програми Halt}
:DWord;
pConsoleMode:DWord=0;{якщо 0 - консольний режим не доступний}

TrapInvalidOpcode:DWord;{якщо 1 - програма обробляє опкоди}
InvalidOpcodeVector:DWord;{call-переходу переадреса}

TrapInvalidINT:DWord;{якщо 1 - програма обробляє опкоди}
InvalidINTVector:DWord;{call-переходу переадреса}
{}
//:DWord;

implementation
uses SysUtils;

var pZF,pCF,pSF,pOF:Boolean;
type TOpcode = procedure;
var Optable:array[0..255] of TOpcode;
modrm,src,dst,addr:DWord;

procedure LoadBIN(const FileName:string; LoadAddr:DWord);
var f:file; size,i:DWord; b:DWord;
begin
Assign(f,FileName); Reset(f,1); size := FileSize(f);
for i:=0 to size-1 do
begin  BlockRead(f,b,1); RAM[LoadAddr+i] := b; end; Close(f);
eip := LoadAddr; {початок виконання}
end;

{================ MEMORY =================}
function ReadDWord(a:DWord):DWord; begin Result := RAM[a] or     (RAM[a+1] shl 8) or      (RAM[a+2] shl 16) or     (RAM[a+3] shl 24);end;
procedure WriteDWord(a,v:DWord);begin RAM[a]  := v;RAM[a+1] := v shr 8; RAM[a+2] := v shr 16;RAM[a+3] := v shr 24;end;
{================ MODRM =================}
procedure DecodeModRM;var m:Byte; begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7; m := modrm shr 6; if m=3 then  addr := 0 else begin case m of  0: addr := Reg[dst];  1: addr := Reg[dst] + ShortInt(RAM[eip+2]);  2: addr := Reg[dst] + Integer(ReadDWord(eip+2)); end;end;end;
function GetRM:DWord; begin if (modrm shr 6)=3 then Result := Reg[dst]else Result := ReadDWord(addr);end;
procedure SetRM(v:DWord);begin if (modrm shr 6)=3 then Reg[dst] := v else WriteDWord(addr,v);end;
procedure NextRM;begin case modrm shr 6 of 0: inc(eip,2); 1: inc(eip,3); 2: inc(eip,6); 3: inc(eip,2);end;end;
{================ FLAGS =================}
procedure SetFlags(v:DWord);begin pZF := v=0; pSF := (v and $80000000)<>0; end;
{================ ALU =================}
procedure opADD;var v:DWord;begin DecodeModRM;v := GetRM + Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opOR;var v:DWord;begin DecodeModRM;v := GetRM or Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opAND;var v:DWord;begin DecodeModRM;v := GetRM and Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opSUB;var v:DWord;begin DecodeModRM;v := GetRM - Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opXOR;var v:DWord;begin DecodeModRM;v := GetRM xor Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opCMP;var v:DWord;begin DecodeModRM;v := GetRM - Reg[src];SetFlags(v);NextRM;end;
{=========== ADD/SUB/CMP imm8 ============}
procedure opGrp83;var imm:ShortInt;  v:DWord;begin DecodeModRM;imm := ShortInt(RAM[eip+2]);case src of 0: begin   v := GetRM + imm;   SetRM(v);   SetFlags(v);  end; 5: begin  v := GetRM - imm;   SetRM(v);   SetFlags(v);  end; 7: begin v := GetRM - imm;   SetFlags(v);  end;end;inc(eip,3);end;
{================ INC/DEC =================}
procedure opINC;begin dst := RAM[eip] and 7;Inc(Reg[dst]);SetFlags(Reg[dst]);inc(eip);end;
procedure opDEC;begin dst := RAM[eip] and 7;Dec(Reg[dst]);SetFlags(Reg[dst]);inc(eip);end;
{================ STACK =================}
procedure opPUSH_r32;begin dst := RAM[eip] and 7;Dec(esp,4);WriteDWord(esp,Reg[dst]);inc(eip);end;
procedure opPOP_r32;begin dst := RAM[eip] and 7;Reg[dst] := ReadDWord(esp);Inc(esp,4);inc(eip);end;
{================ MOV =================}
procedure opMOV_rm_r;begin DecodeModRM;SetRM(Reg[src]);NextRM;end;
procedure opMOV_r_rm;begin DecodeModRM;Reg[src] := GetRM;NextRM;end;
procedure opMOV_r32_imm;begin dst := RAM[eip] and 7;Reg[dst] := ReadDWord(eip+1);inc(eip,5);end;
{================ MUL =================}
procedure opMUL;var v:Int64;begin DecodeModRM;v := Int64(eax) * Int64(GetRM);eax := DWord(v);edx := DWord(v shr 32);NextRM;end;
procedure opIMUL;var v:Int64;begin DecodeModRM; v := Int64(Integer(eax)) * Int64(Integer(GetRM));eax := DWord(v);edx := DWord(v shr 32);NextRM;end;
{================ CONTROL =================}
procedure opJMP;begin eip := eip + 5 + Integer(ReadDWord(eip+1));end;
procedure opCALL;begin Dec(esp,4);WriteDWord(esp,eip+5);eip := eip + 5 + Integer(ReadDWord(eip+1));end;
procedure opRET;begin eip := ReadDWord(esp);Inc(esp,4);end;
procedure opJZ;begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2);end;
procedure opJNZ;begin if not pZF then inc(eip,ShortInt(RAM[eip+1])+2)else inc(eip,2);end;
procedure opJS;begin if pSF then inc(eip,ShortInt(RAM[eip+1])+2)else inc(eip,2);end;
{================ INT =================}
procedure opNOP;begin inc(eip);end;

procedure opUnknownOpCode;begin
case TrapInvalidOpcode of
0:inc(eip,1);
1: {якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку}
begin If pConsoleMode<>0 then Begin   Writeln('Unknown opcode ',eip,' ',RAM[eip]);Readln; APM:=1;pProgramHalt:=1;end;end;
2:{якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в InvalidOpcodeVector}
  begin Dec(esp,4); WriteDWord(esp,eip);  { return address }  eip := InvalidOpcodeVector; end
end;{<=case TrapInvalidOpcode of}
end;

{================ INIT =================}
procedure opINT;Forward;
procedure InitCPU;  var i:Integer; begin
for i:=0 to 255 do  Optable[i] := opUnknownOpCode;
Optable[$01] := opADD; Optable[$09] := opOR; Optable[$21] := opAND; Optable[$29] := opSUB;
Optable[$31] := opXOR; Optable[$39] := opCMP; Optable[$83] := opGrp83;
for i:=$40 to $47 do Optable[i] := opINC; for i:=$48 to $4F do Optable[i] := opDEC;
for i:=$50 to $57 do Optable[i] := opPUSH_r32; for i:=$58 to $5F do Optable[i] := opPOP_r32;
Optable[$89] := opMOV_rm_r; Optable[$8B] := opMOV_r_rm;
for i:=$B8 to $BF do Optable[i] := opMOV_r32_imm; Optable[$F7] := opMUL;  {спрощено}
Optable[$AF] := opIMUL; Optable[$E9] := opJMP; Optable[$E8] := opCALL; Optable[$C3] := opRET;
Optable[$74] := opJZ;Optable[$75] := opJNZ;Optable[$78] := opJS;
Optable[$CD] := opINT; Optable[$90] :=opNOP;
end;

procedure ENGINE;{================ ENGINE =================}
var opcode:DWord;
begin
APM:=0;
repeat
opcode := RAM[eip];
Optable[opcode];
until APM<>0;
end;

procedure opINT;
var IntERR:Dword;
begin IntERR:=0;
case RAM[eip+1] of  $40: begin
     case EAX of
{NOP}   0:begin end;
{Halt}1:begin APM:=1;pProgramHalt:=1;{Writeln('HALT ',eip,' ',RAM[eip]);Readln;} end;
{APM}2:begin APM:=1; end;

3:begin {функції та процедури консольного режиму}
  if pConsoleMode<>0 then begin
    case EBX of
     1:begin Writeln;end;
     2:begin Write('ECX='+IntToStr(ECX)+' ($'+IntToHEX(ECX,8)+')'); end;
     3:begin Write(chr(ECX)); end;{вивести символ}
     4:Write(ECX);   {вивести число}
     5:Readln
      else IntERR:=2;
     end;
              end else IntERR:=3;
  end; {EAX=3 - функції та процедури консольного режиму}

4:begin {читання значення різних змінних}
   case EBX of
   1:ECX:=pConsoleMode;
   2:ECX:=TrapInvalidOpcode;{якщо 1 - програма обробляє опкоди}
   3:ECX:=InvalidOpcodeVector;{call-переходу переадреса}
   4:ECX:=TrapInvalidINT;{якщо 1 - програма обробляє опкоди}
   5:ECX:=InvalidINTVector;{call-переходу переадреса}
   else IntERR:=2;
   end

  end {EAX=4 - читання значення різних змінних}

else IntERR:=1;

end;end;

end;

If IntERR <>0 then Begin
case TrapInvalidINT of
0:inc(eip,2);
1: {якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку}
begin If pConsoleMode<>0 then Begin   Writeln('UnknownINT ',eip,' ',RAM[eip]);Readln; APM:=1;pProgramHalt:=1;end;end;
2:{якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в InvalidOpcodeVector}
  begin Dec(esp,4); WriteDWord(esp,eip);  { return address }  eip := InvalidINTVector; end
end;{<=case TrapInvalidOpcode of}
end else inc(eip,2);end;

end.

Emul32console.dpr
program Emul32;{Delphi7}
{$APPTYPE CONSOLE}
uses
  Windows,
  uASM32 in 'UNIT\uASM32.pas';

BEGIN
  pConsoleMode:=1;
InitCPU; esp := SizeOf(RAM);   {стек}
LoadBIN('BOOT.bin',0);

pProgramHalt:=0;
repeat
Engine;
until pProgramHalt<>0;

WinExec('ArhWinRAR.Bat',SW_ShowMaximized);
END.

"Документація (чернетка).txt"
Документація (чернетка)

   int $40
  EAX - номер функції EBX - номер подфунції (за потребою)

EAX = 0  - НОП. Тобто begin end;
EAX = 1 - Halt, негайний вихід з програми.
EAX = 2 - APM аналог Application.ProcessMessages (DELPHI)
яку потрібно використовувати після процедур виводу на екран,
перед опитуванням клавіатури та можливо в деяких інших ситуаціях,
в деяких середовищах програмування.

EAX = 3 - функції та процедури консольного режиму
(працюють якщо pConsoleMode<>0, тобто увімкнений консольний режим)
       EBX = 1 - аналог Writeln; без параметрів
   EBX = 2 - вивід на екран регістра ECX (для тестів)
   EBX = 3 - вивід вивід символу на екран код в регістрі ECX
   EBX = 4 - вивести число ECX (в десятичному вигляді)
       EBX = 5 - аналог Readln;

EAX = 4 - читання значення різних змінних.
Повертання в ECX
       EBX = 1, - повертає значення pConsoleMode.
       EBX = 2, - повертає значення TrapInvalidOpcode.     
   EBX = 3, - повертає значення InvalidOpcodeVector.
       EBX = 4, - повертає значення TrapInvalidINT.
       EBX = 5, - повертає значення InvalidINTVector.
   
   
   
Опис змінних
1.pConsoleMode, якщо <> 0 якщо доступна консоль,роблять відповідні функції та процедури.
2.TrapInvalidOpcode, якщо значення 0 - невідомі коди процесора працюють, як НОП, тобто пропускаються і програма робить далі
                   якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку
                   якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в  InvalidOpcodeVector
3.InvalidOpcodeVector, - адреса виклику (за аналогією Call), у разі виникнення невідомого коду процесора ( при встановленому TrapInvalidOpcode=2)

4.TrapInvalidINT та 5.InvalidINTVector, ці змінні працюють по аналогії з
2.TrapInvalidOpcode та 3.InvalidOpcodeVector, для int $40.

0

48

Добре, я пройшовся по твоєму коду і документації й можу дати чітку оцінку та рекомендації.

---

1. Перевірка на помилки

Синтаксис Delphi виглядає в цілому коректно, компілюватися має.

В opINT: у частині для EBX = 5 є Readln else IntERR:=2; — синтаксично це помилка, треба:

5: Readln;
else IntERR:=2;

В ENGINE виклик Optable[opcode]; не виконує процедуру, треба додавати дужки, бо це тип procedure:

Optable[opcode]();

У LoadBIN змінна b типу DWord, а BlockRead(f,b,1) читає 1 байт — краще Byte:

var b: Byte;

Імена змінних pProgramHalt та APM оголошені через {} коментарі, але в Delphi краще без {} у var.

---

2. Які параметри варто зберігати при помилках

Для обробки помилок невідомих opcode та INT $40 рекомендую зберігати у змінні:

1. EIP помилки — адресу команди, що викликала помилку.

2. Значення першого байта коду — щоб бачити, що спрацювало.

3. Значення регістрів — EAX, EBX, ECX, EDX, можливо ESP/EBP для відновлення стану.

4. Trap — 0,1,2 щоб знати режим.

5. Vector адреса — InvalidOpcodeVector / InvalidINTVector.

Можна додати структуру:

type TCPUError = record
  EIP: DWord;
  OpCode: Byte;
  EAX, EBX, ECX, EDX, ESP, EBP: DWord;
  TrapMode: DWord;
  Vector: DWord;
end;
var LastCPUError: TCPUError;

І перед переходом на InvalidOpcodeVector або при зупинці заповнювати.

---

3. Реалізовані команди та “помилкові” ланцюги

У твоєму Optable ти задаєш лише перший байт опкоду.

Якщо команду можна мати декілька варіантів (наприклад, ADD r/m32, r32 та ADD r32, r/m32), то вони кодуються різними байтами після modrm.

Як додати обробку "помилкових ланцюгів":

У DecodeModRM обробляти m=0..3 для адресації.

Додати окремі процедури для кожного "довгого варіанту" команди, і присвоїти їх до Optable відповідних кодів:

Optable[$01] := opADD_rm_r;
Optable[$03] := opADD_r_rm;

Тобто не міняти основний код, а просто додавати варіанти для решти кодів.

---

4. Не потрібно перероблювати повний код

Так, правильно. Тобі достатньо:

Підправити виклик процедури в ENGINE.

Виправити opINT для EBX = 5.

Додати структуру для збереження стану при помилках.

Додати обробку варіантів команд у Optable.

---

Додаткові рекомендації

1. opUnknownOpCode можна зробити більш інформативним:

procedure opUnknownOpCode;
begin
  LastCPUError.EIP := eip;
  LastCPUError.OpCode := RAM[eip];
  LastCPUError.EAX := eax; LastCPUError.EBX := ebx;
  LastCPUError.ECX := ecx; LastCPUError.EDX := edx;
  LastCPUError.ESP := esp; LastCPUError.EBP := ebp;
  LastCPUError.TrapMode := TrapInvalidOpcode;
  LastCPUError.Vector := InvalidOpcodeVector;

  case TrapInvalidOpcode of
    0: inc(eip,1);
    1: if pConsoleMode<>0 then begin
         Writeln('Unknown opcode at ', eip, ': ', RAM[eip]);
         Readln; APM:=1; pProgramHalt:=1;
       end;
    2: begin
         Dec(esp,4); WriteDWord(esp,eip);
         eip := InvalidOpcodeVector;
       end;
  end;
end;

2. Для INT $40 схоже можна завести LastINTError із станом регістрів.

3. Не забудь в Optable для кожного додаткового варіанту коду просто присвоювати процедуру без зміни інших.

---

Якщо хочеш, я можу зробити короткий приклад як додати логування невідомих opcode та INT $40, який ти просто вставиш у твій uASM32.pas і він працюватиме. Це не змінює решту коду, а дає безпечну обробку помилок і збереження стану.

Хочеш, щоб я це показав?

0

49

1. Ось таку реалізацію хочу помилок опкодів, див все пов'зане з
ErrorOpCode1;ErrorOpCode2;OpcodeERRValue і т.д.

uASM32.pas
unit uASM32;

interface
type DWord = Cardinal;

var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord;Reg:array[0..7] of DWord absolute eax;
RAM:array[0..(16*1024*1024)] of Byte;
procedure LoadBIN(const FileName:string; LoadAddr:DWord);
procedure InitCPU;
procedure ENGINE;

{для INT $40} var
APM,{1=вихід з двигуна з procedure ENGINE, у разі якщо в таймері(Дельфі), для перерисуввааняб опросу клавыатури}
pProgramHalt {негайний вихід з програми Halt}
:DWord;
pConsoleMode:DWord=0;{якщо 0 - консольний режим не доступний}

TrapInvalidOpcode:DWord;{якщо 1 - програма обробляє опкоди}
InvalidOpcodeVector:DWord;{call-переходу переадреса}
OpcodeERRValue, OpcodeERReax, OpcodeERRecx, OpcodeERRedx, OpcodeERRebx, OpcodeERResp,
OpcodeERRebp, OpcodeERResi, OpcodeERRedi, OpcodeERReip, OpcodeERRRAMeip :DWord;

TrapInvalidINT:DWord;{якщо 1 - програма обробляє опкоди}
InvalidINTVector:DWord;{call-переходу переадреса}
{}
//:DWord;

implementation
uses SysUtils;

var pZF,pCF,pSF,pOF:Boolean;
type TOpcode = procedure;
var Optable:array[0..255] of TOpcode;
modrm,src,dst,addr:DWord;

procedure LoadBIN(const FileName:string; LoadAddr:DWord);
var f:file; size,i:DWord; b:Byte;
begin
Assign(f,FileName); Reset(f,1); size := FileSize(f);
for i:=0 to size-1 do
begin  BlockRead(f,b,1); RAM[LoadAddr+i] := b; end; Close(f);
eip := LoadAddr; {початок виконання}
end;

procedure OpcodeERRHasOperatedMistake; {якщо зпрацювала помилка опкоду}
begin OpcodeERReax:=eax; OpcodeERRecx:=ecx; OpcodeERRedx:=edx; OpcodeERRebx:=ebx;
OpcodeERResp:=esp; OpcodeERRebp:=ebp; OpcodeERResi:=esi; OpcodeERRedi:=edi;
OpcodeERReip:=eip; OpcodeERRRAMeip:=RAM[eip]
end;

{================ MEMORY =================}
function ReadDWord(a:DWord):DWord; begin Result := RAM[a] or     (RAM[a+1] shl 8) or      (RAM[a+2] shl 16) or     (RAM[a+3] shl 24);end;
procedure WriteDWord(a,v:DWord);begin RAM[a]  := v;RAM[a+1] := v shr 8; RAM[a+2] := v shr 16;RAM[a+3] := v shr 24;end;
{================ MODRM =================}
procedure DecodeModRM;var m:Byte; begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7; m := modrm shr 6; if m=3 then  addr := 0 else begin case m of  0: addr := Reg[dst];  1: addr := Reg[dst] + ShortInt(RAM[eip+2]);  2: addr := Reg[dst] + Integer(ReadDWord(eip+2));
else begin if TrapInvalidOpcode<>0 then begin OpcodeERRValue:=$FFFFFFFF;APM:=1;end;end;   end;end;end;
function GetRM:DWord; begin if (modrm shr 6)=3 then Result := Reg[dst]else Result := ReadDWord(addr);end;
procedure SetRM(v:DWord);begin if (modrm shr 6)=3 then Reg[dst] := v else WriteDWord(addr,v);end;
procedure NextRM;begin case modrm shr 6 of 0: inc(eip,2); 1: inc(eip,3); 2: inc(eip,6); 3: inc(eip,2);end;end;
{================ FLAGS =================}
procedure SetFlags(v:DWord);begin pZF := v=0; pSF := (v and $80000000)<>0; end;
{================ ALU =================}
procedure opADD;var v:DWord;begin DecodeModRM;v := GetRM + Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opOR;var v:DWord;begin DecodeModRM;v := GetRM or Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opAND;var v:DWord;begin DecodeModRM;v := GetRM and Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opSUB;var v:DWord;begin DecodeModRM;v := GetRM - Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opXOR;var v:DWord;begin DecodeModRM;v := GetRM xor Reg[src];SetRM(v);SetFlags(v);NextRM;end;
procedure opCMP;var v:DWord;begin DecodeModRM;v := GetRM - Reg[src];SetFlags(v);NextRM;end;
{=========== ADD/SUB/CMP imm8 ============}
procedure opGrp83;var imm:ShortInt;  v:DWord;begin DecodeModRM;imm := ShortInt(RAM[eip+2]);case src of 0: begin   v := GetRM + imm;   SetRM(v);   SetFlags(v);  end; 5: begin  v := GetRM - imm;   SetRM(v);   SetFlags(v);  end; 7: begin v := GetRM - imm;   SetFlags(v);  end;end;inc(eip,3);end;
{================ INC/DEC =================}
procedure opINC;begin dst := RAM[eip] and 7;Inc(Reg[dst]);SetFlags(Reg[dst]);inc(eip);end;
procedure opDEC;begin dst := RAM[eip] and 7;Dec(Reg[dst]);SetFlags(Reg[dst]);inc(eip);end;
{================ STACK =================}
procedure opPUSH_r32;begin dst := RAM[eip] and 7;Dec(esp,4);WriteDWord(esp,Reg[dst]);inc(eip);end;
procedure opPOP_r32;begin dst := RAM[eip] and 7;Reg[dst] := ReadDWord(esp);Inc(esp,4);inc(eip);end;
{================ MOV =================}
procedure opMOV_rm_r;begin DecodeModRM;SetRM(Reg[src]);NextRM;end;
procedure opMOV_r_rm;begin DecodeModRM;Reg[src] := GetRM;NextRM;end;
procedure opMOV_r32_imm;begin dst := RAM[eip] and 7;Reg[dst] := ReadDWord(eip+1);inc(eip,5);end;
{================ MUL =================}
procedure opMUL;var v:Int64;begin DecodeModRM;v := Int64(eax) * Int64(GetRM);eax := DWord(v);edx := DWord(v shr 32);NextRM;end;
procedure opIMUL;var v:Int64;begin DecodeModRM; v := Int64(Integer(eax)) * Int64(Integer(GetRM));eax := DWord(v);edx := DWord(v shr 32);NextRM;end;
{================ CONTROL =================}
procedure opJMP;begin eip := eip + 5 + Integer(ReadDWord(eip+1));end;
procedure opCALL;begin Dec(esp,4);WriteDWord(esp,eip+5);eip := eip + 5 + Integer(ReadDWord(eip+1));end;
procedure opRET;begin eip := ReadDWord(esp);Inc(esp,4);end;
procedure opJZ;begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2);end;
procedure opJNZ;begin if not pZF then inc(eip,ShortInt(RAM[eip+1])+2)else inc(eip,2);end;
procedure opJS;begin if pSF then inc(eip,ShortInt(RAM[eip+1])+2)else inc(eip,2);end;
{================ INT =================}
procedure opNOP;begin inc(eip);end;

procedure ErrorOpCode1;
begin
case TrapInvalidOpcode of
0:inc(eip,1);
1: {якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку}
begin If pConsoleMode<>0 then Begin OpcodeERRHasOperatedMistake;OpcodeERRValue:=1;Writeln('Unknown opcode ',eip,' ',RAM[eip]);Readln; APM:=1;pProgramHalt:=1;end;end;
2:{якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в InvalidOpcodeVector}
  begin OpcodeERRHasOperatedMistake;OpcodeERRValue:=1;Dec(esp,4); WriteDWord(esp,eip);  { return address }  eip := InvalidOpcodeVector; end
end;{<=case TrapInvalidOpcode of}
end;

procedure ErrorOpCode2;
begin
case TrapInvalidOpcode of
0:inc(eip,2);
1: {якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку}
begin If pConsoleMode<>0 then Begin OpcodeERRHasOperatedMistake;OpcodeERRValue:=2;Writeln('Unknown opcode ',eip,' ',RAM[eip]);Readln; APM:=1;pProgramHalt:=1;end;end;
2:{якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в InvalidOpcodeVector}
  begin OpcodeERRHasOperatedMistake;OpcodeERRValue:=2;Dec(esp,4); WriteDWord(esp,eip);  { return address }  eip := InvalidOpcodeVector; end
end;{<=case TrapInvalidOpcode of}end;

{================ INIT =================}
procedure opINT;Forward;
procedure InitCPU;  var i:Integer; begin
for i:=0 to 255 do  Optable[i] := ErrorOpCode1;OpcodeERRValue:=0;
Optable[$01] := opADD; Optable[$09] := opOR; Optable[$21] := opAND; Optable[$29] := opSUB;
Optable[$31] := opXOR; Optable[$39] := opCMP; Optable[$83] := opGrp83;
for i:=$40 to $47 do Optable[i] := opINC; for i:=$48 to $4F do Optable[i] := opDEC;
for i:=$50 to $57 do Optable[i] := opPUSH_r32; for i:=$58 to $5F do Optable[i] := opPOP_r32;
Optable[$89] := opMOV_rm_r; Optable[$8B] := opMOV_r_rm;
for i:=$B8 to $BF do Optable[i] := opMOV_r32_imm; Optable[$F7] := opMUL;  {спрощено}
Optable[$AF] := opIMUL; Optable[$E9] := opJMP; Optable[$E8] := opCALL; Optable[$C3] := opRET;
Optable[$74] := opJZ;Optable[$75] := opJNZ;Optable[$78] := opJS;
Optable[$CD] := opINT; Optable[$90] :=opNOP;
end;

procedure ENGINE;{================ ENGINE =================}
var opcode:DWord;
begin
APM:=0; repeat opcode := RAM[eip]; Optable[opcode]();until APM<>0;
if (pProgramHalt=0) and (OpcodeERRValue=$FFFFFFFF) then ErrorOpCode2;
end;

procedure opINT;
var IntERR:Dword;
begin IntERR:=0;
case RAM[eip+1] of  $40: begin
     case EAX of
{NOP}   0:begin end;
{Halt}1:begin APM:=1;pProgramHalt:=1;{Writeln('HALT ',eip,' ',RAM[eip]);Readln;} end;
{APM}2:begin APM:=1; end;

3:begin {функції та процедури консольного режиму}
  if pConsoleMode<>0 then begin
    case EBX of
     1:begin Writeln;end;
     2:begin Write('ECX='+IntToStr(ECX)+' ($'+IntToHEX(ECX,8)+')'); end;
     3:begin Write(chr(ECX)); end;{вивести символ}
     4:Write(ECX);   {вивести число}
     5:Readln;
      else IntERR:=2;
     end;
              end else IntERR:=3;
  end; {EAX=3 - функції та процедури консольного режиму}

4:begin {читання значення різних змінних}
   case EBX of
   1:ECX:=pConsoleMode;
   2:ECX:=TrapInvalidOpcode;{якщо 1 - програма обробляє опкоди}
   3:ECX:=InvalidOpcodeVector;{call-переходу переадреса}
   4:ECX:=TrapInvalidINT;{якщо 1 - програма обробляє опкоди}
   5:ECX:=InvalidINTVector;{call-переходу переадреса}
   else IntERR:=2;
   end

  end {EAX=4 - читання значення різних змінних}

else IntERR:=1;

end;end;

end;

If IntERR <>0 then Begin
case TrapInvalidINT of
0:inc(eip,2);
1: {якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку}
begin If pConsoleMode<>0 then Begin   Writeln('UnknownINT ',eip,' ',RAM[eip]);Readln; APM:=1;pProgramHalt:=1;end;end;
2:{якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в InvalidOpcodeVector}
  begin Dec(esp,4); WriteDWord(esp,eip);  { return address }  eip := InvalidINTVector; end
end;{<=case TrapInvalidOpcode of}
end else inc(eip,2);end;

end.

SasEmul32.dpr
program Emul32;{Delphi7}
{$APPTYPE CONSOLE}
uses
  Windows,
  uASM32 in 'UNIT\uASM32.pas';

BEGIN
  pConsoleMode:=1;
InitCPU; esp := SizeOf(RAM);   {стек}
LoadBIN('BOOT.bin',0);

pProgramHalt:=0;
repeat
Engine;
until pProgramHalt<>0;

WinExec('ArhWinRAR.Bat',SW_ShowMaximized);
END.

Документація (чернетка)

   int $40
  EAX - номер функції EBX - номер подфунції (за потребою)

EAX = 0  - НОП. Тобто begin end;
EAX = 1 - Halt, негайний вихід з програми.
EAX = 2 - APM аналог Application.ProcessMessages (DELPHI)
яку потрібно використовувати після процедур виводу на екран,
перед опитуванням клавіатури та можливо в деяких інших ситуаціях,
в деяких середовищах програмування.

EAX = 3 - функції та процедури консольного режиму
(працюють якщо pConsoleMode<>0, тобто увімкнений консольний режим)
       EBX = 1 - аналог Writeln; без параметрів
   EBX = 2 - вивід на екран регістра ECX (для тестів)
   EBX = 3 - вивід вивід символу на екран код в регістрі ECX
   EBX = 4 - вивести число ECX (в десятичному вигляді)
       EBX = 5 - аналог Readln;

EAX = 4 - читання значення різних змінних.
Повертання в ECX
       EBX = 1, - повертає значення pConsoleMode.
       EBX = 2, - повертає значення TrapInvalidOpcode.     
   EBX = 3, - повертає значення InvalidOpcodeVector.
       EBX = 4, - повертає значення TrapInvalidINT.
       EBX = 5, - повертає значення InvalidINTVector.
   
   
   
Опис змінних
1.pConsoleMode, якщо <> 0 якщо доступна консоль,роблять відповідні функції та процедури.
2.TrapInvalidOpcode, якщо значення 0 - невідомі коди процесора працюють, як НОП, тобто пропускаються і програма робить далі
                   якщо значення 1 - Емулятор обробляє відсутність коду процесору, зупиняє програму та видає помилку
                   якщо значення 2 - У разі невідомого коду процесора, здійснюється перехід(за аналогією Call), за адресою вказаною, в  InvalidOpcodeVector
3.InvalidOpcodeVector, - адреса виклику (за аналогією Call), у разі виникнення невідомого коду процесора ( при встановленому TrapInvalidOpcode=2)

4.TrapInvalidINT та 5.InvalidINTVector, ці змінні працюють по аналогії з 2.TrapInvalidOpcode та 3.InvalidOpcodeVector, для int $40.

6.OpcodeERRValue, значення помилки 1 -спрацювало в першому коді команди процесору, 2 -спрацювало в другому коді команди процесору
Копія значення регістрів, коли виникла помилка.
7.OpcodeERReax,   8.OpcodeERRecx,   9.OpcodeERRedx,   10.OpcodeERRebx,
11.OpcodeERResp,  12.OpcodeERRebp,  13.OpcodeERResi,  14.OpcodeERRedi,
15.OpcodeERReip,
16.OpcodeERRRAMeip, значення RAM[eip] (першого байту помилки)

0

50

Добре, зроблю повний набір JMP, але без перебудови твого емулятора, щоб ти просто вставив процедури і додав у Optable.

У x86 є 3 основні відносні JMP:

opcode інструкція довжина опис

EB JMP rel8 2 байти короткий
E9 JMP rel16/32 3 або 5 середній / довгий
FF /4 JMP r/m змінна непрямий

Ти робиш 32-бітний CPU, тому E9 у тебе rel32.

---

1️⃣ Короткий JMP (rel8)

opcode EB

procedure opJMP_short;
begin
  inc(eip, ShortInt(RAM[eip+1]) + 2);
end;

додати:

Optable[$EB] := opJMP_short;

---

2️⃣ Середній JMP (rel16)

У 32-бітному режимі це префікс $66 + E9.
Але якщо хочеш реалізувати прямо, можна так:

function ReadWord(a:DWord):Word;
begin
  Result := RAM[a] or (RAM[a+1] shl 8);
end;

procedure opJMP_rel16;
begin
  eip := eip + 3 + SmallInt(ReadWord(eip+1));
end;

(3 байти: opcode + imm16)

---

3️⃣ Довгий JMP (rel32)

У тебе вже є, але покажу правильний вигляд:

procedure opJMP_rel32;
begin
  eip := eip + 5 + Integer(ReadDWord(eip+1));
end;

таблиця:

Optable[$E9] := opJMP_rel32;

---

4️⃣ Непрямий JMP (через регістр або пам'ять)

opcode:

FF /4

тобто:

FF E0   jmp eax
FF E1   jmp ecx
FF 20   jmp [eax]

реалізація:

procedure opJMP_rm;
begin
  DecodeModRM;
  eip := GetRM;
end;

додати:

Optable[$FF] := opJMP_rm;

(але тут треба перевіряти src = 4, бо FF має багато інструкцій)

---

5️⃣ JMP через регістр (швидкий варіант)

якщо хочеш окремо:

procedure opJMP_reg;
begin
  dst := RAM[eip] and 7;
  eip := Reg[dst];
end;

---

📦 Мінімальний набір для твого емулятора

Я б радив реалізувати мінімум ці 3:

EB  short jump
E9  rel32 jump
FF /4 jump r/m32

цього вистачить для:

FASM

MASM

Turbo Assembler

більшості компіляторів

---

🔧 Додай у InitCPU

Optable[$EB] := opJMP_short;
Optable[$E9] := opJMP_rel32;
Optable[$FF] := opJMP_rm;

---

⚠️ Дуже важливо

У FF треба перевіряти групу ModRM, бо там ще є:

FF /0 INC
FF /1 DEC
FF /2 CALL
FF /3 CALL FAR
FF /4 JMP
FF /5 JMP FAR
FF /6 PUSH

тобто:

if src = 4 then eip := GetRM
else ErrorOpCode2;

---

✅ Після цього твій CPU зможе виконувати майже будь-які переходи з FASM/MASM.

---

💡 Якщо хочеш — я можу ще показати 3 дуже важливі JMP, які використовують компілятори Delphi/GCC, і без яких емулятор часто падає. Це буквально ще 20 рядків коду, але дає +70% сумісності.

0

Быстрый ответ

Напишите ваше сообщение и нажмите «Отправить»



Вы здесь » EnotVM » Тестовый форум » SAS ASM32 (2026)