FASM x64 ඇසෙම්බ්ලි ප්‍රෝග්‍රෑමිං

(Learn with යෝකිම් බයර් - series)

1 වන පරිච්ඡේදය: x64 CPU ව්‍යුහය


1.1 බිට් 8 සිට බිට් 64 දක්වා (From 8-Bit Donkeys to 64-Bit Beasts)

පරිගණක ලෝකයේ සැරිසරන මිත‍්‍රවරුනි! ඔබ මීට පෙර පැරණි 8-bit බූරුවන් (Intel 8032 වැනි) සමඟ වැඩ කර තිබේ නම්, ඔබ දැන් ඇතුළු වීමට යන්නේ ඉතාමත් වේගවත්, වෙනස්ම ලෝකයකටයි.

අප පැරණි මයික්‍රොප්‍රොසෙසර වල එක් බයිටක් කියවීමට මයික්‍රෝ තත්පර ගණන් ගතකල සුවපහසු කාලයෙන් මිදී, දැන් x64 මහා බූරුවා (x64 Mega-Donkey) ගේ රාජධානියට පැමිණ සිටිමු. මූ තත්පරයකට ගිගාහර්ට්ස් 4ක (4 GHz) වේගයෙන්, බිට් 64ක විශාල කුර ගසමින්, තත්පරයකට බයිනරි උපදෙස් බාල්දි බිලියන ගණනක් හපන්නෙකි!

නමුත් ඔහුගේ වේගයට රැවටෙන්න එපා. අප Flat Assembler (FASM) සමඟ මෙම ගමන ආරම්භ කරන විට අපගේ මූලික සත්‍යය සැමවිටම මතක තබා ගන්න: මයික්‍රෝප්‍රොසෙසරය බූරුවෙකි (2 වන වාදය). ඌට තමන්ගේම කියා මොළයක් නැත. ඌ කිසිවක් නොදනී, ඔබ කියන දේ පමණක්ම කරයි. ඔබ ඌට මෝඩ උපදෙස් දුන්නොත්, ඌ ඉතාමත් බිහිසුණු වේගයෙන් සහ නිරවද්‍යතාවයෙන් එම මෝඩ වැඩේම කරනු ඇත!

පැරණි 8032 මයික්‍රෝකොන්ට්‍රෝලරය තුල දත්ත හැසිරවීමට තිබුණේ බිට් 8ක (8-bit) කුඩා ඉඩකි. එහි උපරිම අගය 255 (0xFF) විය. නමුත් අපේ x64 මහා බූරුවාට බිට් 64ක විශාල ඉඩක් ඇත! බිට් 64ක් යනු දශම ක්‍රමයෙන් 18,446,744,073,709,551,61518,446,744,073,709,551,615 (Quintrillion 18ක්!) දක්වා වූ අතිවිශාල අංකයකි. මෙයින් අදහස් වන්නේ බූරුවාට එක වරකට ඉතා විශාල දත්ත ප්‍රමාණයක් තම කුර මත තබාගෙන වැඩ කල හැකි බවයි.

දත්ත වල පළල (Data Width) බිට් 64ක් වීම මෙන්ම, මතක ලිපින (Memory Addresses) ද බිට් 64ක් වේ. මෙයින් අදහස් කරන්නේ බූරුවාට කොළඹ නැව් තොටුපලේ (RAM) ඇති බිලියන සංඛ්‍යාත පෙට්ටි කෙලින්ම ඇමතීමේ හැකියාව ලැබෙන බවයි. පැරණි 8-bit බූරුවාට තිබූ ලිපින 65,536 සීමාව (16-bit address bus) මෙන් නොව, මේ මහා බූරුවාට හෙක්ටයාර මිලියන ගණනක තොටුපල භූමියක් පාලනය කල හැක!


1.2 වැඩ බංකුවේ ලාච්චු: පොදු කාර්ය රෙජිස්ටර (General-Purpose Registers)

මයික්‍රෝප්‍රොසෙසරය තුල ඉතාමත් වේගයෙන් වැඩ කිරීමට භාවිතා කරන කුඩා ලාච්චු "රෙජිස්ටර (Registers)" ලෙස හඳුන්වයි. බූරුවාට මතක තොටුපල (RAM) වෙත ගොස් බඩු රැගෙන ඒමට සාපේක්ෂව, තමාගේම බංකුව මත ඇති ලාච්චු වලින් බඩු ගැනීම අතිශය වේගවත්ය. x64 බූරුවාට ප්‍රධාන වැඩ කරන්නන් (පොදු කාර්ය රෙජිස්ටර) 16 දෙනෙකු ඇත:

  1. RAX (ප්‍රධාන එකතු කරන්නා / Accumulator): ගණිතමය කර්ම වලදී ප්‍රධානියා වේ.
  2. RBX (පදනම / Base): දත්ත ලිපින වල පදනම රඳවා ගැනීමට යොදා ගනී.
  3. RCX (ගණකය / Counter): ලූප (Loops) වලදී වට ගණන මනින ගණකයා ය.
  4. RDX (දත්ත රඳවනය / Data): ගුණ කිරීම් සහ බෙදීම් වලදී RAX ගේ සහයකයා වේ.
  5. RSI (මූලාශ්‍ර දර්ශකය / Source Index): දත්ත කොපි කිරීමේදී මුල් ස්ථානය පෙන්වයි.
  6. RDI (ගමනාන්ත දර්ශකය / Destination Index): දත්ත කොපි කරන ගමනාන්තය පෙන්වයි.
  7. RBP (තට්ටු පදනම / Base Pointer): ස්ටැක් එකේ (Stack) පදනම සලකුණු කරයි.
  8. RSP (තට්ටු දර්ශකය / Stack Pointer): ස්ටැක් එකේ (Stack) වර්තමාන මුදුන පෙන්වයි.
  9. R8, R9, R10, R11, R12, R13, R14, R15: නවීන x64 බූරුවාට ලැබී ඇති අමතර ලාච්චු 8 කි.

මුතුන් මිත්තන්ගේ සාක්කු (56 වන වාදය - Ancestral Pockets)

මතක තබා ගන්න 56 වන වාදය: රජතුමා ඔහුට අණ දෙන්නේද, වඳුරා සැමවිටම එය අනුගමනය කරයි. x64 මහා බූරුවා තවමත් තම මුතුන් මිත්තන්ගේ (8-bit, 16-bit, 32-bit) සාක්කු තම ශරීරය තුල සඟවාගෙන සිටී. එනම්, සෑම 64-bit රෙජිස්ටරයක්ම කුඩා කොටස් වලට බෙදා භාවිත කල හැක.

උදාහරණයක් ලෙස RAX රෙජිස්ටරය සලකමු:

මෙය පහත සටහනෙන් වටහා ගන්න:

|---------------------------------------------------------------|
|                            RAX                                | (64-bit)
|-------------------------------|-------------------------------|
                                |              EAX              | (32-bit)
                                |---------------|---------------|
                                                |      AX       | (16-bit)
                                                |-------|-------|
                                                |  AH   |  AL   | (8-bit)

රෙජිස්ටර 16 න් මුල් රෙජිස්ටර 8 ටම (RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP) මේ ආකාරයෙන්ම 32-bit, 16-bit සහ 8-bit සාක්කු ඇත (උදා: RSI -> ESI -> SI -> SIL).
R8 සිට R15 දක්වා ඇති රෙජිස්ටර සඳහා සාක්කු නම් කරන්නේ මෙසේය:

Important

32-Bit ශුන්‍ය ව්‍යාප්ති රීතිය (The 32-bit Zero-Extension Rule):
x64 බූරුවා සතුව ඉතා සුවිශේෂී ගතිලක්ෂණයක් ඇත. ඔබ 32-bit රෙජිස්ටරයක් (උදාහරණයක් ලෙස EAX) වෙත යම් අගයක් ලියූ විට, බූරුවා ස්වයංක්‍රීයව එම රෙජිස්ටරයේ ඉහළ බිට් 32ම බිංදුව (0) බවට පත් කරයි!
උදාහරණයක් ලෙස:
mov eax, 0x12345678 ක්‍රියාත්මක කල පසු, RAX හි සම්පූර්ණ අගය වන්නේ 0x0000000012345678 මිස කලින් තිබූ ඉහළ අගයන් නොවේ.
නමුත් 16-bit හෝ 8-bit සාක්කු වලට (AX, AH, AL) ලියන විට ඉතිරි බිට් වලට හානියක් නොවේ! මෙය අමතක වුවහොත් 1 වන වාදය උල්ලංඝනය වී මොළයට හානි සිදුවනු නොඅනුමානය!


1.3 උපදෙස් කසය: RIP රෙජිස්ටරය (The Instruction Pointer)

පැරණි 8-bit පරිගණක වල තිබූ "ප්‍රෝග්‍රෑම් කවුන්ටරය (Program Counter - PC)" x64 ව්‍යුහය තුල හඳුන්වන්නේ RIP (Instruction Pointer Register) ලෙසයි.

මූ තමයි බූරුවා මෙහෙයවන කසය! බූරුවා ඊළඟට කෑ යුතු බයිනරි ආහාරය (Instruction) තිබෙන මතක ලිපිනය (Memory Address) සැමවිටම අඩංගු වන්නේ RIP තුලය.

6 වන වාදය: මයික්‍රෝප්‍රොසෙසරය ක්‍රමාණුකූලත්වයට හුරුවී ඇත.
බූරුවා වැඩ කරන්නේ ඉතා පිළිවෙලටය. ඌ එක් උපදෙසක් කා (Execute කර) අවසන් වූ සැණින්, RIP රෙජිස්ටරය ස්වයංක්‍රීයව ඊළඟ උපදෙස ඇති ලිපිනය වෙත යොමු වේ.

ඔබට බූරුවාගේ මෙම ක්‍රමාණුකූල ගමන වෙනස් කිරීමට අවශ්‍ය නම් (උදාහරණයක් ලෙස යම් කොන්දේසියක් මත වෙනත් තැනකට පැනීමට), ඔබට RIP වෙත කෙලින්ම අගයන් ලිවිය නොහැක (උදාහරණයක් ලෙස mov rip, 100 වැනි දේ ලිවීම සපුරා තහනම්ය!).

ඒ වෙනුවට ඔබ JMP (Jump), CALL හෝ කොන්දේසි සහිත පැනීම් (Conditional Jumps - JE, JNE ආදී) භාවිතා කල යුතුය. එවිට බූරුවා තම කසය වන RIP ඔබ පවසන නව ලිපිනයට හරවා ගනී.


FASM x64 ඇසෙම්බ්ලි ප්‍රෝග්‍රෑමිං (FASM x64 Assembly Programming)

2 වන පරිච්ඡේදය: FASM සැකසුම සහ කේත නීති (Windows Target)


2.1 FASM යනු කුමක්ද? "Flat" ඇසෙම්බ්ලි වල සුන්දරත්වය (Why FASM? The Beauty of "Flat" Assembly)

මයික්‍රෝප්‍රොසෙසර බූරුවා සමඟ සෘජුවම ගනුදෙනු කිරීමට අපට උපකාර වන මෙවලම වන්නේ ඇසෙම්බ්ලර් (Assembler) එකයි. වෙළඳපොලේ MASM, NASM වැනි නොයෙකුත් ඇසෙම්බ්ලර් වර්ග තිබුණද, අප අපගේ ගමන සඳහා තෝරා ගන්නේ Flat Assembler (FASM) ය.

ඇයි FASM? අනෙකුත් ඇසෙම්බ්ලර් හරියට වැඩකට නැති නීති පනවන, වටයෙන් යන රජයේ නිලධාරීන් වැනිය. MASM හෝ NASM වලදී ඔබ මුලින්ම කේතය ලියා, එය ඇසෙම්බ්ල් කර වස්තු ගොනුවක් (Object File - .obj) සාදා, ඉන්පසු එය ලින්කර් (Linker) එකක් නැමති තවත් වෙනම ප්‍රෝග්‍රෑම් එකක් හරහා රන් කරවා අවසාන ක්‍රියාත්මක කල හැකි ගොනුවක් (.exe) සාදාගත යුතුය. මෙය හරියට කොළඹ වරායට එන බඩු ලොරි, තීරුබදු නිලධාරීන් කිහිපදෙනෙකු ලවා පරීක්ෂා කරවා ප්‍රමාද කරනවා වැනි කරදරකාරී ක්‍රියාවකි!

FASM යනු "Flat" ඇසෙම්බ්ලර් එකකි. මෙයින් අදහස් කරන්නේ ඌට වෙනම ලින්කර්ලා අවශ්‍ය නොවන බවයි! FASM විසින් ඔබ ලියන ඇසෙම්බ්ලි පෙළ කෙලින්ම කියවා, තත්පරයෙන් සියයෙන් පංගුවකින් වින්ඩෝස් වලට සෘජුවම ක්‍රියාත්මක කල හැකි PE64 (Portable Executable 64-bit) ගොනුවක් නිර්මාණය කර දෙයි. ඌ ඉතා සරල, කාර්යක්ෂම, කිසිදු බාහිර සැරසිලි නැති සූපවේදියෙකි. බූරුවාට අවශ්‍ය ආහාරය (Binary code) බඳුනටම කෙලින්ම බෙදා දීමට ඌ දනී.


2.2 සෙමිකෝලන් (😉: කේතයන්හි ආරක්ෂක වැට (Semicolons: The Fences of Code)

අප බූරුවා සමඟ සන්නිවේදනය කිරීමේදී, අප ලියන සියලුම දේ බූරුවාට ක්‍රියාත්මක කල නොහැක. අපට අපේ මොළය ආරක්ෂා කර ගැනීම සඳහා කේතය අස්සේ විවිධ සටහන් (Comments) තබා ගැනීමට සිදුවේ.

බූරුවාට දෙන විධානයන් සහ අපේ සටහන් එකිනෙකින් වෙන් කරන ආරක්ෂක වැට වන්නේ සෙමිකෝලන් (😉 සලකුණයි.

mov rax, 100   ; RAX රෙජිස්ටරය නැමති ලාච්චුවට 100 දමන්න.

සෙමිකෝලන් එකක් යෙදූ සැනින්, FASM විසින් එම පේළියේ ඉතිරි කොටස බූරුවාට නොපෙනෙන සේ වසා දමයි. බූරුවා දකින්නේ සෙමිකෝලන් එකට වම් පැත්තේ ඇති mov rax, 100 යන කොටස පමණි. දකුණු පැත්තේ ඔබ සිංහලෙන් හෝ ඉංග්‍රීසියෙන් ලියා ඇති "RAX එකට 100 දමන්න" යන්න බූරුවාට අදාළ නැත.

ඔබ සෙමිකෝලන් එකක් දැමීමට අමතක කළහොත්, බූරුවා එම සිංහල හෝ ඉංග්‍රීසි වචනද තමාගේ කෑමක් (විධානයක්) යැයි සිතා සැපීමට උත්සාහ කරයි. එහි ප්‍රතිඵලය වන්නේ "Invalid Instruction" කියා FASM ඇසෙම්බ්ලර් එක කෑගසා වැඩ වර්ජනය කිරීමයි!


2.3 සින්ටැක්ස් රීති: විධාන, කොමා සහ වරහන් (The Syntax Rules)

FASM හි කේත ලිවීමේදී පිළිපැදිය යුතු මූලික නීති කිහිපයක් ඇත.

1. විධානයේ ව්‍යුහය (Instruction Structure)

සැමවිටම ඇසෙම්බ්ලි විධානයක් ලියනු ලබන්නේ පහත ආකාරයටය:

විධානය  ගමනාන්තය, ප්‍රභවය
(Instruction Destination, Source)

එනම්, දත්ත ගමන් කරන්නේ දකුණේ සිට වමටයි!

mov rbx, 5    ; RBX (ගමනාන්තය) වෙත 5 (ප්‍රභවය) ඇතුල් කරන්න. rbx = 5

මෙතැනදී විධානය (mov) සහ පළමු රෙජිස්ටරය (rbx) අතර අවම වශයෙන් එක් හිස්තැනක් (Space) හෝ ටැබ් (Tab) එකක් තිබිය යුතුය. එමෙන්ම ගමනාන්තය සහ ප්‍රභවය වෙන් කිරීමට කොමාවක් (,) අනිවාර්යයෙන්ම යෙදිය යුතුය. කොමාව අමතක කිරීමෙන් බූරුවා ව්‍යාකූල වී 178 වන වාදය (කිසිවෙකු පරිපූර්ණ නැත!) සිහිපත් කරවනු ඇත.

2. වරහන් වල මැජික් එක: [...] (Brackets vs Immediate values)

ඇසෙම්බ්ලි ලියන බොහෝ නවකයින්ට මොළයට හානි සිදුවන්නේ (1 වන වාදය උල්ලංඝනය වන්නේ) මෙම වරහන් නීතිය නිසි ලෙස වටහා නොගැනීම නිසාය.

හොඳින් සිතන්න, RBX රෙජිස්ටරය තුල 0x2000 නැමති අගය ඇතැයි සිතමු.

Tip

කොළඹ ජැටියේ රීතිය (The Jetty Rule):
වරහන් නැතිව ලියූ විට ඔබ ගනුදෙනු කරන්නේ රෙජිස්ටරයේ ඇති අංකය සමඟ පමණි.
වරහන් [...] යෙදූ විට, ඔබ බූරුවාට අණ කරන්නේ එම අංකය ලිපිනයක් (Address) ලෙස සලකා, කොළඹ ජැටියේ (RAM) අදාළ ලිපිනයට ගොස් පෙට්ටිය ඇර එහි ඇති දත්ත රැගෙන එන ලෙසයි!


2.4 වින්ඩෝස් FASM වැඩසටහනක ව්‍යුහය (Structure of a Windows PE64 Program)

අප ලියන කේතය වින්ඩෝස් රාජසභාව තුල ක්‍රියාත්මක වීමට නම්, වින්ඩෝස් රජු බලාපොරොත්තු වන ව්‍යුහයකට අපේ කේතය සකස් කල යුතුය. පහත දැක්වෙන්නේ FASM මගින් වින්ඩෝස් කොන්සෝලයක් සඳහා ලියන සරලම වැඩසටහනක ව්‍යුහයයි:

; ==========================================================
; වින්ඩෝස් 64-bit සඳහා සරලම FASM වැඩසටහන
; ==========================================================

format PE64 console               ; අපි සාදන්නේ වින්ඩෝස් 64-bit කොන්සෝල් (Console) වැඩසටහනක් බව FASM ට කියයි
entry start                       ; ප්‍රෝග්‍රෑම් එක පටන් ගන්නා තැන "start" ලේබලය ලෙස සලකුණු කරයි

section '.code' code readable executable
start:
    ; අපේ බූරුවාට දෙන උපදෙස් මෙතැන ලියමු
    mov ecx, 0                    ; ExitCode = 0 (පළමු පරාමිතිය ECX රෙජිස්ටරයට)
    call [ExitProcess]            ; වින්ඩෝස් රාජ සභා නිලධාරියා අමතා ප්‍රෝග්‍රෑම් එක නවත්වන්න

section '.idata' import data readable writeable
    library kernel32, 'kernel32.dll'

    import kernel32,\
           ExitProcess, 'ExitProcess'

ව්‍යුහය පැහැදිලි කිරීම:

  1. format PE64 console: මෙයින් FASM සූපවේදියාට පවසන්නේ අපට අවශ්‍ය ලින්කර් කරදරයක් නැති වින්ඩෝස් PE (Portable Executable) 64-bit Console ගොනුවක් බවයි.
  2. entry start: බූරුවාට වැඩේ පටන් ගැනීමට සිතියමේ ලකුණු කර ඇති ආරම්භක කණුව (Entry Point) "start" ලෙස නම් කරයි.
  3. section '.code' ...: බූරුවාට කෑමට දෙන බයිනරි ආහාර (instructions/code) අඩංගු වන සිතියමේ කොටසයි. මෙයට කියවීමේ (readable) සහ ක්‍රියාත්මක කිරීමේ (executable) අවසර ඇත.
  4. section '.idata' ...: අපගේ ප්‍රෝග්‍රෑම් එකට වින්ඩෝස් රාජසභාවෙන් (Windows OS) ලබාගන්නා සහය සේවාවන් (APIs) ආනයනය (Import) කරගන්නා තොටුපලයි. මෙහිදී අප kernel32.dll නැමති නැවෙන් ExitProcess නැමති රාජකාරි නිලධාරියාව ආනයනය කර ගෙන ඇත.
  5. call [ExitProcess]: බූරුවාට රාජකාරිය අවසන් කර ගෙදර යාමට වින්ඩෝස් නිලධාරියා කැඳවයි.

මෙම ව්‍යුහය ඔබේ කේතයේ පදනම වේ. මෙය නිරවුල්ව තබා ගැනීමෙන් 1 වන වාදය උල්ලංඝනය වීම වළක්වා ගත හැක!


FASM x64 ඇසෙම්බ්ලි ප්‍රෝග්‍රෑමිං (FASM x64 Assembly Programming)

3 වන පරිච්ඡේදය: වැඩ බංකුව පුරවමු (දත්ත හැසිරවීම සහ මතකය)


3.1 MOV විධානය: බූරුවාගේ සවල (The MOV Instruction: The Donkey's Shovel)

අපේ මහා බූරුවා වැඩ කරන ස්ථානයේ ඇති ප්‍රධානතම මෙවලම වන්නේ MOV (Move) විධානයයි. ඌට මෙම විධානය හරියට සවලක් (Shovel) වැනිය. ඌ කරන්නේ මෙම සවලෙන් එක තැනක ඇති දත්ත ගෙන තවත් තැනක තැන්පත් කිරීමයි.

නමුත් මතක තබා ගන්න, බූරුවාට සවල් හැසිරවීමේදී වින්ඩෝස් රාජසභාවේ දැඩි නීති රීති මාලාවකට යටත් වීමට සිදුවේ. ඌට ඕනෑම විදියකට දත්ත හෙලවිය නොහැක.

අවසර ලත් සවල් පහරවල් (Allowed MOV Combinations):

  1. රෙජිස්ටරයක සිට තවත් රෙජිස්ටරයකට (Register to Register):

    mov rax, rbx       ; RBX ලාච්චුවේ ඇති දේ RAX ලාච්චුවට දමන්න
    

    මෙය ඉතා වේගවත්ය. බූරුවා තම බංකුව මතම ඇති ලාච්චු දෙකක් අතර දත්ත මාරු කරයි.

  2. නියත අගයක් රෙජිස්ටරයකට (Immediate to Register):

    mov rcx, 100       ; RCX ලාච්චුවට කෙලින්ම 100 අගය ඇතුල් කරන්න
    
  3. මතකයේ සිට රෙජිස්ටරයකට (Memory to Register):

    mov rdx, [rax]     ; RAX හි ඇති ලිපිනයට අදාළ මතක පෙට්ටියෙන් (RAM) බඩු ගෙන RDX ලාච්චුවට දමන්න
    
  4. රෙජිස්ටරයක සිට මතකයට (Register to Memory):

    mov [rsi], rdi     ; RDI ලාච්චුවේ ඇති බඩු RSI හි ඇති ලිපිනයට අදාළ මතක පෙට්ටියට (RAM) දමන්න
    
  5. නියත අගයක් මතකයට (Immediate to Memory):

    mov dword [rax], 500 ; RAX හි ඇති ලිපිනයට අදාළ මතක පෙට්ටියට 500 අගය දමන්න
    
    Important

    පෙට්ටියේ ප්‍රමාණය හැඟවීමේ රීතිය (Size Directives Rule):
    ඔබ mov [rax], 500 ලෙස පමණක් ලියුවහොත් බූරුවා වහාම වැඩ නතර කර ව්‍යාකූල වේ! ඌ අසන්නේ: "මහා ස්වාමීනි, මම මෙම 500 අගය දැමිය යුත්තේ කොතරම් විශාල පෙට්ටියකටද? බයිට් 1ක (byte) පෙට්ටියකටද? බයිට් 2ක (word) පෙට්ටියකටද? බයිට් 4ක (dword) පෙට්ටියකටද? නැතහොත් බයිට් 8ක (qword) පෙට්ටියකටද?"
    ඌට ප්‍රමාණය නොකිව්වොත් ඌ එම බර පෙට්ටිය තම කුර මත හෙළාගෙන 1 වන වාදය උල්ලංඝනය කරගනී. එබැවින් අප සැමවිටම ප්‍රමාණය පැහැදිලි කල යුතුය:

    • byte [rax] (බිට් 8)
    • word [rax] (බිට් 16)
    • dword [rax] (බිට් 32)
    • qword [rax] (බිට් 64)

බූරුවාගේ සීමාව (The Forbidden Shovel Move):

මතකයේ සිට මතකයට සෘජුවම දත්ත මාරු කිරීම සපුරා තහනම්ය!

mov [rax], [rbx]      ; !!! මෙය වැරදියි (Syntax Error) !!!

බූරුවාට එකවර මතකයේ තැන් දෙකක් සමඟ ගනුදෙනු කල නොහැක. ඌට ඇත්තේ සවල් හැසිරවිය හැකි දෑත් දෙකක් පමණක් වන අතර ඌට එකවර ජැටියේ (RAM) පෙට්ටි දෙකක් ඇර බඩු මාරු කල නොහැක.

එබැවින් අප මුලින්ම ජැටියේ එක් පෙට්ටියක ඇති බඩු අපේ ට්‍රක් රථයකට (රෙජිස්ටරයකට) පටවාගෙන, ඉන්පසු එම ට්‍රක් රථයේ සිට අනෙක් පෙට්ටියට දැමිය යුතුය:

mov rcx, [rbx]        ; පළමුව RBX ලිපිනයේ ඇති දත්ත RCX ට්‍රක් රථයට පටවන්න
mov [rax], rcx        ; දෙවනුව RCX ට්‍රක් රථයේ ඇති දත්ත RAX ලිපිනයට බාන්න

3.2 කොළඹ නැව් තොටුපල සහ RAM (The Colombo Jetty Metaphor)

දැන් අප අපගේ සුප්‍රසිද්ධ කොළඹ නැව් තොටුපල (Colombo Jetty) උපමාව වෙත හැරෙමු.

පරිගණකයේ ඇති ප්‍රධාන මතකය (RAM - Random Access Memory) යනු කොළඹ නැව් තොටුපලයි. එහි එක පෙළට සකස් කරන ලද, අනුක්‍රමික අංක සහිත බිලියන ගණනක තොටුපල පෙට්ටි (Memory Locations / Addresses) ඇත.

සෑම පෙට්ටියකටම අනන්‍ය වූ ලිපිනයක් (Address) ඇත. මෙම ලිපිනය 0 සිට 18,446,744,073,709,551,61518,446,744,073,709,551,615 දක්වා වූ 64-bit අංකයකි.

අපේ රෙජිස්ටර (RAX, RBX ආදිය) යනු මෙම ජැටිය පුරා ධාවනය වන කුඩා දත්ත ප්‍රවාහන ට්‍රක් රථ වේ.

FASM හි දත්ත අංශය (Data Section) තුල අපට මේවා මෙසේ ප්‍රකාශ කල හැක:

section '.data' data readable writeable
    sugar_box    db 25                ; බයිට් 1ක සීනි පෙට්ටියක් (අගය 25)
    rice_bag     dw 1000              ; බයිට් 2ක සහල් මල්ලක් (අගය 1000)
    gold_bar     dd 500000            ; බයිට් 4ක රන් බාර් එකක් (අගය 500000)
    ship_cargo   dq 0x11223344556677  ; බයිට් 8ක විශාල නැව් බඩු පෙට්ටියක්

මෙම පෙට්ටි කියවීමට නම් බූරුවාට වරහන් [...] ආධාරයෙන් ජැටිය වෙත යාමට නියෝග කල යුතුය:

mov al, [sugar_box]     ; sugar_box පෙට්ටියෙන් බයිට් 1ක දත්ත AL සාක්කුවට ගන්න
mov rbx, [ship_cargo]   ; ship_cargo පෙට්ටියෙන් බයිට් 8ක දත්ත RBX ලාච්චුවට ගන්න

3.3 RIP-සම්බන්ධිත ලිපින ක්‍රමය (RIP-Relative Addressing)

වින්ඩෝස් 64-bit රාජධානියේ (PE64) ඉතා වැදගත් ආරක්ෂක නීතියක් ක්‍රියාත්මක වේ. එය ASLR (Address Space Layout Randomization) ලෙස හඳුන්වයි.

මුහුදු කොල්ලකරුවන් (Hackers) අපගේ මතක තොටුපලට කඩා වැදී රන් බාර් සොරකම් කිරීම වැළැක්වීම සඳහා, වින්ඩෝස් රජු විසින් අපගේ ප්‍රෝග්‍රෑම් එක කොළඹ ජැටියේ ක්‍රියාත්මක වන සෑම වාරයකදීම ඌට කැමති අහඹු ස්ථානයක (Random base address) තැන්පත් කරයි. අද 0x1000 ලිපිනයේ තිබූ ප්‍රෝග්‍රෑම් එක හෙට 0x9000 ලිපිනයට මාරු විය හැක!

මෙවැනි තත්වයක් යටතේ, අපේ දත්ත පෙට්ටි තිබෙන නිශ්චිත ස්ථානය (Absolute Address) කල්තියා දැනගැනීමට ක්‍රමයක් නැත. බූරුවා ව්‍යාකූල වනු ඇත!

මේ සඳහා යොදාගන්නා අපූරු විසඳුම වන්නේ RIP-සම්බන්ධිත ලිපින ක්‍රමය (RIP-Relative Addressing) ය.

බූරුවාට තම කසය වන RIP (Instruction Pointer) සැමවිටම තමා ලඟ ඇති බැවින්, තමා වර්තමානයේ සිටින ස්ථානය ඌ දනී. ඌ තම දත්ත පෙට්ටි සොයා ගන්නේ තමා සිටින තැන සිට ඇති දුර (Displacement) ගණනය කිරීමෙනි.

උදාහරණයක් ලෙස:
"මම දැන් සිටින තැන (RIP) සිට හරියටම පියවර 500ක් ඉදිරියෙන් sugar_box පෙට්ටිය ඇත."

ප්‍රෝග්‍රෑම් එක ජැටියේ කොතැනක තැන්පත් කලද, කේතය (Instructions) සහ දත්ත (Data) අතර ඇති පරතරය වෙනස් නොවේ. එබැවින් බූරුවාට තම RIP කසය පදනම් කරගෙන පහසුවෙන්ම දත්ත සොයාගත හැක.

FASM හිදී අපට මෙම සංකීර්ණ ගණිතය කිරීමට අවශ්‍ය නැත. FASM විසින් මෙය ස්වයංක්‍රීයව සිදුකරයි:

mov eax, [sugar_box]   ; FASM විසින් මෙය පසුබිමින් [rip + sugar_box] ලෙස සකසයි

මෙමගින් බූරුවා කිසිදු ව්‍යාකූලත්වයකින් තොරව, වින්ඩෝස් ජැටියේ ඕනෑම තැනක සිට තම රාජකාරිය සාර්ථකව නිම කරයි!


4 වන පරිච්ඡේදය: බූරුවාගේ ගණිතය සහ ලූප (Arithmetic & Loops)

ආයුබෝවන් මිතුරනි! ඔන්න අපි පැමිණ සිටින්නේ අපේ පොතේ ඉතාමත්ම රසවත් සහ වැදගත් පරිච්ඡේදයකටයි. මතකද අපේ 2 වන වාදය: x64 මයික්‍රෝප්‍රොසෙසරය බූරුවෙකි (The x64 CPU is a Donkey)? මූට තනිවම හිතන්න බැහැ. හැබැයි මූ සංඛ්‍යා හපන්න උපන් හපනෙක්! (Number Cruncher). අපේ මේ සිලිකන් බූරුවාට එකතු කරන්න, අඩු කරන්න, ගුණ කරන්න සහ බෙදන්න පුළුවන්. හැබැයි ඌ ඒ හැමදේම කරන්නේ ඔබ ඌට දෙන නියෝග වලට අකුරටම අනුව විතරයි.

මේ පරිච්ඡේදයේදී අපි බූරුවාට ගණිතය උගන්වන්නේ කොහොමද, බෙදීමේදී තියෙන භයානක උගුල් වලින් බේරෙන්නේ කොහොමද, සහ ඌව එකම වටේ රවුම් ගස්වන්නේ (Loops) කොහොමද කියලා ඉතාමත් විස්තරාත්මකව බලමු. 1 වන වාදය සැමවිටම මතක තබාගන්න: මොළයට හානි සිදුකර ගැනීමෙන් වලකින්න!


4.1 සංඛ්‍යා හැපීම: ගණිතමය විධාන (Arithmetic Operations)

x64 මහා බූරුවාට තම වැඩ බංකුවේ ඇති රෙජිස්ටර (Registers) සහ මතක ලිපින (Memory Addresses) වල ඇති අංක සමඟ සෙල්ලම් කිරීමට ප්‍රධාන ආයුධ කිහිපයක් තිබේ.

4.1.1 එකතු කිරීම (ADD) සහ අඩු කිරීම (SUB)

මේවා ඉතාමත් සරලයි. හරියට බූරුවාට කෑම බාල්දියක් දෙනවා වගෙයි.

Important

බූරුවාගේ දෑත් දෙකේ රීතිය:
අපේ බූරුවාට එකවර වැඩ කල හැක්කේ එක මතක ලිපිනයක් (Memory Address) සමඟ පමණි. එනම්, ඔබට add [mem1], [mem2] ලෙස මතක ලිපින දෙකක් කෙලින්ම එකතු කළ නොහැක! ඌට මතකයෙන් මතකයට කෙලින්ම බඩු ඇදීමට ශක්තියක් නැත. ඔබ මුලින්ම එක් මතක අගයක් රෙජිස්ටරයකට පටවාගෙන, පසුව එය අනෙකට එකතු කල යුතුය.


4.1.2 එකකින් වැඩි කිරීම (INC) සහ අඩු කිරීම (DEC)

ඔබට කිසියම් අගයක් 1කින් පමණක් වැඩි කිරීමට හෝ අඩු කිරීමට අවශ්‍ය වූ විට add rax, 1 හෝ sub rax, 1 ලිවීම මොළයට කරදරයකි (1 වන වාදය). ඒ සඳහා කෙටි ක්‍රම දෙකක් තිබේ:

Warning

සිලිකන් කොඩිවල රහස (RFLAGS Quirk):
INC සහ DEC විධාන මඟින් Carry Flag (CF) (කැරි කොඩිය) වෙනස් නොකරයි! මෙය එකතු කිරීමේ සහ අඩු කිරීමේ සාමාන්‍ය රීති වලට වඩා වෙනස්ය. බූරුවාගේ පැරණි මුතුන් මිත්තන්ගේ සිට පැමිණි මේ ක්‍රමය ලූප වලදී ඉතා ප්‍රයෝජනවත් වේ.


4.1.3 සයින්ඩ් ගුණ කිරීම (IMUL - Signed Multiplication)

ගුණ කිරීමේදී x64 ප්‍රොසෙසරයට ක්‍රම කිහිපයක් ඇත. අප මෙහිදී භාවිතා කරන්නේ සයින්ඩ් (Signed - සලකුණක් සහිත, එනම් ධන/සෘණ) ගුණ කිරීම සඳහා වන IMUL විධානයයි.

FASM හි IMUL ආකාර තුනකින් ලිවිය හැක:

  1. එක් පරාමිතියක් සහිත ආකාරය (Single-operand):

    imul rbx         ; RAX = RAX * RBX
    

    මෙහිදී බූරුවා සිතන්නේ ඔබ ගුණ කරන්නේ RAX සමඟ කියාය. ගුණ කර ලැබෙන අවසාන අගය බිට් 64 සීමාව ඉක්මවා යා හැකි බැවින්, බූරුවා ප්‍රතිඵලය බිට් 128ක රෙජිස්ටර යුගලයක් වන RDX:RAX හි ගබඩා කරයි. (පහළ බිට් 64 RAX හිද, ඉහළ බිට් 64 RDX හිද ඇත).

  2. පරාමිතීන් දෙකක් සහිත ආකාරය (Two-operand):

    imul rax, rbx    ; RAX = RAX * RBX
    

    මෙහිදී ගුණ කර ලැබෙන අගය කෙලින්ම පළමු රෙජිස්ටරයට (RAX) ලියවේ. ඉහළ කොටස ඉවත දමනු ලැබේ.

  3. පරාමිතීන් තුනක් සහිත ආකාරය (Three-operand):

    imul rax, rbx, 5 ; RAX = RBX * 5
    

    මෙහිදී RBX හි ඇති අගය 5න් ගුණ කර ප්‍රතිඵලය RAX තුල තැන්පත් කරයි.


4.1.4 සයින්ඩ් බෙදීම (IDIV - Signed Division) සහ RDX:RAX රීතිය

මෙන්න අපේ බූරුවා බොහෝ දෙනෙකුට මොළයේ හානි (Brain Damage) සිදුකරන තැන! බෙදීම සඳහා භාවිතා කරන්නේ IDIV (Signed Division) විධානයයි. බූරුවාට බෙදීමක් කිරීමට නම් ඉතා දැඩි නීති රීති මාලාවක් අනුගමනය කල යුතුය.

බූරුවාගේ බෙදීමේ නීතිය:
ඔබ 64-bit අගයකින් බෙදන විට, බෙදීමට ලක්වන සංඛ්‍යාව (Dividend) බිට් 128ක් විය යුතුය! එම බිට් 128 තැන්පත් විය යුත්තේ RDX:RAX යන රෙජිස්ටර දෙක ඒකාබද්ධ කිරීමෙනි.

|-----------------------------------------------|
|          RDX (ඉහළ බිට් 64) | RAX (පහළ බිට් 64)          |  = 128-bit Dividend
|-----------------------------------------------|

ඔබට බෙදීමට අවශ්‍ය වන්නේ RAX තුල ඇති 64-bit අගයක් පමණක් නම්, ඔබ බෙදීමට පෙර අනිවාර්යයෙන්ම RDX රෙජිස්ටරය නිවැරදිව සකස් කල යුතුය.

  1. ඔබ බෙදන්නේ ධන සංඛ්‍යා පමණක් නම් සහ RDX හි ඇති කුණු දත්ත ඉවත් කිරීමට අවශ්‍ය නම්, xor rdx, rdx මඟින් RDX ශුන්‍ය කරන්න.
  2. නමුත් සැබෑ සයින්ඩ් බෙදීමේදී (Signed Division), ඔබ RAX හි ඇති අගයේ සලකුණ (Sign bit - ධන ද සෘණ ද යන්න) RDX දක්වා දිගු කල යුතුය. ඒ සඳහා CQO (Convert Quadword to Octoword) නැමැති විශේෂ විධානය භාවිතා කරයි.

බූරුවා බෙදා අවසන් වූ පසු ප්‍රතිඵල දෙකක් ලබා දෙයි:

වැඩ කරන ආකාරය පෙන්වන සම්පූර්ණ උදාහරණයක්:

mov rax, 100     ; බෙදීමට අවශ්‍ය අගය (100) RAX ට දමන්න
cqo              ; RAX හි සලකුණ RDX දක්වා දිගු කරන්න (RDX:RAX = 100)
mov rbx, 7       ; බෙදන සංඛ්‍යාව (7) RBX ට දමන්න
idiv rbx         ; RDX:RAX අගය RBX න් බෙදන්න (100 / 7)

; දැන්:
; RAX = 14 (බෙදුම් ඵලය - Quotient)
; RDX = 2  (ඉතිරිය - Remainder)
Caution

බෙදීමට පෙර RDX සකස් නොකළහොත්, RDX හි ඇති කුණු දත්ත නිසා බෙදුම් අගය අතිවිශාල වී බූරුවා කලබල වේ. එවිට Windows විසින් Integer Division by Zero හෝ Overflow Exception එකක් ලබාදී ඔබේ ප්‍රෝග්‍රෑම් එක බසයෙන් එලියට ඇද දමනු ඇත!


4.2 බූරුවා හැරවීම: ලේබල් (Labels) සහ පැනීම් (Jumps)

මතකද අපේ 6 වන වාදය: මයික්‍රෝප්‍රොසෙසරය ක්‍රමාණුකූලත්වයට හුරුවී ඇත (The CPU is addicted to order)? ඌ සැමවිටම ඊළඟ උපදෙස වෙත ක්‍රමාණුකූලව ගමන් කරයි. බූරුවාට වෙනත් පාරක යන්නට කිව යුත්තේ JUMP විධානයක් මගිනි.

4.2.1 මඟ සලකුණු: ලේබල් (Labels)

අපට පැනීමට අවශ්‍ය ස්ථාන සලකුණු කිරීමට ලේබල් යොදා ගනී. FASM හි ලේබලයක් ලියන්නේ නම ලියා අගට කෝලන් : ලකුණ තැබීමෙනි.

my_destination:  ; මෙය ලේබලයක්

FASM හි දේශීය ලේබල් (Local Labels):
ඔබ ප්‍රෝග්‍රෑම් එක පුරාම loop_start, exit වැනි නම් නැවත නැවත ලියන විට නම් එකිනෙක පැටලී මොළයට හානි විය හැක. FASM හි මෙයට කදිම විසඳුමක් ඇත. ලේබලයකට මුලින් තිතක් (.) තැබූ විට එය දේශීය ලේබලයක් (Local Label) බවට පත්වේ. එය වලංගු වන්නේ ඊට ඉහළින්ම ඇති සාමාන්‍ය ලේබලයට පමණි.

start:
.loop:           ; start.loop ලෙස ක්‍රියා කරයි
    dec rcx
    jnz .loop

another_func:
.loop:           ; another_func.loop ලෙස ක්‍රියා කරයි. කිසිම ගැටලුවක් නැත!
    inc rbx
    jmp .loop

4.2.2 කොන්දේසි විරහිතව පැනීම (JMP - Unconditional Jump)

කිසිදු ප්‍රශ්නයක්, කොන්දේසියක් නැතිව කෙලින්ම ලේබලයකට පැනීමට මෙය භාවිතා කරයි.

jmp my_destination

4.2.3 කොන්දේසි සහිතව පැනීම (Conditional Jumps)

මෙහිදී බූරුවා පනින්නේ ඊට පෙර සිදුවූ ගණනය කිරීමකදී ඔසවන ලද "සිලිකන් කොඩි" (RFLAGS) දෙස බලා ඒවා සත්‍ය නම් පමණි. (කොඩි ගැන 5 වන පරිච්ඡේදයේදී වඩාත් විස්තරාත්මකව කතා කරමු).

නිතර භාවිතා වන කොන්දේසි සහිත පැනීම් කිහිපයක් මෙසේය:


4.3 වටේ යාම: ලූප නිර්මාණය (Loops)

පරිගණකයක ඇති ප්‍රධානම ප්‍රයෝජනයක් වන්නේ එකම වැඩේ නැවත නැවත ඉතා වේගයෙන් කිරීමයි. බූරුවා රවුමේ දිවවීමට (Loop) අප බහුලවම භාවිතා කරන්නේ RCX රෙජිස්ටරය ගණකය (Counter) ලෙස යොදාගැනීමයි.

අප සාමාන්‍යයෙන් කරන්නේ:

  1. RCX ලාච්චුවට ලූපය දිවිය යුතු වාර ගණන දැමීම.
  2. වැඩ කොටස සිදු කිරීම.
  3. dec rcx මඟින් ගණකය 1කින් අඩු කිරීම.
  4. jnz (Jump if Not Zero) මඟින් RCX තවමත් 0 වී නැත්නම් නැවත ලූපයේ ආරම්භයට පැනීම.

1 සිට 10 දක්වා වූ සංඛ්‍යා ලූපයකින් එකතු කරන ආකාරය (1+2+3+...+10 = 55):

    xor rax, rax     ; RAX = 0 (එකතුව තබා ගන්නා ලාච්චුව)
    mov rcx, 10      ; RCX = 10 (ගණකය 10 සිට ආරම්භ වේ)

.sum_loop:
    add rax, rcx     ; RAX = RAX + RCX (10, 9, 8... එකතු වේ)
    dec rcx          ; RCX = RCX - 1
    jnz .sum_loop    ; RCX තවමත් 0 නොවේ නම් .sum_loop වෙත ආපසු පනින්න

    ; ලූපයෙන් ඉවත් වන විට RAX හි 55 (Hex 0x37) අගය ඇත!

දැක්කා නේද? අපේ බූරුවා ඉතාමත් සතුටින් රවුමේ දුවමින්, තත්පරයෙන් බිලියනයකින් පංගුවකදී ගණනය කිරීම් අවසන් කරයි.

ඊළඟ පරිච්ඡේදයේදී අපි බූරුවා මේ තීරණ ගැනීමට බලන "සිලිකන් කොඩි" (Flags) සහ ඒවා පාලනය කරන්නේ කෙසේද කියා බලමු. සෙමෙන් ඉදිරියට යමු, මොළය පරිස්සම් කරගනිමින්!


5 වන පරිච්ඡේදය: කොඩි එසවීම (The RFLAGS Register)

පෙර පරිච්ඡේදයේදී අපි කොන්දේසි සහිතව පැනීම (Conditional Jumps) ගැන කෙටියෙන් ඉගෙන ගත්තෙමු. නමුත් බූරුවා මේ තීරණ ගන්නේ කෙසේද? ඌට ඇස් දෙකක් තිබේද? නැත! ඌට ඇත්තේ නාවිකයන්ගේ සිලිකන් කොඩි (RFLAGS Register) පමණි.

මෙම පරිච්ඡේදයේදී අපි බූරුවාගේ කොඩි කුටීරය හෙවත් RFLAGS රෙජිස්ටරය ක්‍රියාත්මක වන ආකාරයත්, CMP සහ TEST විධාන වලින් කොඩි හසුරුවන ආකාරයත්, JUMP භාවිතා නොකර බූරුවාට තීරණ ගැනීමට උගන්වන CMOVcc විධානයත් ඉතාමත් පැහැදිලිව ඉගෙන ගනිමු. මතක තබාගන්න 78 වන වාදය: බාල්දිය උතුරා ගියහොත් කොඩියක් ඔසවන්න! (When the bucket overflows, raise the red flag!).


5.1 නෞකාවේ සිලිකන් කොඩි (RFLAGS Register)

අපේ x64 මහා බූරුවා ගණිතමය හෝ තාර්කික වැඩක් කල සැණින්, ඌ කල කාර්යයේ ප්‍රතිඵලය විස්තර කිරීමට කොඩි කිහිපයක් ඉහළට ඔසවයි. මෙම කොඩි සියල්ල එකතු කර තබා ගන්නා විශේෂ ලාච්චුවක් තිබේ. එය RFLAGS රෙජිස්ටරය ලෙස හඳුන්වයි.

අපට ඉතාමත් වැදගත් වන කොඩි 4ක් මෙන්න:

+-----------------------------------------------------------+
|                        RFLAGS                             |
+---------+---------+---------+---------+-------------------+
| OF (3)  | SF (2)  | ZF (1)  | CF (0)  |  (අනෙකුත් කොඩි...)  |
+---------+---------+---------+---------+-------------------+

1. ශුන්‍ය කොඩිය (Zero Flag - ZF)

ඔබ කල ගණනය කිරීමක අවසාන ප්‍රතිඵලය හරියටම බින්දුව (0) වුවහොත්, බූරුවා වහාම තම ශුන්‍ය කොඩිය ඔසවයි (ZF = 1). ප්‍රතිඵලය 0 නොවේ නම් කොඩිය පහත් කරයි (ZF = 0).

2. කැරි කොඩිය (Carry Flag - CF)

අපේ බූරුවා නියමිත බිට් ප්‍රමාණය ඉක්මවා ගණන් හැදීමේදී අගයක් සීමාවෙන් එලියට පනින විට හෝ අඩු කිරීමකදී ණයට (Borrow) ගැනීමට සිදුවන විට මෙම කොඩිය ඔසවයි (CF = 1). මෙය අත්සනක් නැති සාමාන්‍ය අංක (Unsigned numbers) සඳහා සීමාව ඉක්මවා යාම සලකුණු කරයි.

3. සයින් කොඩිය (Sign Flag - SF)

අවසාන ප්‍රතිඵලය සෘණ අගයක් (Negative) වුවහොත් බූරුවා සයින් කොඩිය ඔසවයි (SF = 1). ප්‍රතිඵලයේ ඉහළම බිට් එක (Sign Bit) 1 වුවහොත් මූ කෙලින්ම මෙම කොඩිය ඔසවයි.

4. ඕවර්ෆ්ලෝ කොඩිය (Overflow Flag - OF)

අත්සනක් සහිත සංඛ්‍යා (Signed numbers) එකතු කිරීමකදී හෝ අඩු කිරීමකදී බාල්දිය උතුරා ගියහොත් (එනම් ධන අගයන් දෙකක් එකතු කර සෘණ අගයක් ලැබීම වැනි දෙයක් සිදුවුවහොත්) බූරුවා ඕවර්ෆ්ලෝ කොඩිය ඔසවයි (OF = 1).


5.2 විමර්ශකයෝ: CMP සහ TEST

බොහෝ විට අපට අවශ්‍ය වන්නේ රෙජිස්ටරයක ඇති අගය වෙනස් නොකර, වෙනත් අගයක් සමඟ සසඳා බලා කොඩි පමණක් එසවීමටයි. ඒ සඳහා අපට විශේෂ විමර්ශකයෝ දෙදෙනෙක් සිටිති.

5.2.1 CMP විධානය (Compare)

cmp destination, source

CMP විධානය ක්‍රියා කරන්නේ හරියට SUB (අඩු කිරීම) විධානය වගෙයි. ඌ destination අගයෙන් source අගය අඩු කරයි. හැබැයි ඌ ලැබෙන උත්තරය කොහේවත් ලියන්නේ නැත! ඌ කරන්නේ අඩු කර බලා ලැබෙන ප්‍රතිඵලයට අනුව RFLAGS හි කොඩි පමණක් වෙනස් කිරීමයි.

භාවිතා කරන ආකාරය:

mov rax, 50
cmp rax, 50      ; 50 සහ 50 සසඳන්න (50 - 50 = 0)
je .equal        ; ZF = 1 බැවින් .equal වෙත පනින්න

5.2.2 TEST විධානය (Logical Compare)

test destination, source

TEST විධානය ක්‍රියා කරන්නේ තාර්කික AND (Logical AND) ක්‍රියාවලියක් ලෙසයි. ඌත් ප්‍රතිඵලය කොහේවත් ලියන්නේ නැත, කොඩි පමණක් වෙනස් කරයි.

බොහෝ විට ඇසෙම්බ්ලි වලදී කිසියම් රෙජිස්ටරයක් බින්දුව (0) ද කියා පරීක්ෂා කිරීමට TEST භාවිතා කරයි.

test rax, rax

රෙජිස්ටරයක් තමන් සමඟම AND කල විට ප්‍රතිඵලය වෙනස් නොවේ. හැබැයි RAX හි 0 තිබුණොත්, තාර්කිකව ලැබෙන ප්‍රතිඵලය 0 වන බැවින් ZF = 1 වේ.

භාවිතා කරන ආකාරය:

test rax, rax    ; RAX බින්දුවද කියා බලන්න
jz .is_zero      ; 0 නම් (ZF = 1) .is_zero වෙත පනින්න

5.3 කොන්දේසි සහිත මාරුවීම් (CMOVcc - Conditional Moves)

අපේ x64 මහා බූරුවා වේගයට අතිශයින්ම ඇබ්බැහි වූ වැඩ පිස්සෙකි (3 සහ 4 වන වාදයන්). ඌ කොතරම් වේගයෙන් දුවනවාද කිවහොත්, කේතයේ JUMP එකක් හමුවූ විට, කොඩි පරීක්ෂා කර අවසන් වන තෙක් නොසිට, ඌට හිතෙන පාරක කලින්ම දුවන්නට පටන් ගනී! මෙයට ශාඛා පුරෝකථනය (Branch Prediction) යැයි කියනු ලැබේ.

නමුත් ඌ ගිය පාර වැරදි වුවහොත්, ඌට ආපසු හැරී පැමිණීමට සිදුවේ. එවිට ඌට හති වැටේ (වේගය බාල වේ). මෙය මඟ හැරීමට නවීන ප්‍රොසෙසර වලට කදිම උපක්‍රමයක් හඳුන්වා දී ඇත: CMOVcc (Conditional Move).

CMOVcc මඟින් කරන්නේ JUMP එකක් නොදා, කොඩියක් එසවී ඇත්නම් පමණක් දත්ත මාරු කිරීමයි.

cmovcc destination, source

මෙහි cc වෙනුවට කොන්දේසිය යෙදේ:

උදාහරණයක් ලෙස: අපට RAX සහ RBX අතරින් විශාලතම අංකය RCX තුලට ගැනීමට අවශ්‍ය යැයි සිතමු.

පැරණි ක්‍රමය (JUMP භාවිතා කර):

    mov rcx, rax     ; මුලින්ම RAX අගය RCX ට ගන්න
    cmp rbx, rax     ; RBX සහ RAX සසඳන්න
    jle .done        ; RBX කුඩා හෝ සමාන නම් .done වෙත පනින්න
    mov rcx, rbx     ; නැත්නම් RBX අගය RCX ට ගන්න
.done:

මෙහිදී බූරුවාට JUMP එකක් කිරීමට සිදුවේ.

නවීන ක්‍රමය (CMOVcc භාවිතා කර):

    mov rcx, rax     ; RCX = RAX
    cmp rbx, rax     ; RBX සහ RAX සසඳන්න
    cmovg rcx, rbx   ; RBX විශාල නම් (Greater) පමණක් RCX = RBX කරන්න

මෙතැන JUMP නැත! බූරුවා පාර මාරු නොකර එක දිගට දුවයි. මෙය ඉතාමත් වේගවත්ය.

සිලිකන් කොඩි කියන්නේ බූරුවාගේ ඇස් කන් වගෙයි. මේවා නිවැරදිව භාවිතා කිරීමෙන් ඔබට බූරුවා ලවා ඉතාමත් සංකීර්ණ තීරණ ගත හැක.

ඊළඟ පරිච්ඡේදයේදී අපි ඇසෙම්බ්ලි වල ඇති වැදගත්ම ගබඩාව වන කැෆටේරියා තට්ටුව (The Stack) සහ උපක්‍රමශීලී උපලේඛන (Procedures/Functions) ගැන ඉගෙන ගනිමු!


6 වන පරිච්ඡේදය: කැෆටේරියා තට්ටුව (Stack Management)

පරිගණක වැඩසටහනක් ධාවනය වන විට දත්ත තාවකාලිකව ගබඩා කර ගැනීමට සහ එක එක වැඩ කොටස් (Subroutines / Functions) වලට පැන ආපසු පැමිණීමට විශේෂ මතක ප්‍රදේශයක් අවශ්‍ය වේ. x64 ලෝකයේ මෙම ප්‍රදේශය හඳුන්වන්නේ තට්ටුව (Stack) කියාය.

මෙම පරිච්ඡේදයේදී අපි තට්ටුව ක්‍රියාත්මක වන ආකාරයත්, RSP රෙජිස්ටරය, PUSH සහ POP විධාන ක්‍රියා කරන ආකාරයත්, උපලේඛන ඇමතීම සඳහා වන CALL සහ RET ක්‍රියාවලියත් ඉතාමත් රසවත් ලෙස ඉගෙන ගනිමු. මතක තබාගන්න 120 වන වාදය: ඔබ කල කුණු කන්දල් ඔබම අස් කරන්න, නැතහොත් ඊළඟට එන කෙනා ලිස්සා වැටෙනු ඇත!


6.1 කැෆටේරියා තැටි රඳවනය (The Stack)

අපේ තට්ටුව (Stack) හරියට කැෆටේරියා එකක පිඟන් හෝ තැටි ගොඩ ගසා ඇති ස්ප්‍රින් (Spring) සහිත මේසයක් වැනිය.

   |               |
   |   [ Plate3 ]  |  <- Top of Stack (RSP)
   |   [ Plate2 ]  |
   |   [ Plate1 ]  |
   +---------------+

මෙම ක්‍රමවේදයට තාක්ෂණිකව LIFO (Last-In, First-Out) හෙවත් "අවසානයට ඇතුල් වන දේ ප්‍රථමයෙන් පිටතට පැමිණීම" යැයි කියනු ලැබේ.


6.1.1 තට්ටු දර්ශකය (Stack Pointer - RSP)

අපේ x64 මහා බූරුවාට තට්ටුවේ මුදුන කොතැනද කියා බලා ගැනීමට විශේෂ දර්ශකයක් ඇත. එය RSP (Stack Pointer) රෙජිස්ටරයයි.

Warning

පහළට දුවන තට්ටුව (Descending Stack):
x64 මතකයේදී තට්ටුව වර්ධනය වන්නේ ඉහළ මතක ලිපින වල සිට පහළ මතක ලිපින දෙසටය. එනම්, ඔබ තට්ටුවට දත්ත දමන විට RSP හි ලිපින අගය අඩු වේ (කැෆටේරියා රඳවනය පහළට යයි). දත්ත පිටතට ගන්නා විට RSP හි ලිපින අගය වැඩි වේ!


6.1.2 තැටි තැබීම (PUSH) සහ පිටතට ගැනීම (POP)

භාවිතය පෙන්වන සරල උදාහරණයක්:

mov rax, 100
mov rbx, 200

push rax         ; තට්ටුවේ 100 ඇත
push rbx         ; තට්ටුවේ 200 (උඩින්ම) සහ 100 ඇත

pop rax          ; තට්ටුවෙන් 200 ඉවත් වී RAX ට යයි (දැන් RAX = 200)
pop rbx          ; තට්ටුවෙන් 100 ඉවත් වී RBX ට යයි (දැන් RBX = 100)

දැක්කා නේද? අගයන් දෙක මාරු විය!


6.2 උපලේඛන කැඳවීම: CALL සහ RET

විශාල ප්‍රෝග්‍රෑම් එකක් ලියන විට හැමදේම එකම තැන ලිවීම 1 වන වාදයට පටහැනිය (මොළයට හානි සිදුවේ!). අපි පොදු වැඩ කොටස් වෙනම කේත කොටස් ලෙස ලියන්නෙමු. මේවා උපලේඛන (Subroutines / Functions) ලෙස හඳුන්වයි.

මෙම උපලේඛන වලට පැන, වැඩේ කර අවසන් වී නැවත ආපසු පැමිණීමට බූරුවාට ක්‍රමයක් අවශ්‍ය වේ. ඒ සඳහා ඌ තට්ටුව (Stack) භාවිතා කරයි.

6.2.1 CALL විධානය (Subroutine Call)

call my_function

CALL විධානය ක්‍රියාත්මක වන විට බූරුවා:

  1. ඊළඟට ක්‍රියාත්මක වීමට තිබූ උපදෙසෙහි මතක ලිපිනය (Return Address - ආපසු පැමිණිය යුතු ලිපිනය) තට්ටුව මත තබයි (PUSH කරයි).
  2. my_function ලේබලය වෙත පනියි (JUMP කරයි).

6.2.2 RET විධානය (Return)

ret

උපලේඛනයේ අවසානයට RET හමුවූ විට බූරුවා:

  1. තට්ටුවේ මුදුනේ ඇති ආපසු පැමිණිය යුතු ලිපිනය පිටතට ගනියි (POP කරයි).
  2. එම ලිපිනය RIP (Instruction Pointer) රෙජිස්ටරයට දමා කෙලින්ම එතැනට පනියි.

ක්‍රියාකාරිත්වය පෙන්වන සම්පූර්ණ සැකසුම:

start:
    call .add_numbers    ; .add_numbers වෙත පනින්න, ආපසු පැමිණිය යුතු ලිපිනය තට්ටුවේ තබන්න
    ; (බූරුවා ආපසු පැමිණෙන්නේ මෙතැනටයි)
    ...
    call [ExitProcess]

.add_numbers:
    mov rax, 10
    add rax, 20
    ret                  ; තට්ටුවෙන් ලිපිනය POP කරගෙන නැවත start වෙත යන්න

6.3 අපිරිසිදු තට්ටුවේ ව්‍යසනය (The Dirty Stack Disaster)

මෙන්න 120 වන වාදය: ඔබේ කුණු කන්දල් ඔබම අස් කරන්න! (Clean up your own mess!).

උපලේඛනයක් තුලදී ඔබ යම් දත්තයක් තට්ටුවට දැමුවහොත් (PUSH), ඔබ උපලේඛනයෙන් ඉවත් වීමට (RET) පෙර අනිවාර්යයෙන්ම එය ඉවත් කල යුතුය (POP හෝ add rsp, 8).

වැරදි ක්‍රමය (කුණු තට්ටුව):

my_function:
    push rbx             ; RBX තට්ටුව මත තැබුවා (RSP අඩු විය)
    ; (මෙහිදී යම් වැඩක් කරන්න)
    ret                  ; අපොයි! RBX තවම තට්ටුවේ ඇත!

මෙහිදී සිදුවන්නේ කුමක්ද? RET ක්‍රියාත්මක වන විට බූරුවා සිතන්නේ තට්ටුවේ මුදුනේ ඇති RBX හි අගය ආපසු පැමිණිය යුතු ලිපිනය කියාය! ඌ කෙලින්ම RBX හි තිබූ අංකය මතක ලිපිනයක් ලෙස ගෙන එතැනට පනින්නට උත්සාහ කරයි.

එතැන ඇත්තේ කිසිසේත්ම වලංගු ලිපිනයක් නොවන බැවින්, Windows විසින් වහාම ක්‍රියාත්මක වී ප්‍රෝග්‍රෑම් එක බිම ඇද දමා Access Violation (ක්‍රෑෂ්) දෝෂයක් ලබාදේ. බූරුවා අතරමං වේ!

නිවැරදි ක්‍රමය:

my_function:
    push rbx             ; RBX සුරැකීම
    ; (වැඩ කොටස කරන්න)
    pop rbx              ; තට්ටුව පිරිසිදු කිරීම (ආපසු මුල් තත්වයට ගැනීම)
    ret                  ; දැන් තට්ටුවේ උඩින්ම ඇත්තේ නිවැරදි ආපසු යන ලිපිනයයි!

තට්ටුව යනු ඉතා වටිනා නමුත් අන්තරාදායක මෙවලමකි. එය පිරිසිදුව තබා ගැනීම ඔබ සතු වගකීමකි.

ඊළඟ පරිච්ඡේදයෙන් අපි වින්ඩෝස් රාජ සභාවේ ආචාරශීලී රීති (Windows x64 Calling Convention) සහ API භාවිතා කර වින්ඩෝස් වලට කතා කරන්නේ කෙසේද කියා ඉගෙන ගනිමු!


7 වන පරිච්ඡේදය: වින්ඩෝස් රාජ සභාවේ ආචාරශීලී රීති (Windows x64 Calling Convention)

මිත‍්‍රවරුනි! අප මෙතෙක් වේලා කතා කළේ අපේ වැඩ බංකුව උඩ අපට ඕනෑ විදිහට, අපේම සෙල්ලම් දමන ආකාරයයි. එනම් රෙජිස්ටර (Registers) සහ තට්ටුව (Stack) සමඟ අපේම කුඩා වපසරියක වැඩ කළ ආකාරයයි. නමුත් ඔබ සදාකාලිකවම තනිවම ජීවත් වන හුදකලා දූපත් වැසියෙක් නොවේ. ඔබට පරිගණකයේ තිරය මත අකුරක් මුද්‍රණය කිරීමට, යතුරු පුවරුවෙන් අකුරක් කියවීමට, නැතහොත් දෘඩ තැටියට (Hard Disk) දත්ත ලියන්නට සිදු වේ.

මේ හැම වැඩක්ම කරන්නේ කවුද? ඔව්, වින්ඩෝස් මෙහෙයුම් පද්ධතිය (Windows Operating System) නමැති මහා රජතුමාය!

ඔබට කිසියම් බාහිර කාර්යයක් කර ගැනීමට අවශ්‍ය වූ විට, ඔබ කෙලින්ම රජ වාසල වෙත ගොස් එහි සිටින ඇමතිවරුන් සහ රාජකීය සේවකයින් (Windows API Functions) ඇමතිය යුතුය. නමුත් මතක තබා ගන්න: වින්ඩෝස් රජතුමා අතිශය රීතිගරුක, දැඩි, සහ ආඩම්බරකාරයෙකි!

ඔබ රාජ සභාවට ඇතුළු වී එහි සිටින ඇමතිවරුන් සමඟ කතා බස් කළ යුත්තේ රජතුමා විසින් පනවා ඇති විශේෂ රාජ සභා ආචාරශීලී රීති මාලාවට අනුකූලවය. මෙම රීති මාලාව පරිගණක විද්‍යාවේදී Microsoft x64 Calling Convention හෝ x64 ABI (Application Binary Interface) ලෙස හඳුන්වයි.

ඔබ මේ ආචාරශීලී රීති එකක් හෝ උල්ලංඝනය කළහොත් සිදු වන්නේ කුමක්ද? රජ වාසලේ සිටින දරුණු ආරක්‍ෂකයින් (Windows Crash Handler) පැමිණ ඔබේ ප්‍රෝග්‍රෑම් එක බෙල්ලෙන් අල්ලා රජ වාසලෙන් එලියට ඇද දමනු ඇත! (එනම්, Access Violation හෝ Crash වීමකි).

එසේ නම්, අපේ 73 වන වාදය මතකයට නඟා ගනිමු: "වැරදි කකුලෙන් නැඟී සිටියහොත් බසය මගහැරී යනු ඇත."

රාජ සභාවේදී අප පිළිපැදිය යුතු ප්‍රධාන ආචාරශීලී රීති 3 ක් ඇත. ඒවා පිළිවෙලින් අධ්‍යයනය කරමු.


7.1 රාජකීය පණිවිඩකරුවන් සිව්දෙනා (Argument Passing Rules)

පැරණි 32-bit (x86) වින්ඩෝස් ලෝකයේදී, යම් ශ්‍රිතයකට (Function) පරාමිතීන් (Arguments / Parameters) යැවීමට භාවිතා කළේ තට්ටුවයි (Stack). එනම් සියලුම බඩු තට්ටුව උඩ ගොඩ ගසා, ඇමතිවරයාට ඒවා එකින් එක බා ගන්න යැයි පැවසුවෙමු.

නමුත් x64 මහා බූරුවාට මේ ක්‍රමය ඉතා කම්මැලි සහ මන්දගාමී ක්‍රියාවකි. ඌට වේගයෙන් දත්ත සැපයිය යුතුය (3 වන වාදය). එබැවින් වින්ඩෝස් රජතුමා විසින් පළමු පරාමිතීන් හතර රැගෙන යාම සඳහා අතිශය වේගවත්, සිලිකන් අශ්වයින් පිට යන රාජකීය පණිවිඩකරුවන් සිව්දෙනෙකු (Registers) නම් කර ඇත.

ඕනෑම Windows API එකක් කැඳවීමේදී, ඔබ ලබා දෙන පළමු පරාමිතීන් 4 මෙම රෙජිස්ටර තුළටම දැමිය යුතුය:

  1. පළමු පරාමිතිය (1st Argument): RCX රෙජිස්ටරය
  2. දෙවන පරාමිතිය (2nd Argument): RDX රෙජිස්ටරය
  3. තෙවන පරාමිතිය (3rd Argument): R8 රෙජිස්ටරය
  4. සිව්වන පරාමිතිය (4th Argument): R9 රෙජිස්ටරය
                  ┌─────────────────────────────────────┐
                  │      Windows API Parameters         │
                  └─────────────────────────────────────┘
                     │         │         │         │
                     ▼         ▼         ▼         ▼
                  ┌─────┐   ┌─────┐   ┌─────┐   ┌─────┐
                  │ RCX │   │ RDX │   │ R8  │   │ R9  │
                  └─────┘   └─────┘   └─────┘   └─────┘
                   1st Arg   2nd Arg   3rd Arg   4th Arg

වැදගත් නිවේදනයක්: ඔබ ඇමතීමට යන API එකට ඇත්තේ පරාමිතීන් 2ක් පමණක් නම්, ඔබ RCX සහ RDX පමණක් භාවිතා කරයි. R8 සහ R9 වලට ඕනෑම කුණු කන්දලක් තිබුණාට රජතුමාට ප්‍රශ්නයක් නැත.

ප්‍රශ්නය: පරාමිතීන් 4 ကට වඩා තිබේ නම් කුමක් කරමුද?

රජ සභාවේ නීතියට අනුව, 5 වන පරාමිතියේ සිට ඉදිරියට ඇති සියල්ල තට්ටුව (Stack) හරහා යැවිය යුතුය.
උදාහරණයක් ලෙස පරාමිතීන් 6 ක් ඇති API එකක් සලකමු.

අප මෙය ප්‍රායෝගිකව කරන අයුරු ඊළඟ පරිච්ඡේදයේදී WriteConsoleA භාවිතයෙන් විස්තරාත්මකව බලමු.


7.2 සෙවනැලි ඉඩ (Shadow Space / Home Space)

රාජ සභාවේ ඇති අමුතුම සහ බොහෝ නවකයන් අතරමං වන නීතිය මෙයයි.

ඔබ පරාමිතීන් 4ක් හෝ ඊට වඩා අඩු ගණනක් ඇති API එකක් කැඳවුවද, ඔබ අනිවාර්යයෙන්ම තට්ටුව (Stack) මත බයිට් 32ක ඉඩක් වෙන් කළ යුතුය! මෙම බයිට් 32 ක ඉඩ ප්‍රමාණය සෙවනැලි ඉඩ (Shadow Space) හෝ නිවාස ඉඩ (Home Space) ලෙස හඳුන්වයි.

sub rsp, 32    ; සෙවනැලි ඉඩ වෙන් කිරීම
call [SomeAPI]
add rsp, 32    ; සෙවනැලි ඉඩ නැවත පිරිසිදු කිරීම (120 වන වාදය!)

ඇයි මේ සෙවනැලි ඉඩක් අවශ්‍ය වන්නේ?

මහා බූරුවාගේ ක්‍රියාකාරිත්වය අනුව, API එක ඇතුලතදී RCX, RDX, R8, R9 යන රෙජිස්ටර වෙනත් වැඩ සඳහා ඉක්මනින් නිදහස් කර ගැනීමට සිදුවිය හැක. එවිට එම රෙජිස්ටර වල තිබූ අගයන් තාවකාලිකව ගබඩා කර තැබීමට API එක විසින් තට්ටුව මත අප වෙන්කළ බයිට් 32 ක ඉඩ භාවිතා කරයි.

රජතුමාගේ දැඩි නියෝගය: ඔබ අමතන API එකට කිසිදු පරාමිතියක් අවශ්‍ය නැති වුවද (උදාහරණයක් ලෙස පරාමිතීන් බිංදුවක් ඇති API එකක් වුවද), ඔබ මෙම බයිට් 32 ක සෙවනැලි ඉඩ තට්ටුව මත වෙන් කළ යුතුය! එසේ නොකළහොත් API එක තට්ටුවේ ඇති වෙනත් දත්ත (උදාහරණයක් ලෙස ආපසු යායුතු ලිපිනය - Return Address) මත තම දත්ත ලියා ඔබේ ප්‍රෝග්‍රෑම් එක සුණු විසුණු කර දමනු ඇත.


7.3 තට්ටු පෙළගැස්ම: බයිට් 16 සීමාවේ රහස (Stack Alignment)

මෙය අපේ 73 වන වාදයට පණ දෙන කොටසයි. වින්ඩෝස් x64 මෙහෙයුම් පද්ධතියේ ඇති සියලුම API ශ්‍රිතයන් නිර්මාණය කර ඇත්තේ එක්තරා ස්ථීර උපකල්පනයක් මතය: "API එක CALL කිරීමට පෙර, තට්ටු දර්ශකය (RSP) බයිට් 16 කට සමාන්තරව (Aligned) තිබිය යුතුය."

බයිට් 16 කට සමාන්තරව තිබීම යන්නෙන් අදහස් කරන්නේ RSP රෙජිස්ටරයේ ඇති ලිපිනයේ අවසාන ඉලක්කම (Hexadecimal වලින්) 0 විය යුතු බවයි. (උදාහරණයක් ලෙස: 0x000000A2C3F4E560).

නමුත් මෙහි ඇති තාක්ෂණික ගැටලුව කුමක්ද?

අප ප්‍රෝග්‍රෑම් එකේදී CALL විධානය ක්‍රියාත්මක කරන විට, ප්‍රොසෙසරය විසින් ස්වයංක්‍රීයව ඊළඟට ක්‍රියාත්මක විය යුතු උපදෙස් ලිපිනය (Return Address) තට්ටුව මතට දමයි (PUSH කරයි). ලිපිනයක් යනු බයිට් 8 ක දත්තයකි.

එවිට සිදු වන්නේ කුමක්ද? බයිට් 16 ට සමාන්තරව තිබූ RSP අගය, තව බයිට් 8 ක් අඩු වී බයිට් 8 කින් අවසන් වන ලිපිනයක් බවට පත් වේ (එනම්, අග 8 කින් අවසන් වේ).

අපේ ප්‍රෝග්‍රෑම් එක ආරම්භයේදී (Entry Point) RSP පවතින්නේ බයිට් 8 ක අසමාන්තර තත්ත්වයකය. අප දැන් API එකක් ඇමතීමට සූදානම් වන්නේ නම්, අපට අවශ්‍ය දේ සලකා බලමු:

  1. අපට සෙවනැලි ඉඩ සඳහා බයිට් 32 ක් අවශ්‍ය වේ.
  2. අපගේ තට්ටුව බයිට් 16 සීමාවට ගෙන ඒමට තවත් බයිට් 8 ක පිරවුමක් (Padding / Alignment) අවශ්‍ය වේ.

එබැවින්, 32 (Shadow Space) + 8 (Alignment) = 40 බයිට්!

අප ප්‍රෝග්‍රෑම් එක ආරම්භයේදීම තට්ටුවෙන් බයිට් 40 ක් අඩු කරන්නේ එබැවිනි:

sub rsp, 40    ; බයිට් 32 ක සෙවනැලි ඉඩ + බයිට් 8 ක පෙළගැස්ම

අපි මෙය ගණිතමය ලෙස තේරුම් ගනිමු.
ප්‍රෝග්‍රෑම් එක ආරම්භ වීමට පෙර RSP අගය 0x...00 (බයිට් 16 ට සමාන්තරව) යැයි සිතන්න.

  1. වින්ඩෝස් විසින් අපේ Entry Point එකට CALL කරන විට:
    • Return Address එක තට්ටුවට වැටේ.
    • RSP = 0x...F8 (දැන් බයිට් 8 කින් අසමාන්තරයි).
  2. අපගේ කේතයේ මුලදීම sub rsp, 40 කළ විට:
    • RSP = 0x...F8 - 40 (Hex 0x28) = 0x...D0.
    • අවසාන ඉලක්කම 0 වේ! එනම් දැන් RSP නැවතත් බයිට් 16 කට සමාන්තරය!
  3. දැන් අප API එකක් CALL කරන විට:
    • තට්ටුව නිසි ලෙස පෙළගස්වා ඇති බැවින් රජතුමා සතුටු වේ, ප්‍රෝග්‍රෑම් එක ක්‍රෑෂ් නොවේ.

120 වන වාදයේ මතක් කිරීම: ඔබ තට්ටුවෙන් බයිට් 40ක් අඩු කළේ නම්, ඔබේ කේතය අවසානයේදී RET කිරීමට පෙර අනිවාර්යයෙන්ම එම බයිට් 40 නැවත එකතු කළ යුතුය (add rsp, 40). නැතහොත් මහා බූරුවාට ආපසු යායුතු ලිපිනය සොයාගත නොහැකිව මිය යනු ඇත!


7.4 රාජ සභා ආචාර විධි පෙන්වන සැකිල්ලක්

පහත දැක්වෙන්නේ වින්ඩෝස් API ඇමතීමේදී භාවිතා කළ යුතු සම්මත ව්‍යුහයයි. මෙය ඔබේ මොළයට හානි සිදුවීම වළක්වා ගැනීමට හොඳම ආයුධයකි (1 වන වාදය).

format PE64 console
entry start

section '.code' code readable executable

start:
    ; 1. රාජ සභාවට ගරු කිරීම: බයිට් 40ක් වෙන් කර තට්ටුව පෙළගැස්වීම
    sub     rsp, 40

    ; 2. පරාමිතීන් සකස් කිරීම
    mov     rcx, 1      ; පළමු පරාමිතිය
    mov     rdx, 2      ; දෙවන පරාමිතිය
    mov     r8, 3       ; තෙවන පරාමිතිය
    mov     r9, 4       ; සිව්වන පරාමිතිය

    ; 3. රාජකීය ඇමතිවරයා ඇමතීම
    ; call    [SomeWindowsAPI]

    ; 4. තට්ටුව පිරිසිදු කර පෙර තිබූ තත්ත්වයට පත් කිරීම
    add     rsp, 40

    ; 5. ප්‍රෝග්‍රෑම් එක වසා දැමීම
    sub     rsp, 40     ; ExitProcess සඳහා නැවත වෙන් කිරීම
    xor     ecx, ecx    ; Exit code 0
    call    [ExitProcess]

මිත‍්‍රවරුනි, රාජ සභාවේ ආචාර විධි දැන් ඔබේ මොළයට ධාරණය වී ඇතැයි මම සිතමි. ඊළඟ පරිච්ඡේදයේදී අපි මෙම රීති ප්‍රායෝගිකව වින්ඩෝස් කොන්සෝලයේදී ක්‍රියාවට නංවමු!


8 වන පරිච්ඡේදය: රජුට කතා කිරීම (Windows Console APIs)

මිත‍්‍රවරුනි! පසුගිය පරිච්ඡේදයේදී අපි වින්ඩෝස් රාජ සභාවේ ආචාර විධි (Calling Convention) ඉගෙන ගත්තෙමු. දැන් අපට ඇත්තේ එම රීති භාවිතා කර රජතුමාගේ ඇමතිවරුන් සමඟ සැබවින්ම කතාබස් කිරීමයි.

අපට පරිගණක තිරය (Console) සමඟ ගනුදෙනු කිරීමට අවශ්‍ය වූ විට, අපට පිහිටට එන්නේ KERNEL32.DLL නමැති රාජකීය දෙපාර්තමේන්තුවයි. මෙම දෙපාර්තමේන්තුවේ අපේ වැඩ සඳහා ඉන්නා ප්‍රධාන ඇමතිවරුන් (APIs) හතර දෙනෙකු අපට හමු වේ:

  1. GetStdHandle (රාජකීය දොරටුපාලයා)
  2. WriteConsoleA (රාජකීය අණබෙරකරු)
  3. ReadConsoleA (රාජකීය ලේකම්වරයා)
  4. ExitProcess (රාජ සභාවෙන් සමුගැනීම)

අපි මෙම ඇමතිවරුන් එකින් එක හඳුනාගෙන, ඔවුන්ට අණ දෙන ආකාරය බලමු.


8.1 KERNEL32.DLL වෙතින් ඇමතිවරුන් කැඳවීම

FASM ඇසෙම්බ්ලර් එක තුල මෙම බාහිර ශ්‍රිතයන් (External Functions) භාවිතා කිරීමට නම්, අප ඒවා ආනයනය (Import) කළ යුතුය. FASM හි ඇති win64ax.inc නමැති උපකාරක ගොනුව භාවිතා කිරීමෙන් අපට මේ වැඩේ ඉතා පහසුවෙන් කරගත හැක.

include 'win64ax.inc'

මෙමගින් වින්ඩෝස් හි ඇති දහස් ගණනක් API වල ලිපින සහ නියතයන් (Constants) ස්වයංක්‍රීයව අපගේ ප්‍රෝග්‍රෑම් එකට එකතු කර දෙයි. 1 වන වාදය වන "මොළයට හානි සිදුකර ගැනීමෙන් වලකින්න!" යන්න ක්‍රියාත්මක වන තවත් අවස්ථාවක් වන්නේ මෙයයි. අපට හැම API එකකම ලිපිනයන් අතින් සෙවීමට අවශ්‍ය නැත, FASM විසින් එය අප වෙනුවෙන් සිදු කරයි.


8.2 කොන්සෝල් ක්‍රියාකාරකම් සඳහා වන API ශ්‍රිතයන්

1. GetStdHandle (හසුරුව ලබා ගැනීම)

මහා බූරුවාට කොන්සෝල් එකට යමක් ලියන්නට හෝ කියවන්නට පෙර, එම කොන්සෝලයට ඇතුල් වීමට විශේෂ අවසර පත්‍රයක් ලබාගත යුතුය. මෙම අවසර පත්‍රය පරිගණක විද්‍යාවේදී හසුරුව (Handle) ලෙස හඳුන්වයි. එය හරියට රජතුමා දෙන විශේෂ මුද්‍රාවක් සහිත ටිකට් පතක් වැනිය.

මේ සඳහා අප GetStdHandle ඇමතිවරයා භාවිතා කරමු.

mov     rcx, STD_OUTPUT_HANDLE   ; RCX = -11
call    [GetStdHandle]           ; RAX = Output Handle
mov     [stdout], rax            ; අපගේ ලාච්චුවක සුරැකීම

2. WriteConsoleA (කොන්සෝලයට ලිවීම)

තිරය මත අකුරු මුද්‍රණය කිරීමේ රාජකීය අණබෙරකරු මූය. මූට වැඩ කිරීමට පරාමිතීන් 5 ක් අවශ්‍ය වේ! ඔව්, පහක්ම!

  1. hConsoleOutput (RCX): අප කලින් ලබාගත් Output Handle එක.
  2. lpBuffer (RDX): අප මුද්‍රණය කිරීමට බලාපොරොත්තු වන වැකිය (String) ගබඩා කර ඇති ස්ථානයේ මතක ලිපිනය (Memory Address).
  3. nNumberOfCharsToWrite (R8): වැකියේ ඇති අකුරු ප්‍රමාණය (දිග).
  4. lpNumberOfCharsWritten (R9): ඇත්තටම අකුරු කීයක් මුද්‍රණය කළාද කියා රජතුමාට ලිවීමට අප විසින් ලබා දෙන තාවකාලික විචල්‍යයක ලිපිනය (Pointer to Variable).
  5. lpReserved (තට්ටුව මත [RSP + 32]): රජතුමා අනාගත වැඩකට වෙන්කර ඇති පරාමිතියකි. මෙයට සැමවිටම 0 (NULL) ලබා දිය යුතුය.
; WriteConsoleA කැඳවීම
mov     rcx, [stdout]                ; 1 වන: Output Handle
lea     rdx, [message]               ; 2 වන: වැකියේ ලිපිනය
mov     r8, message_len              ; 3 වන: වැකියේ දිග
lea     r9, [bytes_written]          ; 4 වන: ලියූ ප්‍රමාණය සුරැකීමට විචල්‍යය
mov     qword [rsp + 32], 0          ; 5 වන: (තට්ටුව මත සෙවනැලි ඉඩෙහි 32 වන බයිටයේ සිට)
call    [WriteConsoleA]

5 වන පරාමිතියේ රහස: අප පසුගිය පරිච්ඡේදයේදී සෙවනැලි ඉඩ (Shadow Space) ලෙස තට්ටුව මත බයිට් 32 ක් වෙන් කළෙමු (sub rsp, 40). 5 වන පරාමිතිය දැමිය යුත්තේ එම වෙන්කළ සෙවනැලි ඉඩට තට්ටුවේ ඉහලින්ම ඇති [rsp + 32] ලිපිනයටය!

3. ReadConsoleA (කොන්සෝලයෙන් කියවීම)

යතුරු පුවරුවෙන් අකුරු කියවන රාජකීය ලේකම්වරයා මූය. මූටද පරාමිතීන් 5ක් අවශ්‍ය වේ.

  1. hConsoleInput (RCX): Input Handle එක.
  2. lpBuffer (RDX): පරිශීලකයා ලියන අකුරු ගබඩා කිරීමට අප වෙන්කළ හිස් බාල්දියේ ලිපිනය (Buffer Address).
  3. nNumberOfCharsToRead (R8): අපට උපරිමයෙන් කියවීමට අවශ්‍ය අකුරු ප්‍රමාණය.
  4. lpNumberOfCharsRead (R9): පරිශීලකයා ඇත්තටම අකුරු කීයක් යතුරු ලියනය කළාද කියා ලිවීමට අප ලබා දෙන විචල්‍යයේ ලිපිනය.
  5. pInputControl (තට්ටුව මත [RSP + 32]): මෙයටද 0 ලබා දෙන්න.
; ReadConsoleA කැඳවීම
mov     rcx, [stdin]                 ; 1 වන: Input Handle
lea     rdx, [input_buffer]          ; 2 වන: බෆරයේ ලිපිනය
mov     r8, 100                      ; 3 වන: උපරිම අකුරු 100ක්
lea     r9, [bytes_read]             ; 4 වන: කියවූ ප්‍රමාණය සුරැකීමට විචල්‍යය
mov     qword [rsp + 32], 0          ; 5 වන: තට්ටුව මත
call    [ReadConsoleA]

4. ExitProcess (ප්‍රෝග්‍රෑම් එක අවසන් කිරීම)

වැඩ අවසන් වූ පසු මහා බූරුවා නිකම්ම අත්හැර දැමුවහොත් ඌ මතකයේ කෙළවරින් පැන විනාශ වනු ඇත (4 වන වාදය). එබැවින් ඉතා ගෞරවාන්විතව රජ වාසලෙන් පිටවීමට ExitProcess ඇමතිය යුතුය.

xor     ecx, ecx                     ; ECX = 0 (Exit Code)
call    [ExitProcess]

8.3 ප්‍රායෝගික උදාහරණය: "රාජකීය දෝංකාරය" (Echo Program)

දැන් අපි ඉහත ඉගෙන ගත් සියල්ල එකතු කර, සම්පූර්ණ ප්‍රෝග්‍රෑම් එකක් ලියමු. මෙම ප්‍රෝග්‍රෑම් එක මඟින් පරිශීලකයාගේ නම අසා, එය කියවා, නැවත "Hello, [නම]!" ලෙස මුද්‍රණය කරයි.

format PE64 console
entry start

include 'win64ax.inc'

section '.code' code readable executable

start:
    ; 1. රාජ සභා රීතිය: බයිට් 40ක් වෙන් කර තට්ටුව පෙළගැස්වීම (Alignment + Shadow)
    sub     rsp, 40

    ; 2. Output Handle එක ලබා ගැනීම
    mov     rcx, STD_OUTPUT_HANDLE
    call    [GetStdHandle]
    mov     [stdout], rax

    ; 3. Input Handle එක ලබා ගැනීම
    mov     rcx, STD_INPUT_HANDLE
    call    [GetStdHandle]
    mov     [stdin], rax

    ; 4. ප්‍රශ්නය මුද්‍රණය කිරීම: "Enter your name: "
    mov     rcx, [stdout]               ; 1st Arg: Handle
    lea     rdx, [prompt]               ; 2nd Arg: String Address
    mov     r8, prompt_len              ; 3rd Arg: Length
    lea     r9, [bytes_written]         ; 4th Arg: Pointer for output count
    mov     qword [rsp + 32], 0         ; 5th Arg: Stack (NULL)
    call    [WriteConsoleA]

    ; 5. පරිශීලකයාගේ පිළිතුර කියවීම
    mov     rcx, [stdin]                ; 1st Arg: Handle
    lea     rdx, [input_buffer]         ; 2nd Arg: Buffer Address
    mov     r8, 100                     ; 3rd Arg: Max Length
    lea     r9, [bytes_read]            ; 4th Arg: Pointer for read count
    mov     qword [rsp + 32], 0         ; 5th Arg: Stack (NULL)
    call    [ReadConsoleA]

    ; 6. සුභ පැතුම මුද්‍රණය කිරීම: "Hello, "
    mov     rcx, [stdout]
    lea     rdx, [greeting]
    mov     r8, greeting_len
    lea     r9, [bytes_written]
    mov     qword [rsp + 32], 0
    call    [WriteConsoleA]

    ; 7. කියවූ නම නැවත මුද්‍රණය කිරීම
    mov     rcx, [stdout]
    lea     rdx, [input_buffer]         ; අප කියවූ දත්ත ඇති බෆරය
    mov     r8, [bytes_read]            ; කියවූ අකුරු ප්‍රමාණය
    lea     r9, [bytes_written]
    mov     qword [rsp + 32], 0
    call    [WriteConsoleA]

    ; 8. තට්ටුව නැවත යථා තත්ත්වයට පත් කිරීම (120 වන වාදය!)
    add     rsp, 40

    ; 9. ගෞරවාන්විතව සමුගැනීම
    xor     ecx, ecx                    ; Exit Code = 0
    call    [ExitProcess]

section '.data' data readable writeable

    prompt          db 'Enter your name: ', 0
    prompt_len      = $ - prompt - 1

    greeting        db 'Hello, ', 0
    greeting_len    = $ - greeting - 1

    stdout          dq ?
    stdin           dq ?
    bytes_written   dq ?
    bytes_read      dq ?
    input_buffer    db 100 dup (?)      ; අකුරු 100ක් තැබිය හැකි බාල්දියක්

කේතය විසුරුවා හැරීම (Sinhala Code Breakdown):

  1. sub rsp, 40: අප වින්ඩෝස් API ඇමතීමට සූදානම් වන නිසා තට්ටුව බයිට් 16 කට පෙළගස්වා සෙවනැලි ඉඩ ලබා දෙන්නෙමු.
  2. lea rdx, [prompt]: LEA (Load Effective Address) මඟින් prompt වැකියේ ආරම්භක මතක ලිපිනය RDX වෙත පටවයි.
  3. prompt_len = $ - prompt - 1: FASM හි ඇති විශේෂ ලක්ෂණයකි. $ මඟින් වර්තමාන මතක පිහිටීම පෙන්වයි. එයින් prompt හි ආරම්භක ස්ථානය සහ සෙමිකෝලන් හෝ NULL අකුරේ දිග අඩු කළ විට වැකියේ සැබෑ දිග ස්වයංක්‍රීයව ගණනය වේ. 1 වන වාදයට අනුව අපට අතින් අකුරු ගණන් කිරීමට අවශ්‍ය නැත!
  4. input_buffer db 100 dup (?): මතකයේ බයිට් 100ක ඉඩක් වෙන් කර ඒ සියල්ල හිස්ව තබයි (dup (?) යනු Duplicate Undefined යන්නයි). පරිශීලකයා යතුරු ලියනය කරන අකුරු මෙහි තැන්පත් වේ.

මිත‍්‍රවරුනි, මෙම කේතය FASM මඟින් කොම්පයිල් කර ධාවනය කර බලන්න! ඔබ රාජ සභාවේ නීති අකුරටම පිළිපැද්ද නිසා, කිසිදු කරදරයකින් තොරව ඔබේ නම තිරය මත දෝංකාර දෙනු ඇත.


9 වන පරිච්ඡේදය: මන්තර පොත (FASM Macros & Preprocessor)

මිත‍්‍රවරුනි! අපේ x64 මහා බූරුවාට එකම විධානයන් නැවත නැවතත් දීමට සිදුවන විට, අපගේ ඇඟිලි රිදෙන්නටත්, මොළය තැවෙන්නටත් පටන් ගනී. උදාහරණයක් ලෙස, සෑම වින්ඩෝස් API එකක් ඇමතීමට පෙර sub rsp, 40 කිරීමත්, පරාමිතීන් RCX, RDX, R8, R9 වලට දැමීමත්, පසුව add rsp, 40 කිරීමත් ඉතාමත් වෙහෙසකර රාජකාරියකි.

අපේ 1 වන වාදය (მොළයට හානි සිදුවීමෙන් වලකින්න!) නැවත ක්‍රියාවට නැංවීමට කාලය පැමිණ ඇත.

මේ සඳහා FASM අපට ලබා දී ඇති රහස්‍ය මන්තරයක් බඳු මෙවලම වන්නේ පූර්ව-සකසනය (Preprocessor) සහ මැක්‍රෝ (Macros) ය. මැක්‍රෝ යනු සරලවම "කේතය ලියන කේතය" (Code that writes code) වේ.


9.1 මැක්‍රෝස් (Macros) යනු මොනවාද?

මැක්‍රෝ එකක් යනු ඇසෙම්බ්ලි කේත කොටසකට අප විසින් දෙනු ලබන කෙටි නමකි. අප අපගේ සෝස් කේතය (Source Code) කොම්පයිල් (Compile) කිරීමට FASM වෙත ලබා දෙන විට, FASM විසින් අප ලියූ එම කෙටි නම වෙනුවට ඊට අදාළ සම්පූර්ණ කේතයම එතැනට ගෙනත් තබයි (Text Substitution).

මෙය ප්‍රොසෙසරය (මහා බූරුවා) දන්නේවත් නැත. මක්නිසාද යත්, බූරුවා ළඟට කෑම බාල්දිය යාමට පෙර FASM විසින් එම මැක්‍රෝ එක දිග හැර (Expand කර) සාමාන්‍ය කේතය බවට පත් කර අවසන් බැවිනි.

සරල මැක්‍රෝ එකක් ලියමු

අපට රෙජිස්ටරයක් බිංදුව (0) කිරීමට නිතරම xor reg, reg ලියන්නට සිදු වේ. අපි මේ සඳහා clear නමින් මැක්‍රෝ එකක් ලියමු:

macro clear register {
    xor register, register
}

දැන් අපට අපගේ කේතය තුළ මෙසේ ලිවිය හැක:

clear rax    ; FASM විසින් මෙය xor rax, rax බවට හරවයි
clear rbx    ; FASM විසින් මෙය xor rbx, rbx බවට හරවයි

බලන්න, කොතරම් පහසුද? අපේ ඇඟිලි වලට ලැබෙන සහනය මහත්ය!

රාජ සභා ඇමතුම් පහසු කරන මැක්‍රෝ එකක්

වින්ඩෝස් API ඇමතීම පහසු කරවන මැක්‍රෝ එකක් නිර්මාණය කරමු. අපට අවශ්‍ය වන්නේ API එකේ නම දුන් විට ස්වයංක්‍රීයව තට්ටුව පෙළගස්වා එය ඇමතීමටයි:

macro call_api api_name {
    sub rsp, 40
    call [api_name]
    add rsp, 40
}

දැන් අපට ExitProcess ඇමතීමට අවශ්‍ය වූ විට මෙසේ ලිවිය හැක:

xor ecx, ecx
call_api ExitProcess

9.2 මැක්‍රෝ (Macros) සහ උපක්‍රම (Subroutines) අතර වෙනස

ප්‍රෝග්‍රෑමිං ලෝකයේ නවකයන් නිතරම පටලවා ගන්නා තවත් දෙයක් නම් මැක්‍රෝ (Macros) සහ උපක්‍රම/ශ්‍රිත (Subroutines / Functions) අතර වෙනසයි.

ලක්ෂණය මැක්‍රෝ (Macro) උපක්‍රමය (Subroutine / Function)
ක්‍රියාකාරිත්වය FASM විසින් කේතය කෙලින්ම පිටපත් කර තබයි (Inlined). CALL සහ RET විධාන මඟින් වෙනත් මතක ලිපිනයකට පනී.
වේගය ඉතාමත් වේගවත්ය. පැනීම් (Jumps) හෝ තට්ටු හැසිරවීම් නැත. පැනීම් නිසා සහ තට්ටුවේ වැඩ නිසා සුළු වේලාවක් ගතවේ.
ප්‍රමාණය කේතය නැවත නැවත පිටපත් වන නිසා ෆයිල් එකේ ප්‍රමාණය (Executable Size) විශාල වේ. කේතය ඇත්තේ එකම තැනක බැවින් ප්‍රමාණය ඉතා කුඩා වේ.
භාවිතය කුඩා, නිතර භාවිතා වන වේගවත් වැඩ සඳහා සුදුසුය. විශාල, සංකීර්ණ සහ දිගු කේත කොටස් සඳහා සුදුසුය.

9.3 ව්‍යුහයන්ගේ ශක්තිය (The Power of Structs & Constants)

මතකයේ ඇති දත්ත සමූහයක් ක්‍රමානුකූලව පෙළගස්වා තැබීමට FASM හි ව්‍යුහයන් (Structs) භාවිතා කළ හැක.

උදාහරණයක් ලෙස, තිරය මත ඇති ලක්ෂ්‍යයක් (Point) සලකමු. එහි XX ඛණ්ඩාංකය සඳහා බයිට් 8 ක් සහ YY ඛණ්ඩාංකය සඳහා බයිට් 8 ක් ඇත. අපට මෙය ව්‍යුහයක් ලෙස මෙසේ අර්ථ දැක්විය හැක:

struc POINT {
    .x dq ?
    .y dq ?
}

දැන් අපට .data සෙක්ෂන් එක තුළ මෙවැනි ලක්ෂ්‍යයන් සෑදිය හැක:

section '.data' data readable writeable
    my_point POINT

මෙම ලක්ෂ්‍යයේ XX අගය වෙනස් කිරීමට අවශ්‍ය නම් අපට මෙසේ කළ හැක:

mov rax, 150
mov [my_point.x], rax    ; my_point හි X අගයට 150 දැමීම

මෙලෙස ව්‍යුහයන් භාවිතා කිරීමෙන් අපේ මතකයේ ඇති දත්ත වල ලිපිනයන් (Offsets) අතින් ගණනය කිරීමට සිදු නොවේ. මතක තබා ගන්න: "අතින් ගණනය කරන්නට ගියහොත් 178 වන වාදය (කිසිවෙකු පරිපූර්ණ නැත) ක්‍රියාත්මක වී වැරදි සිදුවනු ඇත!"

නියතයන් (Constants)

කේතය තුළ එකම අංකය නැවත නැවත ලියනවා වෙනුවට ඒවා නියතයන් ලෙස අර්ථ දැක්වීමෙන් කේතය කියවීමේ හැකියාව වැඩි වේ. FASM හි නියතයක් අර්ථ දක්වන්නේ = හෝ define මඟින්ය.

BUFFER_SIZE = 100
STD_OUTPUT_HANDLE = -11

මිත‍්‍රවරුනි! මෙම පරිච්ඡේදයෙන් අපේ බූරුවාට ඉතා පහසුවෙන් සහ අපගේ මොළය වෙහෙස නොකර වැඩ ගන්නා අයුරු ඔබ ඉගෙන ගත්තේය. මීළඟ පරිච්ඡේදය වන්නේ අප මෙතෙක් ඉගෙන ගත් සියල්ලේ සාරාංශය සහ ඔබේ පණ බේරාගැනීමේ Survival Guide එක අඩංගු ඇමුණුමයි (Annex)!


10 වන පරිච්ඡේදය: ඇමුණුම (Annex) - -(Survival Guide & Cheat Sheet)

මිත‍්‍රවරුනි! අපේ ගමනේ අවසාන නැවතුමට අපි පැමිණ සිටිමු. මෙම පරිච්ඡේදය ලියා ඇත්තේ ඔබ යුධ පිටියේ (Coding) තනිවම සටන් කරන විට, මහා බූරුවා කලබල වී ඔබේ ප්‍රෝග්‍රෑම් එක කුඩුපට්ටම් කර දමන මොහොතක, ඔබේ පණ බේරාගැනීම සඳහාම පමණි.

මෙය නිතරම ළඟ තබාගෙන කියවිය හැකි කෙටි සටහන් එකතුවකි (Cheat Sheet).


10.1 රෙජිස්ටර වැඩකරුවන්ගේ නාමලේඛනය

x64 බූරුවාගේ වැඩ බංකුවේ සිටින ප්‍රධාන සේවකයින් 16 දෙනා සහ ඔවුන්ගේ සම්ප්‍රදායික රාජකාරි මෙසේය:

රෙජිස්ටරය ප්‍රමාණය සම්ප්‍රදායික කාර්යය (Traditional Role) API කැඳවීමේදී (Microsoft ABI Status)
RAX බිට් 64 ගණනය කිරීම් සහ ශ්‍රිතයන්ගේ ප්‍රතිඵලය (Return Value) වෙනස් වේ (Volatile)
RBX බිට් 64 දත්ත තබාගන්නා පදනම (Base Register) සුරැකිය යුතුය (Non-Volatile)
RCX බිට් 64 ලූප ගණකය (Counter) / 1 වන පරාමිතිය වෙනස් වේ (Volatile)
RDX බිට් 64 දත්ත (Data) / බෙදීමේදී ඉතිරිය / 2 වන පරාමිතිය වෙනස් වේ (Volatile)
RSI බිට් 64 ප්‍රභව දර්ශකය (Source Index) සුරැකිය යුතුය (Non-Volatile)
RDI බිට් 64 ගමනාන්ත දර්ශකය (Destination Index) සුරැකිය යුතුය (Non-Volatile)
RBP බිට් 64 තට්ටුවේ පදනම (Base Pointer) සුරැකිය යුතුය (Non-Volatile)
RSP බිට් 64 තට්ටු දර්ශකය (Stack Pointer) - ඉහලම පිඟාන සුරැකිය යුතුය (Non-Volatile)
R8 බිට් 64 සාමාන්‍ය කාර්යයන් / 3 වන පරාමිතිය වෙනස් වේ (Volatile)
R9 බිට් 64 සාමාන්‍ය කාර්යයන් / 4 වන පරාමිතිය වෙනස් වේ (Volatile)
R10 - R11 බිට් 64 තාවකාලික ගබඩා (Scratch registers) වෙනස් වේ (Volatile)
R12 - R15 බිට් 64 සාමාන්‍ය කාර්යයන් සුරැකිය යුතුය (Non-Volatile)

වැදගත් රහසක් (Volatile vs Non-Volatile):

  • Volatile (වෙනස් වන): ඔබ වින්ඩෝස් API එකක් ඇමතූ සැනින්, රජතුමා විසින් RAX, RCX, RDX, R8, R9, R10, R11 යන රෙජිස්ටර වල ඇති දත්ත විනාශ කර දමනු ඇත! එබැවින්, API එකක් කැඳවීමට පෙර මේවායේ ඇති වටිනා දත්ත වෙනත් තැනක සඟවා තබන්න.
  • Non-Volatile (සුරැකෙන): API එක ක්‍රියාත්මක වී අවසන් වුවද RBX, RSI, RDI, RBP, RSP, R12 - R15 යන රෙජිස්ටර වල දත්ත වලට හානියක් නොවේ. ඒවා සුරක්ෂිතව පවතී.

10.2 බහුලව භාවිතා වන විධාන (Common Instruction Set)


10.3 තට්ටුවේ ව්‍යුහය (Visual Stack Frame Layout)

අපි API එකක් ඇමතීමට පෙර sub rsp, 40 කළ පසු, තට්ටුව මත දත්ත ගොඩගැසී ඇති ආකාරය (ASCII Art ඇසුරින්) මෙන්න:

RSP ලිපිනය             තට්ටුවේ අන්තර්ගතය (Stack Contents)
                  ┌────────────────────────────────────────┐
[RSP + 0]         │  Shadow Space for RCX (බයිට් 8)         │ ◄─── RSP පෙන්වන ස්ථානය
                  ├────────────────────────────────────────┤
[RSP + 8]         │  Shadow Space for RDX (බයිට් 8)         │
                  ├────────────────────────────────────────┤
[RSP + 16]        │  Shadow Space for R8  (බයිට් 8)         │
                  ├────────────────────────────────────────┤
[RSP + 24]        │  Shadow Space for R9  (බයිට් 8)         │
                  ├────────────────────────────────────────┤
[RSP + 32]        │  5 වන පරාමිතිය (5th Argument) / Padding │ ◄─── WriteConsole හි 5 වන පරාමිතිය
                  ├────────────────────────────────────────┤
[RSP + 40]        │  Return Address (ප්‍රධාන කේතයට ආපසු පාර) │
                  └────────────────────────────────────────┘

10.4 පොදු ක්‍රෑෂ් සහ ඒවායින් බේරෙන මඟ (Troubleshooting Guide)

1. ක්‍රෑෂ් කේතය: Access Violation (0xC0000005)

2. ප්‍රෝග්‍රෑම් එක එකතැන හිරවීම (Infinite Loop or Freeze)

3. API එකෙන් පසු දත්ත වෙනස් වී හෝ විනාශ වී තිබීම


10.5 FASM ලිවීමේදී පරීක්ෂා කළ යුතු අවසාන පිරික්සුම් ලැයිස්තුව (Checklist)

මිත‍්‍රවරුනි! දැන් ඔබ සතුව අවශ්‍ය සියලුම ආයුධ සහ දැනුම ඇත. මහා බූරුවා ඔබට අවශ්‍ය පරිදි මෙල්ල කර ගැනීමට මෙම පොත ඔබට මහත් පිටුවහලක් වනු ඇතැයි මම විශ්වාස කරමි.