Lắp ráp - Hướng dẫn nhanh
Hợp ngữ là gì?
Mỗi máy tính cá nhân đều có một bộ vi xử lý quản lý các hoạt động số học, logic và điều khiển của máy tính.
Mỗi dòng bộ xử lý có bộ hướng dẫn riêng để xử lý các hoạt động khác nhau như nhận dữ liệu nhập từ bàn phím, hiển thị thông tin trên màn hình và thực hiện nhiều công việc khác nhau. Tập hợp các hướng dẫn này được gọi là 'hướng dẫn ngôn ngữ máy'.
Bộ xử lý chỉ hiểu các lệnh ngôn ngữ máy, là các chuỗi của 1 và 0. Tuy nhiên, ngôn ngữ máy quá tối nghĩa và phức tạp để sử dụng trong phát triển phần mềm. Vì vậy, hợp ngữ cấp thấp được thiết kế cho một nhóm bộ xử lý cụ thể biểu thị các lệnh khác nhau dưới dạng mã tượng trưng và ở dạng dễ hiểu hơn.
Ưu điểm của Hợp ngữ
Có hiểu biết về hợp ngữ khiến người ta nhận thức được -
- Cách các chương trình giao tiếp với hệ điều hành, bộ xử lý và BIOS;
- Cách dữ liệu được biểu diễn trong bộ nhớ và các thiết bị bên ngoài khác;
- Cách bộ xử lý truy cập và thực hiện lệnh;
- Hướng dẫn truy cập và xử lý dữ liệu như thế nào;
- Cách một chương trình truy cập các thiết bị bên ngoài.
Các ưu điểm khác của việc sử dụng hợp ngữ là -
Nó yêu cầu ít bộ nhớ và thời gian thực thi hơn;
Nó cho phép các công việc phức tạp dành riêng cho phần cứng một cách dễ dàng hơn;
Nó phù hợp cho những công việc cần thời gian;
Nó phù hợp nhất để viết các quy trình dịch vụ ngắt và các chương trình thường trú trong bộ nhớ khác.
Các tính năng cơ bản của phần cứng PC
Phần cứng bên trong chính của PC bao gồm bộ xử lý, bộ nhớ và thanh ghi. Thanh ghi là thành phần bộ xử lý chứa dữ liệu và địa chỉ. Để thực thi một chương trình, hệ thống sẽ sao chép chương trình đó từ thiết bị bên ngoài vào bộ nhớ trong. Bộ xử lý thực hiện các lệnh của chương trình.
Đơn vị cơ bản của lưu trữ máy tính là một chút; nó có thể là BẬT (1) hoặc TẮT (0) và một nhóm 8 bit liên quan tạo thành một byte trên hầu hết các máy tính hiện đại.
Vì vậy, bit chẵn lẻ được sử dụng để làm cho số bit trong một byte là số lẻ. Nếu tính chẵn lẻ là chẵn, hệ thống sẽ giả định rằng đã xảy ra lỗi chẵn lẻ (mặc dù hiếm gặp), có thể do lỗi phần cứng hoặc rối loạn điện.
Bộ xử lý hỗ trợ các kích thước dữ liệu sau:
- Word: một mục dữ liệu 2 byte
- Doubleword: một mục dữ liệu 4 byte (32 bit)
- Quadword: một mục dữ liệu 8 byte (64 bit)
- Đoạn văn: vùng 16 byte (128 bit)
- Kilobyte: 1024 byte
- Megabyte: 1,048,576 byte
Hệ thống số nhị phân
Mọi hệ thống số đều sử dụng ký hiệu vị trí, tức là mỗi vị trí mà chữ số được viết có một giá trị vị trí khác nhau. Mỗi vị trí là lũy thừa của cơ số, là 2 đối với hệ thống số nhị phân và các lũy thừa này bắt đầu từ 0 và tăng thêm 1.
Bảng sau đây cho thấy các giá trị vị trí cho một số nhị phân 8 bit, trong đó tất cả các bit đều được BẬT.
Giá trị bit | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
Định vị giá trị dưới dạng lũy thừa của cơ số 2 | 128 | 64 | 32 | 16 | số 8 | 4 | 2 | 1 |
Số bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Giá trị của một số nhị phân dựa trên sự hiện diện của 1 bit và giá trị vị trí của chúng. Vì vậy, giá trị của một số nhị phân đã cho là -
1 + 2 + 4 + 8 +16 + 32 + 64 + 128 = 255
tương tự như 2 8 - 1.
Hệ thống số thập lục phân
Hệ thống số thập lục phân sử dụng cơ số 16. Các chữ số trong hệ thống này nằm trong khoảng từ 0 đến 15. Theo quy ước, các chữ cái từ A đến F được dùng để biểu diễn các chữ số thập lục phân tương ứng với các giá trị thập phân từ 10 đến 15.
Số thập lục phân trong máy tính được sử dụng để viết tắt các biểu diễn nhị phân dài dòng. Về cơ bản, hệ thống số thập lục phân đại diện cho một dữ liệu nhị phân bằng cách chia đôi mỗi byte và biểu thị giá trị của mỗi nửa byte. Bảng sau cung cấp các giá trị tương đương thập phân, nhị phân và thập lục phân:
Số thập phân | Biểu diễn nhị phân | Biểu diễn hệ thập lục phân |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
số 8 | 1000 | số 8 |
9 | 1001 | 9 |
10 | 1010 | A |
11 | 1011 | B |
12 | 1100 | C |
13 | 1101 | D |
14 | 1110 | E |
15 | 1111 | F |
Để chuyển một số nhị phân sang số tương đương với hệ thập lục phân, hãy chia nó thành các nhóm gồm 4 nhóm liên tiếp, mỗi nhóm bắt đầu từ bên phải và viết các nhóm đó trên các chữ số tương ứng của số thập lục phân.
Example - Số nhị phân 1000 1100 1101 0001 tương đương với hệ thập lục phân - 8CD1
Để chuyển đổi một số thập lục phân sang nhị phân, chỉ cần viết mỗi chữ số thập lục phân thành 4 chữ số nhị phân tương đương của nó.
Example - Số thập lục phân FAD8 tương đương với nhị phân - 1111 1010 1101 1000
Số học nhị phân
Bảng sau minh họa bốn quy tắc đơn giản cho phép cộng nhị phân:
(Tôi) | (ii) | (iii) | (iv) |
---|---|---|---|
1 | |||
0 | 1 | 1 | 1 |
+0 | +0 | +1 | +1 |
= 0 | = 1 | = 10 | = 11 |
Các quy tắc (iii) và (iv) cho thấy sự mang 1 bit vào vị trí bên trái tiếp theo.
Example
Thập phân | Nhị phân |
---|---|
60 | 00111100 |
+42 | 00101010 |
102 | 01100110 |
Giá trị nhị phân âm được biểu thị bằng two's complement notation. Theo quy tắc này, để chuyển một số nhị phân thành giá trị âm của nó là đảo ngược các giá trị bit của nó và thêm 1 .
Example
Số 53 | 00110101 |
Đảo ngược các bit | 11001010 |
Thêm 1 | 0000000 1 |
Số -53 | 11001011 |
Để trừ một giá trị cho một giá trị khác, hãy chuyển đổi số bị trừ sang định dạng phần bù của hai và cộng các số .
Example
Trừ 42 lấy 53
Số 53 | 00110101 |
Số 42 | 00101010 |
Đảo ngược các bit của 42 | 11010101 |
Thêm 1 | 0000000 1 |
Số -42 | 11010110 |
53 - 42 = 11 | 00001011 |
Phần tràn của 1 bit cuối cùng bị mất.
Giải quyết dữ liệu trong bộ nhớ
Quá trình mà bộ xử lý kiểm soát việc thực hiện các lệnh được gọi là fetch-decode-execute cycle hoặc là execution cycle. Nó bao gồm ba bước liên tục -
- Tìm nạp lệnh từ bộ nhớ
- Giải mã hoặc xác định hướng dẫn
- Thực hiện hướng dẫn
Bộ xử lý có thể truy cập một hoặc nhiều byte bộ nhớ tại một thời điểm. Chúng ta hãy xem xét một số thập lục phân 0725H. Con số này sẽ yêu cầu hai byte bộ nhớ. Byte bậc cao hoặc byte quan trọng nhất là 07 và byte bậc thấp là 25.
Bộ xử lý lưu trữ dữ liệu theo chuỗi byte ngược, tức là byte bậc thấp được lưu trong địa chỉ bộ nhớ thấp và byte bậc cao ở địa chỉ bộ nhớ cao. Vì vậy, nếu bộ xử lý mang giá trị 0725H từ thanh ghi đến bộ nhớ, nó sẽ chuyển 25 đầu tiên đến địa chỉ bộ nhớ thấp hơn và 07 đến địa chỉ bộ nhớ tiếp theo.
x: địa chỉ bộ nhớ
Khi bộ xử lý lấy dữ liệu số từ bộ nhớ để đăng ký, nó lại đảo ngược các byte. Có hai loại địa chỉ bộ nhớ -
Địa chỉ tuyệt đối - một tham chiếu trực tiếp về vị trí cụ thể.
Địa chỉ đoạn (hoặc độ lệch) - địa chỉ bắt đầu của đoạn bộ nhớ có giá trị bù.
Thiết lập môi trường cục bộ
Hợp ngữ phụ thuộc vào tập lệnh và kiến trúc của bộ xử lý. Trong hướng dẫn này, chúng tôi tập trung vào bộ vi xử lý Intel-32 như Pentium. Để làm theo hướng dẫn này, bạn sẽ cần -
- Máy tính IBM hoặc bất kỳ máy tính tương thích nào tương đương
- Bản sao hệ điều hành Linux
- Bản sao của chương trình trình hợp dịch NASM
Có nhiều chương trình hợp ngữ tốt, chẳng hạn như -
- Microsoft Assembler (MASM)
- Borland Turbo Assembler (TASM)
- Trình lắp ráp GNU (GAS)
Chúng tôi sẽ sử dụng trình hợp dịch NASM, vì nó là -
- Miễn phí. Bạn có thể tải xuống từ nhiều nguồn web khác nhau.
- Được ghi chép đầy đủ và bạn sẽ nhận được nhiều thông tin trên mạng.
- Có thể được sử dụng trên cả Linux và Windows.
Cài đặt NASM
Nếu bạn chọn "Công cụ phát triển" trong khi cài đặt Linux, bạn có thể được cài đặt NASM cùng với hệ điều hành Linux và bạn không cần tải xuống và cài đặt riêng. Để kiểm tra xem bạn đã cài đặt NASM chưa, hãy thực hiện các bước sau:
Mở một thiết bị đầu cuối Linux.
Kiểu whereis nasm và nhấn ENTER.
Nếu nó đã được cài đặt, thì một dòng như nasm: / usr / bin / nasm sẽ xuất hiện. Nếu không, bạn sẽ chỉ thấy nasm : , thì bạn cần cài đặt NASM.
Để cài đặt NASM, hãy thực hiện các bước sau:
Kiểm tra trang web netwide Assemblybler (NASM) để biết phiên bản mới nhất.
Tải xuống kho lưu trữ nguồn Linux
nasm-X.XX.ta.gz
, đâuX.XX
là số phiên bản NASM trong kho lưu trữ.Giải nén kho lưu trữ vào một thư mục tạo ra một thư mục con
nasm-X. XX
.cd đến
nasm-X.XX
và gõ./configure. Tập lệnh shell này sẽ tìm trình biên dịch C tốt nhất để sử dụng và thiết lập Makefiles cho phù hợp.Kiểu make để xây dựng mã nhị phân nasm và ndisasm.
Kiểu make install để cài đặt nasm và ndisasm trong / usr / local / bin và cài đặt man pages.
Điều này sẽ cài đặt NASM trên hệ thống của bạn. Ngoài ra, bạn có thể sử dụng bản phân phối RPM cho Fedora Linux. Phiên bản này cài đặt đơn giản hơn, chỉ cần nhấp đúp vào tệp RPM.
Một chương trình hợp ngữ có thể được chia thành ba phần:
Các data phần,
Các bss phần và
Các text phần.
Các dữ liệu Mục
Các dataphần được sử dụng để khai báo dữ liệu khởi tạo hoặc hằng số. Dữ liệu này không thay đổi trong thời gian chạy. Bạn có thể khai báo các giá trị hằng số khác nhau, tên tệp hoặc kích thước bộ đệm, v.v., trong phần này.
Cú pháp khai báo phần dữ liệu là:
section.data
Các bss Mục
Các bssphần được sử dụng để khai báo các biến. Cú pháp khai báo phần bss là:
section.bss
Các văn bản phần
Các textđược sử dụng để giữ mã thực tế. Phần này phải bắt đầu bằng phần khai báoglobal _start, cho kernel biết nơi bắt đầu thực thi chương trình.
Cú pháp khai báo phần văn bản là -
section.text
global _start
_start:
Bình luận
Nhận xét hợp ngữ bắt đầu bằng dấu chấm phẩy (;). Nó có thể chứa bất kỳ ký tự in được bao gồm cả khoảng trống. Nó có thể tự xuất hiện trên một dòng, như -
; This program displays a message on screen
hoặc, trên cùng một dòng cùng với một chỉ dẫn, như -
add eax, ebx ; adds ebx to eax
Câu lệnh hợp ngữ
Các chương trình hợp ngữ bao gồm ba loại câu lệnh:
- Hướng dẫn hoặc hướng dẫn có thể thực thi,
- Chỉ thị Assembler hoặc giả hoạt động và
- Macros.
Các executable instructions hoặc đơn giản instructionscho bộ xử lý biết phải làm gì. Mỗi hướng dẫn bao gồm mộtoperation code(opcode). Mỗi lệnh thực thi tạo ra một lệnh ngôn ngữ máy.
Các assembler directives hoặc là pseudo-opsnói với người lắp ráp về các khía cạnh khác nhau của quá trình lắp ráp. Đây là những lệnh không thể thực thi và không tạo ra các lệnh ngôn ngữ máy.
Macros về cơ bản là một cơ chế thay thế văn bản.
Cú pháp của câu lệnh hợp ngữ
Các câu lệnh hợp ngữ được nhập một câu lệnh trên mỗi dòng. Mỗi câu lệnh tuân theo định dạng sau:
[label] mnemonic [operands] [;comment]
Các trường trong dấu ngoặc vuông là tùy chọn. Một lệnh cơ bản có hai phần, phần thứ nhất là tên của lệnh (hoặc phần ghi nhớ) sẽ được thực thi và phần thứ hai là các toán hạng hoặc các tham số của lệnh.
Sau đây là một số ví dụ về các câu lệnh hợp ngữ điển hình:
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
Chương trình Hello World trong Assembly
Mã hợp ngữ sau đây hiển thị chuỗi 'Hello World' trên màn hình:
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Hello, world!
Biên dịch và liên kết một chương trình lắp ráp trong NASM
Đảm bảo rằng bạn đã thiết lập đường dẫn nasm và ldnhị phân trong biến môi trường PATH của bạn. Bây giờ, hãy thực hiện các bước sau để biên dịch và liên kết chương trình trên -
Nhập mã trên bằng trình soạn thảo văn bản và lưu nó dưới dạng hello.asm.
Đảm bảo rằng bạn đang ở trong cùng một thư mục với nơi bạn đã lưu hello.asm.
Để lắp ráp chương trình, hãy nhập nasm -f elf hello.asm
Nếu có bất kỳ lỗi nào, bạn sẽ được nhắc về điều đó ở giai đoạn này. Nếu không, một tệp đối tượng của chương trình của bạn có tênhello.o sẽ được tạo ra.
Để liên kết tệp đối tượng và tạo tệp thi hành có tên hello, hãy nhập ld -m elf_i386 -s -o hello hello.o
Thực thi chương trình bằng cách gõ ./hello
Nếu bạn đã làm mọi thứ chính xác, nó sẽ hiển thị 'Xin chào, thế giới!' trên màn hình.
Chúng ta đã thảo luận về ba phần của một chương trình lắp ráp. Các phần này cũng đại diện cho các phân đoạn bộ nhớ khác nhau.
Điều thú vị là nếu bạn thay thế từ khóa phần bằng phân đoạn, bạn sẽ nhận được kết quả tương tự. Hãy thử đoạn mã sau -
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Hello, world!
Phân đoạn bộ nhớ
Mô hình bộ nhớ phân đoạn chia bộ nhớ hệ thống thành các nhóm gồm các phân đoạn độc lập được tham chiếu bởi các con trỏ nằm trong các thanh ghi phân đoạn. Mỗi phân đoạn được sử dụng để chứa một loại dữ liệu cụ thể. Một phân đoạn được sử dụng để chứa mã lệnh, một phân đoạn khác lưu trữ các phần tử dữ liệu và phân đoạn thứ ba giữ ngăn xếp chương trình.
Theo thảo luận ở trên, chúng ta có thể chỉ định các phân đoạn bộ nhớ khác nhau như:
Data segment - Nó được đại diện bởi .data phần và .bss. Phần .data dùng để khai báo vùng nhớ, nơi lưu trữ các phần tử dữ liệu cho chương trình. Phần này không thể được mở rộng sau khi các phần tử dữ liệu được khai báo và nó vẫn giữ nguyên trong suốt chương trình.
Phần .bss cũng là phần bộ nhớ tĩnh chứa các bộ đệm để dữ liệu được khai báo sau này trong chương trình. Bộ nhớ đệm này không được lấp đầy.
Code segment - Nó được đại diện bởi .textphần. Điều này xác định một khu vực trong bộ nhớ lưu các mã lệnh. Đây cũng là một khu vực cố định.
Stack - Đoạn này chứa các giá trị dữ liệu được truyền cho các hàm và thủ tục trong chương trình.
Hoạt động của bộ xử lý chủ yếu liên quan đến xử lý dữ liệu. Dữ liệu này có thể được lưu trữ trong bộ nhớ và được truy cập từ đó. Tuy nhiên, việc đọc dữ liệu từ và lưu trữ dữ liệu vào bộ nhớ làm chậm bộ xử lý, vì nó liên quan đến các quá trình phức tạp gửi yêu cầu dữ liệu qua bus điều khiển và vào bộ nhớ lưu trữ và nhận dữ liệu qua cùng một kênh.
Để tăng tốc hoạt động của bộ xử lý, bộ xử lý bao gồm một số vị trí lưu trữ bộ nhớ trong, được gọi là registers.
Các thanh ghi lưu trữ các phần tử dữ liệu để xử lý mà không cần phải truy cập vào bộ nhớ. Một số thanh ghi hạn chế được tích hợp trong chip xử lý.
Bộ xử lý đăng ký
Có 10 thanh ghi bộ xử lý 32 bit và sáu thanh ghi 16 bit trong kiến trúc IA-32. Các thanh ghi được nhóm thành ba loại:
- Đăng ký chung,
- Thanh ghi điều khiển và
- Thanh ghi phân đoạn.
Các thanh ghi chung được chia thành các nhóm sau:
- Đăng ký dữ liệu,
- Thanh ghi con trỏ, và
- Các thanh ghi chỉ mục.
Đăng ký dữ liệu
Bốn thanh ghi dữ liệu 32-bit được sử dụng cho các phép toán số học, logic và các phép toán khác. Các thanh ghi 32-bit này có thể được sử dụng theo ba cách:
Như các thanh ghi dữ liệu 32-bit hoàn chỉnh: EAX, EBX, ECX, EDX.
Các nửa dưới của thanh ghi 32 bit có thể được sử dụng như bốn thanh ghi dữ liệu 16 bit: AX, BX, CX và DX.
Các nửa thấp hơn và cao hơn của bốn thanh ghi 16 bit nói trên có thể được sử dụng như tám thanh ghi dữ liệu 8 bit: AH, AL, BH, BL, CH, CL, DH và DL.
Một số thanh ghi dữ liệu này có công dụng cụ thể trong các phép toán số học.
AX is the primary accumulator; nó được sử dụng trong đầu vào / đầu ra và hầu hết các lệnh số học. Ví dụ, trong phép toán nhân, một toán hạng được lưu trong thanh ghi EAX hoặc AX hoặc AL tùy theo kích thước của toán hạng.
BX is known as the base register, vì nó có thể được sử dụng trong việc lập chỉ mục.
CX is known as the count register, như các thanh ghi ECX, CX lưu trữ số vòng lặp trong các hoạt động lặp lại.
DX is known as the data register. Nó cũng được sử dụng trong các hoạt động đầu vào / đầu ra. Nó cũng được sử dụng với thanh ghi AX cùng với DX cho các phép toán nhân và chia liên quan đến các giá trị lớn.
Thanh ghi con trỏ
Các thanh ghi con trỏ là các thanh ghi EIP, ESP và EBP 32-bit và các phần bên phải 16-bit tương ứng là IP, SP và BP. Có ba loại thanh ghi con trỏ -
Instruction Pointer (IP)- Thanh ghi IP 16 bit lưu trữ địa chỉ offset của lệnh tiếp theo sẽ được thực hiện. IP kết hợp với thanh ghi CS (như CS: IP) cung cấp địa chỉ đầy đủ của lệnh hiện tại trong đoạn mã.
Stack Pointer (SP)- Thanh ghi SP 16 bit cung cấp giá trị offset bên trong ngăn xếp chương trình. SP kết hợp với thanh ghi SS (SS: SP) đề cập đến vị trí hiện tại của dữ liệu hoặc địa chỉ trong ngăn xếp chương trình.
Base Pointer (BP)- Thanh ghi BP 16 bit chủ yếu giúp tham chiếu các biến tham số được truyền cho chương trình con. Địa chỉ trong thanh ghi SS được kết hợp với offset trong BP để lấy vị trí của tham số. BP cũng có thể được kết hợp với DI và SI làm thanh ghi cơ sở để định địa chỉ đặc biệt.
Sổ đăng ký chỉ mục
Thanh ghi chỉ mục 32-bit, ESI và EDI, và các phần ngoài cùng bên phải 16-bit của chúng. SI và DI, được sử dụng để đánh chỉ mục và đôi khi được sử dụng để cộng và trừ. Có hai bộ con trỏ chỉ mục -
Source Index (SI) - Nó được sử dụng làm chỉ mục nguồn cho các hoạt động chuỗi.
Destination Index (DI) - Nó được sử dụng làm chỉ mục đích cho các hoạt động chuỗi.
Đăng ký kiểm soát
Thanh ghi con trỏ lệnh 32-bit và thanh ghi cờ 32-bit kết hợp được coi là thanh ghi điều khiển.
Nhiều lệnh liên quan đến so sánh và tính toán toán học và thay đổi trạng thái của các cờ và một số lệnh có điều kiện khác kiểm tra giá trị của các cờ trạng thái này để đưa luồng điều khiển đến vị trí khác.
Các bit cờ phổ biến là:
Overflow Flag (OF) - Nó chỉ ra sự tràn của một bit bậc cao (bit ngoài cùng bên trái) của dữ liệu sau một phép toán số học có dấu.
Direction Flag (DF)- Nó xác định hướng trái hoặc phải để di chuyển hoặc so sánh dữ liệu chuỗi. Khi giá trị DF là 0, hoạt động chuỗi sẽ có hướng từ trái sang phải và khi giá trị được đặt thành 1, hoạt động chuỗi sẽ có hướng từ phải sang trái.
Interrupt Flag (IF)- Nó xác định xem các ngắt bên ngoài như nhập bàn phím, v.v., có được bỏ qua hoặc xử lý hay không. Nó vô hiệu hóa ngắt bên ngoài khi giá trị là 0 và cho phép ngắt khi được đặt thành 1.
Trap Flag (TF)- Nó cho phép thiết lập hoạt động của bộ vi xử lý ở chế độ một bước. Chương trình DEBUG mà chúng tôi sử dụng đặt cờ bẫy, vì vậy chúng tôi có thể thực hiện từng lệnh một.
Sign Flag (SF)- Nó cho thấy dấu hiệu của kết quả của một phép tính số học. Cờ này được đặt theo dấu hiệu của một mục dữ liệu sau phép toán số học. Dấu hiệu được biểu thị bằng bậc cao của bit ngoài cùng bên trái. Kết quả dương tính xóa giá trị của SF thành 0 và kết quả âm tính đặt nó thành 1.
Zero Flag (ZF)- Nó cho biết kết quả của một phép toán số học hoặc so sánh. Kết quả khác không xóa cờ 0 thành 0 và kết quả 0 đặt nó thành 1.
Auxiliary Carry Flag (AF)- Nó chứa giá trị mang từ bit 3 đến bit 4 theo một phép toán số học; dùng cho chuyên ngành số học. AF được đặt khi một phép toán số học 1 byte gây ra chuyển từ bit 3 sang bit 4.
Parity Flag (PF)- Nó cho biết tổng số bit 1 trong kết quả thu được từ một phép toán số học. Số bit 1 chẵn xóa cờ chẵn lẻ thành 0 và số bit lẻ 1 đặt cờ chẵn lẻ thành 1.
Carry Flag (CF)- Nó chứa giá trị mang 0 hoặc 1 từ một bit bậc cao (ngoài cùng bên trái) sau một phép tính số học. Nó cũng lưu trữ nội dung của bit cuối cùng của hoạt động thay đổi hoặc xoay .
Bảng sau chỉ ra vị trí của các bit cờ trong thanh ghi Cờ 16 bit:
Cờ: | O | D | Tôi | T | S | Z | A | P | C | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit không: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | số 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Đăng ký phân đoạn
Phân đoạn là các khu vực cụ thể được xác định trong một chương trình để chứa dữ liệu, mã và ngăn xếp. Có ba phân đoạn chính -
Code Segment- Nó chứa tất cả các lệnh được thực thi. Thanh ghi đoạn mã 16 bit hoặc thanh ghi CS lưu trữ địa chỉ bắt đầu của đoạn mã.
Data Segment- Nó chứa dữ liệu, hằng số và vùng làm việc. Thanh ghi Phân đoạn dữ liệu 16 bit hoặc thanh ghi DS lưu trữ địa chỉ bắt đầu của phân đoạn dữ liệu.
Stack Segment- Nó chứa dữ liệu và địa chỉ trả về của các thủ tục hoặc chương trình con. Nó được thực hiện như một cấu trúc dữ liệu 'ngăn xếp'. Thanh ghi Phân đoạn ngăn xếp hoặc thanh ghi SS lưu trữ địa chỉ bắt đầu của ngăn xếp.
Ngoài các thanh ghi DS, CS và SS, còn có các thanh ghi phân đoạn bổ sung khác - ES (phân đoạn phụ), FS và GS, cung cấp các phân đoạn bổ sung để lưu trữ dữ liệu.
Trong lập trình hợp ngữ, một chương trình cần truy cập các vị trí bộ nhớ. Tất cả các vị trí bộ nhớ trong một phân đoạn đều có liên quan đến địa chỉ bắt đầu của phân đoạn. Một phân đoạn bắt đầu bằng địa chỉ chia đều cho 16 hoặc thập lục phân 10. Vì vậy, chữ số hex ngoài cùng bên phải trong tất cả các địa chỉ bộ nhớ như vậy là 0, thường không được lưu trữ trong thanh ghi phân đoạn.
Thanh ghi phân đoạn lưu trữ các địa chỉ bắt đầu của một phân đoạn. Để có được vị trí chính xác của dữ liệu hoặc lệnh trong một phân đoạn, cần phải có giá trị offset (hoặc độ dời). Để tham chiếu bất kỳ vị trí bộ nhớ nào trong một phân đoạn, bộ xử lý kết hợp địa chỉ phân đoạn trong thanh ghi phân đoạn với giá trị offset của vị trí.
Thí dụ
Nhìn vào chương trình đơn giản sau để hiểu việc sử dụng các thanh ghi trong lập trình hợp ngữ. Chương trình này hiển thị 9 ngôi sao trên màn hình cùng với một thông báo đơn giản -
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 '*'
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Displaying 9 stars
*********
Lời gọi hệ thống là các API cho giao diện giữa không gian người dùng và không gian nhân. Chúng tôi đã sử dụng các cuộc gọi hệ thống. sys_write và sys_exit, để ghi vào màn hình và thoát khỏi chương trình, tương ứng.
Cuộc gọi hệ thống Linux
Bạn có thể sử dụng các lệnh gọi hệ thống Linux trong các chương trình hợp ngữ của mình. Bạn cần thực hiện các bước sau để sử dụng lệnh gọi hệ thống Linux trong chương trình của mình -
- Đặt số cuộc gọi hệ thống vào sổ đăng ký EAX.
- Lưu trữ các đối số cho lệnh gọi hệ thống trong thanh ghi EBX, ECX, v.v.
- Gọi ngắt liên quan (80h).
- Kết quả thường được trả về trong thanh ghi EAX.
Có sáu thanh ghi lưu trữ các đối số của lệnh gọi hệ thống được sử dụng. Đây là EBX, ECX, EDX, ESI, EDI và EBP. Các thanh ghi này nhận các đối số liên tiếp, bắt đầu bằng thanh ghi EBX. Nếu có nhiều hơn sáu đối số, thì vị trí bộ nhớ của đối số đầu tiên được lưu trữ trong thanh ghi EBX.
Đoạn mã sau cho thấy việc sử dụng lệnh gọi hệ thống sys_exit -
mov eax,1 ; system call number (sys_exit)
int 0x80 ; call kernel
Đoạn mã sau cho thấy việc sử dụng lệnh gọi hệ thống 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
Tất cả các cuộc gọi tổng hợp được liệt kê trong /usr/include/asm/unistd.h , cùng với số của chúng (giá trị cần đặt trong EAX trước khi bạn gọi int 80h).
Bảng sau đây cho thấy một số lệnh gọi hệ thống được sử dụng trong hướng dẫn này:
% eax | Tên | % ebx | % ecx | % edx | % esx | % edi |
---|---|---|---|---|---|---|
1 | sys_exit | int | - | - | - | - |
2 | sys_fork | struct pt_regs | - | - | - | - |
3 | sys_read | int không dấu | char * | size_t | - | - |
4 | sys_write | int không dấu | const char * | size_t | - | - |
5 | sys_open | const char * | int | int | - | - |
6 | sys_close | int không dấu | - | - | - | - |
Thí dụ
Ví dụ sau đây đọc một số từ bàn phím và hiển thị nó trên màn hình -
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Please enter a number:
1234
You have entered:1234
Hầu hết các lệnh hợp ngữ đều yêu cầu các toán hạng được xử lý. Một địa chỉ toán hạng cung cấp vị trí, nơi dữ liệu được xử lý được lưu trữ. Một số lệnh không yêu cầu toán hạng, trong khi một số lệnh khác có thể yêu cầu một, hai hoặc ba toán hạng.
Khi một lệnh yêu cầu hai toán hạng, toán hạng đầu tiên thường là đích, chứa dữ liệu trong một thanh ghi hoặc vị trí bộ nhớ và toán hạng thứ hai là nguồn. Nguồn chứa dữ liệu được phân phối (định địa chỉ ngay lập tức) hoặc địa chỉ (trong thanh ghi hoặc bộ nhớ) của dữ liệu. Nói chung, dữ liệu nguồn vẫn không bị thay đổi sau khi hoạt động.
Ba phương thức địa chỉ cơ bản là -
- Đăng ký địa chỉ
- Giải quyết ngay lập tức
- Định địa chỉ bộ nhớ
Đăng ký địa chỉ
Trong chế độ định địa chỉ này, một thanh ghi chứa toán hạng. Tùy thuộc vào lệnh, thanh ghi có thể là toán hạng đầu tiên, toán hạng thứ hai hoặc cả hai.
Ví dụ,
MOV DX, TAX_RATE ; Register in first operand
MOV COUNT, CX ; Register in second operand
MOV EAX, EBX ; Both the operands are in registers
Vì việc xử lý dữ liệu giữa các thanh ghi không liên quan đến bộ nhớ, nó cung cấp khả năng xử lý dữ liệu nhanh nhất.
Giải quyết ngay lập tức
Một toán hạng tức thời có giá trị không đổi hoặc một biểu thức. Khi một lệnh có hai toán hạng sử dụng địa chỉ tức thì, toán hạng đầu tiên có thể là một thanh ghi hoặc vị trí bộ nhớ, và toán hạng thứ hai là một hằng số tức thì. Toán hạng đầu tiên xác định độ dài của dữ liệu.
Ví dụ,
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
Định địa chỉ bộ nhớ trực tiếp
Khi các toán hạng được chỉ định trong chế độ định địa chỉ bộ nhớ, cần có quyền truy cập trực tiếp vào bộ nhớ chính, thường là tới đoạn dữ liệu. Cách giải quyết này dẫn đến việc xử lý dữ liệu chậm hơn. Để xác định vị trí chính xác của dữ liệu trong bộ nhớ, chúng ta cần địa chỉ bắt đầu phân đoạn, địa chỉ này thường được tìm thấy trong thanh ghi DS và một giá trị offset. Giá trị bù đắp này còn được gọi làeffective address.
Trong chế độ định địa chỉ trực tiếp, giá trị offset được chỉ định trực tiếp như một phần của lệnh, thường được chỉ định bằng tên biến. Trình hợp dịch tính toán giá trị bù và duy trì một bảng ký hiệu, bảng này lưu trữ các giá trị bù của tất cả các biến được sử dụng trong chương trình.
Trong định địa chỉ bộ nhớ trực tiếp, một trong các toán hạng tham chiếu đến một vị trí bộ nhớ và toán hạng khác tham chiếu đến một thanh ghi.
Ví dụ,
ADD BYTE_VALUE, DL ; Adds the register in the memory location
MOV BX, WORD_VALUE ; Operand from the memory is added to register
Định địa chỉ bù trừ trực tiếp
Chế độ địa chỉ này sử dụng các toán tử số học để sửa đổi một địa chỉ. Ví dụ: hãy xem các định nghĩa sau xác định các bảng dữ liệu:
BYTE_TABLE DB 14, 15, 22, 45 ; Tables of bytes
WORD_TABLE DW 134, 345, 564, 123 ; Tables of words
Các thao tác sau truy cập dữ liệu từ các bảng trong bộ nhớ vào các thanh ghi:
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
Định địa chỉ bộ nhớ gián tiếp
Chế độ địa chỉ này sử dụng khả năng của máy tính của Phân đoạn: Định địa chỉ bù đắp . Nói chung, các thanh ghi cơ sở EBX, EBP (hoặc BX, BP) và các thanh ghi chỉ mục (DI, SI), được mã hóa trong dấu ngoặc vuông cho các tham chiếu bộ nhớ, được sử dụng cho mục đích này.
Định địa chỉ gián tiếp thường được sử dụng cho các biến chứa một số phần tử như mảng. Địa chỉ bắt đầu của mảng được lưu trữ trong thanh ghi EBX.
Đoạn mã sau cho biết cách truy cập các phần tử khác nhau của biến.
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
Hướng dẫn MOV
Chúng tôi đã sử dụng lệnh MOV được sử dụng để di chuyển dữ liệu từ không gian lưu trữ này sang không gian lưu trữ khác. Lệnh MOV nhận hai toán hạng.
Cú pháp
Cú pháp của lệnh MOV là:
MOV destination, source
Lệnh MOV có thể có một trong năm dạng sau:
MOV register, register
MOV register, immediate
MOV memory, immediate
MOV register, memory
MOV memory, register
Xin lưu ý rằng -
- Cả hai toán hạng trong hoạt động MOV phải có cùng kích thước
- Giá trị của toán hạng nguồn không đổi
Lệnh MOV đôi khi gây ra sự mơ hồ. Ví dụ, hãy xem các câu lệnh -
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
Không rõ bạn muốn di chuyển một byte tương đương hay từ tương đương với số 110. Trong những trường hợp như vậy, bạn nên sử dụng type specifier.
Bảng sau đây cho thấy một số loại chỉ định loại phổ biến:
Loại thông số | Số byte được giải quyết |
---|---|
BYTE | 1 |
WORD | 2 |
DWORD | 4 |
QWORD | số 8 |
TBYTE | 10 |
Thí dụ
Chương trình sau đây minh họa một số khái niệm đã thảo luận ở trên. Nó lưu trữ một tên 'Zara Ali' trong phần dữ liệu của bộ nhớ, sau đó thay đổi giá trị của nó thành một tên khác 'Nuha Ali' theo chương trình và hiển thị cả hai tên.
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 '
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Zara Ali Nuha Ali
NASM cung cấp nhiều define directivesđể dành không gian lưu trữ cho các biến. Chỉ thị xác định trình hợp dịch được sử dụng để phân bổ không gian lưu trữ. Nó có thể được sử dụng để dự trữ cũng như khởi tạo một hoặc nhiều byte.
Phân bổ không gian lưu trữ cho dữ liệu được khởi tạo
Cú pháp cho câu lệnh phân bổ lưu trữ cho dữ liệu được khởi tạo là:
[variable-name] define-directive initial-value [,initial-value]...
Trong đó, tên-biến là định danh cho mỗi không gian lưu trữ. Trình hợp dịch liên kết một giá trị bù đắp cho mỗi tên biến được xác định trong phân đoạn dữ liệu.
Có năm dạng cơ bản của chỉ thị xác định -
Chỉ thị | Mục đích | Không gian lưu trữ |
---|---|---|
DB | Xác định Byte | phân bổ 1 byte |
DW | Định nghĩa Word | phân bổ 2 byte |
DD | Xác định Doubleword | phân bổ 4 byte |
DQ | Xác định Quadword | phân bổ 8 byte |
DT | Xác định mười byte | phân bổ 10 byte |
Sau đây là một số ví dụ về việc sử dụng chỉ thị định nghĩa:
choice DB 'y'
number DW 12345
neg_number DW -12345
big_number DQ 123456789
real_number1 DD 1.234
real_number2 DQ 123.456
Xin lưu ý rằng -
Mỗi byte ký tự được lưu trữ dưới dạng giá trị ASCII của nó trong hệ thập lục phân.
Mỗi giá trị thập phân được tự động chuyển đổi thành tương đương nhị phân 16 bit và được lưu trữ dưới dạng số thập lục phân.
Bộ xử lý sử dụng thứ tự byte endian nhỏ.
Các số âm được chuyển đổi thành biểu diễn phần bù 2 của nó.
Các số dấu phẩy động ngắn và dài được biểu diễn bằng 32 hoặc 64 bit, tương ứng.
Chương trình sau đây cho thấy việc sử dụng chỉ thị xác định:
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'
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
y
Phân bổ không gian lưu trữ cho dữ liệu chưa được khởi tạo
Các lệnh dự trữ được sử dụng để dự trữ không gian cho dữ liệu chưa được khởi tạo. Các chỉ thị dự trữ nhận một toán hạng duy nhất chỉ định số lượng đơn vị không gian được dự trữ. Mỗi chỉ thị xác định có một chỉ thị dự trữ liên quan.
Có năm hình thức cơ bản của chỉ thị dự trữ -
Chỉ thị | Mục đích |
---|---|
RESB | Đặt trước một Byte |
RESW | Đặt trước một từ |
RESD | Đặt trước một từ đôi |
RESQ | Đặt trước một Quadword |
NGHỈ NGƠI | Đặt trước 10 byte |
Nhiều định nghĩa
Bạn có thể có nhiều câu lệnh định nghĩa dữ liệu trong một chương trình. Ví dụ -
choice DB 'Y' ;ASCII of y = 79H
number1 DW 12345 ;12345D = 3039H
number2 DD 12345679 ;123456789D = 75BCD15H
Trình hợp dịch phân bổ bộ nhớ liền kề cho nhiều định nghĩa biến.
Nhiều lần khởi tạo
Chỉ thị TIMES cho phép nhiều lần khởi tạo với cùng một giá trị. Ví dụ, một mảng có tên là các dấu có kích thước 9 có thể được định nghĩa và khởi tạo bằng 0 bằng cách sử dụng câu lệnh sau:
marks TIMES 9 DW 0
Lệnh TIMES hữu ích trong việc xác định mảng và bảng. Chương trình sau đây hiển thị 9 dấu hoa thị trên màn hình -
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 '*'
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
*********
Có một số chỉ thị được cung cấp bởi NASM để xác định các hằng số. Chúng ta đã sử dụng lệnh EQU trong các chương trước. Chúng tôi sẽ đặc biệt thảo luận về ba chỉ thị -
- EQU
- %assign
- %define
Chỉ thị EQU
Các EQUChỉ thị được sử dụng để xác định các hằng số. Cú pháp của chỉ thị EQU như sau:
CONSTANT_NAME EQU expression
Ví dụ,
TOTAL_STUDENTS equ 50
Sau đó, bạn có thể sử dụng giá trị không đổi này trong mã của mình, như -
mov ecx, TOTAL_STUDENTS
cmp eax, TOTAL_STUDENTS
Toán hạng của một câu lệnh EQU có thể là một biểu thức:
LENGTH equ 20
WIDTH equ 10
AREA equ length * width
Đoạn mã trên sẽ xác định VÙNG là 200.
Thí dụ
Ví dụ sau minh họa việc sử dụng chỉ thị 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Chỉ thị% chỉ định
Các %assignChỉ thị có thể được sử dụng để xác định các hằng số giống như chỉ thị EQU. Chỉ thị này cho phép xác định lại. Ví dụ: bạn có thể xác định TOTAL không đổi là -
%assign TOTAL 10
Sau đó trong mã, bạn có thể xác định lại nó là -
%assign TOTAL 20
Chỉ thị này phân biệt chữ hoa chữ thường.
% Xác định Chỉ thị
Các %defineChỉ thị cho phép xác định cả hằng số và hằng chuỗi. Lệnh này tương tự như #define trong C. Ví dụ: bạn có thể xác định PTR hằng số là:
%define PTR [EBP+4]
Đoạn mã trên thay thế PTR bằng [EBP + 4].
Chỉ thị này cũng cho phép định nghĩa lại và nó có phân biệt chữ hoa chữ thường.
Hướng dẫn INC
Lệnh INC được sử dụng để tăng một toán hạng. Nó hoạt động trên một toán hạng duy nhất có thể nằm trong thanh ghi hoặc trong bộ nhớ.
Cú pháp
Lệnh INC có cú pháp sau:
INC destination
Đích của toán hạng có thể là toán hạng 8 bit, 16 bit hoặc 32 bit.
Thí dụ
INC EBX ; Increments 32-bit register
INC DL ; Increments 8-bit register
INC [count] ; Increments the count variable
Hướng dẫn DEC
Lệnh DEC được sử dụng để giảm một toán hạng. Nó hoạt động trên một toán hạng duy nhất có thể nằm trong thanh ghi hoặc trong bộ nhớ.
Cú pháp
Lệnh DEC có cú pháp sau:
DEC destination
Đích của toán hạng có thể là toán hạng 8 bit, 16 bit hoặc 32 bit.
Thí dụ
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]
Hướng dẫn ADD và SUB
Các lệnh ADD và SUB được sử dụng để thực hiện phép cộng / trừ dữ liệu nhị phân đơn giản theo kích thước byte, từ và từ đôi, tức là để cộng hoặc trừ các toán hạng 8 bit, 16 bit hoặc 32 bit, tương ứng.
Cú pháp
Các lệnh ADD và SUB có cú pháp sau:
ADD/SUB destination, source
Lệnh ADD / SUB có thể diễn ra giữa -
- Đăng ký để đăng ký
- Bộ nhớ để đăng ký
- Đăng ký vào bộ nhớ
- Đăng ký dữ liệu không đổi
- Bộ nhớ đến dữ liệu không đổi
Tuy nhiên, giống như các lệnh khác, không thể thực hiện các thao tác chuyển bộ nhớ vào bộ nhớ bằng lệnh ADD / SUB. Thao tác ADD hoặc SUB đặt hoặc xóa các cờ tràn và cờ mang.
Thí dụ
Ví dụ sau sẽ yêu cầu hai chữ số từ người dùng, lưu trữ các chữ số trong thanh ghi EAX và EBX tương ứng, thêm các giá trị, lưu kết quả vào vị trí bộ nhớ ' res ' và cuối cùng là hiển thị kết quả.
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The sum is:
7
Hướng dẫn MUL / IMUL
Có hai hướng dẫn để nhân dữ liệu nhị phân. Lệnh MUL (Multiply) xử lý dữ liệu chưa được ký và IMUL (Integer Multiply) xử lý dữ liệu đã ký. Cả hai hướng dẫn đều ảnh hưởng đến cờ Chở và Tràn.
Cú pháp
Cú pháp cho hướng dẫn MUL / IMUL như sau:
MUL/IMUL multiplier
Phép nhân và trong cả hai trường hợp sẽ nằm trong một bộ tích lũy, tùy thuộc vào kích thước của phép nhân và hệ số nhân và tích được tạo ra cũng được lưu trữ trong hai thanh ghi tùy thuộc vào kích thước của toán hạng. Phần sau giải thích các hướng dẫn MUL với ba trường hợp khác nhau:
Sr.No. | Các tình huống |
---|---|
1 | When two bytes are multiplied − Số nhân và nằm trong thanh ghi AL, và số nhân là một byte trong bộ nhớ hoặc trong một thanh ghi khác. Sản phẩm có trong AXE. 8 bit bậc cao của sản phẩm được lưu trong AH và 8 bit bậc thấp được lưu trong AL.
|
2 | When two one-word values are multiplied − Số nhân và phải nằm trong thanh ghi AX, và số nhân là một từ trong bộ nhớ hoặc một thanh ghi khác. Ví dụ: đối với một lệnh như MUL DX, bạn phải lưu trữ số nhân trong DX và cấp số nhân trong AX. Sản phẩm kết quả là một từ kép, sẽ cần hai thanh ghi. Phần bậc cao (ngoài cùng bên trái) được lưu trữ trong DX và phần bậc thấp hơn (ngoài cùng bên phải) được lưu trữ trong AX.
|
3 | When two doubleword values are multiplied − Khi hai giá trị từ kép được nhân lên, thì số nhân và phải nằm trong EAX và hệ số nhân là giá trị từ kép được lưu trữ trong bộ nhớ hoặc trong một thanh ghi khác. Sản phẩm được tạo ra được lưu trữ trong thanh ghi EDX: EAX, tức là 32 bit bậc cao được lưu trong thanh ghi EDX và 32 bit bậc thấp được lưu trong thanh ghi EAX.
|
Thí dụ
MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL
Thí dụ
Ví dụ sau nhân 3 với 2 và hiển thị kết quả:
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The result is:
6
Hướng dẫn DIV / IDIV
Phép toán chia tạo ra hai phần tử - a quotient và một remainder. Trong trường hợp nhân, tràn không xảy ra vì các thanh ghi độ dài kép được sử dụng để giữ tích. Tuy nhiên, trong trường hợp phân chia, tràn có thể xảy ra. Bộ xử lý tạo ra một ngắt nếu xảy ra tràn.
Lệnh DIV (Chia) được sử dụng cho dữ liệu chưa có dấu và IDIV (Chia số nguyên) được sử dụng cho dữ liệu có dấu.
Cú pháp
Định dạng cho lệnh DIV / IDIV -
DIV/IDIV divisor
Cổ tức nằm trong một bộ tích lũy. Cả hai lệnh đều có thể hoạt động với các toán hạng 8 bit, 16 bit hoặc 32 bit. Thao tác này ảnh hưởng đến tất cả sáu cờ trạng thái. Phần sau giải thích ba trường hợp chia với kích thước toán hạng khác nhau:
Sr.No. | Các tình huống |
---|---|
1 | When the divisor is 1 byte − Cổ tức được giả định là trong thanh ghi AX (16 bit). Sau khi chia, thương sẽ chuyển vào thanh ghi AL và phần còn lại đi vào thanh ghi AH.
|
2 | When the divisor is 1 word − Cổ tức được giả định là dài 32 bit và trong thanh ghi DX: AX. 16 bit bậc cao nằm trong DX và 16 bit bậc thấp nằm trong AX. Sau khi phân chia, thương số 16 bit đi vào thanh ghi AX và phần còn lại 16 bit đi vào thanh ghi DX.
|
3 | When the divisor is doubleword − Cổ tức được giả định là dài 64 bit và trong thanh ghi EDX: EAX. 32 bit bậc cao nằm trong EDX và 32 bit bậc thấp trong EAX. Sau khi phân chia, thương số 32 bit đi vào thanh ghi EAX và phần còn lại 32 bit đi vào thanh ghi EDX.
|
Thí dụ
Ví dụ sau chia 8 với 2. dividend 8 được lưu trữ trong 16-bit AX register và divisor 2 được lưu trữ trong 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The result is:
4
Tập lệnh của bộ xử lý cung cấp các lệnh AND, OR, XOR, TEST và NOT Boolean logic để kiểm tra, thiết lập và xóa các bit tùy theo nhu cầu của chương trình.
Định dạng cho các hướng dẫn này -
Sr.No. | Chỉ dẫn | định dạng |
---|---|---|
1 | VÀ | AND toán hạng1, toán hạng2 |
2 | HOẶC LÀ | HOẶC toán hạng1, toán hạng2 |
3 | XOR | Toán hạng XOR1, toán hạng2 |
4 | KIỂM TRA | TEST toán hạng 1, toán hạng 2 |
5 | KHÔNG PHẢI | KHÔNG phải toán hạng 1 |
Toán hạng đầu tiên trong tất cả các trường hợp có thể nằm trong thanh ghi hoặc trong bộ nhớ. Toán hạng thứ hai có thể nằm trong thanh ghi / bộ nhớ hoặc một giá trị tức thời (hằng số). Tuy nhiên, không thể thực hiện các thao tác chuyển bộ nhớ vào bộ nhớ. Các lệnh này so sánh hoặc khớp các bit của các toán hạng và đặt các cờ CF, OF, PF, SF và ZF.
Hướng dẫn AND
Lệnh AND được sử dụng để hỗ trợ các biểu thức logic bằng cách thực hiện thao tác AND bit. Phép toán bitwise AND trả về 1, nếu các bit phù hợp từ cả hai toán hạng là 1, ngược lại nó trả về 0. Ví dụ:
Operand1: 0101
Operand2: 0011
----------------------------
After AND -> Operand1: 0001
Phép toán AND có thể được sử dụng để xóa một hoặc nhiều bit. Ví dụ: giả sử thanh ghi BL chứa 0011 1010. Nếu bạn cần xóa các bit bậc cao về 0, bạn VÀ nó với 0FH.
AND BL, 0FH ; This sets BL to 0000 1010
Hãy lấy một ví dụ khác. Nếu bạn muốn kiểm tra xem một số nhất định là số lẻ hay số chẵn, một bài kiểm tra đơn giản sẽ là kiểm tra bit quan trọng nhất của số đó. Nếu đây là 1, số này là số lẻ, số khác là số chẵn.
Giả sử số có trong thanh ghi AL, chúng ta có thể viết:
AND AL, 01H ; ANDing with 0000 0001
JZ EVEN_NUMBER
Chương trình sau đây minh họa điều này -
Thí dụ
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Even Number!
Thay đổi giá trị trong thanh ghi ax bằng một chữ số lẻ, như -
mov ax, 9h ; getting 9 in the ax
Chương trình sẽ hiển thị:
Odd Number!
Tương tự như vậy để xóa toàn bộ sổ đăng ký, bạn có thể VÀ nó với 00H.
Hướng dẫn HOẶC
Lệnh OR được sử dụng để hỗ trợ biểu thức logic bằng cách thực hiện phép toán OR theo bit. Toán tử OR theo bitwise trả về 1, nếu các bit phù hợp từ một trong hai hoặc cả hai toán hạng là một. Nó trả về 0, nếu cả hai bit đều bằng không.
Ví dụ,
Operand1: 0101
Operand2: 0011
----------------------------
After OR -> Operand1: 0111
Phép toán OR có thể được sử dụng để thiết lập một hoặc nhiều bit. Ví dụ, giả sử thanh ghi AL chứa 0011 1010, bạn cần đặt bốn bit bậc thấp, bạn có thể HOẶC nó với giá trị 0000 1111, tức là FH.
OR BL, 0FH ; This sets BL to 0011 1111
Thí dụ
Ví dụ sau minh họa lệnh OR. Hãy để chúng tôi lưu trữ giá trị 5 và 3 trong thanh ghi AL và BL, sau đó là lệnh,
OR AL, BL
nên lưu trữ 7 trong thanh ghi 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
7
Hướng dẫn XOR
Lệnh XOR thực hiện thao tác XOR theo bit. Phép toán XOR đặt bit kết quả thành 1, nếu và chỉ khi các bit từ các toán hạng khác nhau. Nếu các bit từ các toán hạng giống nhau (cả 0 hoặc cả 1), bit kết quả sẽ bị xóa thành 0.
Ví dụ,
Operand1: 0101
Operand2: 0011
----------------------------
After XOR -> Operand1: 0110
XORing một toán hạng với chính nó sẽ thay đổi toán hạng thành 0. Điều này được sử dụng để xóa sổ đăng ký.
XOR EAX, EAX
Hướng dẫn TEST
Lệnh TEST hoạt động giống như hoạt động AND, nhưng không giống như lệnh AND, nó không thay đổi toán hạng đầu tiên. Vì vậy, nếu chúng ta cần kiểm tra xem một số trong thanh ghi là chẵn hay lẻ, chúng ta cũng có thể thực hiện việc này bằng cách sử dụng lệnh TEST mà không cần thay đổi số ban đầu.
TEST AL, 01H
JZ EVEN_NUMBER
Hướng dẫn NOT
Lệnh NOT thực hiện thao tác NOT bitwise. Phép toán NOT đảo ngược các bit trong một toán hạng. Toán hạng có thể nằm trong một thanh ghi hoặc trong bộ nhớ.
Ví dụ,
Operand1: 0101 0011
After NOT -> Operand1: 1010 1100
Việc thực thi có điều kiện trong hợp ngữ được thực hiện bằng một số lệnh lặp và rẽ nhánh. Các hướng dẫn này có thể thay đổi luồng điều khiển trong một chương trình. Thực hiện có điều kiện được quan sát trong hai tình huống:
Sr.No. | Hướng dẫn có điều kiện |
---|---|
1 | Unconditional jump Điều này được thực hiện bởi lệnh JMP. Việc thực thi có điều kiện thường liên quan đến việc chuyển quyền điều khiển tới địa chỉ của một lệnh không tuân theo lệnh đang thực thi. Việc chuyển giao quyền kiểm soát có thể được chuyển tiếp, để thực hiện một bộ hướng dẫn mới hoặc lùi lại, để thực hiện lại các bước tương tự. |
2 | Conditional jump Điều này được thực hiện bởi một tập hợp các lệnh nhảy j <điều kiện> tùy thuộc vào điều kiện. Các lệnh có điều kiện chuyển điều khiển bằng cách ngắt dòng tuần tự và chúng thực hiện bằng cách thay đổi giá trị bù trong IP. |
Chúng ta hãy thảo luận về lệnh CMP trước khi thảo luận về các lệnh có điều kiện.
Hướng dẫn CMP
Lệnh CMP so sánh hai toán hạng. Nó thường được sử dụng trong thực thi có điều kiện. Hướng dẫn này về cơ bản trừ một toán hạng với toán hạng kia để so sánh xem các toán hạng có bằng nhau hay không. Nó không làm phiền toán hạng đích hoặc nguồn. Nó được sử dụng cùng với lệnh nhảy có điều kiện để ra quyết định.
Cú pháp
CMP destination, source
CMP so sánh hai trường dữ liệu số. Toán hạng đích có thể nằm trong thanh ghi hoặc trong bộ nhớ. Toán hạng nguồn có thể là một dữ liệu, thanh ghi hoặc bộ nhớ không đổi (tức thời).
Thí dụ
CMP DX, 00 ; Compare the DX value with zero
JE L7 ; If yes, then jump to label L7
.
.
L7: ...
CMP thường được sử dụng để so sánh xem giá trị bộ đếm có đạt đến số lần một vòng lặp cần được chạy hay không. Hãy xem xét điều kiện điển hình sau:
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
Nhảy vô điều kiện
Như đã đề cập trước đó, điều này được thực hiện bởi lệnh JMP. Việc thực thi có điều kiện thường liên quan đến việc chuyển quyền điều khiển tới địa chỉ của một lệnh không tuân theo lệnh đang thực thi. Việc chuyển giao quyền kiểm soát có thể được chuyển tiếp, để thực hiện một bộ hướng dẫn mới hoặc lùi lại, để thực hiện lại các bước tương tự.
Cú pháp
Lệnh JMP cung cấp tên nhãn nơi luồng điều khiển được chuyển ngay lập tức. Cú pháp của lệnh JMP là:
JMP label
Thí dụ
Đoạn mã sau minh họa hướng dẫn 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
Nhảy có điều kiện
Nếu một số điều kiện cụ thể được thỏa mãn trong bước nhảy có điều kiện, thì luồng điều khiển được chuyển đến một lệnh đích. Có rất nhiều lệnh nhảy có điều kiện tùy thuộc vào điều kiện và dữ liệu.
Sau đây là các hướng dẫn nhảy có điều kiện được sử dụng trên dữ liệu có dấu được sử dụng cho các phép toán số học:
Chỉ dẫn | Sự miêu tả | Cờ đã được kiểm tra |
---|---|---|
JE / JZ | Nhảy bằng hoặc nhảy bằng không | ZF |
JNE / JNZ | Nhảy không bằng hoặc nhảy không bằng 0 | ZF |
JG / JNLE | Nhảy lớn hơn hoặc nhảy không ít / bằng | OF, SF, ZF |
JGE / JNL | Nhảy lớn hơn / bằng hoặc nhảy không ít | OF, SF |
JL / JNGE | Nhảy ít hơn hoặc nhảy không lớn hơn / bằng | OF, SF |
JLE / JNG | Nhảy ít hơn / bằng hoặc nhảy không lớn hơn | OF, SF, ZF |
Sau đây là các hướng dẫn nhảy có điều kiện được sử dụng trên dữ liệu không dấu được sử dụng cho các phép toán logic:
Chỉ dẫn | Sự miêu tả | Cờ đã được kiểm tra |
---|---|---|
JE / JZ | Nhảy bằng hoặc nhảy bằng không | ZF |
JNE / JNZ | Nhảy không bằng hoặc nhảy không bằng 0 | ZF |
JA / JNBE | Nhảy lên trên hoặc nhảy không xuống dưới / Bằng | CF, ZF |
JAE / JNB | Nhảy lên trên / bằng hoặc nhảy không xuống dưới | CF |
JB / JNAE | Nhảy xuống dưới hoặc nhảy không trên / bằng | CF |
JBE / JNA | Nhảy xuống dưới / Bằng nhau hoặc Nhảy không lên trên | AF, CF |
Các lệnh nhảy có điều kiện sau đây có công dụng đặc biệt và kiểm tra giá trị của các cờ:
Chỉ dẫn | Sự miêu tả | Cờ đã được kiểm tra |
---|---|---|
JXCZ | Nhảy nếu CX bằng 0 | không ai |
JC | Nhảy nếu mang | CF |
JNC | Nhảy nếu không mang | CF |
JO | Nhảy nếu tràn | CỦA |
JNO | Nhảy nếu không có tràn | CỦA |
JP / JPE | Nhảy chẵn lẻ hoặc nhảy chẵn lẻ | PF |
JNP / JPO | Nhảy không chẵn lẻ hoặc nhảy chẵn lẻ lẻ | PF |
JS | Dấu nhảy (giá trị âm) | SF |
JNS | Nhảy không có dấu (giá trị dương) | SF |
Cú pháp cho tập lệnh J <condition> -
Thí dụ,
CMP AL, BL
JE EQUAL
CMP AL, BH
JE EQUAL
CMP AL, CL
JE EQUAL
NON_EQUAL: ...
EQUAL: ...
Thí dụ
Chương trình sau đây hiển thị biến lớn nhất trong ba biến. Các biến là các biến có hai chữ số. Ba biến num1, num2 và num3 lần lượt có các giá trị 47, 22 và 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The largest digit is:
47
Lệnh JMP có thể được sử dụng để thực hiện các vòng lặp. Ví dụ: đoạn mã sau có thể được sử dụng để thực thi phần nội dung vòng lặp 10 lần.
MOV CL, 10
L1:
<LOOP-BODY>
DEC CL
JNZ L1
Tuy nhiên, tập lệnh của bộ xử lý bao gồm một nhóm các lệnh lặp để thực hiện lặp. Hướng dẫn LOOP cơ bản có cú pháp sau:
LOOP label
Trong đó, nhãn là nhãn đích xác định lệnh đích như trong lệnh nhảy. Lệnh LOOP giả định rằngECX register contains the loop count. Khi lệnh lặp được thực hiện, thanh ghi ECX giảm và điều khiển nhảy đến nhãn đích, cho đến khi giá trị thanh ghi ECX, tức là bộ đếm đạt đến giá trị bằng không.
Đoạn mã trên có thể được viết là -
mov ECX,10
l1:
<loop body>
loop l1
Thí dụ
Chương trình sau sẽ in số từ 1 đến 9 trên màn hình:
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
123456789:
Dữ liệu số thường được biểu diễn trong hệ thống nhị phân. Các lệnh số học hoạt động trên dữ liệu nhị phân. Khi các số được hiển thị trên màn hình hoặc được nhập từ bàn phím, chúng ở dạng ASCII.
Cho đến nay, chúng tôi đã chuyển đổi dữ liệu đầu vào này ở dạng ASCII sang nhị phân để tính toán số học và chuyển đổi kết quả trở lại dạng nhị phân. Đoạn mã sau đây cho thấy điều này:
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The sum is:
7
Tuy nhiên, các chuyển đổi như vậy có chi phí cao và lập trình hợp ngữ cho phép xử lý các số theo cách hiệu quả hơn, ở dạng nhị phân. Số thập phân có thể được biểu diễn dưới hai dạng:
- Biểu mẫu ASCII
- BCD hoặc dạng thập phân được mã hóa nhị phân
Biểu diễn ASCII
Trong biểu diễn ASCII, số thập phân được lưu trữ dưới dạng chuỗi ký tự ASCII. Ví dụ: giá trị thập phân 1234 được lưu trữ dưới dạng:
31 32 33 34H
Trong đó, 31H là giá trị ASCII cho 1, 32H là giá trị ASCII cho 2, v.v. Có bốn hướng dẫn để xử lý số trong biểu diễn ASCII -
AAA - Điều chỉnh ASCII sau khi bổ sung
AAS - Điều chỉnh ASCII sau khi trừ
AAM - Điều chỉnh ASCII sau khi nhân
AAD - Điều chỉnh ASCII trước khi phân chia
Các hướng dẫn này không lấy bất kỳ toán hạng nào và giả sử toán hạng bắt buộc phải có trong thanh ghi AL.
Ví dụ sau sử dụng hướng dẫn AAS để chứng minh khái niệm:
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The Result is:
6
Đại diện BCD
Có hai kiểu biểu diễn BCD -
- Biểu diễn BCD được giải nén
- Biểu diễn BCD đóng gói
Trong biểu diễn BCD được giải nén, mỗi byte lưu trữ tương đương nhị phân của một chữ số thập phân. Ví dụ: số 1234 được lưu trữ dưới dạng:
01 02 03 04H
Có hai hướng dẫn để xử lý những con số này -
AAM - Điều chỉnh ASCII sau khi nhân
AAD - Điều chỉnh ASCII trước khi phân chia
Bốn lệnh điều chỉnh ASCII, AAA, AAS, AAM và AAD, cũng có thể được sử dụng với biểu diễn BCD được giải nén. Trong biểu diễn BCD đóng gói, mỗi chữ số được lưu trữ bằng cách sử dụng bốn bit. Hai chữ số thập phân được đóng gói thành một byte. Ví dụ: số 1234 được lưu trữ dưới dạng:
12 34H
Có hai hướng dẫn để xử lý những con số này -
DAA - Điều chỉnh số thập phân sau khi cộng
DAS - Điều chỉnh số thập phân sau khi trừ
Không hỗ trợ phép nhân và chia trong biểu diễn BCD đóng gói.
Thí dụ
Chương trình sau đây cộng hai số thập phân có 5 chữ số và hiển thị tổng. Nó sử dụng các khái niệm trên -
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 ' '
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The Sum is:
35801
Chúng tôi đã sử dụng các chuỗi có độ dài thay đổi trong các ví dụ trước của chúng tôi. Các chuỗi có độ dài thay đổi có thể có nhiều ký tự theo yêu cầu. Nói chung, chúng tôi chỉ định độ dài của chuỗi bằng một trong hai cách sau:
- Độ dài chuỗi lưu trữ rõ ràng
- Sử dụng một ký tự lính canh
Chúng ta có thể lưu trữ độ dài chuỗi một cách rõ ràng bằng cách sử dụng ký hiệu bộ đếm vị trí $ đại diện cho giá trị hiện tại của bộ đếm vị trí. Trong ví dụ sau -
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
$ trỏ đến byte sau ký tự cuối cùng của biến chuỗi msg . Vì thế,$-msgcho biết độ dài của chuỗi. Chúng tôi cũng có thể viết
msg db 'Hello, world!',0xa ;our dear string
len equ 13 ;length of our dear string
Ngoài ra, bạn có thể lưu trữ các chuỗi có ký tự sentinel ở cuối để phân tách một chuỗi thay vì lưu trữ độ dài chuỗi một cách rõ ràng. Ký tự sentinel phải là một ký tự đặc biệt không xuất hiện trong một chuỗi.
Ví dụ -
message DB 'I am loving it!', 0
Hướng dẫn chuỗi
Mỗi lệnh chuỗi có thể yêu cầu một toán hạng nguồn, một toán hạng đích hoặc cả hai. Đối với các phân đoạn 32 bit, các lệnh chuỗi sử dụng thanh ghi ESI và EDI để trỏ tới toán hạng nguồn và đích tương ứng.
Tuy nhiên, đối với các đoạn 16 bit, thanh ghi SI và DI được sử dụng để trỏ đến nguồn và đích tương ứng.
Có năm hướng dẫn cơ bản để xử lý chuỗi. Họ là -
MOVS - Lệnh này di chuyển 1 Byte, Word hoặc Doubleword của dữ liệu từ vị trí bộ nhớ sang vị trí khác.
LODS- Lệnh này tải từ bộ nhớ. Nếu toán hạng là một byte, nó được tải vào thanh ghi AL, nếu toán hạng là một từ, nó được tải vào thanh ghi AX và một từ kép được tải vào thanh ghi EAX.
STOS - Lệnh này lưu dữ liệu từ thanh ghi (AL, AX, hoặc EAX) vào bộ nhớ.
CMPS- Lệnh này so sánh hai mục dữ liệu trong bộ nhớ. Dữ liệu có thể có kích thước byte, từ hoặc từ kép.
SCAS - Lệnh này so sánh nội dung của một thanh ghi (AL, AX hoặc EAX) với nội dung của một mục trong bộ nhớ.
Mỗi lệnh trên đều có phiên bản byte, từ và từ kép, và các lệnh chuỗi có thể được lặp lại bằng cách sử dụng tiền tố lặp lại.
Các lệnh này sử dụng cặp thanh ghi ES: DI và DS: SI, trong đó thanh ghi DI và SI chứa các địa chỉ bù hợp lệ tham chiếu đến các byte được lưu trữ trong bộ nhớ. SI thường được kết hợp với DS (đoạn dữ liệu) và DI luôn được kết hợp với ES (đoạn phụ).
Thanh ghi DS: SI (hoặc ESI) và ES: DI (hoặc EDI) lần lượt trỏ tới toán hạng nguồn và toán hạng đích. Toán hạng nguồn được giả định là tại DS: SI (hoặc ESI) và toán hạng đích tại ES: DI (hoặc EDI) trong bộ nhớ.
Đối với địa chỉ 16 bit, thanh ghi SI và DI được sử dụng, và đối với địa chỉ 32 bit, thanh ghi ESI và EDI được sử dụng.
Bảng sau cung cấp các phiên bản khác nhau của lệnh chuỗi và không gian giả định của các toán hạng.
Hướng dẫn cơ bản | Toán hạng tại | Hoạt động Byte | Hoạt động từ | Hoạt động từ kép |
---|---|---|---|---|
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 | KHOÁ | SCASD |
Tiền tố lặp lại
Tiền tố REP, khi được đặt trước lệnh chuỗi, ví dụ - REP MOVSB, gây ra sự lặp lại lệnh dựa trên bộ đếm được đặt tại thanh ghi CX. REP thực hiện lệnh, giảm CX đi 1 và kiểm tra xem CX có bằng không hay không. Nó lặp lại quá trình xử lý lệnh cho đến khi CX bằng không.
Cờ hướng (DF) xác định hướng của hoạt động.
- Sử dụng CLD (Clear Direction Flag, DF = 0) để thực hiện thao tác từ trái sang phải.
- Sử dụng STD (Đặt cờ hướng, DF = 1) để thực hiện thao tác từ phải sang trái.
Tiền tố REP cũng có các biến thể sau:
REP: Đó là sự lặp lại vô điều kiện. Nó lặp lại hoạt động cho đến khi CX bằng không.
REPE hoặc REPZ: Nó là lặp lại có điều kiện. Nó lặp lại thao tác trong khi cờ số không biểu thị bằng / không. Nó dừng khi ZF cho biết không bằng / không hoặc khi CX bằng không.
REPNE hoặc REPNZ: Nó cũng là lặp lại có điều kiện. Nó lặp lại hoạt động trong khi cờ số không cho biết không bằng / không. Nó dừng khi ZF cho biết bằng / không hoặc khi CX giảm xuống 0.
Chúng ta đã thảo luận rằng các chỉ thị định nghĩa dữ liệu cho trình hợp dịch được sử dụng để cấp phát bộ nhớ cho các biến. Biến cũng có thể được khởi tạo với một số giá trị cụ thể. Giá trị khởi tạo có thể được chỉ định ở dạng thập lục phân, thập phân hoặc nhị phân.
Ví dụ: chúng ta có thể xác định biến từ 'tháng' theo một trong hai cách sau:
MONTHS DW 12
MONTHS DW 0CH
MONTHS DW 0110B
Các chỉ thị định nghĩa dữ liệu cũng có thể được sử dụng để xác định mảng một chiều. Hãy để chúng tôi xác định một mảng một chiều các số.
NUMBERS DW 34, 45, 56, 67, 75, 89
Định nghĩa trên khai báo một mảng sáu từ, mỗi từ được khởi tạo với các số 34, 45, 56, 67, 75, 89. Điều này phân bổ 2x6 = 12 byte không gian bộ nhớ liên tiếp. Địa chỉ tượng trưng của số đầu tiên sẽ là SỐ và địa chỉ của số thứ hai sẽ là SỐ + 2, v.v.
Hãy để chúng tôi lấy một ví dụ khác. Bạn có thể xác định một mảng có tên khoảng không quảng cáo có kích thước 8 và khởi tạo tất cả các giá trị bằng 0, như:
INVENTORY DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
Có thể viết tắt là -
INVENTORY DW 0, 0 , 0 , 0 , 0 , 0 , 0 , 0
Chỉ thị TIMES cũng có thể được sử dụng cho nhiều lần khởi tạo với cùng một giá trị. Sử dụng TIMES, mảng INVENTORY có thể được định nghĩa là:
INVENTORY TIMES 8 DW 0
Thí dụ
Ví dụ sau minh họa các khái niệm trên bằng cách xác định mảng 3 phần tử x, mảng này lưu trữ ba giá trị: 2, 3 và 4. Nó thêm các giá trị trong mảng và hiển thị tổng 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
9
Các thủ tục hoặc chương trình con rất quan trọng trong hợp ngữ, vì các chương trình hợp ngữ có xu hướng có kích thước lớn. Các thủ tục được xác định bằng một cái tên. Theo sau tên này, phần thân của thủ tục được mô tả thực hiện một công việc được xác định rõ ràng. Kết thúc thủ tục được biểu thị bằng một câu lệnh trả về.
Cú pháp
Sau đây là cú pháp để xác định một thủ tục:
proc_name:
procedure body
...
ret
Thủ tục được gọi từ một hàm khác bằng cách sử dụng lệnh CALL. Lệnh CALL phải có tên của thủ tục được gọi làm đối số như hình dưới đây:
CALL proc_name
Thủ tục được gọi trả lại điều khiển cho thủ tục gọi bằng cách sử dụng lệnh RET.
Thí dụ
Hãy để chúng tôi viết một thủ tục rất đơn giản có tên là sum để thêm các biến được lưu trữ trong thanh ghi ECX và EDX và trả về tổng trong thanh ghi 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
The sum is:
9
Cấu trúc dữ liệu ngăn xếp
Ngăn xếp là một cấu trúc dữ liệu giống như mảng trong bộ nhớ trong đó dữ liệu có thể được lưu trữ và xóa khỏi vị trí được gọi là 'đỉnh' của ngăn xếp. Dữ liệu cần được lưu trữ được 'đẩy' vào ngăn xếp và dữ liệu cần truy xuất được 'bật ra' từ ngăn xếp. Stack là một cấu trúc dữ liệu LIFO, tức là dữ liệu được lưu trữ trước sẽ được truy xuất sau cùng.
Hợp ngữ cung cấp hai hướng dẫn cho các hoạt động ngăn xếp: PUSH và POP. Các hướng dẫn này có các cú pháp như -
PUSH operand
POP address/register
Không gian bộ nhớ dành riêng trong phân đoạn ngăn xếp được sử dụng để thực hiện ngăn xếp. Các thanh ghi SS và ESP (hoặc SP) được sử dụng để triển khai ngăn xếp. Phần trên cùng của ngăn xếp, trỏ đến mục dữ liệu cuối cùng được chèn vào ngăn xếp được chỉ đến bởi thanh ghi SS: ESP, trong đó thanh ghi SS trỏ đến phần đầu của phân đoạn ngăn xếp và SP (hoặc ESP) đưa ra phần bù vào phân đoạn ngăn xếp.
Việc triển khai ngăn xếp có các đặc điểm sau:
Chỉ có words hoặc là doublewords có thể được lưu vào ngăn xếp, không phải một byte.
Ngăn xếp phát triển theo hướng ngược lại, tức là về phía địa chỉ bộ nhớ thấp hơn
Đỉnh của ngăn xếp trỏ đến mục cuối cùng được chèn trong ngăn xếp; nó trỏ đến byte thấp hơn của từ cuối cùng được chèn vào.
Như chúng ta đã thảo luận về việc lưu trữ các giá trị của các thanh ghi trong ngăn xếp trước khi sử dụng chúng để sử dụng; nó có thể được thực hiện theo cách sau -
; 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
Thí dụ
Chương trình sau đây hiển thị toàn bộ bộ ký tự ASCII. Chương trình chính gọi một thủ tục có tên là display , nó sẽ hiển thị bộ ký tự 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'
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
...
...
Thủ tục đệ quy là một thủ tục gọi chính nó. Có hai loại đệ quy: trực tiếp và gián tiếp. Trong đệ quy trực tiếp, thủ tục gọi chính nó và trong đệ quy gián tiếp, thủ tục đầu tiên gọi thủ tục thứ hai, đến lượt nó gọi thủ tục đầu tiên.
Đệ quy có thể được quan sát trong nhiều thuật toán toán học. Ví dụ, hãy xem xét trường hợp tính giai thừa của một số. Giai thừa của một số được cho bởi phương trình -
Fact (n) = n * fact (n-1) for n > 0
Ví dụ: giai thừa của 5 là 1 x 2 x 3 x 4 x 5 = 5 x giai thừa của 4 và đây có thể là một ví dụ điển hình về việc hiển thị một thủ tục đệ quy. Mọi thuật toán đệ quy phải có một điều kiện kết thúc, nghĩa là, việc gọi đệ quy của chương trình phải được dừng lại khi một điều kiện được đáp ứng. Trong trường hợp thuật toán giai thừa, điều kiện cuối đạt được khi n bằng 0.
Chương trình sau đây cho biết cách giai thừa n được thực hiện trong hợp ngữ. Để giữ cho chương trình đơn giản, chúng ta sẽ tính giai thừa 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Factorial 3 is:
6
Viết macro là một cách khác để đảm bảo lập trình mô-đun trong hợp ngữ.
Macro là một chuỗi các lệnh, được gán bằng tên và có thể được sử dụng ở bất kỳ đâu trong chương trình.
Trong NASM, macro được định nghĩa bằng %macro và %endmacro các chỉ thị.
Macro bắt đầu bằng chỉ thị% macro và kết thúc bằng chỉ thị% endmacro.
Cú pháp để định nghĩa macro -
%macro macro_name number_of_params
<macro body>
%endmacro
Trong đó, number_of_params chỉ định các tham số số, macro_name chỉ định tên của macro.
Macro được gọi bằng cách sử dụng tên macro cùng với các tham số cần thiết. Khi bạn cần sử dụng một số chuỗi hướng dẫn nhiều lần trong một chương trình, bạn có thể đặt các hướng dẫn đó trong macro và sử dụng nó thay vì viết hướng dẫn mọi lúc.
Ví dụ, một nhu cầu rất phổ biến đối với các chương trình là viết một chuỗi ký tự trên màn hình. Để hiển thị một chuỗi ký tự, bạn cần chuỗi hướng dẫn sau:
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
Trong ví dụ trên về hiển thị một chuỗi ký tự, các thanh ghi EAX, EBX, ECX và EDX đã được sử dụng bởi lệnh gọi hàm INT 80H. Vì vậy, mỗi khi bạn cần hiển thị trên màn hình, bạn cần lưu các thanh ghi này trên ngăn xếp, gọi INT 80H và sau đó khôi phục giá trị ban đầu của các thanh ghi từ ngăn xếp. Vì vậy, có thể hữu ích khi viết hai macro để lưu và khôi phục dữ liệu.
Chúng tôi nhận thấy rằng, một số lệnh như IMUL, IDIV, INT, v.v., cần một số thông tin được lưu trữ trong một số thanh ghi cụ thể và thậm chí trả về giá trị trong một số thanh ghi cụ thể. Nếu chương trình đã sử dụng các thanh ghi đó để lưu giữ dữ liệu quan trọng, thì dữ liệu hiện có từ các thanh ghi này sẽ được lưu trong ngăn xếp và được khôi phục sau khi lệnh được thực thi.
Thí dụ
Ví dụ sau cho thấy việc xác định và sử dụng macro:
; 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Hệ thống coi bất kỳ dữ liệu đầu vào hoặc đầu ra nào dưới dạng luồng byte. Có ba luồng tệp tiêu chuẩn -
- Đầu vào chuẩn (stdin),
- Đầu ra tiêu chuẩn (stdout), và
- Lỗi chuẩn (stderr).
Trình mô tả tệp
A file descriptorlà một số nguyên 16 bit được gán cho một tệp dưới dạng id tệp. Khi một tệp mới được tạo hoặc một tệp hiện có được mở, bộ mô tả tệp được sử dụng để truy cập tệp.
Bộ mô tả tệp của các luồng tệp tiêu chuẩn - stdin, stdout và stderr lần lượt là 0, 1 và 2.
Con trỏ tệp
A file pointerchỉ định vị trí cho thao tác đọc / ghi tiếp theo trong tệp theo byte. Mỗi tệp được coi là một chuỗi các byte. Mỗi tệp đang mở được liên kết với một con trỏ tệp chỉ định độ lệch tính bằng byte, liên quan đến phần đầu của tệp. Khi một tệp được mở, con trỏ tệp được đặt thành không.
Cuộc gọi hệ thống xử lý tệp
Bảng sau đây mô tả ngắn gọn các lệnh gọi hệ thống liên quan đến xử lý tệp:
% eax | Tên | % ebx | % ecx | % edx |
---|---|---|---|---|
2 | sys_fork | struct pt_regs | - | - |
3 | sys_read | int không dấu | char * | size_t |
4 | sys_write | int không dấu | const char * | size_t |
5 | sys_open | const char * | int | int |
6 | sys_close | int không dấu | - | - |
số 8 | sys_creat | const char * | int | - |
19 | sys_lseek | int không dấu | off_t | int không dấu |
Các bước cần thiết để sử dụng lệnh gọi hệ thống giống như chúng ta đã thảo luận trước đó -
- Đặt số cuộc gọi hệ thống vào sổ đăng ký EAX.
- Lưu trữ các đối số cho lệnh gọi hệ thống trong thanh ghi EBX, ECX, v.v.
- Gọi ngắt liên quan (80h).
- Kết quả thường được trả về trong thanh ghi EAX.
Tạo và mở tệp
Để tạo và mở tệp, hãy thực hiện các tác vụ sau:
- Đặt lệnh gọi hệ thống sys_creat () số 8, vào thanh ghi EAX.
- Đặt tên tệp vào sổ đăng ký EBX.
- Đặt quyền tệp vào thanh ghi ECX.
Lệnh gọi hệ thống trả về bộ mô tả tệp của tệp được tạo trong thanh ghi EAX, trong trường hợp lỗi, mã lỗi nằm trong thanh ghi EAX.
Mở một tệp hiện có
Để mở tệp hiện có, hãy thực hiện các tác vụ sau:
- Đặt lệnh gọi hệ thống sys_open () số 5, vào thanh ghi EAX.
- Đặt tên tệp vào sổ đăng ký EBX.
- Đặt chế độ truy cập tệp vào thanh ghi ECX.
- Đặt quyền tệp vào thanh ghi EDX.
Lệnh gọi hệ thống trả về bộ mô tả tệp của tệp được tạo trong thanh ghi EAX, trong trường hợp lỗi, mã lỗi nằm trong thanh ghi EAX.
Trong số các chế độ truy cập tệp, thường được sử dụng nhất là: chỉ đọc (0), chỉ ghi (1) và đọc-ghi (2).
Đọc từ một tệp
Để đọc từ một tệp, hãy thực hiện các tác vụ sau:
Đặt lệnh gọi hệ thống sys_read () số 3, vào thanh ghi EAX.
Đặt bộ mô tả tệp vào thanh ghi EBX.
Đặt con trỏ vào bộ đệm đầu vào trong thanh ghi ECX.
Đặt kích thước bộ đệm, tức là số byte cần đọc, vào thanh ghi EDX.
Lệnh gọi hệ thống trả về số byte được đọc trong thanh ghi EAX, trong trường hợp lỗi, mã lỗi nằm trong thanh ghi EAX.
Ghi vào tệp
Để ghi vào tệp, hãy thực hiện các tác vụ sau:
Đặt lệnh gọi hệ thống sys_write () số 4, vào thanh ghi EAX.
Đặt bộ mô tả tệp vào thanh ghi EBX.
Đặt con trỏ đến bộ đệm đầu ra trong thanh ghi ECX.
Đặt kích thước bộ đệm, tức là số byte cần ghi, vào thanh ghi EDX.
Lệnh gọi hệ thống trả về số byte thực tế được ghi trong thanh ghi EAX, trong trường hợp lỗi, mã lỗi nằm trong thanh ghi EAX.
Đóng tệp
Để đóng tệp, hãy thực hiện các tác vụ sau:
- Đặt lệnh gọi hệ thống sys_close () số 6, vào thanh ghi EAX.
- Đặt bộ mô tả tệp vào thanh ghi EBX.
Lệnh gọi hệ thống trả về, trong trường hợp có lỗi, mã lỗi trong thanh ghi EAX.
Cập nhật tệp
Để cập nhật tệp, hãy thực hiện các tác vụ sau:
- Đặt lệnh gọi hệ thống sys_lseek () số 19, vào thanh ghi EAX.
- Đặt bộ mô tả tệp vào thanh ghi EBX.
- Đặt giá trị bù vào thanh ghi ECX.
- Đặt vị trí tham chiếu cho độ lệch trong thanh ghi EDX.
Vị trí tham chiếu có thể là:
- Bắt đầu tệp - giá trị 0
- Vị trí hiện tại - giá trị 1
- Cuối tệp - giá trị 2
Lệnh gọi hệ thống trả về, trong trường hợp có lỗi, mã lỗi trong thanh ghi EAX.
Thí dụ
Chương trình sau tạo và mở một tệp có tên myfile.txt và viết văn bản 'Chào mừng đến với Điểm Hướng dẫn' vào tệp này. Tiếp theo, chương trình đọc từ tệp và lưu trữ dữ liệu vào một bộ đệm có tên là thông tin . Cuối cùng, nó hiển thị văn bản như được lưu trữ trong thông tin .
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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Written to file
Welcome to Tutorials Point
Các sys_brk()lệnh gọi hệ thống được cung cấp bởi hạt nhân, để cấp phát bộ nhớ mà không cần di chuyển nó sau này. Cuộc gọi này phân bổ bộ nhớ ngay sau hình ảnh ứng dụng trong bộ nhớ. Chức năng hệ thống này cho phép bạn đặt địa chỉ khả dụng cao nhất trong phần dữ liệu.
Lệnh gọi hệ thống này nhận một tham số, là địa chỉ bộ nhớ cao nhất cần được thiết lập. Giá trị này được lưu trữ trong thanh ghi EBX.
Trong trường hợp có bất kỳ lỗi nào, sys_brk () trả về -1 hoặc trả về chính mã lỗi âm. Ví dụ sau minh họa cấp phát bộ nhớ động.
Thí dụ
Chương trình sau phân bổ 16kb bộ nhớ bằng cách gọi hệ thống 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
Khi đoạn mã trên được biên dịch và thực thi, nó tạo ra kết quả sau:
Allocated 16 kb of memory!