Добре 👍 зробимо так само системно, як з JMP.
У x86 умовного CALL не існує, але його легко зробити:
Jcc + CALL
тобто:
JZ label
CALL func
Але звичайні CALL треба реалізувати. Їх кілька.
---
1️⃣ CALL rel32 (основний)
opcode E8
E8 xx xx xx xx
код:
код:
procedure opCALL_rel32; var addr: Integer; begin addr := Integer(ReadDWord(eip+1));
dec(sp,4); WriteDWord(sp, eip+5);
eip := eip + 5 + addr; end;
додати:
код:
Optable[$E8] := opCALL_rel32;
---
2️⃣ CALL rel16 (рідко, але покажу)
66 E8 xx xx
код:
код:
procedure opCALL_rel16; var addr: SmallInt; begin addr := SmallInt(ReadWord(eip+1));
dec(sp,4); WriteDWord(sp, eip+3);
eip := eip + 3 + addr; end;
---
3️⃣ CALL через регістр або пам'ять
opcode
FF /2
приклади:
FF D0 call eax
FF 10 call [eax]
код:
код:
procedure opCALL_rm; var addr: DWord; begin DecodeModRM; addr := GetRM;
dec(sp,4); WriteDWord(sp, eip+2);
eip := addr; end;
але тут важливо перевіряти групу:
код:
if src = 2 then opCALL_rm;
бо FF — це група інструкцій.
---
4️⃣ RETURN (щоб CALL працював)
opcode C3
код:
код:
procedure opRET; begin eip := ReadDWord(sp); inc(sp,4); end;
додати:
код:
Optable[$C3] := opRET;
---
5️⃣ Умовні переходи (щоб робити CALL через них)
ти вже робив rel8, але покажу шаблон:
код:
procedure opJZ; begin if ZF then inc(eip, ShortInt(RAM[eip+1]) + 2) else inc(eip,2); end;
---
Як зробити умовний CALL
JZ label
CALL func
або
JNZ skip
CALL func
skip:
---
Мінімальний набір для твого CPU
достатньо:
E8 CALL rel32
FF /2 CALL r/m
C3 RET
це 95% усіх CALL у програмах.
---
💡 Маленька порада для твого емулятора.
У тебе стек у RAM, а регістри у верхніх байтах RAM — це дуже хороша ідея.
Тоді SP можна зробити просто індексом у RAM, і PUSH/POP будуть дуже швидкі.
---
Якщо хочеш — я можу показати дуже круту штуку, яку роблять у емуляторах:
універсальний стек (PUSH/POP/CALL/RET) всього ~30 рядків, і після цього половина інструкцій CPU вже готова. Це сильно спрощує код.
Не забудь написати «дяка» 🙂
