โครงสร้างพื้นฐานการเขียนโปรแกรม Assembly (MASM)
วันนี้เราจะมาดูโครงสร้างพื้นฐานสำหรับการเขียนโปรแกรม Assembly กันว่าเป็นอย่างไร บทความนี้จะเป็นพื้นฐานต่อยอดไปบทความอื่น ๆ ทำให้เราเข้าใจโครงสร้างในการเขียนโปรแกรม ตลอดจนการ Assembler โปรแกรมให้ได้โปรแกรมที่สามารถทำงานได้ (Executable) ไปดูกันเลยครับ..
คอนเซ็ปต์ใหม่สำหรับวันนี้
- การใช้งานโปรแกรม WinAsm และ MASM32
- Assembler directive
- การใช้คำสั่ง Invoke กับ Win32 API
API Function ใหม่สำหรับวันนี้
- ExitProcess
การใช้งาน WinAsm
ภาพหน้าจอต่อไปนี้แสดงให้เห็นส่วนติดต่อกับผู้ใช้ของ WinAsm ซอร์สโค้ดที่คุณพิมพ์จะไปปรากฏในหน้าต่าง Code Editor Plan (จะอยู่ทางด้านซ้าย) หน้าต่าง Explorer Pane (อยู่ทางด้านขวา) จะแสดงไฟล์ต่าง ๆ ซึ่งเป็นส่วนหนึ่งของโปรเจ็คปัจจุบัน ซึ่งประกอบไปด้วยแอสแซมบลี่โค้ด, Include Files, Resource เป็นต้น ปุ่ม “Go All” (ที่เม้าส์ชี้อยู่ในภาพ) จะทำการ Assemble และ link ซอร์สโค้ดของคุณ และจะรันไฟล์ผลลัพท์ที่ได้ คำสั่งที่ถูกส่งผ่านไปยัง Assembler และ linker ตลอดจนผลลัพท์ที่ได้จะถูกแสดงผลใน Output Pane (ด้านล่าง)

โค้ด 7 บรรทัดที่แสดงด้านบน เป็นการแสดงให้เห็นโปรแกรมเล็กที่สุดที่จะ Assembler, link และ รันโปรแกรม มันประกอบไปด้วยคำสั่ง RET หนึ่งคำสั่ง ณ จุดเริ่มต้นของโปรแกรม ซึ่งเมื่อรันโปรแกรม จะหลุดออกมาที่วินโดว์ทันที เพื่อเป็นการทำให้โครงร่างนี้ เป็นโครงสร้างโปรแกรมที่ใช้งานจริง เราจะแทนที่ RET ด้วย ExitProcess – ซึ่งเป็น Standard API สำหรับการสิ้นสุดโปรแกรมในวินโดว์
สำหรับการเริ่มต้นเขียนโปรแกรม ให้เปิด WinAsm และ คลิกปุ่มซ้ายสุดของ Toolbar ซึ่งก่อนหน้าที่ “recent projects” ได้ถูก disable ไม่ให้ทำงานตอนเริ่มโปรแกรม ผมใช้ New Project Wizard Add-In ซึ่งเป็นการเพิ่มความสามารถของฟังก์ชั่นที่มีอยู่ได้อย่างมาก (ลองกลับไปดูการติดตั้งจากตอนเก่า ๆ นะครับ) หากคุณกำหนดค่าของระบบเหมือนกับที่กล่าวมา คุณควรจะเห็นหน้าต่างนี้ :

คลิก “Next” และ “Finish” โปรเจ็คประเภท Standard EXE ถูกเลือกโดย default พร้อมกับการกำหนดออปชั่น ของการสร้าง Win32 GUI executable สำหรับ assembler และ linker และคุณจะเห็นหน้าจอ code editor ว่าง ๆ
ให้คุณวาง skeleton.asm (download ได้จาก attached file) ไปในหน้าต่าง code editor และคลิก “Go All” จะมีหน้าต่างปรากฏขึ้นให้คุณเซฟ โปรเจ็ค และ asm ไฟล์ ให้คุณเซฟไว้ในตำแหน่งไหนก็ได้ตามสะดวก ก่อนที่จะทำการ Build และ รันไฟล์ของคุณ โค้ดของคุณควรจะมีหน้าตาแบบนี้
{codecitation class="brush:plain;gutter:false;"}</p> <div>.386</div> <div>.model flat, stdcall</div> <div>option casemap:none</div> <div>include kernel32.inc</div> <div>includelib kernel32.lib</div> <div>.data</div> <div>.data?</div> <div>.const</div> <div>.code</div> <div>start:</div> <div>invoke ExitProcess,0</div> <div>end start</div> <p>{/codecitation}</p> <p>เราจะมาวิเคราะห์โค้ดเหล่านี้บรรทัดต่อบรรทัดกัน โค้ดส่วนใหญ่ต่อไปนี้เป็นการแสดง directives (คำสั่งที่จะไปสั่งงาน assembler อีกที) มันมีโค้ดโปรแกรมแค่บรรทัดเดียวเท่านั้น – เป็นการเรียกไปยัง API ฟังก์ชัน ExitProcess</p> <p><strong>.386</strong> – บอก MASM ให้ใช้ชุดคำสั่ง Intel 80386</p> <p><strong>.model flat, stdcall</strong> – กำหนด memory โมเดลเป็นแบบ “flat” และใช้การ call แบบมาตรฐาน (standard call)</p> <p><strong>option casemap:none</strong> – กำหนดให้ labels เป็นแบบ case-sensitive.</p> <p><strong>include kernel32.inc</strong> – บอก MASM ประมวลผลไฟล์ที่ระบุ (เหมือนกับการที่เรา copy ไฟล์ที่ระบุมาใส่ไว้ใน Source code นี้)</p> <p><strong>includelib kernel32.lib</strong> – บอกให้ linker ทำการ import libraries เพื่อที่จะนำมา link ด้วย</p> <p><strong>.data</strong> – กำหนด initialised data (ตัวแปรที่มีการกำหนดค่าเริ่มต้นให้) เริ่มที่นี่.</p> <p><strong>.data?</strong> – กำหนด uninitialised data ที่นี่ (ตัวแปรที่ยังไม่ได้กำหนดค่าให้). พื่นที่ใน memory จะถูกจองไว้เมื่อโปรแกรมถูกโหลด แต่ขนาดของไฟล์บนดิสค์ (disk) จะไม่เพิ่มขึ้น</p> <p><strong>.const</strong> – ค่าคงที่ถูกประกาศไว้ตรงนี้</p> <p><strong>.code</strong> – กำหนดให้ executable code เริ่มจากตรงนี้</p> <p><strong>start:</strong> - label สำหรับระบุจุดเริ่มต้นของโค้ด (ให้เราจำไว้ว่า label จะต้องตามด้วย ":" เมื่อถูกประกาศครั้งแรก).
invoke ExitProcess,0 – การทำงานจะเริ่มที่นี่, ที่คำสั่งแรกภายใน label ที่กำหนดไว้หลังจากสิ้นสุด code directive ตอนนี้มีแค่คำสั่งเดียว และมันไปเรียก “ExitProcess” API function ซึ่งเป็นคำสั่งในการสิ้นสุดโปรแกรม และส่งกลับการควบคุมไปยัง Windows
end start – เป็นเครื่องหมายสำหรับการสิ้นสุดของ module และ label ที่กำหนด (label นี้ไม่ต้องการ ":" เพราะว่ามันถูกำหนดมาก่อนหน้านี้แล้ว
มาทำความรู้กับ Win32 API กัน
Windows API (Application Programming Interface) ประกอบด้วยชุดของ data types, constants, functions, และ structures ที่ขนาดใหญ่ เพื่อใช้ในการสร้าง application สำหรับระบบปฏิบัติการ Windows API functions ส่วนใหญ่รวมถึง ExitProcess ซึ่งใช้งานด้านบน จะถูกเก็บอยู่ใน 3 DLLs หลัก คือ:
- KERNEL32.DLL - Low level kernel services
- GDI32.DLL - Graphics Device Interface: drawing and printing
- USER32.DLL - User Interface controls, windows and messaging services
ถ้าคุณใช้ windows API functions ในโปรแกรมของคุณ โปรแกรมของคุณต้อง "import" functions จาก DLLs Import libraries (.lib ไฟล์) ซึ่งจะเก็บข้อมูลที่ linker ต้องการ เพื่อเรียกใช้งานฟังก์ชั่นที่อยู่ใน DLLs ระบบสามารถโหลด DLL ที่กำหนด และกำหนดตำแหน่งของ Export Function ที่ต้องการเมื่อโค้ดของคุณถูกสั่งให้ทำงาน (Executed) ตัวอย่างเช่น เมื่อเรียกใช้งาน ExitProcess function ซึ่งอยู่ภายใน kernel32.dll คุณต้อง link โค้ดของคุณเข้ากับ import library “kernel32.lib” อย่างไรก็ตาม library files ไม่ใช่สิ่งเดียวที่คุณต้องการ คุณยังต้องการ include file Kernel32.inc ด้วยเหมือนกัน
Include (.inc) ไฟล์จะเป็นที่เก็บ "prototypes" ซึ่งระบุ attributes ของฟังก์ชั่นทั้งหมดที่เก็บอยู่ใน DLL ที่มีชื่อเดียวกัน มันสามารถสร้างได้โดยอัตโนมัติจากโปรแกรมชื่อ lib2inc มันไม่ใช่เรื่องง่ายที่จะสร้าง Library files โดยอัตโนมัติจาก DLL ที่กำหนด แต่โชคดีที่ API Function ส่วนใหญ่ที่คุณต้องการ อยู่ใน DLL ซึ่ง MASM32 ที่แจกจ่ายมี Library files ที่เหมาะสมมาให้แล้ว
เอกสาร Win32 API ที่เป็นทางการถูกเขียนขึ้นสำหรับโปรแกรมเมอร์ C และ C++ และโดยปกติ API function ถูกกำหนดในรูปแบบต่อไปนี้:
{codecitation class="brush:plain;gutter:false;"}ReturnType FunctionName ( ParamType1 ParamName1, ParamType2 ParamName2,...);{/codecitation}</p> <p>ตัวอย่างการใช้งานฟังก์ชั่น SetWindowText จาก Win32 Programmer's Reference:</p> <p><img src="images/stories/Programming/Assembly/Asm_Skeleton/asm_skeleton3.jpg" border="0" alt="asm_skeleton3" width="325" height="78" />
เป็นเรื่องง่ายมากที่จะแปลง C syntax ไปเป็น Assembler “BOOL” เป็นชนิดของข้อมูลซึ่งเป็นผลลัพธ์ที่คืนกลับมาของฟังก์ชั่น และสามารถละเลยได้ “HWND” ที่เห็นซ้ำกันแสดงขนาดและชนิดของข้อมูลถูกใส่เข้าไปยังฟังก์ชั่น (จริงๆ แล้วมันเป็นแค่ DWORD ธรรมดา – ชนิดข้อมูลของ windows ทั้งหมดเหล่านี้ถูกกำหนดไว้ใน windows.inc อยู่ใน C:\masm32\include) และถัดมาเป็นการแสดงชื่อของข้อมูล(เป็นคำย่อของ handle to window) นี่เป็นการแสดงใน assembler อย่างง่าย – ตัวแปล หรือค่าคงที่ ที่ฟังก์ชั่นต้องการถูก PUSH ไปบน stack และฟังก์ชั่นถูกเรียนใช้โดยชื่อ:
PUSH lpString
PUSH hWnd
CALL SetWindowText
จำไว้เสมอว่าใน assembler พารามิเตอร์จะถูก PUSH บน Stack ในทางกลับกัน เมื่อเทียบกับกำหนดในภาษา C จาก Win32.hlp จะเป็นจากขวามาซ้าย นี่เป็นรูปแบบการเรียกใช้งาน STDCALL ที่ใช้ใน Windows ASM โดยเฉพาะ
ตอนนี้คนได้รู้วิธีการใช้งาน API ที่แสดงอยู่ใน Win32.hlp คุณสามารถตรวจสอบคำอธิบายของแต่ละ API function ที่เราจะใช้ในบทความสอนชุดนี้
การใช้งาน Invoke
การรวม prototypes เข้าไปในซอร์สโคดของคุณมีประโยชน์ 2 ประการใน MASM ข้อแรกคือคำสั่ง INVOKE สามารถใช้แทนการเรียกใช้ลำดับ PUSH/CALL แบบเก่าได้ การใช้ INVOKE มีความกระชับและพารามิเตอร์ไม่ต้องการการวางแบบกลับทาง
INVOKE SetWindowText,hWnd,lpString
ประโยชน์อย่างที่ 2 คือ INVOKE อนุญาตให้ทำการประเภทของข้อมูล (type-checking) ซึ่ง linker สามารถทำสัญญาลักษณ์ข้อผิดพลาด(Flag an error) หากมีการส่งค่าพารามิเตอร์ไม่ถูกต้องให้กับฟังก์ชั่นในโค้ดของคุณ prototypes แต่ละอันของฟังก์ชั่นสามารถเพิ่มเข้าไปได้เอง แทนการ include .inc ไฟล์ แต่ต้องอยู่ก่อนหน้าโค้ดคำสั่ง INVOKE
การใช้งาน prototype และ invoke สามารถใช้กับฟังก์ชั่นที่คุณเขียนขึ้นมาเองได้ ดังที่เราจะได้เห็นต่อไป
ทั้งหมดนี้เป็นการอธิบายให้เห็นถึงลำดับของการเขียนโปรแกรมในภาษา Assembly รวมถึงโครงสร้างของตัวภาษาที่จำเป็น ซึ่งบทความอันนี้จะเป็นพื้นฐานในการเขียนโปรแกรมในบทต่อๆ ไป ของให้คุณลองเอาไปเขียนดูให้คุยเคยก่อนนะครับ... แล้วเจอกันบทความหน้า :)
ที่มา: Win32 Assembler Coding โดย Goppit
PUSH lpString
PUSH hWnd
CALL SetWindowText
จำไว้เสมอว่าใน assembler พารามิเตอร์จะถูก PUSH บน Stack ในทางกลับกัน เมื่อเทียบกับกำหนดในภาษา C จาก Win32.hlp จะเป็นจากขวามาซ้าย นี่เป็นรูปแบบการเรียกใช้งาน STDCALL ที่ใช้ใน Windows ASM โดยเฉพาะ
ตอนนี้คนได้รู้วิธีการใช้งาน API ที่แสดงอยู่ใน Win32.hlp คุณสามารถตรวจสอบคำอธิบายของแต่ละ API function ที่เราจะใช้ในบทความสอนชุดนี้
การใช้งาน Invoke
การรวม prototypes เข้าไปในซอร์สโคดของคุณมีประโยชน์ 2 ประการใน MASM ข้อแรกคือคำสั่ง INVOKE สามารถใช้แทนการเรียกใช้ลำดับ PUSH/CALL แบบเก่าได้ การใช้ INVOKE มีความกระชับและพารามิเตอร์ไม่ต้องการการวางแบบกลับทาง
INVOKE SetWindowText,hWnd,lpString
ประโยชน์อย่างที่ 2 คือ INVOKE อนุญาตให้ทำการประเภทของข้อมูล (type-checking) ซึ่ง linker สามารถทำสัญญาลักษณ์ข้อผิดพลาด(Flagan error) หากมีการส่งค่าพารามิเตอร์ไม่ถูกต้องให้กับฟังก์ชั่นในโค้ดของคุณ prototypes แต่ละอันของฟังก์ชั่นสามารถเพิ่มเข้าไปได้เอง แทนการ include .inc ไฟล์ แต่ต้องอยู่ก่อนหน้าโค้ดคำสั่ง INVOKE
การใช้งาน prototype และ invoke สามารถใช้กับฟังก์ชั่นที่คุณเขียนขึ้นมาเองได้ ดังที่เราจะได้เห็นต่อไป