Professional Documents
Culture Documents
Hầu hết các hệ điều hành máy tính hiện nay, đặc biệt là các hệ điều hành của
Microsoft, đều hỗ trợ hai dạng cấu trúc tập tin thực thi có thể hoạt động trên nó, đó
là tập tin cấu trúc dạng COM và tập tin cấu trúc dạng EXE. Có nhiều điểm khác
nhau giữa hai cấu trúc chương trình này, nhưng điểm khác biệt lớn nhất là: Các
chương trình cấu trúc dạng EXE gồm 3 đoạn: Mã lệnh (Code), dữ liệu (Data) và
Ngăn xếp (Stack). Khi hoạt động, 3 đoạn này sẽ được nạp vào 3 đoạn (Segment)
bộ nhớ tách biệt trên bộ nhớ;
Các chương trình dạng COM thì ngược lại, nó chỉ có 1 đoạn mã lệnh, trong
đó chứa cả mã lệnh và ngăn xếp. Vì thế, khi được nạp vào bộ nhớ để hoạt động nó
chỉ được cấp phát một đoạn bộ nhớ. Rõ ràng kích thước của một chương trình dạng
COM không thể vượt quá giới hạn của một đoạn bộ nhớ (với Intel 8088/80286 và
MSDOS, 1 Segment bộ nhớ = 64KB).
Trong khi đó một chương trình dạng EXE có thể lớn hơn 3 Segment bộ
nhớ. Do đó, khi thiết kế các chương trình lớn, với chức năng phức tạp, trong đó có
liên kết giữa các modun chương trình khác nhau thì ta phải thiết kế theo cấu trúc
chương trình dạng EXE.
Hợp ngữ hỗ trợ thiết kế cả hai dạng cấu trúc chương trình EXE và COM,
mỗi dạng phù hợp với một nhóm trình biên dịch nào đó. Muốn biên dịch một
chương trình hợp ngữ sang dạng EXE thì ngoài việc nó phải được viết theo cấu
trúc dạng EXE ta còn cần phải sử dụng một trình biên dịch phù hợp. Điều này cũng
tương tự với việc muốn có một chương trình thực thi dạng COM.
Văn bản của một chương trình hợp ngữ dạng EXE cũng cho thấy rõ nó
gồm 3 đoạn: Code, Data và Stack. Tương tự, văn bản của chương trình hợp ngữ
dạng COM cho thấy nó chỉ có 1 đoạn: Code, cả Data và Stack (không tường minh)
đều nằm ở đây.
Một chương trình hợp ngữ gồm hai thành phần chính: phần lệnh hợp ngữ
và phần chỉ dẫn biên dịch. Chỉ có các lệnh là được biên dịch thành ngôn ngữ máy.
Phần hướng dẫn biên dịch không được dịch sang ngôn ngữ máy, nó chỉ có tác dụng
với các trình biên dịch. Thông thường mỗi chương trình biên dịch có một nhóm
hướng dẫn biên dịch phù hợp với nó, những với các hướng dẫn biên dịch cơ bản và
đơn giản thì nó phù hợp với hầu hết các trình biên dịch hợp ngữ hiện nay. Trong tài
liệu này chúng tôi sử dụng các hướng dẫn biên dịch phù hợp với trình biên dịch
Microsoft Macro Assembler (MASM).
Cấu trúc chương trình được giới thiệu sau đây sử dụng các hướng dẫn
biên dịch định nghĩa đoạn đơn giản (.Model, .Code, .Stack, .Data) phù hợp với
MASM, TASM (Turbo Macro Assembler), A86. Việc sử dụng định nghĩa đoạn
đơn giản sẽ làm cho văn bản chương trình sáng sủa và dễ đọc hơn. Với các định
nghĩa đoạn đơn giản ta cũng có thể xây dựng được các chương trình từ đơn giản
đến phức tạp.
Cấu trúc chương trình dạng COM:
.Model <Chế độ bộ nhớ>
.Code
ORG 100h
<Nhãn chính>:
JMP <Thủ tục chính>
<Khai báo dữ liệu đặt tại đây>
<Thủ tục chính> PROC
<Các lệnh của chương trình đặt tại đây>
<Thủ tục chính> Endp
<Các thủ tục khác đặt tại đây>
End <Nhãn chính>
Trong cấu trúc chương trình trên các từ khóa Model, Code, ORG, Proc, Endp, End
là các hướng dẫn biên dịch. <Nhãn chính> là nhãn của lệnh Jmp.
Cấu trúc này cho thấy rõ, một chương trình hợp ngữ dạng COM chỉ có 1 đoạn, đó
chính là đoạn Code (đoạn mã lệnh), trong này bao gồm cả phần khai báo dữ liệu.
Các khai báo dữ liệu trong chương trình dạng COM có thể đặt ở đầu hoặc ở cuối
chương trình, nhưng với việc sử dụng định nghĩa đoạn đơn giản các khai báo dữ
liệu phải đặt ở đầu chương trình.
Chỉ dẫn ORG 100h và lệnh JMP <Thủ tục chính> sẽ được đề cập trở lại ở
các phần sau đây của tài liệu này.
Cấu trúc chương trình dạng EXE:
.Model <Chế độ bộ nhớ>
.Stack 100h
.Data
<Khai báo dữ liệu đặt tại đây>
.Code
<Thủ tục chính> PROC
<Các lệnh của chương trình đặt tại đây>
<Thủ tục chính> Endp
<Các thủ tục khác đặt tại đây>
END
Trong cấu trúc chương trình trên các từ khóa Model, Code, Data, Stack, Proc,
Endp, End là các hướng dẫn biên dịch.
Cấu trúc này cho thấy rõ, một chương trình hợp ngữ dạng gồm 3 đoạn: đoạn Code,
chứa toàn bộ mã lệnh của chương trình. Đoạn Data, chứa phần khai báo dữ liệu của
chương trình. Đoạn Stack, nơi chứa stack (ngăn xếp) của chương trình khi chương
trình được nạp vào bộ nhớ để hoạt động.
Chỉ dẫn .Stackđặt ở đầu chương trình với mục đích khai báo kích thước của Stack
dùng cho chương trình sau này. Kích thước thường được chọn là 100h (256) byte.
Chỉ dẫn .Model được đặt ở đầu cả cấu trúc chương trình dạng COM và EXE với
mục đích khai báo chế độ bộ nhớ mà chương trình sử dụng.
Ví dụ: Sau đây là hai chương trình hợp ngữ đơn giản, dạng COM và dạng EXE,
cùng thực hiện nhiệm vụ in ra màn hình 3 dòng văn bản như sau :
Nguyen Kim Le Tuan
Nguyen Le Tram Thanh
Nguyen Le Tram Uyen
Hai chương trình dưới đây chỉ có tác dụng minh họa cho việc sử dụng các hướng
dẫn biên dịch định nghĩa đoạn đơn giản và giúp các bạn thấy được những điểm
giống nhau, khác nhau giữa hai dạng cấu trúc chương trình dạng COM và EXE, vì
vậy, ở đây các bạn chưa cần quan tâm đến ý nghĩa của các lệnh và các hàm/ngắt
trong nó. Phần lệnh hợp ngữ và các hàm/ngắt sẽ được trình bày ngay sau đây.
Chương trình viết theo cấu trúc dạng COM:
.Model Small
.Code
ORG 100h
Start:
Jmp Main
MyChildren DB ‘Nguyen Kim Le Tuan’,0Ah,0Dh
DB ‘Nguyen Le Tram Thanh’,0Ah,0Dh
DB ‘Nguyen Le Tram Uyen’,’$’
Main PROC
;------- in ra mot xau voi ham 09/21h -------
Mov Ah, 09h
Lea Dx, MyChildren
Int 21h
;------- ket thuc chuong trinh -------
Int 20h
Main Endp
End Start
Chương trình này chọn chế độ bộ nhớ Small. Tên thủ tục chính là Main (tên thủ
tục chính là tùy ý). Nhãn chính của chương trình là Start (tên thủ tục chính là tùy
ý), đó chính là nhãn của lệnh Jmp. Phần khai báo dữ liệu chỉ khai báo một biến, đó
là MyChildren.
Chương trình này gọi hàm 4Ch của ngắt 21h để kết thúc chương trình. Có thể gọi
ngắt 20h để kết thúc các chương trình dạng COM.
Chương trình viết theo cấu trúc dạng EXE:
.Model Small
.Stack 100h
.Data
MyChildren DB ‘Nguyen Kim Le Tuan’,0Ah,0Dh
DB ‘Nguyen Le Tram Thanh’,0Ah,0Dh
DB ‘Nguyen Le Tram Uyen’,’$’
.Code
Main PROC
;------- khởi tạo DS -------
Mov Ax, @Data
Mov DS, Ax
;------- in ra mot xau voi ham 09/21h -------
Mov Ah, 09h
Lea Dx, MyChildren
Int 21h
;------- ket thuc chuong trinh -------
Mov Ah, 4Ch
Int 21h
Main Endp
END Main
Chương trình này chọn chế độ bộ nhớ Small. Khai báo kích thước Stack là 100h
byte. Phần khai báo dữ liệu được đặt trong đoạn Data, ở đây chỉ khai báo một biến,
đó là MyChildren. Tên thủ tục chính là Main (tên thủ tục chính là tùy ý).
Thao tác đầu tiên của chương trình là trỏ thanh ghi đoạn DS về đầu đoạn Data, hay
còn gọi là khởi tạo thanh ghi đoạn DS:
Mov Ax, @Data
Mov DS, Ax
thao tác này được xem như là bắt buộc đối với cấu trúc chương trình dạng EXE sử
dụng định nghĩa đoạn đơn giản. Các chương trình viết theo cấu trúc dạng EXE phải
gọi hàm 4Ch của ngắt 21h để kết thúc.
Có thể thấy, cấu trúc chương trình dạng COM và cấu trúc chương trình dạng EXE
chỉ khác phần hướng dẫn biên dịch, phần khai báo biến và phần lệnh thao tác chính
hoàn toàn giống nhau. Hai chương trình đơn giản ở trên hoàn toàn giống nhau ở
biến là MyChildren và các lệnh gọi hàm 09h của ngắt 21h để in ra màn hình một
xâu kí tự (xâu này chính là giá trị khởi tạo của biến MyChildren).
Chú ý 1: Trình biên dịch hợp ngữ (Macro Assembler) cho phép các chương trình
được dịch bởi nóc họn sử dụng một trong các chế độ bộ nhớ sau:
- Small: Đoạn mã lệnh (Code) và đoạn dữ liệu (Data) của chương trình đều chỉ có
thể chứa trong một đoạn (segment) bộ nhớ. Tức là, kích thước của chương trình chỉ
có thể tối đa là hai đoạn bộ nhớ. Tuy vậy chế độ bộ nhớ này đủ dùng cho hầu hết
các chương trình hợp ngữ.
- Medium: Đoạn Code của chương trình có thể chiếm nhiều hơn một đoạn bộ nhớ.
Trong khi đó, đoạn Data chỉ có thể chiếm 1 đoạn bộ nhớ.
- Compact: Đoạn Data của chương trình có thể chiếm nhiều hơn một đoạn bộ nhớ.
Trong khi đó, đoạn Code chỉ có thể chiếm 1 đoạn bộ nhớ.
- Large: Đoạn Code và đoan Data của chương trình đều có thể chiếm nhiều hơn
một đoạn bộ nhớ. Nhưng trong trường hợp này không thể định nghĩa một mảng dữ
liệu có kích thước lớn hơn 64 Kbyte.
- Huge: Tương tự như Large, nhưng trong trường hợp này có thể định nghĩa một
mảng dữ liệu có kích thước lớn hơn 64 Kbyte.
Chế độ bộ nhớ Small là đơn giản nhất, được hầu hết các chương trình lựa chọn.
Chú ý 2: Với các chương trình hợp ngữ sử dụng định nghĩa đoạn đơn giản: Khi
được nạp vào bộ nhớ để hoạt động thì các thanh ghi đoạn sẽ tự động trỏ về các
đoạn chương trình tương ứng. Cụ thể: Thanh ghi đoạn CS chứa địa chỉ segment
của đoạn bộ nhớ chứa đoạn Code của chương trình. Thanh ghi đoạn DS (và có thể
cả ES) chứa địa chỉ segment của đoạn bộ nhớ chứa đoạn Data của chương trình.
Thanh ghi đoạn SS chứa địa chỉ segment của đoạn bộ nhớ chứa đoạn Stack của
chương trình.
Tuy nhiên, trong thực tế, khi nạp chương trình EXE vào bộ nhớ DOS luôn dành ra
256 byte đầu tiên của vùng nhớ, mà DOS cấp phát cho chương trình, để chứa PSP
(Program Segment Prefix) của chương trình. PSP chứa các thông tin cần thiết mà
trình biên dịch chuyển đến cho DOS để hỗ trợ DOS trong việc thực hiện chương
trình này, đặc biệt, chương trình cũng có thể truy xuất vùng nhớ PSP. Do đó, DOS
phải đưa địa chỉ segment của vùng nhớ chứa PSP vào cả DS và ES trước khi
chương trình được thực hiện. Tức là, ngay khi chương trình được nạp vào bộ nhớ
DS không phải chứa địa chỉ segment của đoạn Data của chương trình mà chứa địa
chỉ segment của PSP.
Vì vậy, để trỏ DS về lại đoạn Data chương trình chúng ta phải đặt ngay hai lệnh
sau đây ở đầu chương trình viết theo cấu trúc EXE:
Mov Ax, @Data
Mov DS, Ax
Với việc khởi tạo thanh ghi đoạn DS ở trên, địa chỉ segment của tất cả các biến
khai báo trong đoạn Data đều được chứa trong thanh ghi DS, do đó, trong các thao
tác xử lý biến sau này chương trình không cần quan tâm đến địa chỉ segment của
nó nữa.
Chú ý 3: Hợp ngữ còn cho phép các chương trình sử dụng các hướng dẫn biên
dịch định nghĩa đoạn toàn phần, các định nghĩa này phù hợp với hầu hết các trình
biên dịch hợp ngữ hiện nay. Định nghĩa đoạn toàn phần giúp cho việc viết chương
trình hợp ngữ trở nên mềm dẻo và linh hoạt hơn, nó giúp người lập trình có thể
điều khiển thứ tự các đoạn chương trình, kết hợp các đoạn chương trình, liên kết
các đoạn chương trình trong bộ nhớ,... , ngay trong khi lập trình.
Chi tiết về cách sử dụng và mục đích sử dụng của các hướng dẫn biên dịch nói
chung và các định nghĩa đoạn toàn phần nói riêng dễ dàng tìm thấy trong rất
nhiều tài liệu về lập trình hợp ngữ [1], [2]. Ở đây chúng tôi chỉ giới thiệu sơ lược
về nó thông qua ví dụ dưới đây.
Ví dụ: Sau đây là một chương trình dạng EXE sử dụng các hướng dẫn biên dịch
định nghĩa đoạn toàn phần (phù hợp với Macro Assembler):
S_Seg Segment Stack
DB 100h DUP (?)
S_Seg Ends
D_Seg Segmet
MyChildren DB ‘Nguyen Kim Le Tuan’,0Ah,0Dh
DB ‘Nguyen Le Tram Thanh’,0Ah,0Dh
DB ‘Nguyen Le Tram Uyen’,’$’
D_Seg Ends
C_Seg Segment
ASSUME CS:C_Seg, SS:S_Seg, DS:D_Seg
Main PROC
;------- khởi tạo DS -------
Mov Ax, D_Seg
Mov DS, Ax
Mov Ah, 09h
Lea Dx, MyChildren ; địa chỉ offset của biến MyChildren
Int 21h
Mov Ah, 4Ch
Int 21h
Main Endp
C_Seg Ends
END Main
Điều dễ nhận thấy đầu tiên là phần khai báo biến và phần lệnh chính trong chương
trình này hoàn toàn giống như trong chương trình sử dụng định nghĩa đoạn đơn
giản (hai chương trình ví dụ ở trên).
Chương trình này sử dụng hướng dẫn biên dịch định nghĩa đoạn toàn
phần Segment ... Ends để định nghĩa 3 đoạn chương trình với tên lần lượt là:
S_Seg (đoạn stack), D_Seg (đoạn Data), C_Seg (đoạn Code). Tên của các đoạn
được định nghĩa ở đây là tùy ý.
Hướng dẫn biên dịch Assume được sử dụng để báo cho trình biên dịch biết
chương trình muốn chứa địa chỉ segment của các đoạn chương trình trong các
thanh ghi đoạn nào (trỏ thanh ghi đoạn về đoạn chương trình). Cụ thể ở đây là:
Thanh ghi đoạn CS chứa địa chỉ segment của đoạn Code (CS:C_Seg). Thanh ghi
đoạn SS chứa địa chỉ segment của đoạn Stack (SS:S_Seg). Thanh ghi đoạn DS
chứa địa chỉ segment của đoạn Data (DS:C_Seg). Tuy nhiên, trong thực tế Assume
DS:D_Seg không tự động nạp địa chỉ segment của D_Seg vào DS, do đó chương
trình phải nạp trực tiếp bằng các lệnh:
Mov Ax, D_Seg
Mov DS, Ax
Nên nhớ, hướng dẫn biên dịch Segment ... Ends chỉ có tác dụng định nghĩa đoạn,
nó không thể báo cho trình biên dịch biết đoạn được định nghĩa thuộc loại đoạn
chương trình nào (Code, Data, Stack, Extra). Chỉ có định nghĩa Segment Stack ...
Ends là báo cho trình biên dịch biết đoạn được định nghĩa là đoạn Stack, nhờ đó,
khi chương trình được nạp vào bộ nhớ thanh ghi đoạn SS sẽ được trỏ về đoạn này.
Bài 4:
MỘT SỐ LỆNH ASSEMBLY CƠ SỞ
Cú pháp lệnh:
Một lệnh hợp ngữ đầy đủ gồm bốn thành phần sau đây:
[Nhãn lệnh:] <Tên lệnh> [Các toán hạng] [;Lời giải thích]
Trong đó:
- [Nhãn lệnh:]: Là một dãy các kí tự đứng trước câu lệnh (kết thúc bởi dấu hai
chấm (:)), nó được chỉ định thay thế cho địa chỉ của câu lệnh trong các đoạn lệnh
lặp, rẽ nhánh,... Do đó, nó chỉ được sử dụng khi cần.
Trong một chương trình hợp ngữ không thể có hai nhãn lệnh trùng tên, tên của các
nhãn cũng không thể trùng với tên của các thủ tục trong chương trình.
- <Tên lệnh>: Là một trong các lệnh thuộc tập lệnh hợp ngữ (lệnh gợi nhớ:
Mnemonic) của vi xử lý trên máy tính thực hiện lệnh này.
Lệnh hợp ngữ không phân biệt chữ hoa hay chữ thường. Trong chương trình hợp
ngữ mỗi dòng chỉ có thể chứa một lệnh và mỗi lệnh phải được đặt trên một dòng.
- [Các toán hạng]: Là đối tượng mà lệnh tác động vào. Một lệnh hợp ngữ của
Intel 8088/8086 có thể không có toán hạng, có một toán hạng, hoặc có hai toán
hạng. Nếu có hai toán hạng thì toán hạng đứng trước gọi là [Toán hạng đích], toán
hạng đứng sau gọi là [Toán hạng nguồn]. [Toán hạng đích] không thể là một hằng
số.
Một số lệnh hợp ngữ của các Intel 80286/80386/... có thể có đến 3 toán hạng, trong
trường hợp này cũng chỉ có một [Toán hạng đích].
- [;Lời giải thích]: Chỉ có tác dụng với người viết và người đọc chương trình, nó
không có ý nghĩa với trình biên dịch, tức là, không được dịch sang mã máy. Lời
giải thích thường được sử dụng để làm rõ ý nghĩa của câu lệnh (khi người viết thấy
cần). Lời giải thích phải nằm sau dấu chấm phảy (;).
Ví dụ 1: Xét lệnh sau đây:
Lenh_VD: Mov AX,BX ; đặt giá trị thanh ghi BX vào thanh ghi AX
Trong đó:
“đặt giá trị thanh ghi BX vào thanh ghi AX”: Là lời giải thích cho lệnh
này. Trong thực tế lời giải thích thường là tiếng Việt không dấu.
Trong đó: [Toán hạng đích], [Toán hạng nguồn]: tương tự lệnh Mov.
Tác dụng:
Lệnh Inc (Increment): làm tăng giá trị của [Toán hạng đích] lên 1 đơn vị.
Lệnh Dec (Decrement): làm giảm giá trị của [Toán hạng đích] xuống 1 đơn
vị.
Lệnh Add (Addition): lấy giá trị/nội dung của [Toán hạng nguồn] cộng vào
giá trị/nội dung của [Toán hạng đích], kết quả này đặt vào lại [Toán hạng
đích].
Lệnh Sub (Subtract): lấy giá trị/nội dung của [Toán hạng đich] trừ đi giá
trị/nội dung của [Toán hạng nguồn], kết quả này đặt vào lại [Toán hạng đích].
Ví dụ 1:
Mov Ax, 121 ; đặt giá trị 121 vào thanh ghi Ax
Mov Bx, 223 ; đặt giá trị 232 vào thanh ghi Bx
Inc Ax ; Ax = Ax + 1: tăng Ax lên 1 đơn vị (Ax =
122)
Dec Bx ; Bx = Bx + 1: giảm Bx xuống 1 đơn vị (Bx =
222)
Sub Ax, Bx ; Ax = Ax – Bx : Ax = -100
Add Ax, 120 ; Ax = Ax + 120 : Ax = 20
Mov Cx, Ax ; Cx= Ax : Cx = 20
Dãy lệnh trên, đặt giá trị cuối cùng của thanh ghi Ax vào thanh ghi Cx (Cx = 20).
Ví dụ 2:
- Inc Spt ; Spt = Spt + 1; tăng giá trị biến Spt lên 1
đơn vị
- Inc DS:[SI] ; tăng ndung ô nhớ được chỉ bởi DS:SI lên
1 đơn vị
- Add Ax, Var1 ; Ax = Ax + Var1; cộng giá trị biến Var1
vào Ax
- Add Var2, Dx ; Var2 = Var2 + Dx. Biến Var2 là biến
dạng word
- Add Dx, [SI] ; cộng thêm nội dung ô nhớ được chỉ bởi
SI vào Dx
- Add [DI], [SI] ; [DI] = [DI] + [SI] : lệnh không hợp lệ,
vì: không thể
; cộng trực tiếp nội dung hai ô nhớ với
nhau.
; Yêu cầu của lệnh trên có thể được viết lại
như sau:
Mov Ax, [SI] ; lưu tạm nội dung ô nhớ được chỉ bởi SI
và Ax
Mov Bx, [DI] ; lưu tạm nội dung ô nhớ được chỉ bởi DI
và Bx
Add Bx, Ax ; cộng Ax và Bx, kết quả chứa ở Bx
Mov [DI], Bx ; đặt kết quả phép cộng vào lại ô nhớ được
chỉ bởi DI
Ví dụ 3: Cộng thêm giá trị của thanh ghi Ax vào nội dung của ô nhớ tại địa chỉ
offset 0100 trong đoạn DS:
Mov DI, 0100 ; trỏ DI về ô nhớ offset 0100
Mov Bx, DS:[DI] ; lưu tạm ndung ô nhớ DS:DI vào
thanh ghi Bx
Add Bx, Ax ; cộng thêm Ax vào Bx
Mov DS:[DI], Bx ; đặt kết quả vào lại ô nhớ DS:DI
(DS:0100)
Trong trường hợp này ta có thể sử dụng lệnh Add DS:[DI],Ax.
Ví dụ 4: Giả sử tại ô nhớ 0B800:0100 trong bộ nhớ có chứa một word dữ liệu.
Hãy tăng nội dung của ô nhớ này lên một đơn vị.
Mov Ax, 0B800h ; mượn thanh ghi Ax làm trung gian để
chuyển
Mov ES, Ax ; địa chỉ đoạn của ô nhớ cần truy xuất vào ES
Mov DI, 01 ; đặt địa chỉ offset của ô nhớ cần truy xuất vào
DI
;-----------------------; (gọi ngắn gọn: trỏ ES:DI về ô nhớ cần truy xuất)
Mov Dx, ES:[DI] ; chuyển tạm nội dung ô nhớ cần tăng vào
Dx
Inc Dx ; tăng giá trị thanh ghi Dx lên 1 đơn vị
Mov ES:[DI], Dx ; đặt giá trị Dx đã tăng vào lại ô nhớ cần
tăng
Ví dụ 5: Giả sử tại địa chỉ 0A00:0100 trong bộ nhớ có chứa một byte dữ liệu. Hãy
chuyển nội dung của ô nhớ này vào thành ghi AL.
Mov Ax, 0A00h ; (1); Các lệnh (1), (2), (3) trỏ cặp
thanh
Mov ES, Ax ; (2); ghi ES:DI về ô nhớ có địa chỉ
0A00:0100
Mov DI, 0100h ; (3); trong đó 0A00 là địa chỉ
Segment và
;------------------------- ; 0100 là địa chỉ Offset. Lệnh (4)
chuyển nội
Mov Al, ES:[DI] ; (4); dung ô nhớ được chỉ bởi ES:DI
vào Al.
Ví dụ 6: Giả sử tại địa chỉ 0100:0100 trong bộ nhớ có chứa 2 word dữ liệu liên
tiếp (hai ô nhớ liên tiếp). Hãy tính tổng nội dung hai word nhớ này, rồi lấy kết quả
ghi vào ô nhớ tại địa chỉ 0100:0120.
Mov Ax, 0100
Mov ES, Ax ; trỏ cặp thanh ghi ES:SI về đầu vùng
nhớ
Mov SI, 0100 ; cần truy xuất.
Mov DI,0120 ; trỏ cặp thanh ghi ES:DI về ô nhớ
chứa kết quả
;------------------------- ; các ô nhớ này ở trong cùng một
Segment
Mov Ax, ES:[SI] ; lưu tạm nội dung ô nhớ đầu tiên vào
Ax
Add Ax, ES:[SI+2] ; cộng nội dung ô nhớ kế tiếp vào Ax
Mov ES:[DI], Ax ; ghi kết quả vào ô nhớ 0100:0120
Lệnh Add Ax, ES:[SI+2] ở trên sử dụng chế độ định địa chỉ bộ nhớ gián tiếp,
cụ thể là định địa chỉ chỉ mục (sử dụng thanh ghi chỉ mục SI).
Qua 3 ví dụ 4, 5, 6 ta có thể rút ra nguyên tắc cơ bản khi truy xuất dữ liệu/nội dung
của một ô nhớ là: Sử dụng một cặp thanh ghi thích hợp (DS:DI, DS:SI, ES:DI,
ES:SI,...) để chứa địa chỉ logic (gồm cả Segment và Offset) của ô nhớ cần truy
xuất. Thao tác này thường gọi là trỏ về ô nhớ cần truy xuất. Sau đó sử dụng cặp
thanh ghi này để ghi/đọc nội dung của ô nhớ đã được trỏ tới.
Ngoài ra, khi truy xuất ô nhớ cần phải xác định dữ liệu/nội dung tại đó là một Byte
hay một Word và nếu là truy xuất đọc thì kết quả sẽ được lưu vào đâu (thanh ghi
hay ô nhớ).
Chú ý 1:
Không thể cộng trực tiếp hai thanh ghi đoạn. Trong trường hợp này phải sử
dụng các thanh ghi đa năng làm trung gian.
Lệnh Add thực hiện phép cộng không nhớ. Để thực hiện phép cộng có nhớ
(cộng thêm giá trị của cờ nhớ (CF) hiện tại vào kết quả) phải sử dụng
lệnh ADC (ADD with Carry) [2 - 171]. Tương tự với lệnh Sub và SBB [2 -
180].
Để thực hiện phép cộng trên các số/giá trị BCD (Binary Coded Decimal) ta
phải sử dụng các lệnh cộng AAA (Ascii Adjust for Addition) và DAA (Decimal
Adjust for Addition) để điều chỉnh (adjust) kết quả cuối cùng [2 - 172]. Tương
tự, với phép trừ trên các số BCD phải sử dụng lệnh AAS và DAS [2 - 183].
Chú ý 2:
Các thanh ghi của vi xử lý Intel 8086/8088 đều là 16 bít, nên để chứa một
đại lượng dữ liệu 32 bít nó phải dùng 2 thanh ghi, thường là các thanh ghi đa
năng (thanh ghi tích lũy): Ax, Bx, Cx, Dx. Cặp thanh ghi Dx:Ax thường được
sử dụng nhất, khi đó Ax chứa 16 bít thấp, Dx chứa 16 bít cao của đại lượng 32
bít.
Để cộng/trừ trên các số 32 bít ta không thể sử dụng Add/Sub theo cách
thông thường, mà phải thực hiện như sau: Cộng/Trừ 16 bít thấp, sau đó
Cộng/Trừ 16 bít cao. Nếu phép Cộng/Trừ trên 16 bít thấp xuất hiện bít nhớ/bít
mượn thì phải tiến hành điều chỉnh kết quả, nếu không kết quả sẻ sai. Sử dụng
các phép kiểm tra cờ để biết phép Cộng/Trừ có xuất hiện bít nhớ/bít mượn hay
không [1 - 477].
3. Lệnh LOOP
Cú pháp:
Loop <Nhãn đích>
Trong đó: <Nhãn đích> là một nhãn lệnh và nó phải đứng trước lệnh lặp
Loop không quá 126 byte.
Tác dụng: Khi gặp lệnh này chương trình sẽ lặp lại việc thực hiện các lệnh
sau <Nhãn lệnh> đủ n lần, với n được đặt trước trong thanh ghi CX. Sau mỗi
lần lặp CX tự động giảm 1 đơn vị (Cx = Cx - 1) và lệnh lặp sẽ dừng khi Cx
= 0.
Lệnh Loop thường được sử dụng để cài đặt các đoạn chương trình lặp với số
lần lặp xác định, được cho trước trong thanh ghi Cx (tương tự các vòng lặp
For trong các ngôn ngữ lập trình bậc cao).
Ví dụ 1: Xem đoạn lệnh sau đây:
Mov Ax, 6
Mov Cx, 4 ; lặp lại 4 lần
Lap: Add Ax, 10 ; cộng thêm 10 vào Ax
Loop Lap ; lặp lại việc cộng 10 vào Ax đủ
4 lần
Kết thúc đoạn lệnh trên: Ax = 46 (cụ thể: Lần 1: Ax = 6 + 10; Lần 2: Ax =
16 + 10; Lần 3: Ax = 26 + 10; Lần 4: Ax = 36 + 10 = 46).
Ví dụ 2: Xem đoạn lệnh sau đây:
Mov Ax, 6
Mov Bx, 3
Mov Cx, 4 ; lặp lại 4 lần
Lap_TT:
Add Ax, Bx ; cộng thêm giá trị thanh ghi Bx vào
thanh ghi Ax
Inc Bx ; tăng Bx lên 1 đơn vị
Loop Lap_TT ; lặp lại các lệnh sau nhãn lệnh
Lap_TT đủ 4 lần
;----------------------------- ; sau lệnh lặp này Ax = 24, Bx =
7
Mov Dx, Ax ; Dx ß Ax
Mov Cx, Bx ; Cx = 7, sau Loop Cx = 0, được thay
bằng Bx = 7
Kết thúc đoạn lệnh trên: Ax = 24 (Lần 1: Ax = 6 + 3;Lần 2: Ax = 9 +
4; Lần 3: Ax = 13 + 5; Lần 4: Ax = 18 + 6) và Dx = Ax = 24.
Khi gặp lệnh Loop Lap_TT chương trình sẽ quay lại (nếu Cx <> 0) thực
hiện lệnh Add Ax, Bx (Lap_TT là nhãn của lệnh này), tất nhiên khi đó nó
cũng phải thực hiện lại lệnh Inc Bx. Dó đó, có thể nói lệnh Loop này
thực hiện vòng lặp cho cả hai lệnh Add và Inc. Đó cũng chính là lý do mà
người ta thường viết nhãn của các lệnh phải được lặp theo kiểu như trên
(nhãn lệnh và lệnh không cùng trên một dòng).
Ví dụ 3: Xem đoạn lệnh sau đây:
Mov Dx, 6
Mov Cx, 5 ; lặp lại 5 lần
TT:
Add Dx, Cx ; cộng thêm giá trị thanh ghi Cx vào
thanh ghi Dx
Loop TT ; lặp lại các lệnh sau nhãn lệnh TT đủ 5
lần
;-----------------------------;
Mov Bx, Cx
Kết thúc đoạn lệnh trên Bx = Cx = 0 (khi Cx = 0 thì vòng lặpLoop TT kết
thúc) và Dx = 21 (Lần 1: Dx = Dx + Cx = 6 + 5;Lần 2: Dx = Dx + Cx = 11
+ 4; Lần 3: Dx = Dx + Cx = 15 + 3; Lần 4: Dx = Dx + Cx = 18 + 2;Lần 5:
Dx = Dx + Cx = 20 + 1 = 21).
Ví dụ 4: Các lệnh sau đây thực hiện phép gán:
Ax = 2 + 4 + ...+ 100
Mov Ax, 0
Mov Bx, 2
Mov Cx, 50
Lap_TT:
Add Ax, Bx
Add Bx, 2
Loop Lap_TT
Ví dụ 5: Giả sử tại địa chỉ offset 100 trong đoạn nhớ Data (được chỉ bởi
thanh ghi đọan DS) có chứa một mảng dữ liệu, gồm 100 ô nhớ, mỗi ô là một
byte. Hãy cộng thêm 50 đơn vị vào tất cả các ô nhớ trong mảng này.
Mov DI, 0100 ; trỏ cặp thanh ghi DS:DI về
; vùng nhớ cần truy xuất
(DS:0100)
;------------------------
Mov Cx, 100 ; lặp 100 lần vì mảng gồm 100 ô
nhớ
Lap_TangThem:
Mov Dl, DS:[DI] ; lấy nôi dung ô nhớ chỉ bởi
DS:DI lưu vào DL
Add Dl, 50 ; cộng thêm 50 vào Dl
Mov DS:[DI], Dl ; đặt giá trị đã tăng thêm vào
lại ô nhớ DS:DI
Inc DI ; chỉ đến ô nhớ kế tiếp (vì ô nhớ
byte nên tăng 1)
Loop Lap_TangThem ; lặp lại đủ 100 lần (duyệt qua đủ
100 ô nhớ)
Trong trường hợp này ta có thể sử dụng lệnh Add DS:[DI], 50 để tăng
trực tiếp nội dung của ô nhớ, hợp ngữ cho phép điều này. Nhưng cách đã
viết thường được áp dụng hơn, vì tính tổng quát của nó. Nói chung, chúng ta
nên hạn chế tác động trực tiếp lên nôi dung của ô nhớ.
Ví dụ 6: Giả sử tại địa chỉ 0100:0C00 trong bộ nhớ có chứa một xâu kí tự
gồm 50 kí tự (tức là, gồm 50 ô nhớ, mỗi ô 1 byte). Hãy copy xâu kí tự này
sang vùng nhớ bắt đầu tại địa chỉ 0200:0100.
Mov Ax, 0100
Mov DS, Ax ; trỏ cặp thanh ghi DS:SI về
Mov SI, 0C00 ; đầu vùng nhớ chưa xâu cần
copy (0100:0C00)
Mov Ax, 0200
Mov ES, Ax ; trỏ cặp thanh ghi ES:DI về
Mov DI, 0100 ; vùng nhớ chứa xâu kết quả
copy 0200:0100
;------------------------
Mov Cx, 50 ; lặp 50 lần vì xâu gồm 50 kí tự
Lap_Copy:
Mov Bl, DS:[SI] ; mượn Bl để chuyển tường kí tự
từ ô nhớ được
Mov ES:[DI], Bl ; chỉ bởi DS:SI sang ô nhớ được
chỉ bởi ES:DI
Inc SI ; chuyển đến kí tự tiếp theo
Inc DI
Loop Lap_Copy ; lặp lại đủ 50 lần (để copy đủ
50 kí tự)
Trong ví dụ này, vùng nhớ chứa xâu kí tự cần copy (vùng nhớ nguồn) và
vùng nhớ chứa kết quả copy (vùng nhớ đích) nằm ở hai đoạn (segment) nhớ
khác nhau, do đó, ta phải sử dụng hai thanh ghi đoạn dữ liệu khác nhau: DS
và ES.
Qua 2 ví dụ 5 và 6 ta có thể rút ra nguyên tắc cơ bản để truy xuất dữ liệu trên
một vùng nhớ/mảng nhớ:
Chú ý: Lệnh Loop thực hiện vòng lặp cho đến khi Cx = 0, vì thế nó được
xem như lệnh lặp không điều kiện. Thực tế, hợp ngữ còn có các lệnh lặp có
điều kiện, nó cho phép kết thúc vòng lặp trước khi Cx = 0 dựa vào một điều
kiện nào đó. Cụ thể: lệnh LoopE (Loop while Equal): Chỉ lặp lại khi Cx <>
0 và cờ ZF = 1; lệnh LoopZ (Loop while Zero): tương tự LoopE; LoopNE
(Loop while Not Equal): Chỉ lặp lại khi Cx <> 0 và cờ ZF = 0;... [2 - 154].
4. Lênh ̣ LEA (LoadEffectiveAddress)
Cú pháp:
LEA [Toán hạng đích],[Toán hạng nguồn]
Trong đó: [Toán hạng đích]: Là các thanh ghi 16 bít. [Toán hạng nguồn]:
Là địa chỉ của mô ̣t vùng nhớ hay tên của mô ̣t biến.
Tác dụng: Lệnh LEA có tác dụng chuyển địa chỉ offset của [Toán hạng
nguồn] vào [Toán hạng đích]. Lê ̣nh này thường được sử dụng để lấy địa chỉ
offset của mô ̣t biến đã được khai báo trong chương trình. Thanh ghi được sử
dụng trong trường hợp này là thanh ghi cơ sở (BX) và thanh ghi chỉ mục (SI
và DI).
Ví dụ 1:
Lea Bx, DS:[0100] ; chuyển thành phần địa chỉ offset
(0100) vào Bx
Lea DI, XauKT ; chuyển địa chỉ offset của biến
XauKT vào DI
; thao tác này thường được gọi
là trỏ DI và đầu
; biến XauKT
Khi chương trình được nạp vào bô ̣ nhớ để hoạt đô ̣ng thì các biến được khai
báo trong chương trình sẽ được định vị (cấp phát vùng nhớ) tại mô ̣t địa chỉ
xác định trong vùng nhớ Data. Từ đây, để thao tác đến dữ liê ̣u trên các biến
của chương trình thì chương trình phải xác định được địa chỉ segment vào
offset (hai thành phần của địa chỉ logic) của biến. Lê ̣nh LEA ở trên chỉ lấy
được địa chỉ offset của biến, để lấy được địa chỉ segment của nó ta có thể sử
dụng lê ̣nh Mov với toán tử Seg (tương tự có thể sử dụng lê ̣nh Mov với toán
tử Offset để lấy địa chỉ offset của biến). Ví dụ: Các lê ̣nh sau lấy địa chỉ
Segment:Offset của biến XauKT (hay trỏ DS:SI về đầu biến XauKT):
Mov Ax, Seg XauKT ; đưa địa chỉ Segment của biến
XauKT
Mov DS, Ax ; vào thanh ghi DS
Mov SI, Offset XauKT ; đưa địa chỉ Offset của biến
XauKT vào SI
Ví dụ 2: Giả sử biến TenGom (là biến kiểu byte) đã được khai báo như sau:
TenGom DB ‘Nguyen Kim Le Tuan’
Xem các lê ̣nh sau đây (1):
Mov Bx, 0
Mov Al, TenGom[Bx] ; Al =‘N’
Add Bx, 7 ;
Mov Bl, TenGom[Bx] ; Bl =‘K’
Xem các lê ̣nh sau đây (2):
Lea DI, TenGom
Mov Al, [DI] ; Al =‘N’
Mov Bl, [DI + 7] ; Bl =‘K’
Ta có thể thấy, nhóm các lê ̣nh (1) và nhóm các lê ̣nh (2) là tương đương nhau
về tác dụng của nó, nhưng (1): sử dụng trực tiếp tên biến để truy xuất đến
các phần tử của nó; (2): sử dụng thanh ghi chỉ mục DI để truy xuất đến các
phần tử của biến. Trong trường hợp này địa chỉ segment mă ̣c định được chỉ
bởi DS, điều này phù hợp với viê ̣c sử dụng địa chỉ gián tiếp thanh ghi chỉ
mục.
Ví dụ 3: Giả sử tại địa chỉ 0100:0C00 trong bô ̣ nhớ có chứa mô ̣t xâu kí tự
gồm 50 kí tự (tức là, gồm 50 ô nhớ, mỗi ô 1 byte). Hãy copy xâu kí tự này
vào mô ̣t biến trong chương trình.
Với yêu cầu này chương trình phải khai báo mô ̣t biến byte có đô ̣ lớn 50 byte:
LuuTru DB 50 Dup (‘ ‘)
Mov Ax, 0100
Mov DS, Ax ; trỏ că ̣p thanh ghi DS:SI về
Mov SI, 0C00 ; đầu vùng nhớ chưa xâu cần
copy (0100:0C00)
;--------------------------------
Mov Ax, Seg LuuTru ; trỏ că ̣p thanh ghi ES:DI về
Mov ES, Ax ; đầu biến LuuTru
Lea DI, LuuTru
;--------------------------------
Mov Cx, 50
Lap_Copy:
Mov Bh, DS:[SI] ; mượn Bh để chuyển tường kí
tự từ ô nhớ được
Mov ES:[DI], Bh ; chỉ bởi DS:SI sang ô nhớ
được chỉ bởi ES:DI
Inc SI ; chuyển đến kí tự tiếp theo
Inc DI
Loop Lap_Copy ; lă ̣p lại đủ 50 lần (để copy đủ
50 kí tự)
Chú ý: Hợp ngữ còn cung cấp các lê ̣nh LDS (Load Pointer use DS) để
lấy nô ̣i dungtoán hạng bô ̣ nhớ 32 bít đưa vào các thanh ghi 16 bít (mă ̣c định
16 bít cao vào thanh ghi đoạn dữ liê ̣u DS); và lê ̣nh LES (Load Pointer use
DS) tương tự LDS nhưng mă ̣c định 16 bít cao vào thanh ghi đoạn dữ liê ̣u
(thứ hai) ES [2 - 137].
5. Lênh
̣ Mul và Div
Cú pháp:
Trong đó: [Toán hạng nguồn]có thể là thanh ghi hay ô nhớ. Với các lê ̣nh nhân:
[Toán hạng đích] ngầm định là thanh ghi Al hoă ̣c Ax. Với các lê ̣nh chia: [Toán
hạng đích] là mô ̣t trong các thanh ghi đa năng Ax, Bx,...
Tác dụng:
- Lê ̣nh Mul (Multiply): Thực hiê ̣n phép nhân trên số không dấu. Nếu [Toán
hạng nguồn] là toán hạng 8 bít thì lê ̣nh sẽ nhân nô ̣i dung của [Toán hạng nguồn]
với giá trị thanh ghi AL, kết quả 16 bít chứa ở thanh ghi Ax.
Nếu [Toán hạng nguồn] là toán hạng 16 bít thì lê ̣nh sẽ nhân nô ̣i dung của [Toán
hạng nguồn] với giá trị thanh ghi Ax, kết quả 32 bít chứa ở că ̣p thanh ghi Dx:Ax,
phần thấp ở Ax, phần cao ở Dx. Nếu phần cao của kết quả (AH hoă ̣c DX) bằng 0
thì các cờ CF = 0 và OF = 0.
- Lê ̣nh IMul (Interger Multiply): Tương tự lê ̣nh Mul nhưng thực hiê ̣n phép nhân
trên hai số có dấu. Kết quả cũng là mô ̣t số có dấu.
- Lê ̣nh Div (Divide): Thực hiê ̣n phép chia trên số không dấu. Nếu [Toán hạng
nguồn] là toán hạng 8 bít thì lê ̣nh sẽ lấy giá trị của thanh ghi Ax (số bị chia) chia
cho [Toán hạng nguồn] (số chia), kết quả thương số chứa trong thanh ghi Al, số dư
chứa trong thanh ghi Ah.
Nếu [Toán hạng nguồn] là toán hạng 16 bít thì lê ̣nh sẽ lấy giá trị của că ̣p thanh ghi
Dx:Ax (số bị chia) chia cho [Toán hạng nguồn] (số chia), kết quả thương số chứa
trong thanh ghi Ax, số dư chứa trong thanh ghi Dx.
Nếu phép chia cho 0 xảy ra hay thương số vượt quá giới hạn của thanh ghi AL
(chia 8 bít) hay Ax (chia 16 bít) thì CPU sẽ phát sinh lỗi “Divice overflow”.
- Lê ̣nh Idiv (Integer Divide): Tương tự lê ̣nh Div nhưng thực hiê ̣n phép chia trên
hai số có dấu. Kết quả cùng là các số có dấu.
Ví dụ 1:
- Mul Bl ; Ax ßAL * Bl: số bị nhân ngầm định trong
Al
- Mul Bx ; Dx:Ax ßAx * Bx: số bị nhân ngầm định trong
Ax
- Idiv Bl ; Ax/Bl, thương số chứa trong Al, số dư chứa
trong Ah
- Idiv Bx ; Dx:Ax/Bx, thương số chứa trong Ax, số dư
trong Dx
Ví dụ 2: Dãy các lê ̣nh dưới đây sẽ thực hiê ̣n phép gán A = 4*A – 3*B, trong đó A
và B là các biến kiểu word:
Mov Ax, 4 ; số nhân phải được chứa trong Ax
IMul A ; thực hiê ̣n phép nhân
Mov Bx, Ax ; lưu tạm kết quả vào Bx
Mov Ax, 3 ; Ax = 3
Imul B ; Ax = Ax * B
Sub Bx, Ax
Mov A, Bx ; đă ̣t kết quả cuối cùng vào A
Trong trường hợp này ta đã giả sử hiê ̣n tượng tràn đã không xảy ra và kết quả phép
nhân chỉ chứa trong thanh ghi Ax.
Ví dụ 3: Các lê ̣nh sau đây thực hiê ̣n phép: chia -123 cho 24:
Mov Ax, -123 ; đă ̣t số bị chia vào Ax
Mov Bl, 24 ; đă ̣t số chia vào Bl (thanh ghi 8 bít)
Idiv Bl ; chia Ax cho Bl, kết quả chứa ở Al và Ah
Ví dụ 4: Dãy lê ̣nh dưới đây sẽ thực hiê ̣n phép gán A = N! (tính N giai thừa). A là
mô ̣t biến word:
Mov Ax, 1 ; chuẩn bị Ax để lưu kết quả
Mov Cx, N ; tính N!
LapNhan:
Mul Cx ; Ax ßAx * Cx
Loop LapNhan
;---------------------------
Mov A, Ax ; trả kết quả vào biến A
Trong trường hợp này chúng ta giả sử kết quả không vượt quá gới hạn 16 bít.
Chúng ta đã biết: N! = 1 nếu N = 1, N! = N*(N-1)*(N-2)*...*1 nếu N>1, điều này
hoàn toàn phù hợp với sự thay đổi của thanh ghi CX trong lê ̣nh lă ̣p Loop. Do đó, ở
đây ta có thể sử dụng CX như là N trong biểu thức tính giai thừa.
Chú ý: Hợp ngữ cung cấp lê ̣nh AAM (Ascii Adjust for Multiple) để điều chỉnh kết
quả phép nhân trên 2 số BCD dạng không dồn. Và lê ̣nh AAD (Ascii Adjust for
Division) để điều chỉnh kết quả phép chia trên 2 số BCD dạng không dồn. Ngoài ra
còn có lê ̣nh CBW (Convert Byte to Word) và lê ̣nh CWD (Convert Word to
Doubleword) để hỗ trợ cho phép chia trên các số có dấu [2 – 187-200].
Lê ̣nh IMul của vi xử lý Intel 80286 cho phép ghi rõ [Toán hạng đích], [Toán hạng
nguồn] trong câu lê ̣nh, các lê ̣nh này có thể có đến 3 toán hạng [1 - 541].
6. Lênh
̣ logic: NOT – AND – OR – XOR – TEST
Trước khi tìm hiểu về các lê ̣nh logic chúng ta xem lại kết quả thực hiê ̣n các phép
tính logic trên 2 bít nhị phân A và B thông qua bảng sau đây:
0 0 0 0 0 1
0 1 0 1 1 1
1 0 0 1 1 0
1 1 1 1 0 0
Bảng trên cho thấy: Với phép And: kết quả = 1 chỉ khi cả hai bít = 1; Với phép Or:
kết quả = 0 chỉ khi cả hai bít = 0; Với phép Xor: kết quả = 0 khi hai bít giống nhau,
kết quả = 1 khi hai bít khác nhau. Với phép Not: 0 thành 1, 1 thành 0.
Cú pháp:
Trong đó: [Toán hạng đích], [Toán hạng nguồn] có thể là hằng số (trực hằng),
biến, thanh ghi hay địa chỉ ô nhớ. [Toán hạng đích] không thể là hằng số.
Tác dụng: Mỗi lê ̣nh logic thực hiê ̣n phép tính logic tương ứng trên các bít (tương
ứng về vị trí) của [Toán hạng đích] và [Toán hạng nguồn], kết quả được ghi vào lại
[Toán hạng đích]. Riêng lê ̣nh Not, thực hiê ̣n phép đảo bít ngay trên các bít của
[Toán hạng đích]. Hầu hết các lê ̣nh logic đều ảnh hưởng đến các cờ CF, OF, ZF,...
- Lê ̣nh Not (Logical Not): Thực hiê ̣n viê ̣c đảo ngược từng bít trong nô ̣i dung của
[Toán hạng đích]. Lê ̣nh này không làm ảnh hưởng đến các cờ.
Lê ̣nh Not thường được sử dụng để tạo dạng bù 1 của [Toán hạng đích].
- Lê ̣nh And (Logical And): Thực hiê ̣n phép tính logic And trên từng că ̣p bít
(tương ứng về vị trí) của [Toán hạng nguồn] với [Toán hạng đích], kết quả lưu vào
[Toán hạng đích].
Lê ̣nh And thường được sử dụng để xóa (= 0) mô ̣t hoă ̣c nhiều bít xác định nào đó
trong mô ̣t thanh ghi.
- Lê ̣nh Or (Logical Inclusive Or):Thực hiê ̣n phép tính logic Or trên từng că ̣p bít
(tương ứng về vị trí) của [Toán hạng nguồn] với [Toán hạng đích], kết quả lưu vào
[Toán hạng đích].
Lê ̣nh Or thường dùng để thiết lâ ̣p (= 1) mô ̣t hoă ̣c nhiều bít xác định nào đó trong
mô ̣t thanh ghi.
- Lê ̣nh Xor (eXclusive OR):Thực hiê ̣n phép tính logic Xor trên từng că ̣p bít
(tương ứng về vị trí) của [Toán hạng nguồn] với [Toán hạng đích], kết quả lưu vào
[Toán hạng đích].
Lê ̣nh Xor thường dùng để so sánh (bằng nhau hay khác nhau) giá trị của hai toán
hạng, nó cũng giúp phát hiê ̣n ra các bít khác nhau giữa hai toán hạng này.
- Lê ̣nh Test: Tương tự như lê ̣nh And nhưng không ghi kết quả vào lại [Toán
hạng đích], nó chỉ ảnh hưởng đến các cờ CF, OF, ZF,...
Ví dụ 1:
Mov Al,0 ; Al ß0
Not Al ; Al = Not Al. Tức là Al = 0FFh
Ví dụ 2: Cho AL = (10010011)2, BL = (11001100)2.
- And Al, Bl ; Al ß 10010011 And 11001100. Al =
- And Al, 0 ; Al ß 10010011 And 0. Al =
- Or Bl, Al ; Bl ß 11001100 Or 10010011. Al =
- Or Bl, 4 ; Bl ß 11001100 Or 100. Al =
- Xor Al, Bl ; Al ß 10010011 Xor 11001100. Al =
- Xor Bl, Bl ; Bl ß 11001100 Xor 11001100. Bl = 00000000
Ví dụ 3: Để xóa nô ̣i dung thanh ghi nào đó, trong hợp ngữ ta có thể sử dụng mô ̣t
trong các lê ̣nh sau đây:
- Mov Ax, 0
- Sub Ax, Ax
- Xor Ax, Ax ; các că ̣p bít giống nhau thì đều = 0
Ví dụ 5: Lê ̣nh sau đây sẽ xóa (= 0) các bít 3 và 6 của thanh ghi AL, các bít khác
giữ nguyên giá trị:
- And AL, 10110111b ; AL ßAL And 10110111
Trong trường hợp này: dãy bít 10110111 được gọi là dãy bít mă ̣t nạ, các bít 3 (=
0) và 6 (= 0) được gọi là các bít mă ̣t nạ. Như vâ ̣y muốn làm cho bít nào = 0 ta cho
bít mă ̣t nạ tương ứng với nó = 0, các bít còn lại trong dãy bít mă ̣t nạ đều = 1.
Ví dụ 6: Lệnh sau đây sẽ thiết lâ ̣p (= 1) các bít 3 và 6 của thanh ghi AL, các bít
khác giữ nguyên giá trị:
- Or AL, 01001000b ; AL ßAL Or 01001000
Trong trường hợp này: dãy bít 01001000 được gọi là dãy bít mă ̣t nạ, các bít 3 (= 1)
và 6 (= 1) được gọi là các bít mă ̣t nạ. Như vâ ̣y muốn làm cho bít nào = 1 ta cho bít
mă ̣t nạ tương ứng với nó = 1, các bít còn lại trong dãy bít mă ̣t nạ đều = 0.
Ví dụ 7: Lê ̣nh sau đây sẽ kiểm tra bít 12 của thanh ghi AX là = 0 hay = 1:
- And AX, 0001000000000000b ; AX ßAX And
0001000000000000
Với dãy bít mă ̣t nạ như trên, nếu bít 12 của Ax = 0 thì kết quả: Ax = 0, nếu bít 12
của Ax = 1 thì kết quả: Ax <> 0.
Cách dùng lê ̣nh And như trên để kiểm tra bít ít được sử dụng, vì nó làm thay đổi
giá trị của thanh ghi cần kiểm tra (điều này có thể khắc phục bằng lệnh Test) và
phải thêm bước kiểm tra giá trị của Ax (= 0 hay <> 0) mới biết được kết quả kiểm
tra. Ngoài ra, nó cũng chỉ kiểm tra được 1 bít.
Trong thực tế người ta thường sử dụng kết hợp giữa các lê ̣nh dịch bít, lê ̣nh quay
bít, lê ̣nh nhảy,... để kiểm tra các bít trong mô ̣t thanh ghi.
7. Lênh
̣ chuyển dữ liêụ qua cổng: IN và OUT
Cú pháp:
Trong đó:<Địa chỉ cổng> chính là số hiệu cổng (port) mà lệnh nhận nhiệm vụ trao
đổi dữ liệu qua nó. Địa chỉ cổng có thể được ghi trực tiếp dưới dạng một hằng số
hoặc được ghi thông qua thanh ghi Dx.
Tác dụng:
- LênhIn (Input): Đọc một lượng dữ liệu 8 bít từ cổng được chỉ ra ở <Địa chỉ
cổng> đưa vào lưu trữ trong thanh ghi AL.
Nếu địa chỉ cổng nằm trong giới hạn từ 0 đến FF (hệ thập lục phân) thì có thể viết
trực tiếp trong câu lệnh, nếu địa chỉ cổng lớn hơn FF thì ta phải dùng thanh ghi Dx
để chỉ định địa chỉ cổng.
- LệnhOut (Output): Gởi một lượng dữ liệu 8 bít từ thanh ghi AL ra cổng được
chỉ ra ở <Địa chỉ cổng>. Tương tự lệnh In, địa chỉ cổng có thể được viết trực tiếp
trong câu lệnh hoặc thông qua thanh ghi Dx.
Ví dụ 1:
- In Al, 40h ;
- Mov Dx, 3B0h ;
In Al, Dx ;
Ví dụ 2:
- Out 40h, Al ;
- Mov Dx, 3B0h ;
Out Dx, Al ;
Ví dụ 3:
Các khai báo hằng:
DAT EQU 13h ;
POR EQU 7Ch ;
Các lệnh:
Mov Al, POR ;
Mov Bl, DAT ;
Out Bl, Al ;
Mô ̣t số ví dụ minh họa:
Ví dụ 1: Giả sử tại địa chỉ 0100:0120 trong bô ̣ nhớ có chứa mô ̣t mảng dữ liê ̣u, gồm
100 ô nhớ, mỗi ô là 1 word. Hãy tính tổng nô ̣i dung của các ô nhớ này, kết quả
chứa trong thanh ghi Dx.
Mov Ax, 0100
Mov DS, Ax ; trỏ că ̣p thanh ghi DS:DI về
Mov DI, 0120 ; đầu vùng nhớ cần truy xuất
(0100:0120)
;------------------------
Mov Dx, 0 ; chuẩn bị Dx để lưu tổng
Mov Cx, 100 ; lă ̣p 100 lần vì mảng gồm 100 ô nhớ
Lap_TT:
Add Dx, DS:[DI] ; cô ̣ng thêm n/dung ô nhớ chỉ
bởi DS:DI vào Dx
Add DI, 2 ; chỉ đến ô nhớ kế tiếp (vì ô nhớ
word nên tăng 2)
Loop Lap_TT ; lă ̣p lại đủ 100 lần (duyê ̣t qua đủ 100
ô nhớ)
;-----------------------------------
Kết thúc đoạn lê ̣nh trên tổng nô ̣i dung của 100 word nhớ bắt đầu tại địa chỉ
0100:0120 trong bô ̣ nhớ được lưu vào thanh ghi Dx. Ở đây chúng ta bỏ qua khả
năng tràn dữ liê ̣u trong thanh ghi kết quả Dx.
Ví dụ 2: Các lệnh sau đây sẽ copy 100 word dữ liệu từ vùng nhớ bắt đầu tại địa chỉ
0100:0120 sang vùng nhớ bắt đầu tại 0100:0500:
Mov Ax, 0100
Mov DS, Ax
Mov SI, 0120 ; trỏ DS:SI về vùng nhớ nguồn
0100:0120
Mov DI, 0500 ; trỏ DS:DI về vùng nhớ địch
0100:0500
;--------------------
Mov Cx, 100
Lap_Copy:
Mov Ax, DS:[SI] ; lưu tạm nội dụng word nhớ tại
DS:SI vào Ax
Mov DS:[DI], Ax ; ghi giá trị Ax và word nhớ tại
DS:DI
Add SI, 2 ; đến word nhớ tiếp theo
Add DI, 2
Loop Lap_Copy
;-------------------------
Hai vùng bộ nhớ đã cho đều ở trong cùng một segment nên ở đây ta chỉ cần sử
dụng một thanh ghi đoạn dữ liệu DS.
Ví dụ 3: Các lệnh sau đây sẽ tính tổng nội dung của 100 ô nhớ (100 byte nhớ)
trong bộ nhớ, bắt đầu tại địa chỉ 0A00:0120. Kết quả được lưu vào word nhớ ngay
trước vùng nhớ này:
Mov Ax, 0A00h
Mov ES, Ax
Mov SI, 0120h ; trỏ DS:SI về vùng nhớ nguồn
0A00:0120
Mov DI, SI ; trỏ DS:DI về vùng nhớ nguồn
0A00:0120
Sub DI,2 ; trỏ DS:DI về word nhớ trước vùng
nhớ nguồn
;------------------------
Mov Cx, 100
Mov Dx, 0 ; DX chứa tổng
TTong:
Add Dx, Byte PTR ES:[SI] ; cộng n.d của byte nhớ tại
ES:SI vàoDX
Inc SI ; ô nhớ byte
Loop TTong
;----------------------------------------
Mov Word PTR ES:[DI], DX
;----------------------------------------
Trong đoạn lệnh trên chúng ta sử dụng toán tử PTR để định kiểu ô nhớ cần truy
xuất. Lệnh Add Dx, Byte PTR ES:[SI]: lấy nội dung của byte nhớ tại ô nhớ
được chỉ bởi ES:SI cộng thêm vào thanh ghi DX. Nếu ta viết Add Dx, ES:
[SI] thì hệ thống lấy giá trị cả 1 word tại ES:SI cộng thêm vào DX (vì DX là thanh
ghi word (16 bít), điều này không đúng với thực tế, vì chúng ta đang cần truy xuất
đến các byte nhớ. Trong trường hợp này, nếu không dùng toán tử PTR thì lệnh
Add phải viết như sau: Add DL, ES:[SI], khi đó hệ thống chỉ lấy giá trị cả 1
byte tại ES:SI cộng thêm vào DL (vì DL là thanh ghi byte: 8 bít),
Ví dụ 4: Các lệnh sau đây sẽ copy toàn bộ 20 kí tự Ascii từ biến Xau1 vào biến
Xau2. Giả sử Xau1 và Xau2 đã được khai báo trước như sau:
Xau1 DB ‘Khoa CNTT – DHKH Hue’
Xau2 DB 20 Dup (‘ ’)
Các lệnh:
Mov Ax, @Data
Mov DS, Ax
Lea SI, Xau1
Lea DI, Xau2
;------------------------
Mov Cx, 20
Lap_Copy:
Mov Al, Byte PTR DS:[SI]
Mov Byte PTR DS:[DI], Al
Inc SI
Dec DI
Loop Lap_Copy
;-------------------------------------
Các biến của chương trình hợp ngữ được khai báo trong đoạn Data. Hai lệnh đầu
tiên ở trên có tác dụng lấy địa chỉ segment của đoạn Data đưa vào thanh ghi đoạn
DS. Do đó, DS sẽ chứa địa chỉ segment của hai biến Xau1 và Xau2. Hay cụ thể:
DS:SI trỏ về biến Xau1 và DS:DI trỏ về Xau2.
Ví dụ 5: Chương trình sau đây: Để in một xâu kí tự từ trong chương trình ra màn
hình văn bản đơn sắc (môi trường MSDOS).
Chú ý:
- BIOS và MSDOS đã cung cấp nhiều hàm/ngắt để chương trình hợp ngữ ghi
một kí tự hoặc một xâu kí tự ra màn hình, tại vị trí hiện tại của con trỏ màn hình
hoặc tại một tọa độ màn hình xác định nào đó. Kỹ thuật này được gọi là kỹ thuật
ghi ra màn hình gián tiếp thông qua các hàm/ngắt màn hình.
- Biết rằng, tất cả những thông tin mà chúng ta thấy được trên màn hình của một
máy tính đều được lưu trữ trong bộ nhớ màn hình của máy tính đó, theo một cách
nào đó. Tức là, nọi thao tác ghi/đọc trên màn hình đều phải thông qua bộ nhớ màn
hình. Như vậy: Muốn ghi một xâu kí tự ra màn hình chương trình có thể ghi nó vào
bộ nhớ màn hình. Muốn đọc một xâu kí tự từ màn hình chương trình có thể đọc nó
từ bộ nhớ màn hình. Kỹ thuật này được gọi là kỹ thuật truy xuất trực tiếp bộ nhớ
màn hình.
- Mỗi hệ điều hành, mỗi chế độ màn hình sử dụng một đoạn bộ nhớ xác định
(thường là khác nhau) cho bộ nhớ màn hình. Và cách tổ chức lưu trữ thông tin trên
màn hình trong bộ nhớ màn hình cũng khác nhau với các hệ điều hành, các chế độ
màn hình khác nhau.
- Trên môi trường hệ điều hành MSDOS, bộ nhớ màn hình của chế độ nàm hình
đơn sắc 25 dòng (0 đến 24) x 80 cột (0 đếm 79) được lưu trữ tại segment nhớ
0B800, bắt đầu tại offset 0000.
- Tổ chức lưu trữ thông tin trên màn hình trong bộ nhớ màn hình loại này như
sau: Mỗi kí tự trên nàm hình chiếm 1 word trong bộ nhớ màn hình: byte thấp chứa
mã ASCII của kí tự, byte cao chứa thuộc tính (màu chữ, màu nền,...) của kí tự đó.
Từng dòng kí tự trên màn hình, từ trái sang phải (từ cột 0 đến cột 79), được lưu tữ
lần lượt tại các offset liên tiếp nhau trong bộ nhớ màn hình: 80 kí tự của dòng 0
được lưu trữ tại 80 word đầu tiên, 80 kí tự của dòng 1được lưu trữ tại 80 word tiếp
theo,... , 80 kí tự của dòng 79ược lưu trữ tại 80 word cuối cùng trong bộ nhớ nàm
hình. Như vậy ta dễ dàng tính ra được offset trong bộ nhớ màn hình, tương ứng với
một kí tự trên màn hình khi ta biết được tọa độ (dòng, cột) của nó. Cụ thể, offset
của kí tự tạo tọa độ (Dòng, Cột) được tính theo công thức sau:
(80*(Dong - 1) + (Cot - 1))*2
- Bộ nhớ màn hình loại này có kích thước là 25 x 80 x 2 (byte) = 40000 byte.
Bạn đọc tự lập trình thử nhé.
Bài 5:
NGẮT TRONG CHƯƠNG TRÌNH ASSEMLBLY
Ngắt (Interrupt) là các tín hiệu mà các thành phần trong hệ thống, như: thiết bị
ngoại vi, hệ điều hành, chương trình người sử dụng, ..., gửi đến vi xử lý (họ Intel)
mỗi khi nó cần trao đổi thông tin với vi xử lý hay cần được sự phục vụ từ vi xử lý.
Ngắt cũng có thể phát sinh từ chính bên trong vi xử lý khi nó phát hiện một lỗi
nghiêm trong xảy ra trong quá trình xử lý của nó. Khi nhận được một tín hiệu yêu
cầu ngắt vi xử lý sẽ dừng ngay thao tác (lệnh) hiện tại để xem xét và đáp ứng yêu
cầu ngắt đó, sau đo mới tiếp tục lại từ thao tác (lệnh) mà nó bị dừng trước đó.
Các máy IBM_PC, sử dụng vi xử lý Intel, bao gồm nhiều loại ngắt: Ngắt phần
cứng, ngắt phần mềm, ngắt bên trong, ngắt không chắn được (ngắt có độ ưu tiên
cao nhất). Trong đó ngắt phần mềm là các ngắt được phát sinh từ hệ điều hành và
chương trình của người sử dụng, điều này thường thấy trong các chương trình hợp
ngữ.
Hệ thống các ngắt mềm bên trong máy tính IBM_PC được cung cấp bởi BIOS và
hệ điều hành, gồm 256 ngắt, chúng được đánh số từ 0 đến 255 và được gọi là số
hiêu ngắt. Mỗi ngắt mềm tương ứng với một chương con được gọi là chương trình
con phục vụ ngắt, tức là, để đáp ứng một yêu cầu ngắt mềm thì hệ thống sẽ phải
thực hiện một chương trình con phục vụ ngắt tương ứng.
Hợp ngữ cung cấp lệnh Int để các chương trình gọi một ngắt mềm khi cần. Khi gọi
ngắt mềm chương trình phải chỉ ra số hiệu ngắt cần gọi, khi đó hệ thống sẽ phải
gọi thực hiện chương trình con phục vụ ngắt tương ứng để đáp ứng yêu cầu gọi
này.
Trong môi trường hệ điều hành 16 bít MSDOS, ngay sau khi khởi động, hệ điều
hành nạp tất cả 256 chương trình con phục vụ ngắt vào bộ nhớ. Khi đó 256 địa chỉ
(Segment:Offset) các vùng nhớ, nơi định vị của 256 chương trình con phục vụ
ngắt, sẽ được lưu trữ tại một không gian nhớ đặc biệt trong bộ nhớ, vùng nhớ này
được gọi là bảng vector ngắt (mỗi địa chỉ định vị của một chương trình con phục
vụ ngắt được gọi là một vector ngắt.
Như vậy khi hệ thống nhận được một yêu cầu ngắt n (Int n) từ chương trình thì nó
phải: 1. Dừng lệnh hiện tại, đưa giá trị con trỏ lệnh CS:IP hiện tại (đó chính là địa
chỉ của lệnh sau lệnh gọi ngắt) vào lưu trữ ở hai phần tử trên đỉnh Stack; 2. Xác
định phần tử (địa chỉ) trong bảng vector ngắt chứa vector ngắt của ngắt n, lấy giá
trị tại đây (2 phần tử liên tiếp) để đưa vào cặp thanh ghi con trỏ lệnh CS:IP (đây
chính là địa chỉ của lệnh đầu tiên của chương trình con phục vụ ngắt n trong bộ
nhớ); 3. Bắt đầu thực hiện chương trình con phục vụ ngắt cho đến khi gặp lệnh kết
thúc chương trình này, đó là lệnh Iret; 4. Lấy nội dung của hai phần tử trên đỉnh
Stack đặt vào lại cặp thanh ghi con trỏ lệnh để quay trở lại tiếp tục thực hiện lệnh
sau lệnh gọi ngắt trong chương trình.
Những vấn đề liên quan đến Stack và cơ chế của một lời gọi ngắt sẽ được chúng
tôi làm rõ hơn ở phần sau của tài liệu này.
Lê ̣nh Int
Cú pháp:
Int <n>
Trong đó: Trong đó <n> là số hiệu ngắt của một ngắt mềm cần gọi. Tức là, n có thể
là một trong các giá trị từ 0 đến 255, đó chính là số hiệu ngắt của 256 ngắt mềm
mà BIOS và MSDOS cung cấp.
Tác dụng: Lệnh Int (Interrupt) được sử dụng để gọi một ngắt mềm (của BIOS hoặc
MSDOS) trong chương trình hợp ngữ. Khi một ngắt mềm được gọi thì hệ thống sẽ
thực hiện chương trình con phục vụ ngắt tương ứng với nó.
Ví dụ 1:
Ví dụ 2:
Mov Ah, 01 ; gọi ngắt 21h với hàm 01, Hay nói ngược lại:
gọi hàm
Trước khi gọi hàm/ngắt chương trình cần cung cấp đầy đủ dữ liệu vào (nếu có) cho
nó, sau khi hàm/ngắt được thực hiện chương trình cần xác định rõ nó có trả về dữ
liệu kết quả (dữ liệu ra) hay không, nếu có thì chứa ở đâu: thanh ghi hay ô nhớ, và
có tác động đến các cờ hiệu hay không.
Ví dụ 3:
Dãy lệnh trên thực hiện việc gọi hàm 02 của ngắt 10h (ngắt của BIOS), nó thực
hiện việc dịch chuyển con trỏ đến dòng 10 cột 20 của màn hình văn bản.
Ví dụ 4:
Hàm này trả về ngày-tháng-năm hiện tại (theo đồng hồ hệ thống trên máy tính). Cụ
thể: Thanh ghi CX (1980-2099) chứa năm hiện tại, thanh ghi DH (1-12) chứa
tháng hiện tại, thanh ghi DL (1-31) chứa ngày hiện tại. Đồng thời AL cho biết ngày
trong tuần (0 : chủ nhật, 6 : thứ 7).
Ngắt 21h của MSDOS là ngắt thường được sử dụng nhất, nên ở đây chúng tôi chọn
giới thiệu về ngắt này, nhưng chỉ với các hàm vào/ra kí tự/xâu kí tự cơ bản. Chức
năng của mỗi ngắt, chức năng của các hàm trong một ngắt, các yêu cầu dữ liệu
vào/ra của từng hàm trong mỗi ngắt,... dễ dàng tìm thấy trong các tài liệu về lập
trình hệ thống.
Sử dụng:
Vào: Ah = 02
Ra: Không có
Mov Ah, 02
Mov Dl, ‘A’ ;có thể viết lệnh Mov Dl, 41h
Mov Cx, 10
Mov Ah, 02
Int 21h
INC DL
Loop Lap_In
Ví dụ 3: Các lệnh sau đây in xâu kí tự từ trong biến TieuDe ra màn hình. Giả sử
rằng biến TieuDe đã được khai báo như sau:
Các lệnh:
Mov Cx, 25
Mov Ah, 02
Lap_In:
Int 21h
INC DI
Loop Lap_In
;---------------------------
Ví dụ 4: Giả sử tại địa chỉ 0A00:0100 trong bộ nhớ có chứa một xâu kí tự ASCII,
gồm 50 kí tự. Các lệnh sau đây sẽ in xâu kí tự nói trên ra màn hình.
Mov ES, Ax
;-------------------------
Mov Cx, 50
Mov Ah, 02
Lap_In:
Int 21h
INC DI
Loop Lap_In
;--------------------------------------
Tác dụng: In một dãy kí tự (một xâu kí tự) ra màn hình. Địa chỉ của xâu cần in này
phải được chỉ bởi cặp thanh ghi DS:DX và xâu phải được kết thúc bởi dấu $.
Sử dụng:
Vào: Ah = 09
Ra: Không có
Ví dụ 1: Giả sử chương trình đã khai báo biến TieuDe. Viết lệnh in nội dung của
biến TieuDe ra màn hình:
- Trong trường hợp này biến TieuDe phải được khai báo trước như sau:
Mov Ah, 09
Mov Ax, Seg TieuDe
Mov DS, Ax
Int 21h
Trong thự tế, với các chương trình hợp ngữ viết ở dạng đoạn giản đơn, thì không
cần đưa địa chỉ Segment của biến cần in vào DS. Bởi vì:
Các lệnh sau chỉ in các kí tự “Truong DHKH Hue” từ biến TieuDe ra màn hình:
Mov DS, Ax
Add Dx, 11
Mov Ah, 09
Int 21h
Các lệnh sau chỉ in các kí tự “Khoa CNTT” từ biến TieuDe ra màn hình:
Mov DS, Ax
Mov DI, Dx
Add DI, 10
Mov Ah, 09
Int 21h
Ví dụ 3: Giả sử tại địa chỉ 0A00:0100 trong bộ nhớ có chứa một xâu kí tự ASCII,
gồm 50 kí tự. Các lệnh sau đây sẽ in xâu kí tự nói trên ra màn hình.
;-------------------------
Mov DI, Dx
Mov Cx, 50
Add DI, Cx
;-------------------------------------
Mov Ah, 09
Int 21h
;-------------------
Tác dụng:Nhập một kí tự từ bàn phím vào lưu trong thanh ghi AL. Cụ thể là, AL sẽ
chứa mã ASCII của kí tự ghi trên phím nhập.
Sử dụng:
Vào: Ah = 01
Ra: Al = 0: nếu phím nhập là một trong các phím chức năng
Cụ thể như sau: Khi chương trình gọi ngắt 21h với hàm 01 thì màn hình sẽ xuất
hiện một con trỏ nhập, để người sử dụng nhập vào một kí tự từ bàn phím. Khi đó,
nếu người sử dụng gõ một trong các phím chức năng thì AL nhận được giá trị 0,
nếu người sử dụng gõ một phím kí tự nào đó thì AL nhận được mã ASCII của kí tự
đó.
Chú ý: Hàm 08 của ngắt 21h có chức năng tương tự hàm 01 ngắt 21h nhưng kí tự
trên phím gõ không xuất hiện trên màn hình, tất cả đều được xuất hiện bởi kí tự
‘*’.
Ví dụ 1:
Mov Ah, 01 ; với hai lệnh này màn hình sẽ xuất hiện con
trỏ
Ví dụ 2: Các lệnh sau đây cho phép nhập một xâu kí tự, với số lượng kí tự được ấn
định trước, vào biến LuuXau đã khai báo trước trong chương trình
Các lệnh:
Mov DS, Ax
;--------------------------------
Mov Cx, 30
Mov Ah, 01
Nhap_Xau:
Int 21h
INC DI
Loop Nhap_Xau
;--------------------------------------
Trong trường hợp này chúng ta đã giả sử: Người sử dụng chỉ nhập các kí tự (gõ
phím kí tự để nhập), không nhập các phím chức năng.
Trong thực tế, không bao giờ chúng ta sử dụng hàm 01 ngắt 21h để nhập xâu, vì nó
tồn tại hai hạn chế: không thể kết thúc nhập bằng cách gõ phím Enter; số kí tự của
xâu nhập phải được ấn định trước trong chương trình. Để khắc phục, MSDOS cung
cấp hàm 0Ah của ngắt 21h để hỗ trợ nhập xâu kí tự.
Tác dụng:Nhập một xâu kí tự vào một biến đệm cho trước trong chương trình, biến
này phải được chỉ bởi cặp thanh ghi DS:DX. Và biến đệm phải có dạng như sau:
- Byte 2, byte 3, byte 4, ... chứa một trị rỗng (để trống), để chứa các kí tự sẽ
được nhập vào sau này (khi chương trình thực hiện).
Để có được một biến như trên chương trình phải khai báo biến (tên biến là
Xau_Nhap) như sau:
Như vậy Xau_Nhap là một biến kiểu byte, gồm 258 byte. Byte đầu tiên (byte) chứa
trị 256, byte 1 chứa trị 0, 256 byte tiếp theo chứa kí tự trống, được sử dụng để chứa
các kí tự sẽ được nhập sau này. Xau_Nhap chứa tối đa 256 kí tự.
Cũng có thể sử dụng hàm 0Ah/21h để nhập một xâu kí tự vào vùng nhớ có địa chỉ
xác định trong bô nhớ.
Sử dụng:
Vào: Ah = 0Ah
Ví dụ 1: Với khai báo biến đệm Xau_Nhap như trên, nếu sau này chương trình
nhập vào xâu: “Tin hoc” thì:
- Từ byte 2 đến 8 chứa lần lượt các kí tự trong xâu “Tin hoc.
Ví dụ 2: Giả sử chương trình đã khai báo xâu TieuDe như sau:
Các lệnh sau đây sử dụng hàm 0Ah/21h để nhập một xâu kí tự vào biến
TieuDe:
Mov Ds, Ax
Int 21h
Các lệnh sau đây lấy số kí tự thực sự nhập đưa vào lưu trữ trong thanh ghi Cx:
Mov Cx, 0
Các lệnh sau đây sử dụng hàm 02/21h để in xâu kí tự vừa nhập ra lại màn hình:
Mov Cx, 0
Mov Cl, TieuDe[1]
Add DI, 2
Mov Ah, 02
Lap_In:
Int 21h
INC DI
Loop Lap_In
Các lệnh sau đây sử dụng hàm 09/21h để in xâu kí tự vừa nhập ra lại màn hình:
Mov Ds, Ax
Add Dx, 2
Mov DI, Dx
Mov Cx, 0
Add DI, Cx
Int 21h
Ví dụ 3: Chương trình dạng COM sau đây sử dụng hàm 0Ah ngắt 21h để nhập một
xâu kí tự vào biến Buff. Sau đó sử dụng hàm 09h ngắt 21h để in xâu kí tự vừa nhập
ra lại màn hình.
Để đơn giản, chương trình khai báo biến Buff gồm toàn kí tự ‘$’, nhờ đó, khi dùng
hàm 09h/21h để in ra chương trình không cần đưa thêm kí tự ‘$’ vào cuối xâu
nhập, mà chỉ cần trỏ DS:DX về đầu vùng/xâu kí tự cần in ra.
.Model small
.Code
ORG 100h
Start:
JMP Main
Main Proc
Int 21h
;--------------------------
Int 21h
;-------------------------
Mov Ah, 09h
Int 21h
;-------------------------
Add Dx, 2
Int 21h
;-------------------------
Int 20h
Main Endp
End Start
Một số ví dụ:
Ví dụ 1: Giả sử hàm X của ngắt Y khi được gọi sẽ trả về trong cặp thanh ghi ES:SI
địa chỉ của vùng nhớ chứa tên của nhà sản xuất (nhãn hiệu) của vi xử lý đang sử
dụng trên máy tính hiện tại, tên này dài không quá 8 kí tự.
Các lệnh sau đây sẽ in tên nhà sản xuất nói trên ra màn hình:
Mov Ah, X ; hàm cần gọi được đưa vào thanh ghi
ah
;--------------------
LapIn:
Int 21h
Loop LapIn
;------------------------------------
Ví dụ 2: Giả sử hàm X của ngắt Y khi được gọi với AL = 1 (được gọi là hàm con)
sẽ trả về trong cặp thanh ghi ES:DI địa chỉ của vùng nhớ chứa ngày-tháng-năm sản
xuất ROM-BIOS đang sử dụng trên máy tính hiện tại. Đồng thời hàm/ngắt này
cũng cho biết số kí tự trong xâu ngày-tháng-năm trong thanh ghi BX.
Các lệnh sau đây sẽ in xâu ngày-tháng-năm nói trên ra màn hình:
Mov Ah, X ; hàm cần gọi được đưa vào thanh ghi
ah
;--------------------
LapIn:
Int 21h
INC DI ; đến kí tự tiếp theo
Loop LapIn
;-------------------------------------
Ví dụ 3: Hàm 39h của ngắt 21h được sử dụng để tạo thư mục con trên đĩa. Hàm
này quy định: DS:DX chứa xâu tên thư mục cần tạo, bao gồm cả đường dẫntìm đến
thư mục này, xâu này kết thúc bởi trị 0. Nếu việc tạo không thành công thì Cf = 1,
khi đó thanh ghi Ax sẽ chứa mã lỗi.
Các lệnh sau đây sẽ tạo ra thư mục con BTCB trong thư mục ASSEM trên thư mục
gốc ổ đĩa D.
Chương trình phải khai báo biến TenTM, chứa xâu tên thư mục cần tạo như sau:
TenTM DB ‘D:\ASSEM\BTCB’,0
Các lệnh:
Mov DS, Ax
Int 21h
;-------------------------
Jmp KetThuc
TB_Loi:
KetThuc:
...
;----------------------------------------------------
MỘT SỐ VÍ DỤ
Ví dụ 1: Giả sử hàm X của ngắt Y khi được gọi sẽ trả về trong cặp thanh ghi ES:SI
địa chỉ của vùng nhớ chứa tên của nhà sản xuất (nhãn hiệu) của vi xử lý đang sử
dụng trên máy tính hiện tại, tên này dài không quá 8 kí tự.
Các lệnh sau đây sẽ in tên nhà sản xuất nói trên ra màn hình:
Mov Ah, X ; hàm cần gọi được đưa vào thanh
ghi ah
Int Y ; gọi ngắt Y với hàm X
;--------------------
Mov Cx, 8 ; tên dài không quá 8 kí tự
Mov Ah, 02 ; dùng hàm 02/21h để in kí tự ra
nàm hình
LapIn:
Mov Dl, Byte PTR ES:[SI]
Int 21h
INC SI ; đến kí tự tiếp theo
Loop LapIn
;------------------------------------
Ví dụ 2: Giả sử hàm X của ngắt Y khi được gọi với AL = 1 (được gọi là hàm con)
sẽ trả về trong cặp thanh ghi ES:DI địa chỉ của vùng nhớ chứa ngày-tháng-năm sản
xuất ROM-BIOS đang sử dụng trên máy tính hiện tại. Đồng thời hàm/ngắt này
cũng cho biết số kí tự trong xâu ngày-tháng-năm trong thanh ghi BX.
Các lệnh sau đây sẽ in xâu ngày-tháng-năm nói trên ra màn hình:
Mov Ah, X ; hàm cần gọi được đưa vào thanh
ghi ah
Mov Al, 1 ; gọi hàm X với Al = 1
Int Y ; gọi ngắt Y với hàm X
;--------------------
Mov Cx, Bx ; đưa số kí tự của xâu ngày-tháng-
nămvào Cx
Mov Ah, 02 ; dùng hàm 02/21h để in kí tự ra nàm
hình
LapIn:
Mov Dl, Byte PTR ES:[DI]
Int 21h
INC DI ; đến kí tự tiếp theo
Loop LapIn
;-------------------------------------
Ví dụ 3: Hàm 39h của ngắt 21h được sử dụng để tạo thư mục con trên đĩa. Hàm
này quy định: DS:DX chứa xâu tên thư mục cần tạo, bao gồm cả đường dẫntìm đến
thư mục này, xâu này kết thúc bởi trị 0. Nếu việc tạo không thành công thì Cf = 1,
khi đó thanh ghi Ax sẽ chứa mã lỗi.
Các lệnh sau đây sẽ tạo ra thư mục con BTCB trong thư mục ASSEM trên thư mục
gốc ổ đĩa D. Chương trình phải khai báo biến TenTM, chứa xâu tên thư mục cần
tạo như sau:
TenTM DB ‘D:\ASSEM\BTCB’,0
Các lệnh:
Mov Ax, Seg TenTM
Mov DS, Ax
Mov Dx, Offset TenTM
Mov Ah, 39h
Int 21h
;-------------------------
Jc TB_Loi ; nếu CF = 1 thì việc tạo bị lỗi
<In ra thong bao hoan thanh>
Jmp KetThuc
TB_Loi:
<In ra thong bao khong tao duoc thu muc>
KetThuc:
...
;----------------------------------------------------
Ví dụ 4: Sau đây là chương trình dạng COM: In ra tất cả (256) các kí tự có trong
bảng mã ASCII:
.Model Small
.Code
ORG 100h
Start:
Jmp Main
TB DB 'Noi dung Bang ma ASCII:',0Ah,0Dh,'$'
Main Proc
Mov Ah, 09h
Lea Dx, TB
Int 21h
;--------------------------
Mov Cx, 256
Mov Ah, 02
Mov Dl, 0
LapIn:
Int 21h
;-----------------
Mov Bl, Dl
Mov Dl, ' '
Int 21h
Mov Dl, Bl
;-----------------
INC Dl
Loop LapIn
;-------------------------
Int 20h
Main Endp
End Start
Ví dụ 5.1: Sau đây là chương trình dạng COM: Nhập vào một xâu kí tự bất kỳ.
Sau đó in ra lại chính xâu kí tự vừa được nhập vào nhập. Sử dụng hàm 09/21h để
in ra.
.Model Small
.Code
ORG 100h
Start:
JMP Main
TBN DB 'Nhap vao mot xau ki tu: $'
TBX DB 0Ah,0Dh,'Xau vua nhap: $'
Buff DB 100,0,100 Dup (' ')
Main Proc
Mov Ah, 09h
Lea Dx, TBN
Int 21h
;--------------------------
Mov Ah, 0Ah
Lea Dx, Buff
Int 21h
;-------------------------
Mov Cx, 0
Mov Cl, Buff[1]
Add Dx, 2
Mov DI, Dx
Add DI, Cx
Mov Byte PTR [DI], ‘$’
;-------------------------
Mov Ah, 09h
Lea Dx, TBX
Int 21h
;-------------------------
Mov Ah, 09h
Lea Dx, Buff
Add Dx, 2
Int 21h
;-------------------------
Int 20h
Main Endp
End Start
Ví dụ 5.2: Sau đây là chương trình dạng EXE: Nhập vào một xâu kí tự bất kỳ. Sau
đó in ra lại chính xâu kí tự vừa được nhập vào nhập. Sử dụng hàm 02/21h để in ra.
.Model Small
.Stack 100h
.Data
TBN DB 'Nhap vao mot xau ki tu: $'
TBX DB 0Ah,0Dh,'Xau vua nhap: $'
Buff DB 100,0,100 Dup (' ')
.Code
Main Proc
Mov Ax,@Data
Mov DS, Ax
;-------------------------
Mov Ah, 09h
Lea Dx, TBN
Int 21h
;--------------------------
Mov Ah, 0Ah
Lea Dx, Buff
Int 21h
;-------------------------
Mov Cx, 0
Mov Cl, Buff[1]
Add Dx, 2
Mov DI, Dx
;--------------------------
Mov Ah, 09h
Lea Dx, TBX
Int 21h
;-------------------------
Mov Ah, 2
Lap_In:
Mov Dl, [DI]
Int 21h
INC DI
Loop Lap_In
;-------------------------
Mov Ah, 4Ch
Int 21h
Main Endp
End
Ví dụ 6: Sau đây là chương trình dạng COM: Nhập vào một kí tự thường, chương
trình sẽ in ra kí tự in hoa tương ứng.
.Model Small
.Code
ORG 100h
Start:
Jmp Main
TB1 DB 'Nhap vao mot ki tu thuong: $'
TB2 DB 0Ah,0Dh,'Ki tu hoa tuong ung: $'
Main Proc
Mov Ah, 09h
Lea Dx, TB1
Int 21h
;----------------------
Mov Ah, 01
Int 21h
Mov Bl, Al
;-----------------------
Mov Ah, 09h
Lea Dx, TB2
Int 21h
;----------------------
Mov Ah, 02
Mov Dl, Bl
Sub Dl, 20h
Int 21h
;-------------------------
Int 20h
Main Endp
End Start
Ví dụ 7: Sau đây là chương trình dạng COM: Nhập vào một xâu kí tự sau đó in ra
lại xâu đã nhập theo thứ tự đảo ngược.
.Model Small
.Code
ORG 100h
Start:
JMP Main
TBN DB 'Nhap vao mot xau ki tu: $'
TBX DB 0Ah,0Dh,'Xau vua nhap: $'
Buff DB 100,0,100 Dup (' ')
Main Proc
Mov Ah, 09h
Lea Dx, TBN
Int 21h
;--------------------------
Mov Ah, 0Ah
Lea Dx, Buff
Int 21h
;-------------------------
Mov Cx, 0
Mov Cl, Buff[1]
;---------------------------
Mov Ah, 09h
Lea Dx, TBX
Int 21h
;-------------------------
Lea Dx, Buff
Add Dx, 2
Mov DI, Dx
Add DI, Cx
Dec DI
Mov Ah, 02
Lap_In_Nguoc:
Mov Dl, [DI]
Int 21h
DEC DI
Loop Lap_In_Nguoc
;--------------------------------
Int 20h
Main Endp
End Start
Ví dụ 8:,Sau đây là chương trình dạng COM: Nhập vào hai số (số thứ nhất: nhỏ
hơn 5; số thứ hai: nhỏ hơn hoặc bằng 5), sau đó in ra tổng của hai số vừa nhập.
.Model Small
.Code
ORG 100h
Start:
Jmp Main
TBN1 DB 'Nhap so hang thu nhat (nho hon 5): $'
TBN2 DB 0Ah,0Dh,'Nhap so hang thu hai (nho hon bang 5):
$'
TBX DB 0Ah,0Dh,'Tong cua hai so la: $'
Main Proc
Mov Ah, 09h
Lea Dx, TBN1
Int 21h
Mov Ah, 01
Int 21h
Mov Bl, Al
Sub Bl, 30h
;-------------------------
Mov Ah, 09h
Lea Dx, TBN2
Int 21h
Mov Ah, 01
Int 21h
Sub Al, 30h
Add Bl, Al
;-------------------------
Mov Ah, 09h
Lea Dx, TBX
Int 21h
Mov Ah, 02
Mov Dl, Bl
Add Dl, 30h
Int 21h
;------------------------
Int 20
Main Endp
End Start
Bài 6:
Lệnh so sánh:
Cú pháp: Cmp [Toán hạng đích], [Toán hạng nguồn]
Trong đó: [Toán hạng đích], [Toán hạng nguồn] có thể là hằng, biến, thanh
ghi hay ô nhớ. [Toán hạng đích] không thể là hằng số. [Toán hạng đích] và
[Toán hạng nguồn] không thể đồng thời là ô nhớ.
Tác dụng: Lệnh Cmp (Compare) được sử dụng để so sánh giá trị/nội dung
của [Toán hạng đích] so với [Toán hạng nguồn]. Tương tự như lệnh Sub, nó lấy
[Toán hạng đích] trừ đi [Toán hạng nguồn] nhưng kết quả không làm thay đổi
[Toán hạng đích] mà chỉ làm thay đổi giá trị của một số cờ hiệu: CF, ZF, OF,...
Kết quả so sánh của lệnh Cmp là: [Toán hạng đích] > [Toán hạng nguồn]; [Toán
hạng đích] ≥[Toán hạng nguồn]; [Toán hạng đích] < [Toán hạng nguồn]; [Toán
hạng đích] ≤[Toán hạng nguồn]; [Toán hạng đích] = [Toán hạng nguồn]; [Toán
hạng đích] ≠ [Toán hạng nguồn];... mỗi kết quả sẽ tác động (0 →1, 1→0) đến một
cờ tương ứng cụ thể nào đó.
Do đó, để biết được kết quả so sánh chương trình phải sử dụng các lệnh kiểm tra
cờ (đó là cá lệnh nhảy), và chúng phải được đặt ngay sau lệnh so sánh. Như vậy
lệnh Cmp sẽ không có ý nghĩa khi nó đứng độc lập.
Có thể nói ngược lại, lệnh Cmp được sử dụng để cung cấp điều kiện nhảy (thay đổi
giá trị các cờ) cho các lệnh nhảy có điều kiện.
Ví dụ 1:
Cmp Ax, [SI] ; so sánh Ax với nội dung ô nhớ được chỉ bởi
SI
Ví dụ 2:
Cmp Al, ‘A’ ; so sánh giá trị thanh ghi Al với ‘A’
Cmp Al, Var1 ; so sánh giá trị thanh ghi Al với giá trị biến
Var1
Tất cả cá lệnh Cmp ở trên điều không có ý nghĩa, vì nó không cho biết kết quả so
sánh một cách trực tiếp mà phải ánh thông qua các cờ.
Lệnh Cmp không thể sử dụng để so sánh hoặc kiểm tra giá trị của các cờ.
Các lệnh nhảy
Lệnh nhảy không điều kiện:
Cú pháp: Jmp <Vị trí đích>
Trong đó: <Vị trí đích> có thể là nhãn của một lệnh, tên của một thủ tục
hoặc có thể là một thanh ghi, một ô nhớ (đã được định nghĩa) nào đó. <Vị trí
đích> cũng có thể là một biến nào đó, giá trị của nó thường là địa chỉ của một ô
nhớ trong đoạn Code.
Tác dụng: Khi gặp lệnh này chương trình chuyển điều khiển (nhảy đến) đến
thực hiện lệnh sau <Nhãn đích> mà không phụ thuộc vào bất kỳ điều kiện nào.
Cơ chế thực hiện của lệnh Jmp là thay đổi nội dung của cặp thanh ghi con trỏ lệnh
CS:IP. CS:IP mới sẽ là địa chỉ của lệnh sau <Nhãn đích> trong bộ nhớ.
Lệnh Jmp có 3 dạng: Short, Near và Far. Đối với dạng Short và Far thì chỉ có thanh
ghi IP bị thay đổi khi lệnh thực hiện, ngược lại, với dạng Far, khi lệnh thực hiện thì
cả thanh ghi IP và thanh ghi đoạn CS đều bị thay đổi. Hay nói cách khác: Đối với
dạng Short và Near thì lệnh Jmp và <Vị trí đích> phải nằm trong cùng Segment
nhớ, ngược lại, với dạng Far thì lệnh Jmp và <Vị trí đích> có thể nằm ở các
Segment nhớ khác nhau.
Ví dụ 1:
Start:
Jmp Main
TieuDe DB ‘Khoa CNTT – DHHH’
Main Proc
..................
Main Endp
End Start
Ví dụ 2:
Jmp short Main
Jmp Ax
Jmp word PTR [BX]
Jmp dword PTR [BX]
Ví dụ 3:
Reset DD 5BE000F0h
Jmp Reset
Ví dụ 4:
Mov Ax, 15
Mov Bx, 20
Jmp TTong
Add Ax, Bx
TTong:
Sub Ax, Bx
Mov Cx, Ax
Kết thúc đoạn lệnh trên Cx = Ax = -5, vì lệnh Add Ax, Bx không được thực
hiện. Khi gặp lệnh Jmp TTong chương trình nhảy đến thục hiện lệnh sau nhãn
TTong, đó chính là lệnh Sub Ax, Bx.
Lệnh nhảy có điều kiện:
Cú pháp chung: <Lệnh nhảy có điều kiện> <Vị trí đích>
Điều kiện nhảy của các lệnh nhảy này chính là sự thay đổi giá trị của các cờ hiệu,
do đó để tạo điều kiện nhảy cho một lệnh nhảy xác định thì chương trình phải làm
thay đổi giá trị của cờ hiệu tương ứng với nó. Chương trình thường dùng các lệnh
địch bít, quay bít, so sánh,... để làm thay đổi giá trị các cờ hiệu để tạo điều kiện
nhảy cho các lệnh nhay. Cách đơn giản nhất là sử dụng lệnh Cmp ngay trước lệnh
nhảy.
Sau đây là các lệnh nhảy có điều kiện với dữ liệu có dấu:
Các lệnh nhảy với dữ liệu có dấu có thể áp dụng với các dữ liệu không dấu.
Sau đây là các lệnh nhảy có điều kiện dùng chung:
Với các lệnh này, chương trình thường sử dụng các lệnh dịch bít hoặc lệnh quay bít
để tạo điều kiện nhảy nó.
Ví dụ 1a: Dãy lệnh sau đây thực hiện việc gán giá trị cho thanh ghi Cx dựa vào giá
trị của thanh ghi Ax và Dx:
Mov Ax, 12
Mov Dx, 25
;-------------------
Cmp Ax, Dx ; Ax ? Bx
Jg Nhan1 ; nếu Ax > Dx
Jle Nhan2
Nhan1:
Mov Cx, Ax
Jmp Tiep_Tuc
Nhan2:
Mov Cx, Dx
Jmp Tiep_Tuc
Tiep_Tuc:
Mov Bx, Cx
;------------------------
Có thể thấy, ở đây không cần dùng lệnh Jle Nhan2, vì nếu Ax không lớn hơn Dx
thì chắc chẵn nó sẽ nhỏ hơn hoặc bằng Dx. Ngoài ra cũng không cần dùng
lệnh Jmp Tiep_Tuc sau nhãn Nhan2, vì việc chuyển đến lệnh sau
nhãn Tiep_Tuc ở đây là tất nhiên. Vì thế đoạn lệnh trên có thể được viết rút gọn
như trong Ví dụ 1b sau đây.
Ví dụ 1b: Dãy lệnh sau đây là trường hợp rút gọn của dãy lệnh trên:
Mov Ax, 12
Mov Dx, 25
;------------------
Cmp Ax, Dx
Jg Nhan1 ; nếu Ax > Dx
Mov Cx, Dx ; khi Ax ≤Dx
Jmp Tiep_Tuc
Nhan1:
Mov Cx, Ax ; khi Ax > Dx
Tiep_Tuc:
Mov Bx, Cx
;------------------------
Trong cả hai ví dụ trên: khi kết thúc, Bx = Cx = Dx = 25. Nhưng nếu cho
Ax = 120 (Ax > Bx) thì Bx = Cx = Ax = 120.
Ví dụ 2: Giả sử tại địa chỉ 0A000:0100 trong bộ nhớ có chứa một mảng các số
nguyên kiểu byte, gồm 100 phần tử (100 byte).
Các lệnh sau đây tính tổng của các phần tử trong mảng này mà giá trị của nó lớn
hơn 123. Kết quả chứa ở thanh ghi Dx.
Mov Ax, 0A000h
Mov DS, Ax
Mov DI, 0100h
;------------------------
Mov Dx, 0
Mov Cx, 100
Lap_TT:
Mov Al, Byte PTR DS:[DI]
Cmp Al, 123
Jle Tiep_Tuc
Add Dx, Al
Tiep_Tuc:
INC DI ; trỏ đến phần tử kế tiếp
Loop Lap_TT ; lặp lại: kiển tra và tính tổng
;---------------------
Ví dụ 3: Giả sử tại địa chỉ 0C000:00120 trong bộ nhớ có chứa một xâu kí tự, xâu
này được kết thúc bởi giá trị 0 (số 0).
Các lệnh sau đây sẽ đếm xem xâu nói trên gồm bao nhiêu kí tự. Kết quả ghi vào ô
nhớ ngay trước vùng nhớ chứa xâu này:
Mov Ax, 0C000h
Mov ES, Ax
Mov DI, 00120h
Mov SI, DI
;------------------------
Mov Dx, 0
Lap_Dem:
Mov Al, Byte PTR ES:[DI]
Cmp Al, 0 ; so sánh Al với 0
Je KetThuc ; nếu Al = 0: đã đến cuối xâu
INC Dx ; khi Al <> 0: đếm
INC DI ; trỏ đến kí tự kế tiếp
Jmp Lap_Dem ; lặp lại: kiển tra và đếm
KetThuc:
Mov Byte PTR DS:[SI - 1], Dx
;------------------------------------------
Ví dụ 4: Các lệnh sau đây in nội dung bản bản ASCII ra màn hình, nhưng không in
ra các kí tự có mã 07h, 0Ah, 0Dh.
Mov Cx, 256
Mov Ah, 02
Mov Dl, 0
Lap_In:
Cmp Dl, 07h
Je TTuc
Cmp Dl, 0Ah
Je TTuc
Cmp Dl, 0Dh
Je TTuc
Int 21h
TTuc:
INC DL
Loop Lap_In
;-----------------------
Ví dụ 5: Các lệnh sau đây cho phép nhập một xâu kí tự bất kỳ, dài không quá 200
kí tự, từ bàn phím vào biến XauNhap. Sau đó copy các kí tự là chữ cái in hoa trong
xâu vừa nhập vào biến XauHoa.
Trước hết chương trình phải khai báo các biến:
XauNhap DB 200, 0, 200 Dup (‘ ’)
XauHoa DB 200 Dup (‘ ’)
Các lệnh:
Mov Ax, Seg XauNhap
Mov DS, XauNhap
Mov Dx, Offset XauNhap
Mov Ax, Seg XauHoa
Mov ES, XauHoa
Mov DI, Offset XauHoa
;----------------------------------
Mov Ah, 0Ah
Int 21h
;---------------------
Mov Cx, 0
Mov Cl, XauNhap[1]
Mov SI, Dx
Add SI, 2
;------------------------------
Lap_Copy:
Mov Al, DS:[SI]
Cmp Al, ‘A’
Jl TTuc
Cmp Al, ‘Z’
Jg TTuc
Mov ES:[DI], Al
INC DI
TTuc:
INC SI ;
Loop Lap_Copy
;-----------------------
Nên nhớ, trong bảng mã ASCII các kí tự là chữ cái in hoa nằm ở những vị trí liên
tiếp nhau: A, B, C, ..., Z, chúng có mã lần lượt là 65, 66, 67, ..., 90.
Ví dụ 6: Giả sử tại địa chỉ 0F000:FFFE trong bộ nhớ ROM-BIOS có chứa một
byte dữ liệu. Byte này cho biết loại của máy tính đang sử dụng. Cụ thể, nếu byte
này: chứa trị 0FBh: máy PC/XT; chứa trị 0FCh: máy PC/AT; chứa trị 0FFh: máy
PC classic;...
Các lệnh sau đây cho biết máy tính đang sử dụng thuộc loại máy nào:
Trước hết chương trình phải khai báo các biến trả lời:
TB1 DB ‘Day la may PC/XT.$’
TB2 DB ‘Day la may PC/AT.$’
TB3 DB ‘Day la may PC classic.$’
Các lệnh:
Mov Ax, 0F000h
Mov ES, Ax
Mov SI, 0FFFEh
;------------------------
Mov Al, Byte PTR ES:[SI]
Cmp Al, 0FBh
Je TraLoi1
Cmp Al, 0Fch
Je TraLoi2
Cmp Al, 0Ffh
Je TraLoi3
...
TraLoi1:
Mov Ax, Seg TB1
Mov DS, Ax
Lea Dx, Offset TB1
Mov Ah, 09
Int 21h
Jmp KetThuc
TraLoi2:
Mov Ax, Seg TB2
Mov DS, Ax
Lea Dx, Offset TB2
Mov Ah, 09
Int 21h
Jmp KetThuc
TraLoi1:
Mov Ax, Seg TB3
Mov DS, Ax
Lea Dx, Offset TB3
Mov Ah, 09
Int 21h
Jmp KetThuc
.....
KetThuc:
Mov Ah, 4Ch
Int 21h
;----------------------
Có thể nói, ví dụ trên đây là một thao tác điển hình trong lập trình hợp ngữ. Nó
cũng cho thấy chức năng và thế mạnh của ngôn ngữ này. Đây cũng là mục tiêu mà
người lập trình hợp ngữ nhắm tới. Việc truy xuất vào các vùng nhớ dữ liệu để lấy
các byte/word thông tin cấu hình hệ thống là một yêu cầu cơ bản với các ngôn ngữ
lập trình cấp thấp, và nó được thực hiện một cách khá đơn giản trong ngôn ngữ
hợp ngữ. Ví dụ trên đây cũng cho thấy nguyên tắc để làm việc này.
Các lệnh Dịch bít – Quay bít
Các lệnh dịch bít là các lệnh làm cho các bít trong một thanh ghi bị dịch về bên trái
(lệnh ShR) hoặc về bên phải (lệnh ShL) một hoặc nhiều bít. Lệnh quay bít làm cho
các bít trong một thanh ghi quay theo “chiều trái” hoặc theo “chiều phải” một hoặc
nhiều bít. Thông thường các bít được dịch hay được quay đều lần lượt được đưa
qua cờ CF. Do đo, các lệnh dịch bít và quay bít thường được sử dụng để kiểm tra
giá trị bít (= 0 hay = 1) của các bít trong thanh ghi.
Hợp ngữ cung cấp hai dạng lệnh quay bít, quay không qua cờ CF (lệnh RoL và
RoL) và quay có qua cờ CF (lệnh RcL và RcR).
Cú pháp:
Trong đó: [Toán hạng đích] là một thanh ghi 8 bít hoặc 16 bít. <n> là số bít
cần dịch, nếu <n> = 1 thì chỉ định trực tiếp trong câu lệnh, nếu <n> lớn hơn 1
phải chỉ định thông qua thanh ghi CL.
Tác dụng:
Lệnh ShR (Shift Logical Right): Dịch chuyển các bít trong thanh ghi [Toán
hạng đích] sang phải một hoặc nhiều bít. Các bít được dịch lần lượt được đưa
vào cờ CF, cờ CF sẽ chứa bít của lần dịch cuối cùng. Sau khi dịch các bít bị
khuyết (ở bên đối diện) sẽ được thay bằng các bít có trị 0. Tức là, với thanh ghi
8 bít thì sau 8 lần dịch nó sẽ nhận được một dãy 8 bít = 0, tương tự với thanh
ghi 16 bít thì sau 16 lần dịch nó sẽ nhận được một dãy 16 bít = 0. Nếu thanh ghi
AL = 01001001 thì sau khi bị dịch về bên trái 2 bít nó sẽ như sau: AL =
00100100, khi đó CF = 1.
Lệnh ShL (Shift Logical Left): Tương tự như lệnh ShR nhưng các bít được
dịch về phía bên trái.
Lệnh RCR (Rotate through Carry Right): Tương tự như lệnh ShR, nhưng
bít được dịch sẽ được đặt vào lại bít bị khuyết ở bên đối diện. Tức là, với thanh
ghi 8 bít thì sau 8 lần dịch nó sẽ nhận lại dãy bít ban đầu, tương tự với thanh ghi
16 bít thì sau 16 lần dịch nó sẽ nhận lại dãy bít ban đầu. Nếu thanh ghi AL
= 01001001 thì sau khi bị quay về bên trái 2 bít nó sẽ như sau: AL = 00100101,
khi đó CF = 1.
Lệnh RCL (Rotate through Carry Left): Tương tự như lệnh RCR nhưng các
bít được quay về phía bên trái.
Ví dụ 1:
Shr Al, 1
Mov Cl, 2
Ví dụ 2: Các lệnh sau đây đếm số bít bằng 1 trong thanh ghi BX, kết quả chứa ở
thanh ghi Al mà không làm thay đổi giá trị của nó:
Mov Al, 0
Mov Cx, 16
DemBit1:
Rol Bx, 1
Jnc TiepTuc
Inc Al
TiepTuc:
Loop DemBit1
;--------------------------
Ví dụ 3: Các lệnh sau đây đếm số bít giống nhau (tương ứng) giữa hai thanh ghi
Ax và Bx. Kết quả chứa trong biến Dem (Dem DB 0):
Mov Dem, 0
Xor Ax, Bx
Mov Cx, 16
KTra:
Shl Ax, 1
Jnc Bit_0
Jmp TiepTuc
Bit_0:
Inc Dem
TiepTuc:
Loop KTra
;------------------------
Ví dụ 4: Các lệnh sau đây in nội dung của thanh ghi BX ra màn hình dưới dạng sô
nhị phân:
Mov Cx, 16 ; lặp in đủ 16 bít
Mov Ah, 02 ; in ra kí tự với hàm 02/21h
In_NhiPhan:
Shl Bx, 1 ; dịch trái để bít cần in rơi vào cờ CF
Jc In_1 ; lệnh JC nhảy khi cờ CF = 1
Mov Dl, ‘0’ ; CF = 0 tức là bít cần in có trị = 0, nên in ra kí tự
‘0’
Int 21h
Jmp In_Tiep
In_1:
Mov Dl, ‘1’
Int 21h
In_Tiep:
Loop In_NhiPhan
;-------------------------
Ví dụ 5: Các lệnh sau đây nhập một số nhị phân từ bàn phím đưa vào lưu trữ trong
thanh ghi BX:
Mov Bx, 0
Mov Cx, 0
Mov Ax, 0
Mov Ah, 1
Nhap:
Int 21h
Cmp Al, 0Dh ; nếu nhập Enter thì kết thúc
Je KetThuc
;--------------------
Cmp Al, ‘1’
Je Them_Bit
Cmp Al, ‘0’
Je Them_Bit
;----------------------
Mov Ah,09
Mov Dx, Seg TB ; Biến TB được khai báo như sau:
Mov DS, Dx ; TB DB ‘Ban phai nhap 0/1 hoac Enter$’
Mov Dx, Offset TB
Int 21h
Jmp Nhap
Them_Bit:
Sub Al, 30h ; có thẻ sử dụng lệnh And Al, 0Fh
Shl Bx, 1
Or Bx, Al
Inc Cx
Cmp Cx, 17
Jne Nhap
KetThuc:
Mov 4Ch
Int 21h
;----------------------------------
Ví dụ 6: Giả sử tại địa chỉ 0B000:0010 trong bộ nhớ có chứa một byte dữ liệu, nó
cho biết một số thông tin liên quan đến cấu hình hệ thống. Byte 3 cho biết máy tính
hiện tại có (byte 3 = 1) hay không có (byte 3 = 0) cổng cắm USB.
Các lệnh sau đây cho biết máy tính hiện tại (máy thực hiện đoạn lệnh) có hay
không có cổng cắm USB. Kết quả được thông báo thông qua thanh ghi Cx: Cx = 0:
không có cổng cắm USB; Cx = 0FFFFh: có cổng cắm USB.
Mov Ax, 0B000h
Mov DS, Ax
Mov DI, 0010h
;-------------------------
Mov Ax, 0
Mov Al, byte PTR DS:[DI]
;-------------------------------------
Mov Cl, 4
Shr Al, Cl
Jc CoUSB ; co cong USB
Mov Cx, 0 ; khong co cong USB
Jmp KetThuc
CoUSB:
Mov Cx, 0FFFFh
KetThuc:
...
;-------------------------
Trong thực tế, kết quả kiểm tra này thường được trả lời thông qua ngay chính cờ
CF. CF = 0: không có cổng USB, CF = 1: có cổng USB.
Chú ý 1: Có thể sử dụng lệnh dịch bít ShR/ShL để thực hiện phép chia/phép nhân
giá trị của một thanh ghi (chứa số nguyên không dấu) với một số là bội số của 2.
Ví dụ: Hai lệnh sau đây sẽ dịch AL sang trái 3 bít, tức là nhân AL với 8:
Mov Cl, 3
Shl Al, Cl ; Al ß AL * 8, 8 = 23.
Ví dụ: Hai lệnh sau đây sẽ dịch AL sang phải 3 bít, tức là chia AL với 8:
Mov Cl, 3
Shr Al, Cl ; Al ß AL * 8, 8 = 23.
Chú ý 2: Hợp ngữ còn cung cấp các lệnh dịch chuyển số học SAL (Shift
Arithmetic Left) và SAR (Shift Arithmetic Right) . SAL tương tự hoàn toàn ShL,
có thể sử dụng để thực hiện nhân 2 với các số âm. SAR tương tự ShR nhưng bít
cuối cùng của [Toán hạng đích] không bị thay bằng bít 0 mà vẫn giữ nguyên giá trị
cũ, có thể sử dụng để thực hiện chia 2 với các có dấu.
Các lệnh dich bít, quay bít của các vi xử lý Intel 80286/80386/.... cho phép viết số
bít cần dịch, trong trường hợp lớn hơn một, trực tiếp trong lệnh dịch, quay mà
không cần thông qua thanh ghi Cl [1 – 540]