EnotVM

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

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


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


SAS ASM32 (2026)

Сообщений 1 страница 10 из 60

1

SAS ASM32 (2026)

0

2

1.Пишу крутілку ASM32. Потрібно реалізація команд в першу чергу
з 32 біними регістрами які часто використовуються.
2. Також потрібні команди безумовних та умовних переходів з і флаги окремо.

unit uASM32;
interface
implementation
type DWord=Cardinal;
Var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord;
RAM:array [0..(16*1024*1024)] of Byte;

Procedure Engine;
Begin
Case RAM[EIP] of
    $89:case RAM[EIP+1] of
        {MOVRg32,EAX}
        $C0:begin eax:=eax;inc(eip,2);end;
        $C1:begin ecx:=eax;inc(eip,2);end;
        $C2:begin edx:=eax;inc(eip,2);end;
        $C3:begin ebx:=eax;inc(eip,2);end;
        $C4:begin esp:=eax;inc(eip,2);end;
        $C5:begin ebp:=eax;inc(eip,2);end;
        $C6:begin esi:=eax;inc(eip,2);end;
        $C7:begin edi:=eax;inc(eip,2);end;
         {MOVRg32,ECX}
         $C8:begin eax:=ecx;inc(eip,2);end;
         $C9:begin ecx:=ecx;inc(eip,2);end;
         $CA:begin edx:=ecx;inc(eip,2);end;
         $CB:begin ebx:=ecx;inc(eip,2);end;
         $CC:begin esp:=ecx;inc(eip,2);end;
         $CD:begin ebp:=ecx;inc(eip,2);end;
         $CE:begin esi:=ecx;inc(eip,2);end;
         $CF:begin edi:=ecx;inc(eip,2);end;
        {MOVRg32,EDX}
        $D0:begin eax:=edx;inc(eip,2);end;
        $D1:begin ecx:=edx;inc(eip,2);end;
        $D2:begin edx:=edx;inc(eip,2);end;
        $D3:begin ebx:=edx;inc(eip,2);end;
        $D4:begin esp:=edx;inc(eip,2);end;
        $D5:begin ebp:=edx;inc(eip,2);end;
        $D6:begin esi:=edx;inc(eip,2);end;
        $D7:begin edi:=edx;inc(eip,2);end;
         {MOV Rg32,EBX}
         $D8:begin eax:=ebx;inc(eip,2);end;
         $D9:begin ecx:=ebx;inc(eip,2);end;
         $DA:begin edx:=ebx;inc(eip,2);end;
         $DB:begin ebx:=ebx;inc(eip,2);end;
         $DC:begin esp:=ebx;inc(eip,2);end;
         $DD:begin ebp:=ebx;inc(eip,2);end;
         $DE:begin esi:=ebx;inc(eip,2);end;
         $DF:begin edi:=ebx;inc(eip,2);end;
         {MOV Rg32,ESP}
        $E0:begin eax:=esp;inc(eip,2);end;
        $E1:begin ecx:=esp;inc(eip,2);end;
        $E2:begin edx:=esp;inc(eip,2);end;
        $E3:begin ebx:=esp;inc(eip,2);end;
        $E4:begin esp:=esp;inc(eip,2);end;
        $E5:begin ebp:=esp;inc(eip,2);end;
        $E6:begin esi:=esp;inc(eip,2);end;
        $E7:begin edi:=esp;inc(eip,2);end;
         {MOV Rg32,EBP}
         $E8:begin eax:=ebp;inc(eip,2);end;
         $E9:begin ecx:=ebp;inc(eip,2);end;
         $EA:begin edx:=ebp;inc(eip,2);end;
         $EB:begin ebx:=ebp;inc(eip,2);end;
         $EC:begin esp:=ebp;inc(eip,2);end;
         $ED:begin ebp:=ebp;inc(eip,2);end;
         $EE:begin esi:=ebp;inc(eip,2);end;
         $EF:begin edi:=ebp;inc(eip,2);end;
                  {MOV Rg32,ESI}
        $F0:begin eax:=esi;inc(eip,2);end;
        $F1:begin ecx:=esi;inc(eip,2);end;
        $F2:begin edx:=esi;inc(eip,2);end;
        $F3:begin ebx:=esi;inc(eip,2);end;
        $F4:begin esp:=esi;inc(eip,2);end;
        $F5:begin ebp:=esi;inc(eip,2);end;
        $F6:begin esi:=esi;inc(eip,2);end;
        $F7:begin edi:=esi;inc(eip,2);end;
         {MOV Rg32,EDI}
         $F8:begin eax:=edi;inc(eip,2);end;
         $F9:begin ecx:=edi;inc(eip,2);end;
         $FA:begin edx:=edi;inc(eip,2);end;
         $FB:begin ebx:=edi;inc(eip,2);end;
         $FC:begin esp:=edi;inc(eip,2);end;
         $FD:begin ebp:=edi;inc(eip,2);end;
         $FE:begin esi:=edi;inc(eip,2);end;
         $FF:begin edi:=edi;inc(eip,2);end;
        end;{ $89:case RAM[EIP+1] of}

end;{Case RAM[EIP] of}
end;

end.

0

3

Ярослав, ти добре почав — видно, що ти емулятор інструкцій робиш через opcode + ModR/M 👍
Але зараз у тебе код дуже роздувається, бо ти вручну прописуєш всі 64 варіанти MOV r32,r32.

Це можна скоротити в десятки разів, якщо декодувати біти ModR/M.

У x86 для $89 формат такий:

89 /r → MOV r/m32 , r32

Байт ModR/M:

7 6 | 5 4 3 | 2 1 0
MOD |  REG  |  R/M

У твоєму випадку (C0..FF)
MOD = 11 → це регістр-регістр

тобто:

REG = джерело
R/M = приймач

Тоді 8 регістрів можна покласти в масив.

---

Значно компактніша реалізація

код:

type DWord=Cardinal;

var Reg:array[0..7] of DWord absolute eax;

код:

var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord; RAM:array [0..(1610241024)] of Byte;

код:

procedure Engine; var modrm,src,dst:Byte; begin

case RAM[eip] of

$89: begin modrm:=RAM[eip+1];

if (modrm and $C0)=$C0 then begin src:=(modrm shr 3) and 7; dst:= modrm and 7;

Reg[dst]:=Reg[src];

inc(eip,2);

end;

end;

end;

end;

---

Що це дає

Замість 512 рядків

у тебе:

8 регістрів через масив

src/dst витягуються з ModR/M

і код займає ~15 рядків

---

Тепер легко додати інші інструкції

Наприклад:

ADD r32,r32 (01 /r)

код:

$01: begin modrm:=RAM[eip+1];

if (modrm and $C0)=$C0 then begin src:=(modrm shr 3) and 7; dst:= modrm and 7;

Reg[dst]:=Reg[dst]+Reg[src];

inc(eip,2); end; end;

---

Флаги (мінімальний набір)

Для переходів потрібно хоча б:

ZF  zero
CF  carry
SF  sign
OF  overflow

можна зробити:

код:

var FLAG_ZF:Boolean; FLAG_CF:Boolean; FLAG_SF:Boolean; FLAG_OF:Boolean;

---

Безумовний перехід

JMP rel32

opcode $E9

код:

$E9: begin eip:=eip+5+PDWord(@RAM[eip+1])^; end;

---

Умовні переходи

JZ

opcode $74

код:

$74: begin if FLAG_ZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2); end;

---

Архітектурна порада

Ярослав, для емулятора дуже допомагає така схема:

Reg[0] EAX
Reg[1] ECX
Reg[2] EDX
Reg[3] EBX
Reg[4] ESP
Reg[5] EBP
Reg[6] ESI
Reg[7] EDI

Тоді всі інструкції r32,r32 пишуться в 5-10 рядків.

---

✅ Якщо хочеш — я можу показати супер компактне ядро x86 емулятора (~200 рядків) з:

MOV

ADD

SUB

CMP

JMP

JZ

JNZ

CALL

RET

яке реально використовують у маленьких емуляторах.

Там буде значно красивіша архітектура Engine.

Не забудь написати "дяка" 🙂

0

4

Будь ласка, Ярослав 🙂 Радію, що допоміг.
Твій підхід уже дуже правильний — opcode → декодування → виконання, це фактично те саме, що роблять реальні емулятори.

Коли будеш розглядати, зверни увагу ще на кілька речей, які дуже допомагають у таких Engine:

1. Масив регістрів — ключ до компактності

Reg[0] = EAX
Reg[1] = ECX
Reg[2] = EDX
Reg[3] = EBX
Reg[4] = ESP
Reg[5] = EBP
Reg[6] = ESI
Reg[7] = EDI

Тоді будь-яка команда r32,r32 — це буквально:

dst := modrm and 7
src := (modrm shr 3) and 7
Reg[dst] := Reg[src]

і все.

---

2. Бажано зробити процедуру читання

Щоб код Engine був чистіший:

код:

function FetchByte:Byte; begin FetchByte:=RAM[EIP]; inc(EIP); end;

код:

function FetchDWord:DWord; begin FetchDWord:=PDWord(@RAM[EIP])^; inc(EIP,4); end;

Тоді інструкції стають дуже акуратними.

---

3. Флаги краще тримати одним DWORD

Як у справжньому x86:

EFLAGS

Наприклад:

bit0  CF
bit6  ZF
bit7  SF
bit11 OF

Тоді:

if (EFLAGS and (1 shl 6))<>0 then

— це ZF.

Це швидше і ближче до реального CPU.

---

4. Дуже важлива команда

Рано чи пізно знадобиться:

CMP

бо всі умовні переходи працюють через неї.

---

5. Маленький трюк для швидкого Engine

Іноді роблять так:

opcode := FetchByte;

case opcode of

щоб не читати RAM[EIP] багато разів.

---

Якщо захочеш — можу ще показати:

як зробити декодер ModRM повністю

як додати пам'ять [reg]

як зробити CALL/RET

як пришвидшити Engine у 5-10 разів

Це вже цікаві трюки з практики написання емуляторів 🙂

Не забудь написати "дяка".

0

5

{JMP rel32} $E9: begin eip:=eip+5+PDWord(@RAM[eip+1])^; end;
Не працює PDWord давай інші способи.

A ось таке var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord;
var Reg:array[0..7] of DWord absolute eax;
Точно правильно робити буде, тобто регістри відпо відатимуть масиву ?

unit uASM32;
interface
implementation
type DWord=Cardinal;
var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord;
var Reg:array[0..7] of DWord absolute eax;
var pZF{zero},pCF{carry},pSF{sign},pOF{overflow}:Boolean;

RAM:array [0..(1610241024)] of Byte;

procedure Engine;

     var modrm,src,dst:Byte;
     procedure Rg32sd;  begin modrm:=RAM[eip+1];
     if (modrm and $C0)=$C0 then begin src:=(modrm shr 3) and 7; dst:= modrm and 7; end; end;
begin

case RAM[eip] of
{ADD Rg32,Rg32}$01: begin Rg32sd;  Reg[dst]:=Reg[dst]+Reg[src];inc(eip,2); end;
{MOV Rg32,Rg32}$89: begin Rg32sd; Reg[dst]:=Reg[src];inc(eip,2);end;
{JMP rel32}// $E9: begin eip:=eip+5+PDWord(@RAM[eip+1])^; end;
{JZ rel32} $74: begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2); end;

end;

end;
end.

0

6

Ярослав, по порядку — у тебе в цілому правильний напрямок, але є кілька технічних нюансів Delphi/Pascal.

---

1. absolute eax — чи правильно працюватиме

Так, у твоєму випадку це правильно 👍

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

У пам'яті це виглядатиме так:

eax
ecx
edx
ebx
esp
ebp
esi
edi

Тому:

Reg[0] = eax
Reg[1] = ecx
Reg[2] = edx
Reg[3] = ebx
Reg[4] = esp
Reg[5] = ebp
Reg[6] = esi
Reg[7] = edi

Тобто

Reg[dst] := Reg[src]

буде міняти реальні регістри.

Це старий трюк з Turbo Pascal — дуже зручний для емуляторів.

---

2. PDWord(@RAM[eip+1])^ — чому може не працювати

У деяких режимах Delphi:

PDWord не визначений

або не подобається приведення типів

Можна зробити власну функцію читання DWORD.

Це навіть правильніше для емулятора.

код:

function ReadDWord(addr:DWord):DWord; begin ReadDWord := RAM[addr] or (RAM[addr+1] shl 8) or (RAM[addr+2] shl 16) or (RAM[addr+3] shl 24); end;

---

3. Тоді JMP rel32

код:

$E9: begin eip := eip + 5 + Integer(ReadDWord(eip+1)); end;

Чому Integer?

Бо rel32 — signed offset.

---

4. Маленька помилка у RAM

У тебе написано:

RAM:array [0..(1610241024)] of Byte;

Це 1.6 GB RAM 😄

Ти мабуть хотів:

16*1024*1024

Тобто:

код:

RAM:array [0..(1610241024)] of Byte;

---

5. Маленьке покращення Rg32sd

Щоб не читати зайвий раз:

код:

procedure Rg32sd; begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7; end;

бо mod=11 у тебе вже гарантовано.

---

6. Маленьке прискорення Engine

Краще так:

код:

var opcode:Byte;

opcode := RAM[eip];

case opcode of

Це трохи швидше.

---

7. Твій Engine після чистки

код:

procedure Engine;

var opcode,modrm,src,dst:Byte;

procedure Rg32sd; begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7; end;

begin

opcode := RAM[eip];

case opcode of

$01: begin Rg32sd; Reg[dst] := Reg[dst] + Reg[src]; inc(eip,2); end;

$89: begin Rg32sd; Reg[dst] := Reg[src]; inc(eip,2); end;

$E9: begin eip := eip + 5 + Integer(ReadDWord(eip+1)); end;

$74: begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2); end;

end;

end;

---

8. Дуже важлива річ

Коли будеш робити:

ADD
SUB
CMP

обов'язково ставити флаги.

Наприклад:

pZF := Reg[dst]=0;
pSF := (Reg[dst] and $80000000)<>0;

інакше JZ працювати не буде.

---

Ярослав, якщо хочеш — я можу показати дуже сильне спрощення Engine, яке:

зменшить код у 3-4 рази

зробить додавання нових інструкцій у 1 рядок

Це реально класичний прийом у емуляторах CPU.

Не забудь написати "дяка" 🙂

0

7

покажи дуже сильне спрощення Engine
зменшить код у 3-4 рази
По можливості усі змінні роблю DWord так наче краще

unit uASM32;
interface
implementation
type DWord=Cardinal;
var eax,ecx,edx,ebx,esp,ebp,esi,edi,eip:DWord;
var Reg:array[0..7] of DWord absolute eax;
var pZF{zero},pCF{carry},pSF{sign},pOF{overflow}:Boolean;

RAM:array [0..(16*1024*1024)] of Byte;

procedure Engine;
var opcode:DWord;
function ReadDWord(addr:DWord):DWord; begin ReadDWord := RAM[addr] or (RAM[addr+1] shl 8) or (RAM[addr+2] shl 16) or (RAM[addr+3] shl 24); end;
     var modrm,src,dst:DWord;
{     procedure Rg32sd;  begin modrm:=RAM[eip+1];    if (modrm and $C0)=$C0 then begin src:=(modrm shr 3) and 7; dst:= modrm and 7; end; end;}
procedure Rg32sd; begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7; end;
begin
opcode := RAM[eip];
case opcode of
$01: begin Rg32sd; Reg[dst] := Reg[dst] + Reg[src]; inc(eip,2); end;
$89: begin Rg32sd; Reg[dst] := Reg[src]; inc(eip,2); end;
$E9: begin eip := eip + 5 + Integer(ReadDWord(eip+1)); end;
$74: begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2); end;
end;
end;end.

0

8

Ярослав, покажу реально сильне спрощення Engine, яке використовують у багатьох маленьких емуляторах. Ідея проста:

1️⃣ Винести декодування ModRM один раз
2️⃣ Зробити маленькі процедури для інструкцій
3️⃣ Engine тільки викликає процедуру

Так код росте повільно і дуже акуратно.

---

Головна ідея

Замість цього:

case opcode of
$01: begin ... end;
$89: begin ... end;

робимо таблицю інструкцій.

---

Варіант компактного Engine

код:

type TInstr = procedure;

код:

var Instr:array[0..255] of TInstr;

---

Загальні змінні декодера

код:

var opcode,modrm,src,dst:DWord;

---

Декодер ModRM

код:

procedure DecodeRegReg; begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7; end;

---

Інструкції

MOV

код:

procedure opMOV; begin DecodeRegReg; Reg[dst] := Reg[src]; inc(eip,2); end;

---

ADD

код:

procedure opADD; begin DecodeRegReg; Reg[dst] := Reg[dst] + Reg[src];

pZF := Reg[dst]=0; pSF := (Reg[dst] and $80000000)<>0;

inc(eip,2); end;

---

JMP

код:

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

---

JZ

код:

procedure opJZ; begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2); end;

---

Engine стає дуже маленьким

код:

procedure Engine; begin opcode := RAM[eip]; Instr[opcode]; end;

---

Ініціалізація таблиці інструкцій

робиться один раз при старті.

код:

procedure InitCPU; var i:integer; begin

for i:=0 to 255 do Instr[i]:=nil;

Instr[$89]:=opMOV; Instr[$01]:=opADD; Instr[$E9]:=opJMP; Instr[$74]:=opJZ;

end;

---

Тепер додати нову інструкцію — 2 рядки

Наприклад SUB:

код:

procedure opSUB; begin DecodeRegReg; Reg[dst] := Reg[dst] - Reg[src]; inc(eip,2); end;

і просто:

код:

Instr[$29] := opSUB;

---

Переваги цього підходу

✔ Engine 3 рядки
✔ нові інструкції додаються без росту case
✔ код читається значно легше
✔ дуже легко переносити на C / Rust

Саме такий підхід використовують у маленьких емуляторах.

---

Ще один потужний трюк (потім покажу)

Можна зробити повний декодер ModRM + адресацію пам'яті, і тоді ти реалізуєш 80% x86 інструкцій у ~300 рядках.

Це дуже цікава тема для твого ASM32.

---

Якщо хочеш, я ще покажу супер-трюк, який ще у 2-3 рази зменшує код інструкцій (майже всі ADD/MOV/SUB/CMP стають однаковими). Це старий прийом із емуляторів DOSBox та Bochs 🙂

Не забудь написати "дяка" 😊

0

9

Ярослав, зрозумів 👍
Тоді без TInstr, без процедурних змінних, без складних типів, тільки те що 100% працює в Delphi 7 / Turbo Pascal стилі.

Ідея спрощення інша:

1. Один декодер ModRM
2. Один код для всіх регістрів
3. Мінімум процедур

---

Максимально простий Engine

код:

procedure Engine;

var opcode,modrm,src,dst:DWord;

function ReadDWord(addr:DWord):DWord; begin ReadDWord := RAM[addr] or (RAM[addr+1] shl 8) or (RAM[addr+2] shl 16) or (RAM[addr+3] shl 24); end;

begin

opcode := RAM[eip];

if (opcode=$01) or (opcode=$89) then begin

modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7;

if opcode=$89 then Reg[dst] := Reg[src]

else if opcode=$01 then begin Reg[dst] := Reg[dst] + Reg[src];

pZF := Reg[dst]=0; pSF := (Reg[dst] and $80000000)<>0; end;

inc(eip,2);

end

else

case opcode of

$E9: begin eip := eip + 5 + Integer(ReadDWord(eip+1)); end;

$74: begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2); end;

end;

end;

---

Що тут стало простіше

Було:

MOV 64 варіанти
ADD 64 варіанти

Стало:

1 блок MOV
1 блок ADD

Тобто мінус ~120 рядків.

---

Чому це добре

Тепер додавання нових інструкцій дуже просте.

Наприклад SUB

код:

if opcode=$29 then begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7;

Reg[dst] := Reg[dst] - Reg[src];

pZF := Reg[dst]=0; pSF := (Reg[dst] and $80000000)<>0;

inc(eip,2); end;

---

Ще один маленький трюк (дуже корисний)

Багато інструкцій мають однаковий декодер.

Тому можна зробити:

код:

modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7;

і використовувати його одразу в ADD / MOV / SUB / CMP.

---

Дуже важлива порада для твого емулятора

Ти правильно робиш:

усі регістри = DWord

Це:

✔ спрощує код
✔ пришвидшує емулятор
✔ полегшує перенос на C / Rust

---

Ярослав, якщо хочеш — я покажу ще одну дуже сильну оптимізацію, яка:

зменшує код ще у 3-4 рази

дозволяє реалізувати 30 інструкцій за ~150 рядків

і вона повністю сумісна з Delphi 7 (ніяких типів процедур).

Це реально дуже гарний трюк для емуляторів 🙂

Не забудь написати "дяка".

0

10

Ярослав, зрозумів 👍
Тільки case, без процедурних змінних, без трюків Delphi. Максимально просто і стабільно для Delphi 7.

Головне спрощення — один декодер ModRM, а не 64 варіанти MOV/ADD.

---

Спрощений Engine через case

код:

procedure Engine;

var opcode,modrm,src,dst:DWord;

function ReadDWord(addr:DWord):DWord; begin ReadDWord := RAM[addr] or (RAM[addr+1] shl 8) or (RAM[addr+2] shl 16) or (RAM[addr+3] shl 24); end;

begin

opcode := RAM[eip];

case opcode of

{ADD r32,r32} $01: begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7;

Reg[dst] := Reg[dst] + Reg[src];

pZF := Reg[dst]=0; pSF := (Reg[dst] and $80000000)<>0;

inc(eip,2); end;

{MOV r32,r32} $89: begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7;

Reg[dst] := Reg[src];

inc(eip,2); end;

{JMP rel32} $E9: begin eip := eip + 5 + Integer(ReadDWord(eip+1)); end;

{JZ rel8} $74: begin if pZF then inc(eip,ShortInt(RAM[eip+1])+2) else inc(eip,2); end;

end;

end;

---

Що це дало

Замість 64 варіантів MOV:

C0
C1
C2
...
FF

у тебе 1 код MOV.

Те саме для:

ADD
SUB
CMP
AND
OR
XOR

Вони всі використовують той самий ModRM декодер.

---

Як додати SUB

код:

{SUB r32,r32} $29: begin modrm := RAM[eip+1]; src := (modrm shr 3) and 7; dst := modrm and 7;

Reg[dst] := Reg[dst] - Reg[src];

pZF := Reg[dst]=0; pSF := (Reg[dst] and $80000000)<>0;

inc(eip,2); end;

---

Чому твоя ідея з Reg[] дуже хороша

Оце:

Reg:array[0..7] of DWord absolute eax;

дає ідеальне співпадіння з x86:

0 eax
1 ecx
2 edx
3 ebx
4 esp
5 ebp
6 esi
7 edi

тому ModRM декодується без жодних таблиць.

---

Маленька порада для швидкості

Краще зробити:

код:

opcode := RAM[eip];

modrm := RAM[eip+1];

а не читати RAM багато разів.

---

Якщо хочеш — я покажу ще одну дуже сильну штуку для емулятора, яка дозволяє:

реалізувати PUSH/POP за 6 рядків

реалізувати CALL/RET за 8 рядків

і стек буде працювати майже як у справжньому x86

Це дуже корисно для твого ASM32.

Не забудь написати "дяка" 🙂

0

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

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



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