DIV nguồn (Unsigned Divide)
3.2.4. Khung của một chương trình hợp ngữ
Một chương trình mã máy được nạp vào bộ nhớ thường bao gồm các vùng nhớ khác nhau:
• Vùng dữ liệu: Dùng để chứa các biến, kết quả trung gian hay kết quả khi chạy chương trình.
• Vùng mã lệnh: Dùng để chứa mã lệnh của chương trình.
• Vùng ngăn xếp: Dùng để phục vụ cho các hoạt động của chương trình như gọi chương trình con, trở về chương trình chính từ chương trình con.
Một chương trình hợp ngữ cũng có cấu trúc như vậy, để khi được dịch nó sẽ tạo ra mã tương ứng với chương trình mã máy nói trên (có cấu trúc giống như vậy). Chúng ta sẽ khai báo quy mô sử dụng bộ nhớ đối với các vùng nhớ đó để sử dụng một cách phù hợp, tiết kiệm, hiệu quả và đúng với cấu trúc chương trình.
Khai báo quy mơ sử dụng bộ nhớ
Khai báo này xác định kích thước cho đoạn mã và dữ liệu của chương trình.
Sử dụng hướng dẫn chương trình dịch .Model đặt trước các hướng dẫn khác trong chương trình theo mẫu như sau:
.Model Kích_thước
Ví dụ:
.Model Small .Model Tiny
Có các kiểu Kích_thước bộ nhớ cho chương trình hợp ngữ như sau:
• Tiny (hẹp): Mã lệnh và dữ liệu nằm gọn trong một đoạn.
• Small (nhỏ): Mã lệnh trong một đoạn, dữ liệu trong một đoạn.
• Medium (Trung bình): Mã lệnh hơn một đoạn, dữ liệu trong một đoạn.
• Compact (Gọn): Mã lệnh trong một đoạn, dữ liệu hơn một đoạn.
• Large (Lớn), Huge (Rất lớn - khổng lồ): Mã lệnh và dữ liệu hơn một đoạn. Các mảng có thể lớn hơn 64Kbyte.
Khai báo đoạn ngăn xếp
Ngăn xếp là vùng nhớ phục vụ cho các hoạt động của chương trình khi gọi cheơng trình con và trở về chương trình chính từ chương trình con. Tuỳ theo cấu trúcvà quy mơ của chương trình mà ta khai báo kích thước của đoạn này. Việc khai báo được thực hiện nhờ hướng dẫn chương trình dịch .Stack theo mẫu sau:
.Stack Kích_thước
Ví dụ:
.Stack 100
.Stack 100h ; 256
Chú ý: Nếu ta khơng khai báo kích thước của đoạn này thì chương trình dịch sẽ tự
động gán giá trị 1 Kbyte cho vùng ngăn xếp này. Đây là kích thước q lớn đối với một ứng dụng thơng thường. Nói chung ta nên chọn là 100 hoặc 100h là đủ.
Khai báo đoạn dữ liệu
Phần này để định nghĩa các biến của chương trình.
Hằng cũng nên định nghĩa ở đây để đảm bảo sự thống nhất (mặc dù ta có thể định nghĩa hằng ở chỗ khác, lý do là lệnh giả EQU không cấp phát bộ nhớ cho hằng (tên hằng không
tương ứng với một địa chỉ nào) nên ta có thể định nghĩa hằng tự do thoải mái trong chương trình.
Việc khai báo đoạn dữ liệu được thực hiện nhờ hướng dẫn chương trình dịch .Data. Định nghĩa các biến, mảng và hằng được thực hiện tiếp ngay sau đó bằng các lệnh giả thích hợp, của thể như sau:
Ví dụ:
.Data
Chao db 'Xin chao ban!','$' Crlf db 0dh, 0ah, '$'
Pa Equ 300h
Khai báo đoạn mã lệnh
Phần này chứa tồn bộ mã lệnh của chương trình. Việc khai báo đoạn mã được thực hiện nhờ hướng dẫn chương trình dịch .Code như sau:
.Code
Tên_CTC Proc
Các lệnh ; Các lệnh của thanh chương trình chính Call Tên_ctc ; Gọi chương trình con
Tên_CTC Endp
Tổng quát: Một thủ tục được định nghĩa nhờ cặp thủ tục 'Proc - Endp ', chương trình
chính cũng là một thủ tục được định nghĩa như trên. Lệnh giả Proc dùng để báo bắt đầu một thủ tục và lệnh giả Endp dùng để báo kết thúc thủ tục đó. Một chương trình con cũng được định nghĩa dưới dạng một thủ tục nhờ các lệnh giả 'Proc - Endp ' như sau:
Tên_ctc Proc
Các lệnh của chương trình con ở đây Ret
Tên_ctc Endp
Chú ý: Trong chương trình con, tại cuối chương trình có lệnh Ret là lệnh trở về chương
trình chính từ chương trình con.
Để kết thúc tồn bộ chương trình, ta dùng hướng dẫn chương trình dịch End như sau: End Tên_CTC
Khung của chương trình hợp ngữ để dịch ra chương trình *.exe
.Model Small .Stack 100 .Data
;Định nghĩa các biến, mảng, hằng ở đây .Code
Tên_CTC Proc
;Khởi tạo đoạn dữ liệu Mov ax, @Data Mov ds, ax
Mov es, ax ; Nếu cần ; Các lệnh của chương trình chính
; Trở về DOS dùng hàm 4ch của ngắt 21h Mov ah, 4ch
Int 21h
Tên_CTC Endp
;Các chương trình con nếu có được định nghĩa ở đây End Tên_CTC
Khi chương trình *.exe được nạp vào bộ nhớ, DOS sẽ lập ra một một mảng gọi là đoạn mào đầu chương trình (Program Segment Prefix - PSP) gồm 256 byte dùng để chứa các thơng tin liên quan đến chương trình và cả DOS, được gắn vào đầu chương trình. DOS sử dụng các thơng tin này để giúp chạy chương trình, PSP được DOS khởi tạo cho mọi chương trình dù chúng được viết bằng ngơn ngữ nào. Do ngay khi chương trình được nạp vào bộ nhớ, DOS cũng đưa các thông số liên quan đến chương trình vào các thanh ghi DS và ES (cụ thể là DS và ES trỏ vào đầu của PSP) mà không chứa giá trị địa chỉ của các thanh ghi đoạn dữ liệu của chương trình. Để chương trình chạy đúng, ta phải khởi đầu cho các thanh ghi DS và ES nhờ các lệnh:
Với 8088/8086 và một số bộ vi xử lý khác thuộc họ 80x86 của Intel, vì lý do kỹ thuật mà chúng khơng cho phép chuyển giá trị số (chế độ địa chỉ trực tiếp) vào các thanh ghi đoạn nên ta phải dùng thanh ghi ax làm trung gian. Thanh ghi ax cũng có thể thay thế bằng các thanh ghi đa năng khác.
@Data là tên của đoạn dữ liệu, .Data dđịnh nghĩa bởi hướng dẫn chương trình dịch =>
chương trình dịch sẽ dịch tên @Data thành giá trị địa chỉ của đoạn dữ liệu.
Chương trình ví dụ1.asm để dịch ra *,exe, thực hiện xuất một dòng ký tự lên màn hình. Dịng ký tự ở đây là lời chào bất kỳ được hiện giữa 2 dòng trống:
.Model Small .Stack 100 .Data
Chao db 'Chuc ban da thanh cong voi chuong trinh dau tay$' Crlf db 0dh, 0ah, '$'
.Code
Vidu1 Proc
Mov ax, @Data ; Lấy địa chỉ của đoạn dữ liệu Mov ds, ax ; Khởi tạo đoạn dữ liệu
Mov es, ax
; Về đầu dòng mới dùng hàm 9 của ngắt 21h để "hiển thị" cặp ký tự ; xuống dòng (lf: line feed) và về đầu dòng (cr: carriage return) Mov ah, 9
Lea dx, crlf Int 21h
; Hiển thị lời chào dùng hàm 9 của ngắt 21h Mov ah, 9
Lea dx, Chao Int 21h
; Về đầu dòng mới dùng hàm 9 của ngắt 21h
Mov ax, @Data Mov ds, ax Mov es, ax
Mov ah, 9 Lea dx, crlf Int 21h ; Về DOS dùng hàm 4ch của ngắt 21h Mov ah, 4ch Int 21h Vidu1 Endp End Vidu1
Khung của chương trình hợp ngữ để dịch ra chương trình *.com
Với khung chương trình hợp ngữ để dịch ra tệp chương trình chạy được *.exe thì có mặt đầy đủ các đoạn. Ngồi tệp chương trình chạy được có phần mở rộng *.exe ra ta cịn có khả năng dịch chương trình hợp ngữ có kết cấu (cấu trúc) thích hợp ra một loại chương trình chạy được kiểu khác với phần mở rộng *.com. Đây là chương trình ngắn gọn và đơn giản hơn nhiều so với tệp chương trình *.exe mà trong đó các đoạn: đoạn mã, đoạn dữ liệu và đoạn ngăn xếp của chương trình được gói gọn trong một đoạn (64Kbyte) duy nhất là đoạn mã. Với những ứng dụng mà dữ liệu và mã lệnh của chương trình khơng u cầu nhiều về khơng gian nhớ thì ta có thể ghép ln chúng chung vào cùng một đoạn mã rồi tạo ra tệp
*.com. Việc tạo ra tệp này không chỉ tiết kiệm được thời gian và bộ nhớ khi cho chạy
chương trình mà cịn tiết kiệm cả khơng gian nhớ khi phải lưu trữ chúng trên bộ nhớ ngồi (đĩa từ).
Để có thể tạo ra được chương trình với phần mở rộng *.com thì chương trình nguồn hợp ngữ phải có kết cấu thích hợp, một ví dụ như sau:
.Model Tiny .Code
ORG 100h Start: Jmp Continue
; Định nghĩa các biến, mảng, hằng ở đây Continue: Tên_CTC Proc ; Các lệnh của chương trình chính ; Trở về DOS dùng ngắt 20h Int 20h Tên_CTC Endp
; Các chương trình con nếu có được định nghĩa ở đây End Start
Nhìn vào khung chương trình hợp ngữ để dịch ra chương trình .com ta thấy khơng có khai báo đoạn ngăn xếp và đoạn dữ liệu, khai báo quy mô sử dụng bộ nhớ là Tiny (tuy nhiên có thể sử dụng quy mơ bộ nhớ là Small). ở đầu đoạn mã có lệnh giả Org (Origin: điểm xuất phát) và lệnh Jmp (nhảy). Lệnh Org 100h dùng để gán điạ chỉ bắt đầu cho chương trình là 100h trong đoạn mã, bỏ qua vùng nhớ kích thước 100h (256 byte) cho đoạn mào đầu (PSP) từ địa chỉ 0 đến địa chỉ 255.
Lệnh Jmp dùng để nhảy qua phần bộ nhớ dành cho việc định nghĩa các dữ liệu (về nguyên tắc, dữ liệu có thể được đặt ở đầu hoặc cuối đoạn mã nhưng ở đây, nó được đặt ở đầu để có thể áp dụng các định nghĩa đơn giản đã nói). Đích của lệnh nhảy là phần bắt đầu của chương trình chính.
Đặc điểm của chương trình *.com
Vì dung lượng nhớ cực đại của một đoạn là 64Kbyte, nên ta phải chắc chắn rằng chương trình của ta có số lượng byte của mã lệnh và dữ liệu là không lớn (không vượt quá giới hạn cho phép của một đoạn, nếu khơng nó sẽ làm cho cả nhóm nở ra về phía địa chỉ cao của đoạn).
Chương trình phải sử dụng ngăn xếp một cách hạn chế, nếu khơng nó sẽ làm cho đỉnh ngăn xếp dâng lên về phía địa chỉ thấp của đoạn khi hoạt động.Chúng ta phải đảm bảo rằng không thể xảy ra hiện tượng chùm lên nhau của các thông tin tại vùng ngăn xếp và thông tin tại vùng mã lệnh và dữ liệu.
Khi kết thúc chương trình kiểu *.com, để trở về DOS ta dùng ngắt 20h của DOS để làm cho chương trình gọn hơn. Mặc dù ta vẫn có thể dùng hàm 4ch của ngắt 21h để trở về DOS như đã dùng trong chương trình để dịch ra *.exe.
Khi kết thúc tồn bộ chương trình ta dùng hướng dẫn chương trình dịch END kèm theo nhãn Start. Nhãn Start tương ứng địa chỉ lệnh đầu tiên của chương trình trong đoạn mã.
Chúng ta có thể viết lại chương trình trong ví dụ trước (để dịch ra *.exe) thực hiện việc
xuất một xâu ký tự lên màn hình theo khung chương trình để dịch ra *.com: .Model Tiny
.Code
ORG 100h Start: Jmp Continue
Chao db 'Xin chào . . .$' Crlf db 0dh, 0ah,'$' Continue:
Main Proc
Mov ah, 9 ; Về đầu dòng mới dùng hàm 9 của ngắt 21h Lea dx, Crlf
Int 21h ; Hiển thị lời chào Mov ah, 9
Lea dx, Chao Int 21h
Mov ah, 9 ; Về đầu dòng mới dùng hàm 9 của ngắt 21h Lea dx, Crlf
Int 21h
Int 20h ; Trở về DOS dùng ngắt 20h
Main Endp
; Các chương trình con nếu có được định nghĩa ở đây End Start