การประกอบ - คู่มือฉบับย่อ
ภาษาแอสเซมบลีคืออะไร?
คอมพิวเตอร์ส่วนบุคคลแต่ละเครื่องมีไมโครโปรเซสเซอร์ที่จัดการกิจกรรมทางคณิตศาสตร์ตรรกะและการควบคุมของคอมพิวเตอร์
โปรเซสเซอร์แต่ละตระกูลมีชุดคำสั่งของตัวเองสำหรับจัดการการทำงานต่างๆเช่นการรับข้อมูลจากแป้นพิมพ์การแสดงข้อมูลบนหน้าจอและการทำงานอื่น ๆ ชุดคำสั่งเหล่านี้เรียกว่า 'คำแนะนำภาษาเครื่อง'
โปรเซสเซอร์เข้าใจเฉพาะคำสั่งภาษาเครื่องซึ่งเป็นสตริงของ 1 และ 0 อย่างไรก็ตามภาษาเครื่องคลุมเครือและซับซ้อนเกินไปสำหรับการใช้ในการพัฒนาซอฟต์แวร์ ดังนั้นภาษาแอสเซมบลีระดับต่ำจึงได้รับการออกแบบมาสำหรับตระกูลโปรเซสเซอร์เฉพาะที่แสดงถึงคำสั่งต่างๆในรหัสสัญลักษณ์และรูปแบบที่เข้าใจได้ง่ายขึ้น
ข้อดีของภาษาแอสเซมบลี
การมีความเข้าใจในภาษาแอสเซมบลีทำให้รู้เรื่อง -
- วิธีการที่โปรแกรมเชื่อมต่อกับ OS โปรเซสเซอร์และ BIOS
- วิธีแสดงข้อมูลในหน่วยความจำและอุปกรณ์ภายนอกอื่น ๆ
- โปรเซสเซอร์เข้าถึงและดำเนินการคำสั่งอย่างไร
- คำแนะนำเข้าถึงและประมวลผลข้อมูลอย่างไร
- โปรแกรมเข้าถึงอุปกรณ์ภายนอกอย่างไร
ข้อดีอื่น ๆ ของการใช้ภาษาแอสเซมบลีคือ -
ต้องใช้หน่วยความจำและเวลาดำเนินการน้อยลง
ช่วยให้งานที่ซับซ้อนเฉพาะฮาร์ดแวร์ทำได้ง่ายขึ้น
เหมาะสำหรับงานที่มีเวลาสำคัญ
เหมาะที่สุดสำหรับการเขียนรูทีนบริการขัดจังหวะและโปรแกรมที่อาศัยหน่วยความจำอื่น ๆ
คุณสมบัติพื้นฐานของฮาร์ดแวร์พีซี
ฮาร์ดแวร์ภายในหลักของพีซีประกอบด้วยโปรเซสเซอร์หน่วยความจำและรีจิสเตอร์ รีจิสเตอร์คือส่วนประกอบของโปรเซสเซอร์ที่เก็บข้อมูลและที่อยู่ ในการดำเนินการโปรแกรมระบบจะคัดลอกจากอุปกรณ์ภายนอกไปยังหน่วยความจำภายใน โปรเซสเซอร์ดำเนินการตามคำสั่งของโปรแกรม
หน่วยพื้นฐานของการจัดเก็บคอมพิวเตอร์เป็นบิต อาจเป็น ON (1) หรือ OFF (0) และกลุ่มของ 8 บิตที่เกี่ยวข้องจะสร้างไบต์บนคอมพิวเตอร์สมัยใหม่ส่วนใหญ่
ดังนั้นจึงใช้พาริตีบิตเพื่อสร้างจำนวนบิตเป็นไบต์คี่ หากความเท่าเทียมกันระบบจะถือว่ามีข้อผิดพลาดเกี่ยวกับพาริตี (แม้ว่าจะเกิดขึ้นน้อยมาก) ซึ่งอาจเกิดจากความผิดพลาดของฮาร์ดแวร์หรือการรบกวนทางไฟฟ้า
โปรเซสเซอร์รองรับขนาดข้อมูลต่อไปนี้ -
- Word: รายการข้อมูล 2 ไบต์
- Doubleword: รายการข้อมูล 4 ไบต์ (32 บิต)
- Quadword: รายการข้อมูล 8 ไบต์ (64 บิต)
- ย่อหน้า: พื้นที่ 16 ไบต์ (128 บิต)
- กิโลไบต์: 1024 ไบต์
- เมกะไบต์: 1,048,576 ไบต์
ระบบเลขฐานสอง
ระบบตัวเลขทุกระบบใช้สัญกรณ์ตำแหน่งกล่าวคือแต่ละตำแหน่งที่เขียนตัวเลขจะมีค่าตำแหน่งต่างกัน แต่ละตำแหน่งคือพลังของฐานซึ่งก็คือ 2 สำหรับระบบเลขฐานสองและพลังเหล่านี้เริ่มต้นที่ 0 และเพิ่มขึ้นทีละ 1
ตารางต่อไปนี้แสดงค่าตำแหน่งสำหรับเลขฐานสอง 8 บิตโดยที่บิตทั้งหมดถูกตั้งค่าเป็น ON
ค่าบิต | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
ค่าตำแหน่งเป็นกำลังของฐาน 2 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
หมายเลขบิต | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
ค่าของเลขฐานสองขึ้นอยู่กับการมีอยู่ของ 1 บิตและค่าตำแหน่ง ดังนั้นค่าของเลขฐานสองที่ระบุคือ -
1 + 2 + 4 + 8 +16 + 32 + 64 + 128 = 255
ซึ่งเหมือนกับ 2 8 - 1
ระบบเลขฐานสิบหก
ระบบเลขฐานสิบหกใช้ฐาน 16 ตัวเลขในระบบนี้มีตั้งแต่ 0 ถึง 15 ตามแบบแผนตัวอักษร A ถึง F ใช้แทนเลขฐานสิบหกที่สอดคล้องกับค่าทศนิยม 10 ถึง 15
เลขฐานสิบหกในการคำนวณใช้สำหรับการย่อการแทนค่าไบนารีที่มีความยาว โดยทั่วไประบบเลขฐานสิบหกจะแสดงข้อมูลไบนารีโดยการหารแต่ละไบต์เป็นครึ่งหนึ่งและแสดงค่าของแต่ละครึ่งไบต์ ตารางต่อไปนี้แสดงการเทียบเท่าทศนิยมไบนารีและฐานสิบหก -
เลขฐานสิบ | การแสดงไบนารี | การแสดงเลขฐานสิบหก |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
8 | 1,000 | 8 |
9 | 1001 | 9 |
10 | 1010 | ก |
11 | 1011 | ข |
12 | 1100 | ค |
13 | 1101 | ง |
14 | 1110 | จ |
15 | 1111 | ฉ |
ในการแปลงเลขฐานสองให้เทียบเท่าเลขฐานสิบหกให้แบ่งออกเป็นกลุ่ม 4 กลุ่มติดต่อกันโดยเริ่มจากทางขวาและเขียนกลุ่มเหล่านั้นทับตัวเลขที่สอดคล้องกันของเลขฐานสิบหก
Example - เลขฐานสอง 1000 1100 1101 0001 เทียบเท่ากับเลขฐานสิบหก - 8CD1
ในการแปลงเลขฐานสิบหกเป็นเลขฐานสองเพียงแค่เขียนเลขฐานสิบหกแต่ละหลักลงในเลขฐานสองที่เทียบเท่ากัน 4 หลัก
Example - เลขฐานสิบหก FAD8 เทียบเท่ากับเลขฐานสอง - 1111 1010 1101 1000
เลขคณิตไบนารี
ตารางต่อไปนี้แสดงกฎง่ายๆสี่ข้อสำหรับการบวกไบนารี -
(ผม) | (ii) | (สาม) | (iv) |
---|---|---|---|
1 | |||
0 | 1 | 1 | 1 |
+0 | +0 | +1 | +1 |
= 0 | = 1 | = 10 | = 11 |
กฎ (iii) และ (iv) แสดงการพก 1 บิตในตำแหน่งซ้ายถัดไป
Example
ทศนิยม | ไบนารี่ |
---|---|
60 | 00111100 |
+42 | 00101010 |
102 | 01100110 |
ค่าไบนารีที่เป็นลบจะแสดงเป็น two's complement notation. ตามกฎนี้การแปลงเลขฐานสองเพื่อค่าลบของมันคือการกลับค่าบิตและเพิ่ม 1
Example
หมายเลข 53 | 00110101 |
ย้อนกลับบิต | 11001010 |
เพิ่ม 1 | 0000000 1 |
หมายเลข -53 | 11001011 |
ลบค่าหนึ่งจากอีกแปลงหมายเลขที่ถูกหักออกไปเป็นรูปแบบที่สมบูรณ์สองและเพิ่มตัวเลข
Example
ลบ 42 จาก 53
หมายเลข 53 | 00110101 |
หมายเลข 42 | 00101010 |
ย้อนกลับบิตของ 42 | 11010101 |
เพิ่ม 1 | 0000000 1 |
หมายเลข -42 | 11010110 |
53 - 42 = 11 | 00001011 |
ส่วนเกินของ 1 บิตสุดท้ายจะหายไป
การกำหนดแอดเดรสข้อมูลในหน่วยความจำ
กระบวนการที่โปรเซสเซอร์ควบคุมการดำเนินการตามคำสั่งเรียกว่า fetch-decode-execute cycle หรือ execution cycle. ประกอบด้วยสามขั้นตอนต่อเนื่อง -
- ดึงคำสั่งจากหน่วยความจำ
- การถอดรหัสหรือระบุคำสั่ง
- ดำเนินการตามคำสั่ง
โปรเซสเซอร์อาจเข้าถึงหน่วยความจำได้ครั้งละหนึ่งไบต์ ให้เราพิจารณาเลขฐานสิบหก 0725H ตัวเลขนี้จะต้องใช้หน่วยความจำสองไบต์ ไบต์ลำดับสูงหรือไบต์ที่สำคัญที่สุดคือ 07 และไบต์ลำดับต่ำคือ 25
โปรเซสเซอร์จัดเก็บข้อมูลในลำดับไบต์ย้อนกลับกล่าวคือไบต์ลำดับต่ำจะถูกเก็บไว้ในแอดเดรสหน่วยความจำต่ำและไบต์ลำดับสูงในแอดเดรสหน่วยความจำสูง ดังนั้นหากโปรเซสเซอร์นำค่า 0725H จากรีจิสเตอร์ไปยังหน่วยความจำมันจะโอน 25 อันดับแรกไปยังที่อยู่หน่วยความจำล่างและ 07 ไปยังที่อยู่หน่วยความจำถัดไป
x: ที่อยู่หน่วยความจำ
เมื่อโปรเซสเซอร์รับข้อมูลตัวเลขจากหน่วยความจำเพื่อรีจิสเตอร์ข้อมูลจะกลับไบต์อีกครั้ง ที่อยู่หน่วยความจำมีสองประเภท -
ที่อยู่สัมบูรณ์ - การอ้างอิงโดยตรงของตำแหน่งเฉพาะ
ที่อยู่เซ็กเมนต์ (หรือออฟเซ็ต) - ที่อยู่เริ่มต้นของเซ็กเมนต์หน่วยความจำที่มีค่าออฟเซ็ต
การตั้งค่าสภาพแวดล้อมท้องถิ่น
ภาษาแอสเซมบลีขึ้นอยู่กับชุดคำสั่งและสถาปัตยกรรมของโปรเซสเซอร์ ในบทช่วยสอนนี้เรามุ่งเน้นไปที่โปรเซสเซอร์ Intel-32 เช่น Pentium หากต้องการทำตามบทช่วยสอนนี้คุณจะต้อง -
- IBM PC หรือคอมพิวเตอร์ที่เข้ากันได้เทียบเท่า
- สำเนาของระบบปฏิบัติการ Linux
- สำเนาของโปรแกรมแอสเซมเบลอร์ NASM
มีโปรแกรมแอสเซมเบลอร์ที่ดีมากมายเช่น -
- Microsoft Assembler (MASM)
- บอร์แลนด์เทอร์โบแอสเซมเบลอร์ (TASM)
- แอสเซมเบลอร์ GNU (GAS)
เราจะใช้แอสเซมเบลอร์ NASM อย่างที่เป็น -
- ฟรี. คุณสามารถดาวน์โหลดได้จากแหล่งเว็บต่างๆ
- จัดทำเอกสารอย่างดีและคุณจะได้รับข้อมูลมากมายบนเน็ต
- สามารถใช้ได้ทั้งบน Linux และ Windows
การติดตั้ง NASM
หากคุณเลือก "Development Tools" ขณะติดตั้ง Linux คุณอาจได้รับ NASM ติดตั้งพร้อมกับระบบปฏิบัติการ Linux และคุณไม่จำเป็นต้องดาวน์โหลดและติดตั้งแยกต่างหาก สำหรับการตรวจสอบว่าคุณได้ติดตั้ง NASM แล้วให้ทำตามขั้นตอนต่อไปนี้ -
เปิดเทอร์มินัล Linux
ประเภท whereis nasm และกด ENTER
หากติดตั้งไว้แล้วจะมีบรรทัดเช่นnasm: / usr / bin / nasmปรากฏขึ้น มิฉะนั้นคุณจะเห็นเพียงแค่nasm:จากนั้นคุณต้องติดตั้ง NASM
ในการติดตั้ง NASM ให้ทำตามขั้นตอนต่อไปนี้ -
ตรวจสอบเว็บไซต์ netwide แอสเซมเบลอร์ (NASM)สำหรับเวอร์ชันล่าสุด
ดาวน์โหลดไฟล์เก็บถาวรซอร์สของ Linux
nasm-X.XX.ta.gz
โดยที่X.XX
หมายเลขเวอร์ชัน NASM ในไฟล์เก็บถาวรnasm-X. XX
แกะเก็บลงในไดเรกทอรีที่สร้างไดเรกทอรีย่อยเป็นcd ถึง
nasm-X.XX
และพิมพ์./configure. เชลล์สคริปต์นี้จะค้นหาคอมไพเลอร์ C ที่ดีที่สุดเพื่อใช้และตั้งค่า Makefiles ตามนั้นประเภท make เพื่อสร้างไบนารี nasm และ ndisasm
ประเภท make install เพื่อติดตั้ง nasm และ ndisasm ใน / usr / local / bin และเพื่อติดตั้ง man pages
สิ่งนี้ควรติดตั้ง NASM ในระบบของคุณ หรือคุณสามารถใช้การกระจาย RPM สำหรับ Fedora Linux เวอร์ชันนี้ติดตั้งง่ายกว่าเพียงดับเบิลคลิกที่ไฟล์ RPM
โปรแกรมการประกอบสามารถแบ่งออกเป็นสามส่วน -
data มาตรา,
bss ส่วนและ
text มาตรา.
ข้อมูลมาตรา
dataส่วนใช้สำหรับการประกาศข้อมูลเริ่มต้นหรือค่าคงที่ ข้อมูลนี้ไม่เปลี่ยนแปลงขณะรันไทม์ คุณสามารถประกาศค่าคงที่ชื่อไฟล์หรือขนาดบัฟเฟอร์ ฯลฯ ได้ในส่วนนี้
ไวยากรณ์สำหรับการประกาศส่วนข้อมูลคือ -
section.data
bssมาตรา
bssส่วนใช้สำหรับการประกาศตัวแปร ไวยากรณ์สำหรับการประกาศส่วน bss คือ -
section.bss
ข้อความส่วน
textใช้สำหรับเก็บรหัสจริง ส่วนนี้ต้องเริ่มต้นด้วยการประกาศglobal _startซึ่งจะบอกเคอร์เนลที่การเรียกใช้โปรแกรมเริ่มต้นขึ้น
ไวยากรณ์สำหรับการประกาศส่วนข้อความคือ -
section.text
global _start
_start:
ความคิดเห็น
ข้อคิดเห็นภาษาแอสเซมบลีเริ่มต้นด้วยอัฒภาค (;) อาจมีอักขระที่พิมพ์ได้รวมทั้งช่องว่าง มันสามารถปรากฏบนบรรทัดด้วยตัวมันเองเช่น -
; This program displays a message on screen
หรือในบรรทัดเดียวกันพร้อมกับคำสั่งเช่น -
add eax, ebx ; adds ebx to eax
คำสั่งภาษาแอสเซมบลี
โปรแกรมภาษาแอสเซมบลีประกอบด้วยข้อความสามประเภท -
- คำสั่งหรือคำสั่งปฏิบัติการ
- คำสั่ง Assembler หรือ pseudo-ops และ
- Macros.
executable instructions หรือเพียงแค่ instructionsบอกโปรเซสเซอร์ว่าต้องทำอะไร แต่ละคำสั่งประกอบด้วยไฟล์operation code(opcode) คำสั่งปฏิบัติการแต่ละคำสั่งจะสร้างคำสั่งภาษาเครื่องหนึ่งคำสั่ง
assembler directives หรือ pseudo-opsบอกผู้ประกอบเกี่ยวกับแง่มุมต่างๆของกระบวนการประกอบ สิ่งเหล่านี้ไม่สามารถเรียกใช้งานได้และไม่สร้างคำสั่งภาษาเครื่อง
Macros โดยพื้นฐานแล้วเป็นกลไกการแทนที่ข้อความ
ไวยากรณ์ของคำสั่งภาษาแอสเซมบลี
คำสั่งภาษาแอสเซมบลีถูกป้อนหนึ่งคำสั่งต่อบรรทัด แต่ละคำสั่งเป็นไปตามรูปแบบต่อไปนี้ -
[label] mnemonic [operands] [;comment]
ช่องในวงเล็บเหลี่ยมเป็นทางเลือก คำสั่งพื้นฐานมีสองส่วนส่วนแรกคือชื่อของคำสั่ง (หรือตัวช่วยในการจำ) ซึ่งจะดำเนินการและส่วนที่สองคือตัวถูกดำเนินการหรือพารามิเตอร์ของคำสั่ง
ต่อไปนี้เป็นตัวอย่างคำสั่งภาษาแอสเซมบลีทั่วไป -
INC COUNT ; Increment the memory variable COUNT
MOV TOTAL, 48 ; Transfer the value 48 in the
; memory variable TOTAL
ADD AH, BH ; Add the content of the
; BH register into the AH register
AND MASK1, 128 ; Perform AND operation on the
; variable MASK1 and 128
ADD MARKS, 10 ; Add 10 to the variable MARKS
MOV AL, 10 ; Transfer the value 10 to the AL register
โครงการ Hello World ใน Assembly
รหัสภาษาแอสเซมบลีต่อไปนี้แสดงสตริง 'Hello World' บนหน้าจอ -
section .text
global _start ;must be declared for linker (ld)
_start: ;tells linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!', 0xa ;string to be printed
len equ $ - msg ;length of the string
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Hello, world!
การรวบรวมและเชื่อมโยงโปรแกรม Assembly ใน NASM
ตรวจสอบให้แน่ใจว่าคุณได้กำหนดเส้นทางของ nasm และ ldไบนารีในตัวแปรสภาพแวดล้อม PATH ของคุณ ตอนนี้ทำตามขั้นตอนต่อไปนี้เพื่อรวบรวมและเชื่อมโยงโปรแกรมข้างต้น -
พิมพ์โค้ดด้านบนโดยใช้โปรแกรมแก้ไขข้อความและบันทึกเป็น hello.asm
ตรวจสอบให้แน่ใจว่าคุณอยู่ในไดเร็กทอรีเดียวกับที่คุณบันทึกไว้ hello.asm.
ในการประกอบโปรแกรมให้พิมพ์ nasm -f elf hello.asm
หากมีข้อผิดพลาดใด ๆ คุณจะได้รับแจ้งในขั้นตอนนี้ มิฉะนั้นไฟล์อ็อบเจ็กต์ของโปรแกรมของคุณชื่อhello.o จะถูกสร้างขึ้น
หากต้องการเชื่อมโยงไฟล์ออบเจ็กต์และสร้างไฟล์ปฏิบัติการชื่อ hello ให้พิมพ์ ld -m elf_i386 -s -o hello hello.o
ดำเนินการโปรแกรมโดยพิมพ์ ./hello
หากคุณทำทุกอย่างถูกต้องจะแสดงข้อความ 'Hello, world!' บนหน้าจอ.
เราได้กล่าวถึงสามส่วนของโปรแกรมการประกอบแล้ว ส่วนเหล่านี้แสดงถึงเซ็กเมนต์หน่วยความจำต่างๆเช่นกัน
ที่น่าสนใจคือหากคุณแทนที่คำหลักของส่วนด้วยกลุ่มคุณจะได้รับผลลัพธ์เดียวกัน ลองใช้รหัสต่อไปนี้ -
segment .text ;code segment
global _start ;must be declared for linker
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
segment .data ;data segment
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Hello, world!
เซ็กเมนต์หน่วยความจำ
แบบจำลองหน่วยความจำแบบแบ่งส่วนแบ่งหน่วยความจำระบบออกเป็นกลุ่มของเซ็กเมนต์อิสระที่อ้างอิงโดยพอยน์เตอร์ที่อยู่ในการลงทะเบียนเซ็กเมนต์ แต่ละกลุ่มใช้เพื่อบรรจุข้อมูลประเภทหนึ่ง ส่วนหนึ่งใช้เพื่อบรรจุรหัสคำสั่งส่วนอีกส่วนหนึ่งเก็บองค์ประกอบข้อมูลและส่วนที่สามเก็บสแต็กโปรแกรม
จากการอภิปรายข้างต้นเราสามารถระบุส่วนหน่วยความจำต่างๆเป็น -
Data segment - แสดงโดย .data และส่วน .bss. ส่วน. data ใช้เพื่อประกาศขอบเขตหน่วยความจำซึ่งจัดเก็บองค์ประกอบข้อมูลสำหรับโปรแกรม ไม่สามารถขยายส่วนนี้ได้หลังจากที่มีการประกาศองค์ประกอบข้อมูลและจะยังคงคงที่ตลอดทั้งโปรแกรม
ส่วน. bss ยังเป็นส่วนหน่วยความจำแบบคงที่ซึ่งมีบัฟเฟอร์สำหรับข้อมูลที่จะประกาศในโปรแกรมในภายหลัง หน่วยความจำบัฟเฟอร์นี้เต็มไปด้วยศูนย์
Code segment - แสดงโดย .textมาตรา. สิ่งนี้กำหนดพื้นที่ในหน่วยความจำที่เก็บรหัสคำสั่ง นี่เป็นพื้นที่คงที่ด้วย
Stack - ส่วนนี้ประกอบด้วยค่าข้อมูลที่ส่งผ่านไปยังฟังก์ชันและขั้นตอนภายในโปรแกรม
การทำงานของโปรเซสเซอร์ส่วนใหญ่เกี่ยวข้องกับการประมวลผลข้อมูล ข้อมูลนี้สามารถเก็บไว้ในหน่วยความจำและเข้าถึงได้จากที่นั่น อย่างไรก็ตามการอ่านข้อมูลจากและจัดเก็บข้อมูลลงในหน่วยความจำจะทำให้โปรเซสเซอร์ช้าลงเนื่องจากเกี่ยวข้องกับกระบวนการที่ซับซ้อนในการส่งคำขอข้อมูลผ่านบัสควบคุมและไปยังหน่วยเก็บข้อมูลหน่วยความจำและรับข้อมูลผ่านช่องทางเดียวกัน
เพื่อเพิ่มความเร็วในการทำงานของโปรเซสเซอร์โปรเซสเซอร์จะมีที่เก็บหน่วยความจำภายในที่เรียกว่า registers.
รีจิสเตอร์เก็บองค์ประกอบข้อมูลสำหรับการประมวลผลโดยไม่ต้องเข้าถึงหน่วยความจำ ชิปประมวลผลมีการลงทะเบียนจำนวน จำกัด
รีจิสเตอร์โปรเซสเซอร์
มีการลงทะเบียนโปรเซสเซอร์ 32 บิตและ 16 บิตหกตัวในสถาปัตยกรรม IA-32 การลงทะเบียนแบ่งออกเป็นสามประเภท -
- ทะเบียนทั่วไป
- ทะเบียนควบคุมและ
- การลงทะเบียนกลุ่ม
การลงทะเบียนทั่วไปแบ่งออกเป็นกลุ่มต่อไปนี้ -
- การลงทะเบียนข้อมูล
- ตัวชี้ลงทะเบียนและ
- ดัชนีรีจิสเตอร์
รีจิสเตอร์ข้อมูล
การลงทะเบียนข้อมูล 32 บิตสี่ตัวใช้สำหรับการคำนวณทางคณิตศาสตร์ตรรกะและการดำเนินการอื่น ๆ การลงทะเบียน 32 บิตเหล่านี้สามารถใช้ได้สามวิธี -
ตามการลงทะเบียนข้อมูล 32 บิตที่สมบูรณ์: EAX, EBX, ECX, EDX
ครึ่งล่างของรีจิสเตอร์ 32 บิตสามารถใช้เป็นรีจิสเตอร์ข้อมูล 16 บิตสี่ตัว ได้แก่ AX, BX, CX และ DX
ครึ่งล่างและสูงกว่าของรีจิสเตอร์ 16 บิตสี่ตัวที่กล่าวถึงข้างต้นสามารถใช้เป็นรีจิสเตอร์ข้อมูล 8 บิต 8 ตัว: AH, AL, BH, BL, CH, CL, DH และ DL
การลงทะเบียนข้อมูลเหล่านี้บางส่วนมีการใช้งานเฉพาะในการคำนวณทางคณิตศาสตร์
AX is the primary accumulator; ใช้ในอินพุต / เอาต์พุตและคำแนะนำทางคณิตศาสตร์ส่วนใหญ่ ตัวอย่างเช่นในการดำเนินการคูณตัวถูกดำเนินการหนึ่งตัวถูกเก็บไว้ในทะเบียน EAX หรือ AX หรือ AL ตามขนาดของตัวถูกดำเนินการ
BX is known as the base registerเนื่องจากสามารถใช้ในการจัดทำดัชนีแอดเดรส
CX is known as the count registerในฐานะ ECX นั้น CX จะลงทะเบียนเก็บการนับลูปในการดำเนินการซ้ำ
DX is known as the data register. นอกจากนี้ยังใช้ในการดำเนินการอินพุต / เอาต์พุต นอกจากนี้ยังใช้กับการลงทะเบียน AX พร้อมกับ DX สำหรับการคูณและหารการดำเนินการที่เกี่ยวข้องกับค่าขนาดใหญ่
ตัวชี้รีจิสเตอร์
ตัวชี้รีจิสเตอร์คือรีจิสเตอร์ EIP, ESP และ EBP แบบ 32 บิตและส่วน IP, SP และ BP ที่เหมาะสม 16 บิต พอยน์เตอร์รีจิสเตอร์มีสามประเภท -
Instruction Pointer (IP)- การลงทะเบียน IP 16 บิตจะเก็บที่อยู่ออฟเซ็ตของคำสั่งถัดไปที่จะดำเนินการ IP ที่เชื่อมโยงกับทะเบียน CS (เป็น CS: IP) ให้ที่อยู่ที่สมบูรณ์ของคำสั่งปัจจุบันในส่วนรหัส
Stack Pointer (SP)- การลงทะเบียน SP 16 บิตให้ค่าออฟเซ็ตภายในสแตกโปรแกรม SP ที่เชื่อมโยงกับ SS register (SS: SP) หมายถึงตำแหน่งปัจจุบันของข้อมูลหรือแอดเดรสภายในสแตกโปรแกรม
Base Pointer (BP)- การลงทะเบียน BP 16 บิตส่วนใหญ่ช่วยในการอ้างอิงตัวแปรพารามิเตอร์ที่ส่งผ่านไปยังรูทีนย่อย ที่อยู่ใน SS register จะรวมกับค่าชดเชยใน BP เพื่อรับตำแหน่งของพารามิเตอร์ BP ยังสามารถใช้ร่วมกับ DI และ SI เป็นทะเบียนฐานสำหรับการระบุแอดเดรสพิเศษ
ดัชนีรีจิสเตอร์
ดัชนี 32 บิตลงทะเบียน ESI และ EDI และส่วนขวาสุด 16 บิต SI และ DI ใช้สำหรับการกำหนดแอดเดรสที่จัดทำดัชนีและบางครั้งใช้ในการบวกและการลบ ตัวชี้ดัชนีมีสองชุด -
Source Index (SI) - ใช้เป็นดัชนีต้นทางสำหรับการดำเนินการสตริง
Destination Index (DI) - ใช้เป็นดัชนีปลายทางสำหรับการดำเนินการสตริง
รีจิสเตอร์ควบคุม
การลงทะเบียนตัวชี้คำสั่ง 32 บิตและการลงทะเบียนแฟล็ก 32 บิตรวมกันถือเป็นการลงทะเบียนควบคุม
คำสั่งจำนวนมากเกี่ยวข้องกับการเปรียบเทียบและการคำนวณทางคณิตศาสตร์และเปลี่ยนสถานะของแฟล็กและคำแนะนำเงื่อนไขอื่น ๆ ทดสอบค่าของแฟล็กสถานะเหล่านี้เพื่อนำโฟลว์การควบคุมไปยังตำแหน่งอื่น
แฟล็กบิตทั่วไปคือ:
Overflow Flag (OF) - แสดงถึงการล้นของบิตลำดับสูง (บิตซ้ายสุด) ของข้อมูลหลังจากการดำเนินการทางคณิตศาสตร์ที่ลงนาม
Direction Flag (DF)- กำหนดทิศทางซ้ายหรือขวาสำหรับการย้ายหรือเปรียบเทียบข้อมูลสตริง เมื่อค่า DF เป็น 0 การดำเนินการสตริงจะใช้ทิศทางจากซ้ายไปขวาและเมื่อตั้งค่าเป็น 1 การดำเนินการสตริงจะใช้ทิศทางจากขวาไปซ้าย
Interrupt Flag (IF)- กำหนดว่าการขัดจังหวะภายนอกเช่นรายการแป้นพิมพ์ ฯลฯ จะถูกละเว้นหรือประมวลผล ปิดใช้งานการขัดจังหวะภายนอกเมื่อค่าเป็น 0 และเปิดใช้งานการขัดจังหวะเมื่อตั้งค่าเป็น 1
Trap Flag (TF)- อนุญาตให้ตั้งค่าการทำงานของโปรเซสเซอร์ในโหมดขั้นตอนเดียว โปรแกรมดีบักที่เราใช้ตั้งค่าแฟล็กกับดักดังนั้นเราจึงสามารถดำเนินการทีละคำสั่งได้
Sign Flag (SF)- แสดงสัญลักษณ์ของผลลัพธ์ของการดำเนินการทางคณิตศาสตร์ แฟล็กนี้ถูกตั้งค่าตามสัญลักษณ์ของรายการข้อมูลหลังจากการดำเนินการทางคณิตศาสตร์ เครื่องหมายจะแสดงโดยลำดับสูงของบิตซ้ายสุด ผลลัพธ์ที่เป็นบวกจะล้างค่าของ SF เป็น 0 และผลลัพธ์ที่เป็นลบจะตั้งค่าเป็น 1
Zero Flag (ZF)- บ่งชี้ผลลัพธ์ของการคำนวณทางคณิตศาสตร์หรือการเปรียบเทียบ ผลลัพธ์ที่ไม่ใช่ศูนย์จะล้างแฟล็กศูนย์เป็น 0 และผลลัพธ์ที่เป็นศูนย์จะตั้งค่าเป็น 1
Auxiliary Carry Flag (AF)- มีการพกพาจากบิต 3 ถึงบิต 4 หลังจากการดำเนินการทางคณิตศาสตร์ ใช้สำหรับการคำนวณเฉพาะทาง AF ถูกตั้งค่าเมื่อการดำเนินการทางคณิตศาสตร์ 1 ไบต์ทำให้การดำเนินการจากบิต 3 เป็นบิต 4
Parity Flag (PF)- ระบุจำนวน 1 บิตทั้งหมดในผลลัพธ์ที่ได้จากการคำนวณทางคณิตศาสตร์ จำนวนคู่ของ 1 บิตจะล้างแฟล็กพาริตีเป็น 0 และจำนวนคี่ของ 1 บิตตั้งค่าแฟล็กพาริตีเป็น 1
Carry Flag (CF)- มีการดำเนินการ 0 หรือ 1 จากบิตลำดับสูง (ซ้ายสุด) หลังจากการดำเนินการทางคณิตศาสตร์ นอกจากนี้ยังเก็บเนื้อหาของการดำเนินการกะหรือหมุนครั้งสุดท้าย
ตารางต่อไปนี้ระบุตำแหน่งของแฟล็กบิตในการลงทะเบียนแฟล็ก 16 บิต:
ธง: | โอ | ง | ผม | ที | ส | Z | ก | ป | ค | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
บิตไม่: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
การลงทะเบียนกลุ่ม
เซ็กเมนต์คือพื้นที่เฉพาะที่กำหนดไว้ในโปรแกรมสำหรับบรรจุข้อมูลโค้ดและสแต็ก มีสามส่วนหลัก -
Code Segment- มีคำแนะนำทั้งหมดที่จะดำเนินการ การลงทะเบียนกลุ่มรหัส 16 บิตหรือทะเบียน CS จะเก็บที่อยู่เริ่มต้นของส่วนรหัส
Data Segment- ประกอบด้วยข้อมูลค่าคงที่และพื้นที่ทำงาน การลงทะเบียนกลุ่มข้อมูล 16 บิตหรือการลงทะเบียน DS เก็บที่อยู่เริ่มต้นของส่วนข้อมูล
Stack Segment- ประกอบด้วยข้อมูลและที่อยู่ที่ส่งคืนของโพรซีเดอร์หรือรูทีนย่อย มันถูกนำไปใช้เป็นโครงสร้างข้อมูล 'สแตก' การลงทะเบียนกลุ่มสแตกหรือทะเบียน SS เก็บที่อยู่เริ่มต้นของสแต็ก
นอกเหนือจากการลงทะเบียน DS, CS และ SS แล้วยังมีการลงทะเบียนเซ็กเมนต์พิเศษอื่น ๆ - ES (เซ็กเมนต์พิเศษ), FS และ GS ซึ่งมีเซ็กเมนต์เพิ่มเติมสำหรับการจัดเก็บข้อมูล
ในการเขียนโปรแกรมแอสเซมบลีโปรแกรมจำเป็นต้องเข้าถึงตำแหน่งหน่วยความจำ ตำแหน่งหน่วยความจำทั้งหมดภายในเซ็กเมนต์สัมพันธ์กับที่อยู่เริ่มต้นของเซ็กเมนต์ เซ็กเมนต์เริ่มต้นด้วยแอดเดรสที่หารด้วย 16 หรือเลขฐานสิบหก 10 เท่า ๆ กันดังนั้นเลขฐานสิบหกที่อยู่ขวาสุดในที่อยู่หน่วยความจำทั้งหมดจะเป็น 0 ซึ่งโดยทั่วไปจะไม่ถูกเก็บไว้ในการลงทะเบียนเซ็กเมนต์
เซ็กเมนต์ลงทะเบียนเก็บที่อยู่เริ่มต้นของเซ็กเมนต์ ในการรับตำแหน่งที่แน่นอนของข้อมูลหรือคำสั่งภายในเซ็กเมนต์จำเป็นต้องมีค่าออฟเซ็ต (หรือการกระจัด) ในการอ้างอิงตำแหน่งหน่วยความจำในเซ็กเมนต์โปรเซสเซอร์จะรวมแอดเดรสเซ็กเมนต์ในการลงทะเบียนเซ็กเมนต์กับค่าออฟเซ็ตของตำแหน่ง
ตัวอย่าง
ดูโปรแกรมง่ายๆต่อไปนี้เพื่อทำความเข้าใจการใช้รีจิสเตอร์ในการเขียนโปรแกรมแอสเซมบลี โปรแกรมนี้แสดงดาว 9 ดวงบนหน้าจอพร้อมกับข้อความง่ายๆ -
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,9 ;message length
mov ecx,s2 ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Displaying 9 stars',0xa ;a message
len equ $ - msg ;length of message
s2 times 9 db '*'
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Displaying 9 stars
*********
การเรียกระบบคือ API สำหรับอินเทอร์เฟซระหว่างพื้นที่ผู้ใช้และพื้นที่เคอร์เนล เราได้ใช้การโทรของระบบแล้ว sys_write และ sys_exit สำหรับเขียนลงในหน้าจอและออกจากโปรแกรมตามลำดับ
การเรียกระบบ Linux
คุณสามารถใช้การเรียกระบบ Linux ในโปรแกรมประกอบของคุณ คุณต้องทำตามขั้นตอนต่อไปนี้สำหรับการใช้การเรียกระบบ Linux ในโปรแกรมของคุณ -
- ใส่หมายเลขการโทรของระบบในทะเบียน EAX
- จัดเก็บอาร์กิวเมนต์สำหรับการเรียกระบบในการลงทะเบียน EBX, ECX และอื่น ๆ
- โทรไปที่การขัดจังหวะที่เกี่ยวข้อง (80 ชม.)
- โดยปกติผลลัพธ์จะถูกส่งกลับในทะเบียน EAX
มีการลงทะเบียนหกรายการที่เก็บอาร์กิวเมนต์ของการเรียกระบบที่ใช้ เหล่านี้คือ EBX, ECX, EDX, ESI, EDI และ EBP การลงทะเบียนเหล่านี้รับอาร์กิวเมนต์ติดต่อกันโดยเริ่มจากการลงทะเบียน EBX หากมีอาร์กิวเมนต์มากกว่าหกอาร์กิวเมนต์ตำแหน่งหน่วยความจำของอาร์กิวเมนต์แรกจะถูกเก็บไว้ในรีจิสเตอร์ EBX
ข้อมูลโค้ดต่อไปนี้แสดงการใช้การเรียกระบบ sys_exit -
mov eax,1 ; system call number (sys_exit)
int 0x80 ; call kernel
ข้อมูลโค้ดต่อไปนี้แสดงการใช้การเรียกระบบ sys_write -
mov edx,4 ; message length
mov ecx,msg ; message to write
mov ebx,1 ; file descriptor (stdout)
mov eax,4 ; system call number (sys_write)
int 0x80 ; call kernel
syscalls ทั้งหมดแสดงอยู่ใน/usr/include/asm/unistd.hพร้อมกับตัวเลข (ค่าที่จะใส่ใน EAX ก่อนที่คุณจะเรียก int 80h)
ตารางต่อไปนี้แสดงการเรียกระบบบางส่วนที่ใช้ในบทช่วยสอนนี้ -
% eax | ชื่อ | % ebx | % ecx | % edx | % esx | % edi |
---|---|---|---|---|---|---|
1 | sys_exit | int | - | - | - | - |
2 | sys_fork | โครงสร้าง pt_regs | - | - | - | - |
3 | sys_read | int ที่ไม่ได้ลงนาม | ถ่าน * | size_t | - | - |
4 | sys_write | int ที่ไม่ได้ลงนาม | const ถ่าน * | size_t | - | - |
5 | sys_open | const ถ่าน * | int | int | - | - |
6 | sys_close | int ที่ไม่ได้ลงนาม | - | - | - | - |
ตัวอย่าง
ตัวอย่างต่อไปนี้อ่านตัวเลขจากแป้นพิมพ์และแสดงบนหน้าจอ -
section .data ;Data segment
userMsg db 'Please enter a number: ' ;Ask the user to enter a number
lenUserMsg equ $-userMsg ;The length of the message
dispMsg db 'You have entered: '
lenDispMsg equ $-dispMsg
section .bss ;Uninitialized data
num resb 5
section .text ;Code Segment
global _start
_start: ;User prompt
mov eax, 4
mov ebx, 1
mov ecx, userMsg
mov edx, lenUserMsg
int 80h
;Read and store the user input
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ;5 bytes (numeric, 1 for sign) of that information
int 80h
;Output the message 'The entered number is: '
mov eax, 4
mov ebx, 1
mov ecx, dispMsg
mov edx, lenDispMsg
int 80h
;Output the number entered
mov eax, 4
mov ebx, 1
mov ecx, num
mov edx, 5
int 80h
; Exit code
mov eax, 1
mov ebx, 0
int 80h
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Please enter a number:
1234
You have entered:1234
คำสั่งภาษาแอสเซมบลีส่วนใหญ่ต้องการตัวถูกดำเนินการในการประมวลผล แอดเดรสโอเปอแรนด์ระบุตำแหน่งที่เก็บข้อมูลที่จะประมวลผล คำแนะนำบางอย่างไม่จำเป็นต้องใช้ตัวถูกดำเนินการในขณะที่คำแนะนำอื่น ๆ อาจต้องใช้ตัวถูกดำเนินการหนึ่งสองหรือสามตัว
เมื่อคำสั่งต้องการตัวถูกดำเนินการสองตัวโดยทั่วไปตัวถูกดำเนินการตัวแรกจะเป็นปลายทางซึ่งมีข้อมูลในตำแหน่งรีจิสเตอร์หรือหน่วยความจำและตัวถูกดำเนินการที่สองคือแหล่งที่มา แหล่งที่มาประกอบด้วยข้อมูลที่จะจัดส่ง (การกำหนดแอดเดรสทันที) หรือที่อยู่ (ในรีจิสเตอร์หรือหน่วยความจำ) ของข้อมูล โดยทั่วไปข้อมูลต้นทางจะไม่เปลี่ยนแปลงหลังการดำเนินการ
โหมดการกำหนดที่อยู่พื้นฐานสามโหมดคือ -
- ลงทะเบียนที่อยู่
- ที่อยู่ทันที
- ที่อยู่หน่วยความจำ
ลงทะเบียนที่อยู่
ในโหมดการกำหนดแอดเดรสนี้รีจิสเตอร์มีตัวถูกดำเนินการ ขึ้นอยู่กับคำสั่งการลงทะเบียนอาจเป็นตัวถูกดำเนินการตัวแรกตัวถูกดำเนินการที่สองหรือทั้งสองอย่าง
ตัวอย่างเช่น,
MOV DX, TAX_RATE ; Register in first operand
MOV COUNT, CX ; Register in second operand
MOV EAX, EBX ; Both the operands are in registers
เนื่องจากการประมวลผลข้อมูลระหว่างรีจิสเตอร์ไม่เกี่ยวข้องกับหน่วยความจำจึงให้การประมวลผลข้อมูลที่เร็วที่สุด
ที่อยู่ทันที
ตัวถูกดำเนินการทันทีมีค่าคงที่หรือนิพจน์ เมื่อคำสั่งที่มีตัวถูกดำเนินการสองตัวใช้การกำหนดแอดเดรสทันทีตัวถูกดำเนินการตัวแรกอาจเป็นรีจิสเตอร์หรือตำแหน่งหน่วยความจำและตัวถูกดำเนินการที่สองเป็นค่าคงที่ทันที ตัวถูกดำเนินการแรกกำหนดความยาวของข้อมูล
ตัวอย่างเช่น,
BYTE_VALUE DB 150 ; A byte value is defined
WORD_VALUE DW 300 ; A word value is defined
ADD BYTE_VALUE, 65 ; An immediate operand 65 is added
MOV AX, 45H ; Immediate constant 45H is transferred to AX
ที่อยู่หน่วยความจำโดยตรง
เมื่อระบุตัวถูกดำเนินการในโหมดกำหนดแอดเดรสหน่วยความจำจำเป็นต้องมีการเข้าถึงโดยตรงไปยังหน่วยความจำหลักโดยปกติไปยังส่วนข้อมูล วิธีการระบุที่อยู่นี้ส่งผลให้การประมวลผลข้อมูลช้าลง ในการค้นหาตำแหน่งที่แน่นอนของข้อมูลในหน่วยความจำเราจำเป็นต้องมีที่อยู่เริ่มต้นของเซ็กเมนต์ซึ่งโดยทั่วไปจะพบในทะเบียน DS และค่าออฟเซ็ต ค่าชดเชยนี้เรียกอีกอย่างว่าeffective address.
ในโหมดการกำหนดแอดเดรสโดยตรงค่าออฟเซ็ตจะถูกระบุโดยตรงเป็นส่วนหนึ่งของคำสั่งโดยปกติจะระบุด้วยชื่อตัวแปร แอสเซมเบลอร์จะคำนวณค่าออฟเซ็ตและดูแลตารางสัญลักษณ์ซึ่งเก็บค่าออฟเซ็ตของตัวแปรทั้งหมดที่ใช้ในโปรแกรม
ในการกำหนดแอดเดรสหน่วยความจำโดยตรงหนึ่งในตัวถูกดำเนินการอ้างถึงตำแหน่งหน่วยความจำและตัวถูกดำเนินการอื่น ๆ อ้างถึงรีจิสเตอร์
ตัวอย่างเช่น,
ADD BYTE_VALUE, DL ; Adds the register in the memory location
MOV BX, WORD_VALUE ; Operand from the memory is added to register
ที่อยู่ตรงข้ามโดยตรง
โหมดการกำหนดแอดเดรสนี้ใช้ตัวดำเนินการทางคณิตศาสตร์เพื่อแก้ไขที่อยู่ ตัวอย่างเช่นดูคำจำกัดความต่อไปนี้ที่กำหนดตารางข้อมูล -
BYTE_TABLE DB 14, 15, 22, 45 ; Tables of bytes
WORD_TABLE DW 134, 345, 564, 123 ; Tables of words
การดำเนินการต่อไปนี้เข้าถึงข้อมูลจากตารางในหน่วยความจำลงในรีจิสเตอร์ -
MOV CL, BYTE_TABLE[2] ; Gets the 3rd element of the BYTE_TABLE
MOV CL, BYTE_TABLE + 2 ; Gets the 3rd element of the BYTE_TABLE
MOV CX, WORD_TABLE[3] ; Gets the 4th element of the WORD_TABLE
MOV CX, WORD_TABLE + 3 ; Gets the 4th element of the WORD_TABLE
ที่อยู่หน่วยความจำทางอ้อม
โหมดการกำหนดแอดเดรสนี้ใช้ความสามารถของคอมพิวเตอร์ของSegment: Offset addressing โดยทั่วไปฐานจะลงทะเบียน EBX, EBP (หรือ BX, BP) และดัชนีรีจิสเตอร์ (DI, SI) ซึ่งเขียนโค้ดไว้ในวงเล็บเหลี่ยมสำหรับการอ้างอิงหน่วยความจำถูกใช้เพื่อจุดประสงค์นี้
โดยทั่วไปการกำหนดแอดเดรสทางอ้อมจะใช้สำหรับตัวแปรที่มีองค์ประกอบหลายอย่างเช่นอาร์เรย์ ที่อยู่เริ่มต้นของอาร์เรย์จะถูกเก็บไว้ในทะเบียน EBX
ข้อมูลโค้ดต่อไปนี้แสดงวิธีการเข้าถึงองค์ประกอบต่างๆของตัวแปร
MY_TABLE TIMES 10 DW 0 ; Allocates 10 words (2 bytes) each initialized to 0
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
ADD EBX, 2 ; EBX = EBX +2
MOV [EBX], 123 ; MY_TABLE[1] = 123
คำแนะนำ MOV
เราได้ใช้คำสั่ง MOV ที่ใช้สำหรับการย้ายข้อมูลจากพื้นที่เก็บข้อมูลหนึ่งไปยังอีกที่หนึ่งแล้ว คำสั่ง MOV ใช้เวลาสองตัวถูกดำเนินการ
ไวยากรณ์
ไวยากรณ์ของคำสั่ง MOV คือ -
MOV destination, source
คำแนะนำ MOV อาจมีหนึ่งในห้ารูปแบบต่อไปนี้ -
MOV register, register
MOV register, immediate
MOV memory, immediate
MOV register, memory
MOV memory, register
โปรดทราบว่า -
- ตัวถูกดำเนินการทั้งสองในการดำเนินการ MOV ควรมีขนาดเท่ากัน
- ค่าของตัวถูกดำเนินการต้นทางยังคงไม่เปลี่ยนแปลง
คำสั่ง MOV ทำให้เกิดความไม่ชัดเจนในบางครั้ง ตัวอย่างเช่นดูงบ -
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
ไม่ชัดเจนว่าคุณต้องการย้ายการเทียบเท่าไบต์หรือเทียบเท่ากับคำของหมายเลข 110 ในกรณีเช่นนี้ควรใช้ a type specifier.
ตารางต่อไปนี้แสดงตัวระบุประเภททั่วไป -
Type Specifier | ไบต์จ่าหน้า |
---|---|
ไบต์ | 1 |
คำ | 2 |
DWORD | 4 |
QWORD | 8 |
TBYTE | 10 |
ตัวอย่าง
โปรแกรมต่อไปนี้แสดงแนวคิดบางประการที่กล่าวถึงข้างต้น มันเก็บชื่อ 'Zara Ali' ไว้ในส่วนข้อมูลของหน่วยความจำจากนั้นเปลี่ยนค่าเป็นชื่ออื่น 'Nuha Ali' โดยทางโปรแกรมและแสดงทั้งสองชื่อ
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
;writing the name 'Zara Ali'
mov edx,9 ;message length
mov ecx, name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov [name], dword 'Nuha' ; Changed the name to Nuha Ali
;writing the name 'Nuha Ali'
mov edx,8 ;message length
mov ecx,name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
name db 'Zara Ali '
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Zara Ali Nuha Ali
NASM ให้บริการต่างๆ define directivesสำหรับการจองพื้นที่จัดเก็บสำหรับตัวแปร คำสั่งกำหนดแอสเซมเบลอร์ใช้สำหรับการจัดสรรพื้นที่เก็บข้อมูล สามารถใช้เพื่อสำรองและเริ่มต้นไบต์ตั้งแต่หนึ่งไบต์ขึ้นไป
การจัดสรรพื้นที่จัดเก็บสำหรับข้อมูลเริ่มต้น
ไวยากรณ์สำหรับคำสั่งการจัดสรรพื้นที่เก็บข้อมูลสำหรับข้อมูลเริ่มต้นคือ -
[variable-name] define-directive initial-value [,initial-value]...
โดยที่ชื่อตัวแปรคือตัวระบุสำหรับแต่ละพื้นที่จัดเก็บ แอสเซมเบลอร์เชื่อมโยงค่าออฟเซ็ตสำหรับชื่อตัวแปรแต่ละตัวที่กำหนดในส่วนข้อมูล
คำสั่งกำหนดมีห้ารูปแบบพื้นฐาน -
คำสั่ง | วัตถุประสงค์ | พื้นที่จัดเก็บ |
---|---|---|
DB | กำหนดไบต์ | จัดสรร 1 ไบต์ |
DW | กำหนด Word | จัดสรร 2 ไบต์ |
DD | กำหนด Doubleword | จัดสรร 4 ไบต์ |
DQ | กำหนด Quadword | จัดสรร 8 ไบต์ |
DT | กำหนดสิบไบต์ | จัดสรร 10 ไบต์ |
ต่อไปนี้เป็นตัวอย่างบางส่วนของการใช้คำสั่งกำหนด -
choice DB 'y'
number DW 12345
neg_number DW -12345
big_number DQ 123456789
real_number1 DD 1.234
real_number2 DQ 123.456
โปรดทราบว่า -
อักขระแต่ละไบต์จะถูกเก็บเป็นค่า ASCII เป็นเลขฐานสิบหก
ค่าทศนิยมแต่ละค่าจะถูกแปลงเป็นค่าเทียบเท่าไบนารี 16 บิตโดยอัตโนมัติและจัดเก็บเป็นเลขฐานสิบหก
โปรเซสเซอร์ใช้การเรียงลำดับไบต์ endian น้อย
จำนวนลบจะถูกแปลงเป็นการแทนค่า 2 ของมัน
ตัวเลขทศนิยมสั้นและยาวแสดงโดยใช้ 32 หรือ 64 บิตตามลำดับ
โปรแกรมต่อไปนี้แสดงการใช้คำสั่งกำหนด -
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry point
mov edx,1 ;message length
mov ecx,choice ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
choice DB 'y'
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
y
การจัดสรรพื้นที่จัดเก็บสำหรับข้อมูลที่ไม่ได้เริ่มต้น
คำสั่งการสำรองใช้สำหรับการจองพื้นที่สำหรับข้อมูลที่ไม่ได้กำหนดค่าเริ่มต้น คำสั่งการสำรองใช้ตัวถูกดำเนินการเดียวที่ระบุจำนวนหน่วยของพื้นที่ที่จะจอง แต่ละคำสั่งกำหนดมีคำสั่งสงวนที่เกี่ยวข้อง
คำสั่งสงวนมีห้ารูปแบบพื้นฐาน -
คำสั่ง | วัตถุประสงค์ |
---|---|
RESB | จองไบต์ |
RESW | จอง Word |
RESD | จอง Doubleword |
RESQ | จอง Quadword |
ส่วนที่เหลือ | จองสิบไบต์ |
คำจำกัดความหลายคำ
คุณสามารถมีคำสั่งนิยามข้อมูลหลายรายการในโปรแกรม ตัวอย่างเช่น -
choice DB 'Y' ;ASCII of y = 79H
number1 DW 12345 ;12345D = 3039H
number2 DD 12345679 ;123456789D = 75BCD15H
แอสเซมเบลอร์จัดสรรหน่วยความจำต่อเนื่องสำหรับนิยามตัวแปรหลายตัว
การเริ่มต้นหลายครั้ง
คำสั่ง TIMES ช่วยให้การเริ่มต้นหลายครั้งเป็นค่าเดียวกัน ตัวอย่างเช่นอาร์เรย์ที่ชื่อเครื่องหมายขนาด 9 สามารถกำหนดและเริ่มต้นเป็นศูนย์โดยใช้คำสั่งต่อไปนี้ -
marks TIMES 9 DW 0
คำสั่ง TIMES มีประโยชน์ในการกำหนดอาร์เรย์และตาราง โปรแกรมต่อไปนี้จะแสดงดอกจัน 9 ดอกบนหน้าจอ -
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
mov edx,9 ;message length
mov ecx, stars ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
stars times 9 db '*'
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
*********
NASM มีคำสั่งหลายอย่างที่กำหนดค่าคงที่ เราได้ใช้คำสั่ง EQU ในบทก่อนหน้าแล้ว เราจะพูดถึงคำสั่งสามข้อโดยเฉพาะ -
- EQU
- %assign
- %define
คำสั่ง EQU
EQUคำสั่งใช้สำหรับกำหนดค่าคงที่ ไวยากรณ์ของคำสั่ง EQU มีดังนี้ -
CONSTANT_NAME EQU expression
ตัวอย่างเช่น,
TOTAL_STUDENTS equ 50
จากนั้นคุณสามารถใช้ค่าคงที่นี้ในโค้ดของคุณเช่น -
mov ecx, TOTAL_STUDENTS
cmp eax, TOTAL_STUDENTS
ตัวถูกดำเนินการของคำสั่ง EQU สามารถเป็นนิพจน์ -
LENGTH equ 20
WIDTH equ 10
AREA equ length * width
ส่วนโค้ดด้านบนจะกำหนด AREA เป็น 200
ตัวอย่าง
ตัวอย่างต่อไปนี้แสดงให้เห็นถึงการใช้คำสั่ง EQU -
SYS_EXIT equ 1
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
mov eax,SYS_EXIT ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $ - msg2 msg3 db 'Linux assembly programming! ' len3 equ $- msg3
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
% กำหนดคำสั่ง
%assignคำสั่งสามารถใช้เพื่อกำหนดค่าคงที่เป็นตัวเลขเช่นคำสั่ง EQU คำสั่งนี้อนุญาตให้กำหนดนิยามใหม่ ตัวอย่างเช่นคุณอาจกำหนด TOTAL คงที่เป็น -
%assign TOTAL 10
ต่อมาในโค้ดคุณสามารถกำหนดใหม่เป็น -
%assign TOTAL 20
คำสั่งนี้คำนึงถึงตัวพิมพ์เล็กและใหญ่
% กำหนด Directive
%defineคำสั่งอนุญาตให้กำหนดค่าคงที่ทั้งตัวเลขและสตริง คำสั่งนี้คล้ายกับ #define ใน C ตัวอย่างเช่นคุณอาจกำหนดค่าคงที่ PTR เป็น -
%define PTR [EBP+4]
รหัสด้านบนแทนที่PTRด้วย [EBP + 4]
คำสั่งนี้ยังช่วยให้สามารถกำหนดนิยามใหม่ได้และต้องคำนึงถึงขนาดตัวพิมพ์
คำแนะนำ INC
คำสั่ง INC ใช้สำหรับการเพิ่มตัวถูกดำเนินการทีละตัว ทำงานบนตัวถูกดำเนินการเดียวที่สามารถอยู่ในรีจิสเตอร์หรือในหน่วยความจำ
ไวยากรณ์
คำสั่ง INC มีไวยากรณ์ต่อไปนี้ -
INC destination
ปลายทางตัวถูกดำเนินการอาจเป็นตัวถูกดำเนินการ 8 บิต 16 บิตหรือ 32 บิต
ตัวอย่าง
INC EBX ; Increments 32-bit register
INC DL ; Increments 8-bit register
INC [count] ; Increments the count variable
คำสั่ง DEC
คำสั่ง DEC ใช้สำหรับการลดค่าตัวถูกดำเนินการทีละตัว ทำงานบนตัวถูกดำเนินการเดียวที่สามารถอยู่ในรีจิสเตอร์หรือในหน่วยความจำ
ไวยากรณ์
คำสั่ง DEC มีไวยากรณ์ต่อไปนี้ -
DEC destination
ปลายทางตัวถูกดำเนินการอาจเป็นตัวถูกดำเนินการ 8 บิต 16 บิตหรือ 32 บิต
ตัวอย่าง
segment .data
count dw 0
value db 15
segment .text
inc [count]
dec [value]
mov ebx, count
inc word [ebx]
mov esi, value
dec byte [esi]
คำแนะนำในการเพิ่มและย่อย
คำสั่ง ADD และ SUB ใช้สำหรับการเพิ่ม / ลบข้อมูลไบนารีอย่างง่ายในขนาดไบต์คำและคำคู่เช่นสำหรับการเพิ่มหรือลบตัวถูกดำเนินการ 8 บิต 16 บิตหรือ 32 บิตตามลำดับ
ไวยากรณ์
คำสั่ง ADD และ SUB มีไวยากรณ์ต่อไปนี้ -
ADD/SUB destination, source
คำสั่ง ADD / SUB สามารถเกิดขึ้นได้ระหว่าง -
- ลงทะเบียนเพื่อลงทะเบียน
- หน่วยความจำในการลงทะเบียน
- ลงทะเบียนในหน่วยความจำ
- ลงทะเบียนกับข้อมูลคงที่
- หน่วยความจำกับข้อมูลคงที่
อย่างไรก็ตามเช่นเดียวกับคำแนะนำอื่น ๆ การดำเนินการจากหน่วยความจำต่อหน่วยความจำจะไม่สามารถทำได้โดยใช้คำสั่ง ADD / SUB ชุดการดำเนินการ ADD หรือ SUB หรือล้างแฟล็กโอเวอร์โฟลว์และดำเนินการ
ตัวอย่าง
ตัวอย่างต่อไปนี้จะถามตัวเลขสองหลักจากผู้ใช้เก็บตัวเลขในทะเบียน EAX และ EBX ตามลำดับเพิ่มค่าเก็บผลลัพธ์ไว้ในตำแหน่งหน่วยความจำ ' res ' และสุดท้ายแสดงผลลัพธ์
SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
segment .data
msg1 db "Enter a digit ", 0xA,0xD
len1 equ $- msg1 msg2 db "Please enter a second digit", 0xA,0xD len2 equ $- msg2
msg3 db "The sum is: "
len3 equ $- msg3
segment .bss
num1 resb 2
num2 resb 2
res resb 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
; moving the first number to eax register and second number to ebx
; and subtracting ascii '0' to convert it into a decimal number
mov eax, [num1]
sub eax, '0'
mov ebx, [num2]
sub ebx, '0'
; add eax and ebx
add eax, ebx
; add '0' to to convert the sum from decimal to ASCII
add eax, '0'
; storing the sum in memory location res
mov [res], eax
; print the sum
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80
exit:
mov eax, SYS_EXIT
xor ebx, ebx
int 0x80
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
The program with hardcoded variables −
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The sum is:
7
คำสั่ง MUL / IMUL
มีสองคำแนะนำสำหรับการคูณข้อมูลไบนารี คำสั่ง MUL (Multiply) จัดการกับข้อมูลที่ไม่ได้ลงชื่อและ IMUL (Integer Multiply) จะจัดการกับข้อมูลที่เซ็นชื่อ คำแนะนำทั้งสองมีผลต่อแฟล็ก Carry และ Overflow
ไวยากรณ์
ไวยากรณ์สำหรับคำสั่ง MUL / IMUL มีดังนี้ -
MUL/IMUL multiplier
ตัวคูณในทั้งสองกรณีจะอยู่ในตัวสะสมขึ้นอยู่กับขนาดของตัวคูณและตัวคูณและผลิตภัณฑ์ที่สร้างขึ้นจะถูกเก็บไว้ในสองรีจิสเตอร์ขึ้นอยู่กับขนาดของตัวถูกดำเนินการ ส่วนต่อไปนี้อธิบายคำแนะนำของ MUL ในกรณีต่างๆสามกรณี -
ซีเนียร์ | สถานการณ์ |
---|---|
1 | When two bytes are multiplied − ตัวคูณอยู่ในทะเบียน AL และตัวคูณเป็นไบต์ในหน่วยความจำหรือในรีจิสเตอร์อื่น ผลิตภัณฑ์อยู่ใน AX ผลิตภัณฑ์ 8 บิตลำดับสูงจะถูกเก็บไว้ใน AH และ 8 บิตลำดับต่ำจะถูกเก็บไว้ใน AL
|
2 | When two one-word values are multiplied − ตัวคูณควรอยู่ในทะเบียน AX และตัวคูณคือคำในหน่วยความจำหรือรีจิสเตอร์อื่น ตัวอย่างเช่นสำหรับคำสั่งเช่น MUL DX คุณต้องจัดเก็บตัวคูณใน DX และตัวคูณใน AX ผลิตภัณฑ์ผลลัพธ์คือคำสองคำซึ่งจะต้องมีการลงทะเบียนสองรายการ ส่วนลำดับสูง (ซ้ายสุด) จะถูกเก็บไว้ใน DX และส่วนลำดับล่าง (ขวาสุด) จะถูกเก็บไว้ใน AX
|
3 | When two doubleword values are multiplied − เมื่อคูณค่า doubleword สองค่าตัวคูณควรอยู่ใน EAX และตัวคูณคือค่า doubleword ที่เก็บไว้ในหน่วยความจำหรือในรีจิสเตอร์อื่น ผลิตภัณฑ์ที่สร้างขึ้นจะถูกเก็บไว้ในรีจิสเตอร์ EDX: EAX นั่นคือลำดับสูง 32 บิตจะถูกเก็บไว้ในรีจิสเตอร์ EDX และลำดับต่ำ 32 บิตจะถูกเก็บไว้ในรีจิสเตอร์ EAX
|
ตัวอย่าง
MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL
ตัวอย่าง
ตัวอย่างต่อไปนี้คูณ 3 ด้วย 2 และแสดงผลลัพธ์ -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al,'3'
sub al, '0'
mov bl, '2'
sub bl, '0'
mul bl
add al, '0'
mov [res], al
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The result is:
6
คำแนะนำ DIV / IDIV
การดำเนินการหารสร้างสององค์ประกอบ - ก quotient และก remainder. ในกรณีของการคูณการล้นจะไม่เกิดขึ้นเนื่องจากใช้รีจิสเตอร์แบบยาวสองชั้นเพื่อเก็บผลิตภัณฑ์ไว้ อย่างไรก็ตามในกรณีของการแบ่งอาจเกิดการล้น โปรเซสเซอร์จะสร้างอินเทอร์รัปต์หากเกิดโอเวอร์โฟลว์
คำสั่ง DIV (Divide) ใช้สำหรับข้อมูลที่ไม่ได้ลงชื่อและ IDIV (Integer Divide) ใช้สำหรับข้อมูลที่เซ็นชื่อ
ไวยากรณ์
รูปแบบสำหรับคำสั่ง DIV / IDIV -
DIV/IDIV divisor
เงินปันผลอยู่ในตัวสะสม คำสั่งทั้งสองสามารถทำงานร่วมกับตัวถูกดำเนินการ 8 บิต 16 บิตหรือ 32 บิต การดำเนินการมีผลต่อแฟล็กสถานะทั้งหกสถานะ ส่วนต่อไปนี้อธิบายกรณีการแบ่งสามกรณีที่มีขนาดตัวถูกดำเนินการต่างกัน
ซีเนียร์ | สถานการณ์ |
---|---|
1 | When the divisor is 1 byte − เงินปันผลจะถือว่าอยู่ในการลงทะเบียน AX (16 บิต) หลังจากหารแล้วผลหารจะไปที่รีจิสเตอร์ AL และส่วนที่เหลือจะไปที่รีจิสเตอร์ AH
|
2 | When the divisor is 1 word − การจ่ายเงินปันผลจะถือว่ามีความยาว 32 บิตและในการลงทะเบียน DX: AX 16 บิตลำดับสูงอยู่ใน DX และ 16 บิตลำดับต่ำอยู่ใน AX หลังจากหารแล้วผลหาร 16 บิตจะไปที่รีจิสเตอร์ AX และส่วนที่เหลือ 16 บิตจะไปที่รีจิสเตอร์ DX
|
3 | When the divisor is doubleword − การจ่ายเงินปันผลจะถือว่ายาว 64 บิตและในการลงทะเบียน EDX: EAX 32 บิตลำดับสูงอยู่ใน EDX และ 32 บิตลำดับต่ำอยู่ใน EAX หลังจากหารแล้วผลหาร 32 บิตจะไปที่รีจิสเตอร์ EAX และส่วนที่เหลือ 32 บิตจะไปที่รีจิสเตอร์ EDX
|
ตัวอย่าง
ตัวอย่างต่อไปนี้หาร 8 ด้วย 2 dividend 8 ถูกเก็บไว้ในไฟล์ 16-bit AX register และ divisor 2 ถูกเก็บไว้ในไฟล์ 8-bit BL register.
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax,'8'
sub ax, '0'
mov bl, '2'
sub bl, '0'
div bl
add ax, '0'
mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The result is:
4
ชุดคำสั่งโปรเซสเซอร์มีคำสั่ง AND, OR, XOR, TEST และ NOT Boolean logic ซึ่งจะทดสอบตั้งค่าและล้างบิตตามความต้องการของโปรแกรม
รูปแบบสำหรับคำแนะนำเหล่านี้ -
ซีเนียร์ | คำแนะนำ | รูปแบบ |
---|---|---|
1 | และ | และ operand1, operand2 |
2 | หรือ | หรือ operand1, operand2 |
3 | XOR | XOR operand1, operand2 |
4 | ทดสอบ | TEST operand1, operand2 |
5 | ไม่ | ไม่ใช่ตัวถูกดำเนินการ 1 |
ตัวถูกดำเนินการตัวแรกในทุกกรณีอาจอยู่ในรีจิสเตอร์หรือในหน่วยความจำ ตัวถูกดำเนินการที่สองอาจอยู่ในรีจิสเตอร์ / หน่วยความจำหรือค่า (ค่าคงที่) ในทันที อย่างไรก็ตามไม่สามารถดำเนินการจากหน่วยความจำต่อหน่วยความจำได้ คำแนะนำเหล่านี้จะเปรียบเทียบหรือจับคู่บิตของตัวถูกดำเนินการและตั้งค่าแฟล็ก CF, OF, PF, SF และ ZF
คำสั่ง AND
คำสั่ง AND ใช้สำหรับสนับสนุนนิพจน์เชิงตรรกะโดยดำเนินการบิต AND การดำเนินการ bitwise AND จะส่งกลับ 1 หากบิตที่ตรงกันจากตัวถูกดำเนินการทั้งสองเป็น 1 มิฉะนั้นจะส่งกลับ 0 ตัวอย่างเช่น -
Operand1: 0101
Operand2: 0011
----------------------------
After AND -> Operand1: 0001
การดำเนินการ AND สามารถใช้สำหรับการล้างหนึ่งบิตขึ้นไป ตัวอย่างเช่นสมมติว่า BL register มี 0011 1010 หากคุณต้องการล้างบิตลำดับสูงให้เป็นศูนย์ให้คุณและด้วย 0FH
AND BL, 0FH ; This sets BL to 0000 1010
ลองดูตัวอย่างอื่น หากคุณต้องการตรวจสอบว่าตัวเลขที่ระบุเป็นเลขคี่หรือคู่การทดสอบง่ายๆคือการตรวจสอบจำนวนบิตที่มีนัยสำคัญน้อยที่สุด ถ้านี่คือ 1 จำนวนนั้นเป็นเลขคี่มิฉะนั้นจำนวนจะเป็นคู่
สมมติว่าหมายเลขอยู่ในทะเบียน AL เราสามารถเขียน -
AND AL, 01H ; ANDing with 0000 0001
JZ EVEN_NUMBER
โปรแกรมต่อไปนี้แสดงให้เห็นถึงสิ่งนี้ -
ตัวอย่าง
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax, 8h ;getting 8 in the ax
and ax, 1 ;and ax with 1
jz evnn
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, odd_msg ;message to write
mov edx, len2 ;length of message
int 0x80 ;call kernel
jmp outprog
evnn:
mov ah, 09h
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, even_msg ;message to write
mov edx, len1 ;length of message
int 0x80 ;call kernel
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
even_msg db 'Even Number!' ;message showing even number
len1 equ $ - even_msg odd_msg db 'Odd Number!' ;message showing odd number len2 equ $ - odd_msg
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Even Number!
เปลี่ยนค่าในการลงทะเบียน ax ด้วยเลขคี่เช่น -
mov ax, 9h ; getting 9 in the ax
โปรแกรมจะแสดง:
Odd Number!
ในทำนองเดียวกันการล้างทะเบียนทั้งหมดคุณสามารถทำได้ด้วย 00H
หรือคำแนะนำ
คำสั่ง OR ใช้สำหรับสนับสนุนนิพจน์เชิงตรรกะโดยดำเนินการตามบิตหรือการดำเนินการ ตัวดำเนินการ bitwise OR จะคืนค่า 1 หากบิตที่ตรงกันจากตัวถูกดำเนินการอย่างใดอย่างหนึ่งหรือทั้งสองเป็นหนึ่ง จะคืนค่า 0 ถ้าทั้งสองบิตเป็นศูนย์
ตัวอย่างเช่น,
Operand1: 0101
Operand2: 0011
----------------------------
After OR -> Operand1: 0111
การดำเนินการ OR สามารถใช้สำหรับการตั้งค่าหนึ่งบิตขึ้นไป ตัวอย่างเช่นให้เราสมมติว่า AL register มี 0011 1010 คุณต้องตั้งค่าบิตลำดับต่ำสี่ตัวคุณสามารถหรือด้วยค่า 0000 1111 เช่น FH
OR BL, 0FH ; This sets BL to 0011 1111
ตัวอย่าง
ตัวอย่างต่อไปนี้แสดงให้เห็นถึงคำสั่ง OR ให้เราเก็บค่า 5 และ 3 ในการลงทะเบียน AL และ BL ตามลำดับจากนั้นคำสั่ง
OR AL, BL
ควรจัดเก็บ 7 ในทะเบียน AL -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov al, 5 ;getting 5 in the al
mov bl, 3 ;getting 3 in the bl
or al, bl ;or al and bl registers, result should be 7
add al, byte '0' ;converting decimal to ascii
mov [result], al
mov eax, 4
mov ebx, 1
mov ecx, result
mov edx, 1
int 0x80
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
result resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
7
คำสั่ง XOR
คำสั่ง XOR ใช้การดำเนินการ XOR แบบบิต การดำเนินการ XOR ตั้งค่าบิตผลลัพธ์เป็น 1 ในกรณีที่บิตจากตัวถูกดำเนินการต่างกัน ถ้าบิตจากตัวถูกดำเนินการเหมือนกัน (ทั้ง 0 หรือทั้ง 1) บิตผลลัพธ์จะถูกเคลียร์เป็น 0
ตัวอย่างเช่น,
Operand1: 0101
Operand2: 0011
----------------------------
After XOR -> Operand1: 0110
XORing ตัวถูกดำเนินการด้วยตัวเองเปลี่ยนตัวถูกดำเนินการเป็น 0. ใช้เพื่อล้างทะเบียน
XOR EAX, EAX
คำแนะนำในการทดสอบ
คำสั่ง TEST ทำงานเหมือนกับการดำเนินการ AND แต่ไม่เหมือนกับคำสั่ง AND คือไม่เปลี่ยนตัวถูกดำเนินการตัวแรก ดังนั้นหากเราต้องการตรวจสอบว่าตัวเลขในรีจิสเตอร์เป็นเลขคู่หรือเลขคี่เราสามารถทำได้โดยใช้คำสั่ง TEST โดยไม่ต้องเปลี่ยนหมายเลขเดิม
TEST AL, 01H
JZ EVEN_NUMBER
ไม่ใช่คำแนะนำ
คำสั่ง NOT ใช้การดำเนินการบิตไม่ การดำเนินการ NOT จะย้อนกลับบิตในตัวถูกดำเนินการ ตัวถูกดำเนินการอาจอยู่ในรีจิสเตอร์หรือในหน่วยความจำ
ตัวอย่างเช่น,
Operand1: 0101 0011
After NOT -> Operand1: 1010 1100
การดำเนินการตามเงื่อนไขในภาษาแอสเซมบลีทำได้โดยคำสั่งการวนซ้ำและการแตกแขนงหลายคำสั่ง คำแนะนำเหล่านี้สามารถเปลี่ยนขั้นตอนการควบคุมในโปรแกรมได้ การดำเนินการตามเงื่อนไขจะสังเกตได้ในสองสถานการณ์ -
ซีเนียร์ | คำแนะนำตามเงื่อนไข |
---|---|
1 | Unconditional jump สิ่งนี้ดำเนินการโดยคำสั่ง JMP การดำเนินการตามเงื่อนไขมักเกี่ยวข้องกับการถ่ายโอนการควบคุมไปยังที่อยู่ของคำสั่งที่ไม่เป็นไปตามคำสั่งที่กำลังดำเนินการอยู่ อาจโอนการควบคุมไปข้างหน้าเพื่อดำเนินการชุดคำสั่งใหม่หรือย้อนกลับเพื่อดำเนินการขั้นตอนเดิมซ้ำ |
2 | Conditional jump สิ่งนี้ดำเนินการโดยชุดคำสั่งการกระโดด j <condition> ขึ้นอยู่กับเงื่อนไข คำแนะนำแบบมีเงื่อนไขจะโอนการควบคุมโดยทำลายโฟลว์ตามลำดับและดำเนินการโดยเปลี่ยนค่าออฟเซ็ตใน IP |
ให้เราหารือเกี่ยวกับคำสั่ง CMP ก่อนที่จะพูดถึงคำแนะนำแบบมีเงื่อนไข
คำสั่ง CMP
คำสั่ง CMP เปรียบเทียบสองตัวถูกดำเนินการ โดยทั่วไปจะใช้ในการดำเนินการตามเงื่อนไข คำสั่งนี้โดยทั่วไปจะลบตัวถูกดำเนินการหนึ่งตัวออกจากอีกตัวหนึ่งเพื่อเปรียบเทียบว่าตัวถูกดำเนินการเท่ากันหรือไม่ ไม่รบกวนตัวถูกดำเนินการปลายทางหรือต้นทาง ใช้ร่วมกับคำแนะนำการกระโดดแบบมีเงื่อนไขเพื่อการตัดสินใจ
ไวยากรณ์
CMP destination, source
CMP เปรียบเทียบฟิลด์ข้อมูลตัวเลขสองฟิลด์ ตัวถูกดำเนินการปลายทางอาจอยู่ในรีจิสเตอร์หรือในหน่วยความจำ ตัวถูกดำเนินการต้นทางอาจเป็นข้อมูลคงที่ (ทันที) รีจิสเตอร์หรือหน่วยความจำ
ตัวอย่าง
CMP DX, 00 ; Compare the DX value with zero
JE L7 ; If yes, then jump to label L7
.
.
L7: ...
CMP มักใช้เพื่อเปรียบเทียบว่าค่าตัวนับถึงจำนวนครั้งที่ต้องรันลูปหรือไม่ พิจารณาเงื่อนไขทั่วไปดังต่อไปนี้ -
INC EDX
CMP EDX, 10 ; Compares whether the counter has reached 10
JLE LP1 ; If it is less than or equal to 10, then jump to LP1
กระโดดโดยไม่มีเงื่อนไข
ดังที่ได้กล่าวไว้ก่อนหน้านี้ดำเนินการโดยคำสั่ง JMP การดำเนินการตามเงื่อนไขมักเกี่ยวข้องกับการถ่ายโอนการควบคุมไปยังที่อยู่ของคำสั่งที่ไม่เป็นไปตามคำสั่งที่กำลังดำเนินการอยู่ อาจโอนการควบคุมไปข้างหน้าเพื่อดำเนินการชุดคำสั่งใหม่หรือย้อนกลับเพื่อดำเนินการขั้นตอนเดิมซ้ำ
ไวยากรณ์
คำสั่ง JMP ระบุชื่อเลเบลซึ่งโฟลว์ของการควบคุมจะถูกโอนทันที ไวยากรณ์ของคำสั่ง JMP คือ -
JMP label
ตัวอย่าง
ข้อมูลโค้ดต่อไปนี้แสดงคำสั่ง JMP -
MOV AX, 00 ; Initializing AX to 0
MOV BX, 00 ; Initializing BX to 0
MOV CX, 01 ; Initializing CX to 1
L20:
ADD AX, 01 ; Increment AX
ADD BX, AX ; Add AX to BX
SHL CX, 1 ; shift left CX, this in turn doubles the CX value
JMP L20 ; repeats the statements
กระโดดตามเงื่อนไข
หากเงื่อนไขที่ระบุบางอย่างเป็นที่พอใจในการกระโดดแบบมีเงื่อนไขโฟลว์ควบคุมจะถูกโอนไปยังคำสั่งเป้าหมาย มีคำแนะนำการกระโดดตามเงื่อนไขมากมายขึ้นอยู่กับเงื่อนไขและข้อมูล
ต่อไปนี้เป็นคำแนะนำการข้ามแบบมีเงื่อนไขที่ใช้กับข้อมูลที่ลงนามซึ่งใช้สำหรับการคำนวณทางคณิตศาสตร์ -
คำแนะนำ | คำอธิบาย | ทดสอบธงแล้ว |
---|---|---|
JE / JZ | Jump Equal หรือ Jump Zero | ZF |
JNE / JNZ | กระโดดไม่เท่ากันหรือกระโดดไม่เป็นศูนย์ | ZF |
JG / JNLE | กระโดดมากขึ้นหรือกระโดดไม่น้อย / เท่ากัน | ของ, SF, ZF |
JGE / JNL | กระโดดมากกว่า / เท่ากันหรือกระโดดไม่น้อย | ของ, SF |
JL / JNGE | กระโดดน้อยลงหรือกระโดดไม่มากกว่า / เท่ากัน | ของ, SF |
JLE / JNG | กระโดดน้อยลง / เท่ากันหรือกระโดดไม่มาก | ของ, SF, ZF |
ต่อไปนี้เป็นคำแนะนำการกระโดดตามเงื่อนไขที่ใช้กับข้อมูลที่ไม่ได้ลงชื่อซึ่งใช้สำหรับการดำเนินการทางตรรกะ -
คำแนะนำ | คำอธิบาย | ทดสอบธงแล้ว |
---|---|---|
JE / JZ | Jump Equal หรือ Jump Zero | ZF |
JNE / JNZ | กระโดดไม่เท่ากันหรือกระโดดไม่เป็นศูนย์ | ZF |
JA / JNBE | กระโดดขึ้นไปข้างบนหรือกระโดดไม่ต่ำกว่า / เท่ากับ | CF, ZF |
JAE / JNB | กระโดดสูงกว่า / เท่ากันหรือกระโดดไม่ต่ำกว่า | CF |
เจบี / เจเอ็นเอ | กระโดดด้านล่างหรือกระโดดไม่สูงกว่า / เท่ากัน | CF |
JBE / JNA | กระโดดด้านล่าง / เท่ากันหรือกระโดดไม่เหนือ | AF, CF |
คำแนะนำการกระโดดตามเงื่อนไขต่อไปนี้มีการใช้งานพิเศษและตรวจสอบค่าของแฟล็ก -
คำแนะนำ | คำอธิบาย | ทดสอบธงแล้ว |
---|---|---|
JXCZ | กระโดดถ้า CX เป็นศูนย์ | ไม่มี |
JC | กระโดดหากพกพา | CF |
JNC | กระโดดถ้าไม่มีการพกพา | CF |
จ | กระโดดถ้าล้น | ของ |
JNO | กระโดดถ้าไม่มีล้น | ของ |
JP / JPE | Jump Parity หรือ Jump Parity Even | PF |
JNP / JPO | Jump No Parity หรือ Jump Parity Odd | PF |
JS | Jump Sign (ค่าลบ) | SF |
จส | Jump No Sign (ค่าบวก) | SF |
ไวยากรณ์สำหรับชุดคำสั่ง J <condition> -
ตัวอย่าง,
CMP AL, BL
JE EQUAL
CMP AL, BH
JE EQUAL
CMP AL, CL
JE EQUAL
NON_EQUAL: ...
EQUAL: ...
ตัวอย่าง
โปรแกรมต่อไปนี้แสดงตัวแปรสามตัวที่ใหญ่ที่สุด ตัวแปรคือตัวแปรสองหลัก ตัวแปรทั้งสาม num1, num2 และ num3 มีค่า 47, 22 และ 31 ตามลำดับ -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx, [num1]
cmp ecx, [num2]
jg check_third_num
mov ecx, [num2]
check_third_num:
cmp ecx, [num3]
jg _exit
mov ecx, [num3]
_exit:
mov [largest], ecx
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,largest
mov edx, 2
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1
int 80h
section .data
msg db "The largest digit is: ", 0xA,0xD
len equ $- msg
num1 dd '47'
num2 dd '22'
num3 dd '31'
segment .bss
largest resb 2
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The largest digit is:
47
คำสั่ง JMP สามารถใช้สำหรับการใช้งานลูป ตัวอย่างเช่นสามารถใช้ข้อมูลโค้ดต่อไปนี้เพื่อดำเนินการลูปบอดี้ได้ 10 ครั้ง
MOV CL, 10
L1:
<LOOP-BODY>
DEC CL
JNZ L1
อย่างไรก็ตามชุดคำสั่งโปรเซสเซอร์ประกอบด้วยกลุ่มคำสั่งแบบวนซ้ำสำหรับการใช้งานการวนซ้ำ คำสั่ง LOOP พื้นฐานมีไวยากรณ์ต่อไปนี้ -
LOOP label
โดยที่labelคือเลเบลเป้าหมายที่ระบุคำแนะนำเป้าหมายตามคำแนะนำในการกระโดด คำสั่ง LOOP ถือว่าไฟล์ECX register contains the loop count. เมื่อดำเนินการคำสั่งแบบวนซ้ำการลงทะเบียน ECX จะลดลงและตัวควบคุมจะกระโดดไปที่เลเบลเป้าหมายจนกระทั่งค่าทะเบียน ECX นั่นคือตัวนับถึงค่าศูนย์
ข้อมูลโค้ดด้านบนสามารถเขียนเป็น -
mov ECX,10
l1:
<loop body>
loop l1
ตัวอย่าง
โปรแกรมต่อไปนี้จะพิมพ์หมายเลข 1 ถึง 9 บนหน้าจอ -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,10
mov eax, '1'
l1:
mov [num], eax
mov eax, 4
mov ebx, 1
push ecx
mov ecx, num
mov edx, 1
int 0x80
mov eax, [num]
sub eax, '0'
inc eax
add eax, '0'
pop ecx
loop l1
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
num resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
123456789:
โดยทั่วไปข้อมูลตัวเลขจะแสดงในระบบไบนารี คำสั่งเลขคณิตใช้กับข้อมูลไบนารี เมื่อตัวเลขแสดงบนหน้าจอหรือป้อนจากแป้นพิมพ์ตัวเลขจะอยู่ในรูปแบบ ASCII
จนถึงตอนนี้เราได้แปลงข้อมูลอินพุตนี้ในรูปแบบ ASCII เป็นไบนารีสำหรับการคำนวณทางคณิตศาสตร์และแปลงผลลัพธ์กลับเป็นไบนารี รหัสต่อไปนี้แสดงสิ่งนี้ -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The sum is:
7
อย่างไรก็ตามการแปลงดังกล่าวมีค่าใช้จ่ายสูงและการเขียนโปรแกรมภาษาแอสเซมบลีช่วยให้สามารถประมวลผลตัวเลขได้อย่างมีประสิทธิภาพมากขึ้นในรูปแบบไบนารี ตัวเลขทศนิยมสามารถแสดงได้สองรูปแบบ -
- แบบฟอร์ม ASCII
- BCD หรือ Binary Coded แบบฟอร์มทศนิยม
การเป็นตัวแทนของ ASCII
ในการแทนค่า ASCII ตัวเลขทศนิยมจะถูกเก็บเป็นสตริงของอักขระ ASCII ตัวอย่างเช่นค่าทศนิยม 1234 ถูกจัดเก็บเป็น -
31 32 33 34H
โดยที่ 31H คือค่า ASCII สำหรับ 1, 32H คือค่า ASCII สำหรับ 2 และอื่น ๆ มีสี่คำสั่งสำหรับการประมวลผลตัวเลขในการแทน ASCII -
AAA - ASCII ปรับหลังจากเพิ่ม
AAS - ASCII ปรับหลังการลบ
AAM - ASCII ปรับหลังจากการคูณ
AAD - ASCII ปรับก่อนหาร
คำแนะนำเหล่านี้ไม่ใช้ตัวถูกดำเนินการใด ๆ และถือว่าตัวถูกดำเนินการที่จำเป็นต้องอยู่ในทะเบียน AL
ตัวอย่างต่อไปนี้ใช้คำสั่ง AAS เพื่อแสดงแนวคิด -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
sub ah, ah
mov al, '9'
sub al, '3'
aas
or al, 30h
mov [res], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,res ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Result is:',0xa
len equ $ - msg
section .bss
res resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The Result is:
6
การเป็นตัวแทน BCD
การเป็นตัวแทน BCD มีสองประเภท -
- การแสดง BCD ที่ไม่ได้บรรจุ
- การแสดง BCD แบบแพ็ค
ในการแทนค่า BCD ที่คลายแพ็กแต่ละไบต์จะเก็บค่าฐานสองที่เทียบเท่ากับเลขฐานสิบ ตัวอย่างเช่นหมายเลข 1234 ถูกจัดเก็บเป็น -
01 02 03 04H
มีสองคำแนะนำสำหรับการประมวลผลตัวเลขเหล่านี้ -
AAM - ASCII ปรับหลังจากการคูณ
AAD - ASCII ปรับก่อนหาร
คำแนะนำการปรับ ASCII สี่คำสั่งคือ AAA, AAS, AAM และ AAD สามารถใช้กับการแสดง BCD ที่ไม่ได้บรรจุหีบห่อ ในการแทนค่า BCD แต่ละหลักจะถูกจัดเก็บโดยใช้สี่บิต ทศนิยมสองหลักถูกบรรจุลงในไบต์ ตัวอย่างเช่นหมายเลข 1234 ถูกจัดเก็บเป็น -
12 34H
มีสองคำแนะนำสำหรับการประมวลผลตัวเลขเหล่านี้ -
DAA - ปรับทศนิยมหลังจากเพิ่ม
DAS - ทศนิยมปรับหลังจากการลบ
ไม่มีการสนับสนุนสำหรับการคูณและการหารในการแสดง BCD แบบบรรจุ
ตัวอย่าง
โปรแกรมต่อไปนี้จะเพิ่มตัวเลขทศนิยม 5 หลักสองตัวและแสดงผลรวม ใช้แนวคิดข้างต้น -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov esi, 4 ;pointing to the rightmost digit
mov ecx, 5 ;num of digits
clc
add_loop:
mov al, [num1 + esi]
adc al, [num2 + esi]
aaa
pushf
or al, 30h
popf
mov [sum + esi], al
dec esi
loop add_loop
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,5 ;message length
mov ecx,sum ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Sum is:',0xa
len equ $ - msg
num1 db '12345'
num2 db '23456'
sum db ' '
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The Sum is:
35801
เราได้ใช้สตริงความยาวตัวแปรในตัวอย่างก่อนหน้านี้แล้ว สตริงความยาวตัวแปรสามารถมีอักขระได้มากเท่าที่ต้องการ โดยทั่วไปเราระบุความยาวของสตริงด้วยวิธีใดวิธีหนึ่งจากสองวิธี -
- การจัดเก็บความยาวสตริงอย่างชัดเจน
- ใช้อักขระยาม
เราสามารถจัดเก็บความยาวสตริงได้อย่างชัดเจนโดยใช้สัญลักษณ์ตัวนับตำแหน่ง $ ที่แสดงถึงค่าปัจจุบันของตัวนับตำแหน่ง ในตัวอย่างต่อไปนี้ -
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
$ ชี้ไปไบต์หลังจากที่ตัวอักษรตัวสุดท้ายของตัวแปรสตริงผงชูรส ดังนั้น,$-msgให้ความยาวของสตริง เรายังสามารถเขียน
msg db 'Hello, world!',0xa ;our dear string
len equ 13 ;length of our dear string
หรือคุณสามารถจัดเก็บสตริงที่มีอักขระ Sentinel ต่อท้ายเพื่อคั่นสตริงแทนการจัดเก็บความยาวสตริงอย่างชัดเจน อักขระ Sentinel ควรเป็นอักขระพิเศษที่ไม่ปรากฏในสตริง
ตัวอย่างเช่น -
message DB 'I am loving it!', 0
คำแนะนำสตริง
คำสั่งสตริงแต่ละคำสั่งอาจต้องการตัวถูกดำเนินการต้นทางตัวถูกดำเนินการปลายทางหรือทั้งสองอย่าง สำหรับเซ็กเมนต์ 32 บิตคำสั่งสตริงใช้ ESI และ EDI รีจิสเตอร์เพื่อชี้ไปยังตัวถูกดำเนินการต้นทางและปลายทางตามลำดับ
อย่างไรก็ตามสำหรับเซ็กเมนต์ 16 บิต SI และ DI รีจิสเตอร์จะใช้เพื่อชี้ไปยังต้นทางและปลายทางตามลำดับ
มีคำแนะนำพื้นฐานห้าประการสำหรับการประมวลผลสตริง พวกเขาคือ -
MOVS - คำสั่งนี้จะย้าย 1 Byte, Word หรือ Doubleword ของข้อมูลจากตำแหน่งหน่วยความจำไปยังอีกที่หนึ่ง
LODS- คำสั่งนี้โหลดจากหน่วยความจำ ถ้าตัวถูกดำเนินการมีขนาดหนึ่งไบต์จะถูกโหลดลงในทะเบียน AL หากตัวถูกดำเนินการเป็นคำเดียวจะถูกโหลดลงในทะเบียน AX และคำสองคำจะถูกโหลดลงในทะเบียน EAX
STOS - คำสั่งนี้จัดเก็บข้อมูลจากรีจิสเตอร์ (AL, AX หรือ EAX) ไปยังหน่วยความจำ
CMPS- คำสั่งนี้จะเปรียบเทียบรายการข้อมูลสองรายการในหน่วยความจำ ข้อมูลอาจมีขนาดไบต์คำหรือคำสองคำ
SCAS - คำสั่งนี้จะเปรียบเทียบเนื้อหาของรีจิสเตอร์ (AL, AX หรือ EAX) กับเนื้อหาของรายการในหน่วยความจำ
คำสั่งข้างต้นแต่ละคำสั่งมีเวอร์ชันไบต์คำและคำสองคำและคำสั่งสตริงสามารถทำซ้ำได้โดยใช้คำนำหน้าคำซ้ำ
คำแนะนำเหล่านี้ใช้คู่ ES: DI และ DS: SI ของรีจิสเตอร์โดยที่ DI และ SI รีจิสเตอร์มีแอดเดรสออฟเซ็ตที่ถูกต้องซึ่งอ้างถึงไบต์ที่เก็บไว้ในหน่วยความจำ โดยปกติ SI จะเชื่อมโยงกับ DS (ส่วนข้อมูล) และ DI จะเชื่อมโยงกับ ES (เซ็กเมนต์พิเศษ) เสมอ
DS: SI (หรือ ESI) และ ES: DI (หรือ EDI) จะลงทะเบียนชี้ไปยังตัวถูกดำเนินการต้นทางและปลายทางตามลำดับ ตัวถูกดำเนินการต้นทางจะถือว่าอยู่ที่ DS: SI (หรือ ESI) และตัวถูกดำเนินการปลายทางที่ ES: DI (หรือ EDI) ในหน่วยความจำ
สำหรับที่อยู่ 16 บิตจะใช้การลงทะเบียน SI และ DI และสำหรับที่อยู่ 32 บิตจะใช้การลงทะเบียน ESI และ EDI
ตารางต่อไปนี้แสดงคำแนะนำสตริงเวอร์ชันต่างๆและพื้นที่สมมติของตัวถูกดำเนินการ
คำแนะนำพื้นฐาน | โอเปร่าที่ | การทำงานของไบต์ | การทำงานของ Word | การทำงานของคำสองคำ |
---|---|---|---|---|
MOVS | ES: DI, DS: SI | MOVSB | MOVSW | MOVSD |
LODS | AX, DS: SI | LODSB | LODSW | LODSD |
STOS | ES: DI, AX | STOSB | STOSW | STOSD |
CMPS | DS: SI, ES: DI | CMPSB | CMPSW | CMPSD |
SCAS | ES: DI, AX | SCASB | SCASW | SCASD |
คำนำหน้าซ้ำ
คำนำหน้า REP เมื่อตั้งค่าก่อนคำสั่งสตริงเช่น - REP MOVSB ทำให้เกิดการทำซ้ำของคำสั่งตามตัวนับที่วางไว้ที่รีจิสเตอร์ CX REP ดำเนินการตามคำสั่งลด CX ลง 1 และตรวจสอบว่า CX เป็นศูนย์หรือไม่ ทำซ้ำการประมวลผลคำสั่งจนกว่า CX จะเป็นศูนย์
Direction Flag (DF) กำหนดทิศทางของการดำเนินการ
- ใช้ CLD (Clear Direction Flag, DF = 0) เพื่อดำเนินการจากซ้ายไปขวา
- ใช้ STD (Set Direction Flag, DF = 1) เพื่อดำเนินการจากขวาไปซ้าย
คำนำหน้า REP ยังมีรูปแบบต่อไปนี้:
REP: เป็นการทำซ้ำโดยไม่มีเงื่อนไข ทำซ้ำการดำเนินการจนกว่า CX จะเป็นศูนย์
REPE หรือ REPZ: เป็นการทำซ้ำตามเงื่อนไข ทำซ้ำการดำเนินการในขณะที่ค่าสถานะศูนย์แสดงว่าเท่ากับ / ศูนย์ หยุดเมื่อ ZF ระบุว่าไม่เท่ากัน / ศูนย์หรือเมื่อ CX เป็นศูนย์
REPNE หรือ REPNZ: ยังเป็นการทำซ้ำตามเงื่อนไข ทำซ้ำการดำเนินการในขณะที่ค่าสถานะศูนย์ระบุว่าไม่เท่ากัน / ศูนย์ จะหยุดเมื่อ ZF ระบุว่าเท่ากับ / ศูนย์หรือเมื่อ CX ลดลงเป็นศูนย์
เราได้พูดคุยกันแล้วว่าคำสั่งนิยามข้อมูลไปยังแอสเซมเบลอร์ใช้สำหรับการจัดสรรหน่วยเก็บข้อมูลสำหรับตัวแปร ตัวแปรยังสามารถเริ่มต้นด้วยค่าเฉพาะบางอย่าง ค่าเริ่มต้นสามารถระบุได้ในรูปแบบเลขฐานสิบหกฐานสิบหรือฐานสอง
ตัวอย่างเช่นเราสามารถกำหนดตัวแปรคำว่า "เดือน" ด้วยวิธีใดวิธีหนึ่งดังต่อไปนี้ -
MONTHS DW 12
MONTHS DW 0CH
MONTHS DW 0110B
คำสั่งนิยามข้อมูลยังสามารถใช้สำหรับการกำหนดอาร์เรย์หนึ่งมิติ ให้เรากำหนดอาร์เรย์ของตัวเลขหนึ่งมิติ
NUMBERS DW 34, 45, 56, 67, 75, 89
คำจำกัดความข้างต้นประกาศอาร์เรย์หกคำแต่ละคำเริ่มต้นด้วยตัวเลข 34, 45, 56, 67, 75, 89 ซึ่งจะจัดสรรพื้นที่หน่วยความจำต่อเนื่อง 2x6 = 12 ไบต์ ที่อยู่สัญลักษณ์ของหมายเลขแรกจะเป็น NUMBERS และของตัวเลขที่สองจะเป็น NUMBERS + 2 เป็นต้นไป
ให้เรายกตัวอย่างอื่น คุณสามารถกำหนดอาร์เรย์ชื่อสินค้าคงคลังขนาด 8 และเริ่มต้นค่าทั้งหมดด้วยศูนย์เป็น -
INVENTORY DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
ซึ่งสามารถย่อได้ว่า -
INVENTORY DW 0, 0 , 0 , 0 , 0 , 0 , 0 , 0
นอกจากนี้ยังสามารถใช้คำสั่ง TIMES สำหรับการเริ่มต้นหลายค่าให้เป็นค่าเดียวกัน เมื่อใช้ TIMES อาร์เรย์ INVENTORY สามารถกำหนดเป็น:
INVENTORY TIMES 8 DW 0
ตัวอย่าง
ตัวอย่างต่อไปนี้แสดงให้เห็นถึงแนวคิดข้างต้นโดยการกำหนดอาร์เรย์ 3 องค์ประกอบ x ซึ่งเก็บค่าสามค่า: 2, 3 และ 4 จะเพิ่มค่าในอาร์เรย์และแสดงผลรวม 9 -
section .text
global _start ;must be declared for linker (ld)
_start:
mov eax,3 ;number bytes to be summed
mov ebx,0 ;EBX will store the sum
mov ecx, x ;ECX will point to the current element to be summed
top: add ebx, [ecx]
add ecx,1 ;move pointer to next element
dec eax ;decrement counter
jnz top ;if counter not 0, then loop again
done:
add ebx, '0'
mov [sum], ebx ;done, store result in "sum"
display:
mov edx,1 ;message length
mov ecx, sum ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
global x
x:
db 2
db 4
db 3
sum:
db 0
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
9
โพรซีเดอร์หรือรูทีนย่อยมีความสำคัญมากในภาษาแอสเซมบลีเนื่องจากโปรแกรมภาษาแอสเซมบลีมักจะมีขนาดใหญ่ ขั้นตอนถูกระบุโดยชื่อ ตามชื่อนี้จะมีการอธิบายเนื้อหาของขั้นตอนซึ่งทำงานได้ดี สิ้นสุดขั้นตอนจะระบุโดยคำสั่งส่งคืน
ไวยากรณ์
ต่อไปนี้เป็นไวยากรณ์เพื่อกำหนดขั้นตอน -
proc_name:
procedure body
...
ret
โพรซีเดอร์ถูกเรียกใช้จากฟังก์ชันอื่นโดยใช้คำสั่ง CALL คำสั่ง CALL ควรมีชื่อของขั้นตอนที่เรียกว่าเป็นอาร์กิวเมนต์ดังที่แสดงด้านล่าง -
CALL proc_name
ขั้นตอนที่เรียกว่าจะส่งคืนการควบคุมไปยังขั้นตอนการเรียกโดยใช้คำสั่ง RET
ตัวอย่าง
ให้เราเขียนขั้นตอนง่ายๆที่ชื่อsumที่เพิ่มตัวแปรที่เก็บไว้ในทะเบียน ECX และ EDX และส่งกลับผลรวมในทะเบียน EAX -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ecx,'4'
sub ecx, '0'
mov edx, '5'
sub edx, '0'
call sum ;call sum procedure
mov [res], eax
mov ecx, msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx, res
mov edx, 1
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
sum:
mov eax, ecx
add eax, edx
add eax, '0'
ret
section .data
msg db "The sum is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The sum is:
9
โครงสร้างข้อมูลกองซ้อน
สแต็กคือโครงสร้างข้อมูลที่เหมือนอาร์เรย์ในหน่วยความจำซึ่งข้อมูลสามารถจัดเก็บและลบออกจากตำแหน่งที่เรียกว่า 'ด้านบน' ของสแต็ก ข้อมูลที่ต้องการจัดเก็บจะถูก 'ผลัก' เข้าไปในสแต็กและข้อมูลที่จะดึงออกมาจะถูก 'ดึง' ออกจากสแต็ก Stack เป็นโครงสร้างข้อมูล LIFO กล่าวคือข้อมูลที่จัดเก็บไว้ก่อนจะถูกดึงมาล่าสุด
ภาษาแอสเซมบลีมีคำสั่งสองคำสั่งสำหรับการดำเนินการสแต็ก: PUSH และ POP คำแนะนำเหล่านี้มีไวยากรณ์เช่น -
PUSH operand
POP address/register
พื้นที่หน่วยความจำที่สงวนไว้ในส่วนของสแต็กใช้สำหรับการนำสแตกไปใช้งาน การลงทะเบียน SS และ ESP (หรือ SP) ใช้สำหรับการนำสแตกไปใช้งาน ด้านบนสุดของสแต็กซึ่งชี้ไปยังรายการข้อมูลสุดท้ายที่แทรกลงในสแต็กจะถูกชี้ไปที่ SS: ESP register โดยที่ SS register ชี้ไปที่จุดเริ่มต้นของกลุ่ม stack และ SP (หรือ ESP) ให้ค่าชดเชย ส่วนสแต็ก
การใช้งานสแต็กมีลักษณะดังต่อไปนี้ -
เท่านั้น words หรือ doublewords สามารถบันทึกลงในสแต็กไม่ใช่ไบต์
สแต็กเติบโตในทิศทางย้อนกลับกล่าวคือไปยังที่อยู่หน่วยความจำส่วนล่าง
ด้านบนของสแต็กชี้ไปยังรายการสุดท้ายที่แทรกในสแต็ก ชี้ไปที่ไบต์ล่างของคำสุดท้ายที่แทรก
ดังที่เราได้กล่าวถึงการจัดเก็บค่าของรีจิสเตอร์ในสแต็กก่อนที่จะนำไปใช้งาน สามารถทำได้ด้วยวิธีต่อไปนี้ -
; Save the AX and BX registers in the stack
PUSH AX
PUSH BX
; Use the registers for other purpose
MOV AX, VALUE1
MOV BX, VALUE2
...
MOV VALUE1, AX
MOV VALUE2, BX
; Restore the original values
POP BX
POP AX
ตัวอย่าง
โปรแกรมต่อไปนี้แสดงชุดอักขระ ASCII ทั้งหมด โปรแกรมหลักเรียกขั้นตอนที่ชื่อว่าdisplayซึ่งแสดงชุดอักขระ ASCII
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
call display
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
display:
mov ecx, 256
next:
push ecx
mov eax, 4
mov ebx, 1
mov ecx, achar
mov edx, 1
int 80h
pop ecx
mov dx, [achar]
cmp byte [achar], 0dh
inc byte [achar]
loop next
ret
section .data
achar db '0'
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
...
...
กระบวนการเรียกซ้ำคือกระบวนการที่เรียกตัวเอง การเรียกซ้ำมีสองประเภท: ทางตรงและทางอ้อม ในการเรียกซ้ำโดยตรงโพรซีเดอร์จะเรียกตัวเองและในการเรียกซ้ำทางอ้อมโพรซีเดอร์แรกเรียกโพรซีเดอร์ที่สองซึ่งจะเรียกโพรซีเดอร์แรก
การเรียกซ้ำสามารถสังเกตได้ในอัลกอริทึมทางคณิตศาสตร์จำนวนมาก ตัวอย่างเช่นพิจารณากรณีของการคำนวณแฟกทอเรียลของตัวเลข แฟกทอเรียลของตัวเลขกำหนดโดยสมการ -
Fact (n) = n * fact (n-1) for n > 0
ตัวอย่างเช่นแฟกทอเรียลของ 5 คือ 1 x 2 x 3 x 4 x 5 = 5 x แฟกทอเรียลของ 4 และนี่อาจเป็นตัวอย่างที่ดีในการแสดงโพรซีเดอร์แบบวนซ้ำ อัลกอริธึมการเรียกซ้ำทุกรายการต้องมีเงื่อนไขสิ้นสุดกล่าวคือควรหยุดการเรียกโปรแกรมแบบเรียกซ้ำเมื่อเงื่อนไขสำเร็จ ในกรณีของอัลกอริทึมแฟกทอเรียลเงื่อนไขสิ้นสุดจะถึงเมื่อ n เป็น 0
โปรแกรมต่อไปนี้แสดงวิธีการใช้แฟกทอเรียล n ในภาษาแอสเซมบลี เพื่อให้โปรแกรมเรียบง่ายเราจะคำนวณแฟกทอเรียล 3
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov bx, 3 ;for calculating factorial 3
call proc_fact
add ax, 30h
mov [fact], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,fact ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
proc_fact:
cmp bl, 1
jg do_calculation
mov ax, 1
ret
do_calculation:
dec bl
call proc_fact
inc bl
mul bl ;ax = al * bl
ret
section .data
msg db 'Factorial 3 is:',0xa
len equ $ - msg
section .bss
fact resb 1
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Factorial 3 is:
6
การเขียนมาโครเป็นอีกวิธีหนึ่งในการสร้างความมั่นใจในการเขียนโปรแกรมแบบแยกส่วนในภาษาแอสเซมบลี
มาโครคือลำดับของคำสั่งที่กำหนดโดยชื่อและสามารถใช้ได้ทุกที่ในโปรแกรม
ใน NASM มาโครถูกกำหนดด้วย %macro และ %endmacro คำสั่ง
มาโครเริ่มต้นด้วย% macro directive และลงท้ายด้วย% endmacro directive
ไวยากรณ์สำหรับนิยามมาโคร -
%macro macro_name number_of_params
<macro body>
%endmacro
โดยที่number_of_paramsระบุพารามิเตอร์ตัวเลขmacro_nameระบุชื่อของมาโคร
มาโครถูกเรียกใช้โดยใช้ชื่อมาโครพร้อมกับพารามิเตอร์ที่จำเป็น เมื่อคุณจำเป็นต้องใช้ลำดับคำสั่งหลาย ๆ ครั้งในโปรแกรมคุณสามารถใส่คำสั่งเหล่านั้นลงในมาโครและใช้แทนการเขียนคำสั่งตลอดเวลา
ตัวอย่างเช่นความต้องการโปรแกรมทั่วไปคือการเขียนสตริงอักขระในหน้าจอ สำหรับการแสดงสตริงอักขระคุณต้องมีลำดับคำแนะนำดังต่อไปนี้ -
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
ในตัวอย่างข้างต้นของการแสดงสตริงอักขระการลงทะเบียน EAX, EBX, ECX และ EDX ถูกใช้โดยการเรียกฟังก์ชัน INT 80H ดังนั้นทุกครั้งที่คุณต้องการแสดงบนหน้าจอคุณต้องบันทึกรีจิสเตอร์เหล่านี้ในสแต็กเรียกใช้ INT 80H จากนั้นเรียกคืนค่าดั้งเดิมของรีจิสเตอร์จากสแต็ก ดังนั้นการเขียนมาโครสองตัวเพื่อบันทึกและกู้คืนข้อมูลจะเป็นประโยชน์
เราสังเกตเห็นว่าคำแนะนำบางอย่างเช่น IMUL, IDIV, INT ฯลฯ จำเป็นต้องมีข้อมูลบางอย่างเพื่อจัดเก็บไว้ในการลงทะเบียนบางรายการและส่งคืนค่าในรีจิสเตอร์เฉพาะบางรายการ หากโปรแกรมใช้การลงทะเบียนเหล่านั้นในการเก็บรักษาข้อมูลสำคัญอยู่แล้วข้อมูลที่มีอยู่จากการลงทะเบียนเหล่านี้ควรถูกบันทึกไว้ในสแต็กและเรียกคืนหลังจากดำเนินการคำสั่ง
ตัวอย่าง
ตัวอย่างต่อไปนี้แสดงการกำหนดและการใช้มาโคร -
; A macro with two parameters
; Implements the write system call
%macro write_string 2
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
%endmacro
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
write_string msg1, len1
write_string msg2, len2
write_string msg3, len3
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1 msg2 db 'Welcome to the world of,', 0xA,0xD len2 equ $- msg2
msg3 db 'Linux assembly programming! '
len3 equ $- msg3
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
ระบบจะพิจารณาข้อมูลอินพุตหรือเอาต์พุตเป็นสตรีมไบต์ มีสตรีมไฟล์มาตรฐานสามไฟล์ -
- อินพุตมาตรฐาน (stdin)
- เอาต์พุตมาตรฐาน (stdout) และ
- ข้อผิดพลาดมาตรฐาน (stderr)
ตัวอธิบายไฟล์
ก file descriptorคือจำนวนเต็ม 16 บิตที่กำหนดให้กับไฟล์เป็น ID ไฟล์ เมื่อสร้างไฟล์ใหม่หรือเปิดไฟล์ที่มีอยู่ตัวอธิบายไฟล์จะถูกใช้เพื่อเข้าถึงไฟล์
ตัวอธิบายไฟล์ของสตรีมไฟล์มาตรฐาน - stdin, stdout และ stderr คือ 0, 1 และ 2 ตามลำดับ
ตัวชี้ไฟล์
ก file pointerระบุตำแหน่งสำหรับการดำเนินการอ่าน / เขียนที่ตามมาในไฟล์ในรูปของไบต์ แต่ละไฟล์ถือเป็นลำดับไบต์ ไฟล์ที่เปิดแต่ละไฟล์เชื่อมโยงกับตัวชี้ไฟล์ที่ระบุออฟเซ็ตเป็นไบต์โดยสัมพันธ์กับจุดเริ่มต้นของไฟล์ เมื่อเปิดไฟล์ตัวชี้ไฟล์จะถูกตั้งค่าเป็นศูนย์
ระบบจัดการไฟล์โทร
ตารางต่อไปนี้อธิบายสั้น ๆ เกี่ยวกับการเรียกระบบที่เกี่ยวข้องกับการจัดการไฟล์ -
% eax | ชื่อ | % ebx | % ecx | % edx |
---|---|---|---|---|
2 | sys_fork | โครงสร้าง pt_regs | - | - |
3 | sys_read | int ที่ไม่ได้ลงนาม | ถ่าน * | size_t |
4 | sys_write | int ที่ไม่ได้ลงนาม | const ถ่าน * | size_t |
5 | sys_open | const ถ่าน * | int | int |
6 | sys_close | int ที่ไม่ได้ลงนาม | - | - |
8 | sys_creat | const ถ่าน * | int | - |
19 | sys_lseek | int ที่ไม่ได้ลงนาม | off_t | int ที่ไม่ได้ลงนาม |
ขั้นตอนที่จำเป็นสำหรับการใช้การเรียกระบบนั้นเหมือนกันดังที่เราได้กล่าวไว้ก่อนหน้านี้ -
- ใส่หมายเลขการโทรของระบบในทะเบียน EAX
- จัดเก็บอาร์กิวเมนต์สำหรับการเรียกระบบในการลงทะเบียน EBX, ECX และอื่น ๆ
- โทรไปที่การขัดจังหวะที่เกี่ยวข้อง (80 ชม.)
- โดยปกติผลลัพธ์จะถูกส่งกลับในทะเบียน EAX
การสร้างและเปิดไฟล์
สำหรับการสร้างและเปิดไฟล์ให้ดำเนินการดังต่อไปนี้ -
- ใส่การเรียกระบบ sys_creat () หมายเลข 8 ในทะเบียน EAX
- ใส่ชื่อไฟล์ในทะเบียน EBX
- ใส่สิทธิ์ของไฟล์ในทะเบียน ECX
การเรียกระบบจะส่งคืนไฟล์ descriptor ของไฟล์ที่สร้างในทะเบียน EAX ในกรณีที่เกิดข้อผิดพลาดรหัสข้อผิดพลาดจะอยู่ในทะเบียน EAX
การเปิดไฟล์ที่มีอยู่
สำหรับการเปิดไฟล์ที่มีอยู่ให้ดำเนินการดังต่อไปนี้ -
- ใส่การเรียกระบบ sys_open () หมายเลข 5 ในทะเบียน EAX
- ใส่ชื่อไฟล์ในทะเบียน EBX
- ใส่โหมดการเข้าถึงไฟล์ในทะเบียน ECX
- ใส่สิทธิ์ของไฟล์ในการลงทะเบียน EDX
การเรียกระบบจะส่งคืนไฟล์ descriptor ของไฟล์ที่สร้างในทะเบียน EAX ในกรณีที่เกิดข้อผิดพลาดรหัสข้อผิดพลาดจะอยู่ในทะเบียน EAX
ในโหมดการเข้าถึงไฟล์ที่ใช้กันมากที่สุด ได้แก่ อ่านอย่างเดียว (0) เขียนอย่างเดียว (1) และอ่าน - เขียน (2)
อ่านจากไฟล์
สำหรับการอ่านจากไฟล์ให้ดำเนินการดังต่อไปนี้ -
ใส่การเรียกระบบ sys_read () หมายเลข 3 ในทะเบียน EAX
ใส่ file descriptor ในรีจิสเตอร์ EBX
วางตัวชี้ไปที่บัฟเฟอร์อินพุตในทะเบียน ECX
ใส่ขนาดบัฟเฟอร์เช่นจำนวนไบต์ที่จะอ่านในรีจิสเตอร์ EDX
การเรียกระบบจะส่งคืนจำนวนไบต์ที่อ่านในทะเบียน EAX ในกรณีที่เกิดข้อผิดพลาดรหัสข้อผิดพลาดจะอยู่ในทะเบียน EAX
การเขียนไฟล์
สำหรับการเขียนลงไฟล์ให้ดำเนินการดังต่อไปนี้ -
ใส่การเรียกระบบ sys_write () หมายเลข 4 ในทะเบียน EAX
ใส่ file descriptor ในรีจิสเตอร์ EBX
วางตัวชี้ไปที่บัฟเฟอร์เอาต์พุตในทะเบียน ECX
ใส่ขนาดบัฟเฟอร์เช่นจำนวนไบต์ที่จะเขียนในรีจิสเตอร์ EDX
การเรียกระบบจะส่งคืนจำนวนไบต์จริงที่เขียนในทะเบียน EAX ในกรณีที่เกิดข้อผิดพลาดรหัสข้อผิดพลาดจะอยู่ในทะเบียน EAX
การปิดไฟล์
สำหรับการปิดไฟล์ให้ดำเนินการดังต่อไปนี้ -
- ใส่การเรียกระบบ sys_close () หมายเลข 6 ในทะเบียน EAX
- ใส่ file descriptor ในรีจิสเตอร์ EBX
การเรียกระบบจะส่งคืนในกรณีที่เกิดข้อผิดพลาดรหัสข้อผิดพลาดในการลงทะเบียน EAX
การอัปเดตไฟล์
สำหรับการอัปเดตไฟล์ให้ดำเนินการดังต่อไปนี้ -
- ใส่การเรียกระบบ sys_lseek () หมายเลข 19 ในทะเบียน EAX
- ใส่ file descriptor ในรีจิสเตอร์ EBX
- ใส่ค่าชดเชยในทะเบียน ECX
- วางตำแหน่งอ้างอิงสำหรับออฟเซ็ตในรีจิสเตอร์ EDX
ตำแหน่งอ้างอิงอาจเป็น:
- จุดเริ่มต้นของไฟล์ - ค่า 0
- ตำแหน่งปัจจุบัน - ค่า 1
- สิ้นสุดไฟล์ - ค่า 2
การเรียกระบบจะส่งคืนในกรณีที่เกิดข้อผิดพลาดรหัสข้อผิดพลาดในการลงทะเบียน EAX
ตัวอย่าง
โปรแกรมต่อไปนี้สร้างและเปิดไฟล์ชื่อmyfile.txtและเขียนข้อความ "ยินดีต้อนรับสู่ Tutorials Point" ในไฟล์นี้ ถัดไปโปรแกรมอ่านจากไฟล์และเก็บข้อมูลลงในบัฟเฟอร์ชื่อข้อมูล สุดท้ายก็จะแสดงข้อความตามที่จัดเก็บไว้ในข้อมูล
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
;create the file
mov eax, 8
mov ebx, file_name
mov ecx, 0777 ;read, write and execute by all
int 0x80 ;call kernel
mov [fd_out], eax
; write into the file
mov edx,len ;number of bytes
mov ecx, msg ;message to write
mov ebx, [fd_out] ;file descriptor
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
; close the file
mov eax, 6
mov ebx, [fd_out]
; write the message indicating end of file write
mov eax, 4
mov ebx, 1
mov ecx, msg_done
mov edx, len_done
int 0x80
;open the file for reading
mov eax, 5
mov ebx, file_name
mov ecx, 0 ;for read only access
mov edx, 0777 ;read, write and execute by all
int 0x80
mov [fd_in], eax
;read from file
mov eax, 3
mov ebx, [fd_in]
mov ecx, info
mov edx, 26
int 0x80
; close the file
mov eax, 6
mov ebx, [fd_in]
int 0x80
; print the info
mov eax, 4
mov ebx, 1
mov ecx, info
mov edx, 26
int 0x80
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
file_name db 'myfile.txt'
msg db 'Welcome to Tutorials Point'
len equ $-msg
msg_done db 'Written to file', 0xa
len_done equ $-msg_done
section .bss
fd_out resb 1
fd_in resb 1
info resb 26
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Written to file
Welcome to Tutorials Point
sys_brk()การเรียกระบบมีให้โดยเคอร์เนลเพื่อจัดสรรหน่วยความจำโดยไม่จำเป็นต้องย้ายในภายหลัง การโทรนี้จะจัดสรรหน่วยความจำไว้ด้านหลังภาพแอปพลิเคชันในหน่วยความจำ ฟังก์ชันระบบนี้ช่วยให้คุณตั้งค่าที่อยู่สูงสุดที่มีอยู่ในส่วนข้อมูล
การเรียกระบบนี้ใช้เวลาหนึ่งพารามิเตอร์ซึ่งเป็นที่อยู่หน่วยความจำสูงสุดที่จำเป็นในการตั้งค่า ค่านี้จะถูกเก็บไว้ในทะเบียน EBX
ในกรณีที่มีข้อผิดพลาด sys_brk () จะส่งกลับค่า -1 หรือส่งกลับรหัสข้อผิดพลาดเชิงลบเอง ตัวอย่างต่อไปนี้แสดงให้เห็นถึงการจัดสรรหน่วยความจำแบบไดนามิก
ตัวอย่าง
โปรแกรมต่อไปนี้จัดสรรหน่วยความจำ 16kb โดยใช้การเรียกระบบ sys_brk () -
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, 45 ;sys_brk
xor ebx, ebx
int 80h
add eax, 16384 ;number of bytes to be reserved
mov ebx, eax
mov eax, 45 ;sys_brk
int 80h
cmp eax, 0
jl exit ;exit, if error
mov edi, eax ;EDI = highest available address
sub edi, 4 ;pointing to the last DWORD
mov ecx, 4096 ;number of DWORDs allocated
xor eax, eax ;clear eax
std ;backward
rep stosd ;repete for entire allocated area
cld ;put DF flag to normal state
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 80h ;print a message
exit:
mov eax, 1
xor ebx, ebx
int 80h
section .data
msg db "Allocated 16 kb of memory!", 10
len equ $ - msg
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Allocated 16 kb of memory!