Tài liệu Lập trình hệ thống và điều khiển thiết bị - Phạm Văn Cường

pdf 147 trang hapham 2210
Bạn đang xem 20 trang mẫu của tài liệu "Tài liệu Lập trình hệ thống và điều khiển thiết bị - Phạm Văn Cường", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdftai_lieu_lap_trinh_he_thong_va_dieu_khien_thiet_bi_pham_van.pdf

Nội dung text: Tài liệu Lập trình hệ thống và điều khiển thiết bị - Phạm Văn Cường

  1. HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỄN THÔNG LẬP TRÌNH HỆ THỐNG VÀ ĐIỀU KHIỂN THIẾT BỊ (Dùng cho sinh viên hệ đào tạo đại học từ xa) Lưu hành nội bộ HÀ NỘI - 2006
  2. HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỄN THÔNG LẬP TRÌNH HỆ THỐNG VÀ ĐIỀU KHIỂN THIẾT BỊ Biên soạn : THS. PHẠM VĂN CƯỜNG
  3. LỜI NÓI ĐẦU Cuốn giáo trình Lập trình hợp ngữ và điều khiển thiết bị được chia thành 4 chương. Mỗi chương bao gồm các nội dung cơ bản, tóm tắt chương, các câu hỏi và bài tập cho mỗi chương. Chương 1: trình bày về vấn đề liên quan đến bộ vi xử lý 8088 : kiến trúc, chức năng các thành phần và tập lệnh. Ngoài ra, 1 trong các ngắt được sử dụng phổ biến trong lập trình hệ thống- ngắt 21h của hệ điều hành DOS cũng được giới thiệu trong chương này. Chương 2: trình bày về các vấn đề liên quan đến lập trình hợp ngữ: cách thức viết và thực hiện một chương trình, cách thức cài đặt các cấu trúc lập trình trong hợp ngữ và các vấn đề liên quan đến chương trình con và macro. Chương 3: giới thiệu về công cụ gỡ rối debug, chương trình mô phỏng Emu 8086. Liên kết chương trình viết bằng hợp ngữ với chương trình được viết bằng các ngôn ngữ bậc cao như C và Pascal cũng được đề cập ở chương này. Ngoài ra, chương này cò giới thiệu về một số ngắt của BIOS phục vụ thiết bị ngoại vi, chương trình thường trú và chương trình con ngắt. Chương 4: Trình bày về lập trình phối ghép: lập trình modem, bàn phím và màn hình. Đồng thời chương này cũng giới thiệu về một môi trường RadASM để phát triển các ứng dụng viết bằng hợp ngữ trên Windows. Do thời gian có hạn và kinh nghiệm còn hạn chế, cuốn giáo trình sẽ không tránh khỏi các sai sót. Tác giả biên soạn rất mong nhận được ý kiến đóng góp từ các độc giả. Mọi ý kiến góp ý xin gửi về email : pcuongcntt@yahoo.com Xin chân thành cảm ơn! Hà Nội, tháng 11/2006 Tác giả
  4. Chương 1: Giới thiệu CHƯƠNG 1: GIỚI THIỆU 1.1 CẤU TRÚC BỘ VI XỬ LÝ Phần này trình bày kiến trúc bên trong của bộ Vi xử lý 8088 và bộ Vi xử lý Pentium IV 1.1.1 Sơ đồ kiến trúc bộ Vi xử lý 8088 Hình 1.1: Kiến trúc bên trong của bộ Vi xử lý 8088 Bộ vi xử lý 8088 được chia làm 2 khối chính: Khối giao diện bus (BIU) và khối thực hiện lệnh (EU). Các thành phần bên trong của CPU giao tiếp với nhau thông qua các bus trong. Giữa khối giao diện bus và khối thực hiện lệnh được liên hệ với nhau thông qua hàng đợi dữ liệu và hệ thống bus trong. 3
  5. Chương 1: Giới thiệu 1.1.2 Chức năng các thành phần 1. Thành phần điều khiển Bus (Bus Control Logic) Điều khiển các loại tín hiệu trên các bus bao gồm: các tín hiệu trên bus địa chỉ (20 bit), các tín hiệu trên bus dữ liệu (8 bit) và các tín hiệu trên bus điều khiển. Ngoài ra, thành phần này còn làm nhiệm vụ hỗ trợ giao tiếp giữa hệ thống bus trong và bus ngoài. Hệ thống bus ngoài là hệ thống bus kết nối giữa các thành phần của hệ vi xử lý với nhau: CPU, Bộ nhớ trong và Thiết bị vào/ra. 2. Hàng đợi lệnh (Prefetch Queue) Chứa mã lệnh chờ được xử lý. Hàng đợi lệnh có kích thước 4 byte đối với 8088 và 6 byte đối với 8086. Sở dĩ có điều này là vì hàng đợi lệnh phải có kích thước có thể chứa được ít nhất một lệnh có độ dài bất kỳ (dài nhất) của bộ vi xử lý. Mà tập lệnh của 8086 chứa các lệnh có độ dài từ 1-6 byte. Hàng đợi lệnh làm việc theo cơ chế FIFO (First In First Out), nghĩa là lệnh nào được đưa vào hàng đợi lệnh trước sẽ được xử lý trước 3. Khối điều khiển (Control Unit) Khối điều khiển có hai chức năng chính: giải mã lệnh và tạo xung điều khiển . Đầu vào của khối điều khiển là mã lệnh được đọc từ hàng đợi lệnh và đầu ra là các xung điều khiển gửi đến các bộ phận khác nhau bên trong bộ vi xử lý. Quá trình này được thực hiện nhờ hai mạch giải mã lệnh và mạch tạo xung. 4. Khối số học và logic (Arithmetic Logic Unit) Khối số học và logic có chức năng thực hiện các phép tính toán như phép cộng, trừ hay các phép logic như AND, OR, NOT. Đầu vào ALU là hai thanh ghi tạm thời chứa dữ liệu của cho phép tính được lấy từ bus dữ liệu. Kết quả đầu ra của ALU được đưa trở lại bus dữ liệu và phản ánh vào thanh ghi cờ (flag register). 5. Các thanh ghi đoạn (Segment registers) Ta hãy thử xem đoạn chương trình được viết bằng ngôn ngữ C sau: int Cong(int a, int b) { Return (a+b); } void main() { int x=3; int y=4; printf(“Tong: %d”, Cong(x,y)); } Trong chương trình trên có 2 phần: phần khai báo và phần lệnh của chương trình. Trong phần lệnh có thể có lời gọi chương trình con. Như vậy để thực hiện được một chương trình (dạng .EXE) thì người ta cần ít nhất 3 đoạn bộ nhớ (segment). Đoạn dành chứa dữ liệu được khai báo, đoạn chứa mã chương trình, đoạn ngăn 4
  6. Chương 1: Giới thiệu xếp phục vụ cho các lời gọi chương trình con. Mỗi đoạn có kích thước 64KB. Khi chương trình được thực hiện, mỗi đoạn bộ nhớ này được trỏ bởi các thanh ghi đoạn. Đó là: - Thanh ghi đoạn mã CS (Code Segment): trỏ đến đoạn bộ nhớ chứa mã của chương trình. - Thanh ghi đoạn dữ liệu DS (Data Segment): trỏ đến đoạn bộ nhớ chứa các khai báo của chương trình. - Thanh ghi đoạn ngăn xếp SS (Stack Segment): trỏ đến đoạn bộ nhớ dành cho stack. - Ngoài ra, trong nhiều trường hợp người ta sử dụng thêm một đoạn dữ liệu phụ dùng trong trường hợp các dữ liệu cần khai báo vượt quá kích thước cho phép của 1 đoạn (các khai báo mảng, file ). Khi đó thanh ghi đoạn dữ liệu phụ ES (Extra Segment) sẽ trỏ đến đoạn này 6. Các thanh ghi con trỏ và chỉ số (pointers and index registers) Các thanh ghi con trỏ và chỉ số là các thanh ghi 16 bit. Chúng thường được lưu địa chỉ lệch (offset) và kết hợp với thanh ghi đoạn tương ứng tạo thành cặp thanh ghi chứa địa chỉ xác định của mã lệnh, mục dữ liệu, hoặc mục dữ liệu lưu trong stack. Nhờ vào cặp thanh ghi này, người ta có thể tính đia chỉ vật lý cụ thể theo công thức sau: Địa chỉ vật lý = địa chỉ đoạn * 16 + địa chỉ lệch Dưới đây là các thanh ghi con trỏ và chỉ số: - Thanh ghi con trỏ lệnh IP (Instruction Pointer): trỏ vào lệnh kế tiếp sẽ được thực hiện nằm trong đoạn mã do con trỏ CS trỏ tới. Địa chỉ đầy đủ của lệnh là CS:IP. - Thanh ghi con trỏ cơ sở BP (Base Pointer): trỏ vào một mục dữ liệu nằm trong đoạn ngăn xếp SS. Địa chỉ đầy đủ của mục dữ liệu là CS:IP. - Thanh ghi con trỏ ngăn xếp SP (Stack Pointer): trỏ vào đỉnh hiện thời ngăn xếp nằm trong đoạn ngăn xếp SS. Địa chỉ đầy đủ của đỉnh ngăn xếp là SS:SP. - Thanh ghi chỉ số nguồn SI (Source Index): trỏ vào một mục dữ liệu trong đoạn DS. Địa chỉ đầy đủ của mục dữ liệu là DS:SI. - Thanh ghi chỉ số đích DI (Destination Index): trỏ vào một mục dữ liệu trong đoạn DS. Địa chỉ đầy đủ của mục dữ liệu là DS:DI. 7. Các thanh ghi đa năng (Multi-purposed registers) Bộ xử lý 8088 có 4 thanh ghi đa năng 16 bit đó là: AX, BX, CX và DX. Các thanh ghi này cũng có thể được tách ra thành 2 nửa gồm 8 bít cao (nửa cao) gồm bít thứ 8 đến bít thứ 15 và 8 bít thấp (nửa thấp) gồm các bít thứ 0 đến 7. Các nửa thanh ghi này có thể được sử dụng một cách độc lập để chứa các dữ liệu 8 bít. Đó là các nửa thanh ghi: AH và AL, BH và BL, CH và CL, và DH và DL. Trong đó AH, BH, CH, DH la các nửa cao còn AL,BL, CL, DL là các nửa thấp. Ngoài chức năng “đa năng”, mỗi thanh ghi 16 bít thường được sử dụng trong các tác vụ đặc biệt, giống như tên của chúng: - AX (Accumulator) thanh chứa: các kết quả của các phép toán thường được lưu vào thanh ghi này. Ngoài ra, AX còn là toán hạng ẩn cho 1 số phép toán như nhân (AX là thừa số) hoặc chia (AX là số bị chia). 5
  7. Chương 1: Giới thiệu - BX (Base) thanh ghi cơ sở: thường được dùng để chứa các địa chỉ cơ sở. - CX (Count) bộ đếm: CX thường dung để chứa số lần lặp trong trường hợp dùng lệnh LOOP. Ngoài ra, CL còn chứa số lần dịch chuyển, quay trái, quay phải của các toán hạng. - DX (Data) thanh ghi dữ liệu: DX thường được chứa địa chỉ offset của xâu kí tự khi có các thao tác nhập vào xâu hoặc in xâu. DX (cùng với AX) còn tham gia chứa kết quả của phép nhân các số 16 bit hoặc làm số bị chia cho phép chia các số 16 bit. Ngoài ra, DX còn dùng để chứa địa chỉ của các cổng vào/ra trong trường hợp thực hiện các lệnh IN hoặc OUT. 8. Thanh ghi cờ (flag register) Thanh ghi cờ là thanh ghi lưu trữ trạng thái của CPU tại mỗi thời điểm. Thanh ghi cờ có 16 bít, trong đó có 7 bít dự trữ cho tương lai (CPU 8088 chưa dùng đến các bít này). Còn lại 9 bít và mỗi bít tương ứng là một cờ. Kết hợp các lệnh nhảy có điều kiện (conditional jump) với các cờ này, người lập trình dễ dàng hơn 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 x x x x OF DF IF TF SF ZF x AF x PF x CF Hình 1.2: Cấu trúc của thanh ghi cờ của CPU 8088. Các bit được đánh dấu x là các cờ chưa được dùng đến. - Cờ CF (Carry Flag): cờ nhớ CF=1 khi có nhớ hoặc trừ có mượn từ bít có trọng số cao nhất (Most Significant Bit). Ngoài ra, cờ CF=1 trong trường hợp khi thao tác với file hoặc thư mục gây ra lỗi như các lỗi tạo, xóa file và thư mục. - Cờ PF (Parity Flag): cờ chẵn lẻ PF=1 khi tổng số các bít bằng 1 trong kết quả của phép tính là một số chẵn. - Cờ AF (Auxiliary Carry Flag): cờ nhớ phụ AF =1 khi có nhớ từ bít thứ 4 sang bít thứ 5 hoặc có mượn từ bít 5 sang bít thứ 4 trong biểu diễn BCD của 1 số. - Cờ ZF (Zero Flag): cờ Zero ZF=1 khi kết quả tính toán bằng 0. - Cờ SF (Sign Flag): cờ dấu SF=1 khi kết quả tính toán là một số âm. - Cờ TF (Trap Flag): cờ bẫy TF=1 khi CPU đang làm việc ở chế độ chạy từng lệnh. Chế độ này được sử dụng cần thiết khi tìm lỗi (defect) và gỡ lỗi (debug) chương trình. - Cờ IF (Interrupt enable Flag): cờ cho phép ngắt IF=1, cho phép tác động đến yêu cầu ngắt che được (maskable interrupts). - Cờ DF (Direction Flag): cờ hướng DF=1 khi CPU xử lý chuỗi kí tự theo thứ tự từ phải sang trái. - Cờ OF (Overflow Flag): cờ tràn OF=1 khi kết quả là một số bù hai vượt ra ngoài giới hạn biểu diễn dành cho nó. 9. Hệ thống bus trong (Internal bus system) 6
  8. Chương 1: Giới thiệu Hệ thống bus bên trong của CPU 8088 bao gồm 3 loại: - Bus dữ liệu: 16 bít, cho phép di chuyển 2 byte dữ liệu tại một thời điểm - Bus địa chỉ: 20 bít, có thể địa chỉ hóa được 220 bytes và vì thế không gian địa chỉ nhớ của CPU 8088 là 1MB. - Bus điều khiển: truyền tải các tín hiệu điều khiển như RD, WR 1.2 MỘT SỐ CHỨC NĂNG CỦA NGẮT 21H Phần này trình bày các hàm thông dụng của ngắt 21h. Đó là các hàm thao tác vào/ra đối với kí tự, chuỗi ký tự, file, thư mục, kết thúc chương trình và trả lại quyền điều khiển cho Hệ điều hành DOS. Hàm 01: đọc 1 kí tự (có hiện) từ bàn phím Input: AH=01 Output: AL= mã ASCII của ký tự AL=0 nếu gõ vào phím chức năng. Hàm 02: hiện 1 kí tự lên màn hình Input: AH=02 DL= mã ASCII của ký tự cần hiển thị Output: Hàm 08: đọc 1 kí tự (không hiện) từ bàn phím Input: AH=08 Output: AL= mã ASCII của ký tự AL=0 nếu gõ vào phím chức năng. Hàm 09: hiện xâu kí tự kết thúc bởi ‘$’ lên màn hình Input: AH = 09 DX = địa chỉ offset của xâu kí tự Hàm 0Ah: đọc xâu kí tự từ bàn phím Input: AH = 09 DX = địa chỉ offset của vùng đệm chứa xâu kí tự Output: DX = địa chỉ offset của xâu kí tự Hàm 39h: tạo thư mục Input: AH = 39h DX = địa chỉ offset của tên thư mục Output: ƒ Nếu thành công, thư mục được tạo ra ƒ Nếu không thành công, CF=1 và AX= mã lỗi. Hàm 3Ah: xóa thư mục Input: AH = 3Ah DX = địa chỉ offset của tên thư mục 7
  9. Chương 1: Giới thiệu Output: ƒ Nếu thành công, thư mục được xóa ƒ Nếu không thành công, CF=1 và AX=mã lỗi. Hàm 3Ch: tạo file Input: AH = 3Ch DX = địa chỉ offset của tên file CX = thuộc tính file Output: ƒ Nếu thành công, file được tạo ra, CF=0 và AX= thẻ file (file handle) ƒ Nếu không thành công, CF=1 và AX= mã lỗi. Thuộc tính file được định nghĩa như sau: 00h: file bình thường (plain old file) 01h: file chỉ đọc (Read Only) 02h: file ẩn (Hidden from searches) 04h: file hệ thống (system) 08h: thuộc tính cho nhãn đĩa. 10h: thuộc tính cho thư mục con. Hàm 3Dh: mở file Input: AH = 3Dh AL = mode Output: ƒ Nếu thành công, file được tạo ra, CF=0 và AX= thẻ file (file handle) ƒ Nếu không thành công, CF=1 và AX= mã lỗi. Hàm 3Eh: đóng file Input: AH = 3Eh BX = thẻ file Output: ƒ Nếu thành công, file được đóng lại và CF=0 ƒ Nếu không thành công, CF=1 và AX= mã lỗi. Hàm 3Fh: đọc từ file Input: AH = 3Fh DS:DX = địa chỉ offset của vùng đệm CX = số byte cần đọc BX = thẻ file Output: ƒ Nếu thành công, CF=0 và AX= số byte đã đọc được ƒ Nếu không thành công, CF=1 và AX= mã lỗi. 8
  10. Chương 1: Giới thiệu Hàm 40h: ghi vào file Input: AH = 40h DS:DX = địa chỉ offset của vùng đệm CX = số byte cần ghi BX = thẻ file Output: ƒ Nếu thành công, file được ghi và CF=0. ƒ Nếu không thành công, CF=1 và AX= mã lỗi. Hàm 41h: xóa file Input: AH = 41h DX = địa chỉ offset của tên file Output: ƒ Nếu thành công, file bị xóa ƒ Nếu không thành công, CF=1 và AX=mã lỗi. Hàm 4Ch: kết thúc chương trình Input: AH = 4Ch Output: Kết thúc chương trình, trả lại quyền điều khiển cho hệ điều hành. 1.3 GIỚI THIỆU VỀ TẬP LỆNH CỦA 8088 Phần này giới thiệu về một số lệnh thông dụng của bộ vi xử lý 8088. Để tiện dụng cho người học lập trình, các lệnh được chia thành các nhóm lệnh. 1.3.1 Nhóm lệnh di chuyển dữ liệu 1. Lệnh: MOV Chức năng: chuyển giá trị từ toán hạng nguồn vào toán hạng đích Cú pháp: MOV Dst,src Ví dụ Reg1,reg2 Mov AX,BX Reg, data Mov AH,9Fh Mem,reg Mov [BX],AL Reg,mem Mov CL,[3456h] Mem,data Mov PTR [BX], FFh Chú ý: Data chỉ nằm ở phía toán hạng nguồn Hai toán hạng dst và src không thể đồng thời là hai ô nhớ. 2. Lệnh: PUSH Chức năng: chuyển giá trị của toán hạng nguồn vào đỉnh ngăn xếp 9
  11. Chương 1: Giới thiệu Cú pháp: PUSH Src Ví dụ Reg16 push AX Mem16 push x Segreg push DS Chú ý: Toán hạng nguồn luôn có kích thước 16 bít Toán hạng nguồn không thể là data (hằng số) 3. Lệnh: POP Chức năng: Lấy giá trị của đỉnh ngăn xếp đưa vào toán hạng đích Cú pháp: POP Dst Ví dụ Reg16 Pop AX Mem16 Pop x Segreg Pop DS Chú ý: Toán hạng nguồn luôn có kích thước 16 bít Toán hạng đích không thể là data (hằng số) 4. Lệnh: PUSHF Chức năng: chuyển giá trị của thanh ghi cờ vào đỉnh ngăn xếp Cú pháp: PUSHF 5. Lệnh: POPF Chức năng: lấy giá trị đỉnh ngăn xếp lưu vào thanh ghi cờ. Cú pháp: POPF Chú ý: hai lệnh PUSHF và POPF được hệ thống tự động gọi khi chương trình có lệnh gọi ngắt hoặc gọi chương trình con. 6. Lệnh: XCHG Chức năng: Hoán vị giá trị giữa toán hạng nguồn và đích 10
  12. Chương 1: Giới thiệu Cú pháp: XCHG Dst,src Ví dụ Reg,Reg XCHG AX,BX Reg,Mem XCHG AL,[BX] Mem,Reg XCHG [BX],AH 7. Lệnh: IN Chức năng: Đọc giá trị từ 1 cổng vào thanh ghi AL hoặc AX. Cú pháp: IN AL, địa chỉ cổng (8 bít) VD: IN AL,2Eh IN AX, địa chỉ cổng (16 bít) VD: IN AX,2EBEh 8. Lệnh: OUT Chức năng: Chuyển giá trị 1 byte hoặc 1 từ từ thanh ghi AL hoặc AX ra cổng. Cú pháp: OUT địa chỉ cổng (8 bít), AL VD: OUT 2Eh,AL IN địa chỉ cổng (16 bít),AX VD: OUT 2EBEh,AX 1.3.2 Nhóm các lệnh tính toán số học Phần này giới thiệu về các lệnh lien quan đến tính toán số học như các lệnh: cộng, trừ, nhân, chia, so sánh. Đồng thời, cũng giải thích sự tác động của các lệnh này lên các bit của thanh ghi cờ. 1. Lệnh: ADD Chức năng: cộng toán hạng nguồn và toán hạng đích, lưu kết quả vào toán hạng đích. Cú pháp: ADD Dst,src Ví dụ Reg1,reg2 Add AX,BX Reg, data Add AH,19h Mem,reg Add [BX],AL Reg,mem Add CL,[3456h] Mem,data Add [BX], 1Fh Chú ý: - Không cộng trực tiếp 2 biến ô nhớ với nhau - Toán hạng đích không thể là hằng số - Kết quả có thể tác động đến các cờ: OF, SF, ZF, AF, PF, CF. 11
  13. Chương 1: Giới thiệu 2. Lệnh: INC Chức năng: Tăng giá trị của toán hạng đích lên 1. Cú pháp: INC Dst Ví dụ Reg Inc CX Mem Inc x Chú ý: - Toán hạng đích không thể là hằng số - Kết quả có thể tác động đến các cờ: OF, SF, ZF, AF, PF, CF. 3. Lệnh: SUB Chức năng: Trừ toán hạng đích cho toán hạng nguồn, lưu kết quả vào toán hạng đích. Cú pháp: SUB Dst,src Ví dụ Reg1,reg2 Sub AX,BX Reg, data Sub AH,19h Mem,reg Sub [BX],AL Reg,mem Sub CL,[3456h] Mem,data Sub [BX], 1Fh Chú ý: - Không trừ trực tiếp 2 biến ô nhớ với nhau - Toán hạng đích không thể là hằng số - Kết quả có thể tác động đến các cờ: OF, SF, ZF, AF, PF, CF. 4. Lệnh: DEC Chức năng: Giảm giá trị của toán hạng đích đi 1. Cú pháp: DEC Dst Ví dụ Reg DEC CX Mem DEC [BX] Chú ý: - Toán hạng đích không thể là hằng số - Kết quả có thể tác động đến các cờ: OF, SF, ZF, AF, PF, CF. 5. Lệnh: MUL 12
  14. Chương 1: Giới thiệu Chức năng: Nhân nội dung của toán hạng AX hoặc AL với nội dung của toán hạng nguồn. Giá trị của hai toán hạng đều là dạng không dấu. Kết quả sẽ được cất như sau: • Nếu là phép nhân hai toán hạng 8 bít thì kết quả sẽ được đặt trong thanh ghi AX. • Nếu là phép nhân hai toán hạng 16 bít thì kết quả sẽ được đặt trong thanh nghi DX:AX. Cú pháp: MUL Src Ví dụ Reg MUL CL Mem MUL x Chú ý: - Toán hạng nguồn không thể là hằng số - Kết quả có thể tác động đến các cờ: OF, ZF,CF. 6. Lệnh: DIV Chức năng: Chia giá trị của thanh ghi AX hoặc DX:AX cho nội dung của toán hạng nguồn. Giá trị của hai toán hạng đều là dạng không dấu. Kết quả sẽ được cất như sau: • Nếu số bị chia là toán hạng 16 bít thì phần thương sẽ được đặt trong thanh ghi AL và phần dư sẽ được đặt trong thanh ghi AH. • Nếu số bị chia là toán hạng 32 bít thì phần thương sẽ được đặt trong thanh ghi AX và phần dư sẽ được đặt trong thanh ghi DX. Cú pháp: DIV Src Ví dụ Reg DIV CL Mem DIV [BX] Chú ý: - Toán hạng nguồn không thể là hằng số - Kết quả có thể tác động đến các cờ: OF, CF. 7. Lệnh: CMP Chức năng: So sánh giá trị của toán hạng đích và toán hạng nguồn. Nội dung của hai toán hạng đều không thay đổi sau lệnh này. Thực chất, lệnh này thực hiện bằng cách lấy toán hạng đích trừ đi toán hạng nguồn. Kết quả phản ánh lên thanh ghi cờ mà không được lưu lại. Cú pháp: CMP Dst,src Ví dụ Reg1,reg2 Cmp AX,BX Reg, data Cmp AH,9Fh Mem,reg Cmp [BX],AL 13
  15. Chương 1: Giới thiệu Reg,mem Cmp CL,[3456h] Mem,data Cmp [BX], FFh Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Kết quả có thể tác động đến các cờ: OF, SF,ZF,AF,PF,CF. 1.3.3 Nhóm các lệnh thao tác bít Phần này giới thiệu về các lệnh liên quan đến các lệnh logic, các lệnh dịch chuyển bít, các lệnh quay vòng các bit. 1. Lệnh: NOT • Chức năng: Đảo giá trị từng bít một của toán hạng đích. Cú pháp: NOT Dst Ví dụ Reg NOT CL Mem NOT [BX] Chú ý: - Toán hạng đích không thể là hằng số 2. Lệnh: AND Chức năng: Thực hiện phép VÀ logic giữa hai toán hạng. Kết quả đặt ở trong toán hạng đích. Cú pháp: AND Dst,src Ví dụ Reg1,reg2 And AX,BX Reg, data And AH,9Fh Mem,reg And [BX],AL Reg,mem And CL,[3456h] Mem,data And [BX], FFh Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Kết quả có thể tác động đến các cờ: SF,ZF, PF. 3. Lệnh: OR Chức năng: Thực hiện phép HOẶC logic giữa hai toán hạng. Kết quả đặt ở trong toán hạng đích. Cú pháp: 14
  16. Chương 1: Giới thiệu OR Dst,src Ví dụ Reg1,reg2 And AX,BX Reg, data And AH,9Fh Mem,reg And [BX],AL Reg,mem And CL,[3456h] Mem,data And [BX], FFh Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Kết quả có thể tác động đến các cờ: SF,ZF, PF. 4. Lệnh: XOR Chức năng: Thực hiện phép EXCLUSIVE OR logic giữa hai toán hạng (các bít của kết quả có giá trị là 1 nếu hai bít tương ứng của 2 toán hạng là khác nhau). Kết quả đặt ở trong toán hạng đích. Cú pháp: XOR Dst,src Ví dụ Reg1,reg2 Xor AX,BX Reg, data Xor AH,9Fh Mem,reg Xor [BX],AL Reg,mem Xor CL,[3456h] Mem,data Xor [BX], FFh Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Kết quả có thể tác động đến các cờ: SF,ZF, PF. 5. Lệnh: TEST Chức năng: So sánh nội dung của hai toán hạng bằng cách thực hiện lệnh AND giữa hai toán hạng mà không lưu lại kết quả. Kết quả tác động đến thanh ghi cờ. Cú pháp: TEST Dst,src Ví dụ Reg1,reg2 Test AX,BX Reg, data Test AH,9Fh Mem,reg Test [BX],AL Reg,mem Test CL,[3456h] Mem,data Test [BX], FFh Chú ý: 15
  17. Chương 1: Giới thiệu - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Kết quả có thể tác động đến các cờ: SF,ZF, PF. 6. Lệnh: SHL/SAL Chức năng: Dịch trái các bít của toán hạng đích đi COUNT lần. Trong đó CL=COUNT. Cú pháp: SHL/SAL Dst,COUNT Ví dụ Reg SHL AL,CL Mem SHL [BX],CL Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Khi Count=1 thì có thể đặt 1 trực tiếp vào toán hạng, SHL/SAL Dst,1. - Kết quả có thể tác động đến các cờ: OF,SF,ZF, PF,CF. 7. Lệnh: SHR Chức năng: Dịch phải các bít của toán hạng đích đi COUNT lần. Trong đó CL=COUNT. Cú pháp: SHR Dst,COUNT Ví dụ Reg SHR AL,CL Mem SHR [BX],CL Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Khi Count=1 thì có thể đặt 1 trực tiếp vào toán hạng, SHR Dst,1. - Kết quả có thể tác động đến các cờ: OF,SF,ZF, PF,CF. 8. Lệnh: ROR Chức năng: Quay vòng phải các bít của toán hạng đích đi COUNT lần. Trong đó CL=COUNT. Trong mỗi lần quay, giá trị bít thấp nhất vừa chuyển vào thanh ghi cờ CF đồng thời chuyển vào bít cao nhất. Cú pháp: ROR Dst,COUNT Ví dụ Reg ROR AL,CL Mem ROR [BX],CL Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Khi Count=1 thì có thể đặt 1 trực tiếp vào toán hạng, ROR Dst,1. - Kết quả có thể tác động đến các cờ: OF, CF. 16
  18. Chương 1: Giới thiệu 9. Lệnh: ROL Chức năng: Quay vòng trái các bít của toán hạng đích đi COUNT lần. Trong đó CL=COUNT. Trong mỗi lần quay, giá trị bít cao nhất vừa chuyển vào thanh ghi cờ CF đồng thời chuyển vào bít thấp nhất. Cú pháp: ROL Dst,COUNT Ví dụ Reg ROL AL,CL Mem ROL [BX],CL Chú ý: - Hai toán hạng nguồn và đích không thể đồng thời là hằng số hoặc ô nhớ. - Khi Count=1 thì có thể đặt 1 trực tiếp vào toán hạng, ROL Dst,1. - Kết quả có thể tác động đến các cờ: OF, CF. 1.3.4 Nhóm các lệnh làm việc với xâu kí tự 1. Lệnh: MOVSB( hay MOVSW) Chức năng: Chuyển một xâu kí tự theo từng byte (hay theo từng từ) từ một vùng nhớ nguồn sang vùng nhớ đích. Trong đó DS:SI trỏ đến xâu kí tự nguồn và ES:DI trỏ đến xâu kí tự đích. Sau mỗi lần chuyển 1 byte (hoặc 1 từ) thì giá trị của SI và DI tự động tăng lên 1 (hoặc 2) nếu cờ hướng DF=0, hoặc giảm đi 1 (hoặc 2) nếu cờ hướng DF=1. Cú pháp: MOVSB Hoặc MOVSW 2 Lệnh: CMPSB (CMPSW) Chức năng: So sánh hai xâu kí tự theo từng byte (hay theo từng từ) nằm ở hai vùng nhớ. Trong đó, DS:SI và ES:DI trỏ đến hai xâu kí tự. Sau mỗi lần so sánh từng byte (hoặc từng từ) thì giá trị của SI và DI tự động tăng lên 1 (hoặc 2) nếu cờ hướng DF=0, hoặc giảm đi 1 (hoặc 2) nếu cờ hướng DF=1. Cú pháp: CMPSB Hoặc CMPSW - Kết quả có thể tác động đến các cờ: OF, SF,ZF,AF,PF, CF. 3 Lệnh: LODSB (LODSW) Chức năng: Chuyển nội dung theo từng byte (hay theo từng từ) của vùng nhớ trỏ bởi DS:SI vào thanh ghi AL (hoặc AX). Sau mỗi lần chuyển từng byte (hoặc từng từ) thì giá trị của SI tự động tăng lên 1 (hoặc 2) nếu cờ hướng DF=0, hoặc giảm đi 1 (hoặc 2) nếu cờ hướng DF=1. Cú pháp: LODSB Hoặc 17
  19. Chương 1: Giới thiệu LODSW 4. Lệnh: STOSB (STOSW) Chức năng: Chuyển nội dung theo từng byte (hay theo từng từ) của thanh ghi AL (hoặc AX) vùng nhớ trỏ bởi ES:DI. Sau mỗi lần chuyển từng byte (hoặc từng từ) thì giá trị của DI tự động tăng lên 1 (hoặc 2) nếu cờ hướng DF=0, hoặc giảm đi 1 (hoặc 2) nếu cờ hướng DF=1. Cú pháp: STOSB Hoặc STOSW 1.3.5 Nhóm các lệnh nhảy Nhóm các lệnh nhảy bao gồm 4 nhóm nhỏ: các lệnh nhảy không điều kiện, các lệnh nhảy có điều kiện, các lệnh lặp và các lệnh gọi ngắt mềm. a. Các lệnh nhảy không điều kiện 1. Lệnh CALL Chức năng: Gọi chương trình con Cú pháp: CALL Địa chỉ Nhãn Tên chương trình con Reg Mem 2. Lệnh RET Chức năng: Quay trở về chương trình đã gọi chương trình con Cú pháp: RET 3. Lệnh JMP Chức năng: Lệnh nhảy không điều kiện Cú pháp: JMP Địa chỉ Nhãn Tên chương trình con Reg Mem Chú ý: Bước nhảy của lệnh nhảy này nằm trong một đoạn 64KB. b. Các lệnh nhảy có điều kiện 18
  20. Chương 1: Giới thiệu Chức năng: Lệnh nhảy không điều kiện Cú pháp: Lệnh Toán hạng Giải thích JE/JZ Nhãn Nhảy nếu ZF=1 hoặc 2 toán hạng của phép so sánh bằng nhau JNE/JNZ Nhãn Nhảy nếu ZF=0 hoặc 2 toán hạng của phép so sánh khác nhau JL/JNGE Nhãn Nhảy nếu toán hạng bên trái nhỏ hơn toán hạng bên phải của phép so sánh (CF=1) JB/JNAE/JC Nhãn Nhảy nếu toán hạng bên trái nhỏ hơn toán hạng bên phải của phép so sánh (SF OF hoặc ZF=0) JBE/JNA Nhãn Nhảy nếu toán hạng bên trái nhỏ hơn hoặc bằng toán hạng bên phải của phép so sánh (Cờ CF=1 hoặc SF=1) JG/JNLE Nhãn Nhảy nếu toán hạng bên trái lớn hơn toán hạng bên phải của phép so sánh. JA/JNBE Nhãn Nhảy nếu toán hạng bên trái lớn hơn hoặc bằng toán hạng bên phải của phép so sánh (Cờ CF=0 hoặc ZF=0) JGE/JNL Nhãn Nhảy nếu toán hạng bên trái lớn hơn hoặc bằng toán hạng bên phải của phép so sánh (Cờ SF=OF). JAE/JNB/JNC Nhãn Nhảy nếu toán hạng bên trái lớn hơn hoặc bằng toán hạng bên phải của phép so sánh (Cờ CF=0). JP/JPE Nhãn Nhảy nếu cờ parity là chẵn (PF=1) JNP/JPO Nhãn Nhảy nếu cờ parity là lẻ (PF=0) JO Nhãn Nhảy nếu tràn (OF=1) JNO Nhãn Nhảy nếu không tràn (OF=0) JS Nhãn Nhảy nếu cờ dấu =1 (SF=1) JNS Nhãn Nhảy nếu cờ dấu =0 (SF=0) JCXZ Nhãn Nhảy nếu giá trị của thanh ghi CX =0. Chú ý: Các bước nhảy của các lệnh nhảy có điều kiện không vượt quá 128 byte. c. Các lệnh lặp Chức năng: Thực hiện vòng lặp cho đến khi điều kiện thỏa mãn. Cú pháp: 19
  21. Chương 1: Giới thiệu Lệnh Toán hạng Giải thích LOOP Nhãn Lặp khối lệnh từ Nhãn đến LOOP cho đến khi giá trị của CX=0. Sau mỗi lần thực hiện vòng lặp giá trị của CX tự động giảm đi 1. LOOPZ/LOOPE Nhãn Lặp khối lệnh từ Nhãn đến LOOPZ hoặc LOOPE cho đến khi giá trị của CX=0 và cờ ZF=1. Sau mỗi lần thực hiện vòng lặp giá trị của CX tự động giảm đi 1. LOOPNZ/LOOPNE Nhãn Lặp khối lệnh từ Nhãn đến LOOPZ hoặc LOOPE cho đến khi giá trị của CX<>0 và cờ ZF=0. Sau mỗi lần thực hiện vòng lặp giá trị của CX tự động giảm đi 1. d. Các lệnh gọi ngắt mềm. 1. Lệnh: INT Chức năng: Thực hiện ngắt mềm. Cú pháp: INT số hiệu ngắt (dạng hexa) Các cờ bị tác động: IF, TF 2. Lệnh: IRET Chức năng: Trở về chương trình chính (chương trình gọi nó) sau khi thực hiện chương trình con phục vụ ngắt. Cú pháp: IRET Các cờ bị tác động: OF,SF,ZF,AF,PF,CF. 1.3.6 Các lệnh điều khiển khác Phần này giới thiệu về một số lệnh điều khiển: thao tác với thanh ghi cờ, lệnh HLT và NOP. 1. Lệnh: CLC Chức năng: Xóa giá trị cờ CF về 0 (CF=0). Cú pháp: CLC Các cờ bị tác động: CF. 2. Lệnh: CMC Chức năng: Đảo giá trị hiện thời của cờ CF. Cú pháp: CMC Các cờ bị tác động: CF. 3. Lệnh: STC Chức năng: Đặt cờ CF=1. Cú pháp: STC Các cờ bị tác động: CF. 4. Lệnh: CLD Chức năng: Xoá giá trị cờ DF về 0 (DF=0). Cú pháp: CLD 20
  22. Chương 1: Giới thiệu Các cờ bị tác động: DF. 5. Lệnh: STD Chức năng: Đặt giá trị cờ DF bằng 1 (DF=1). Cú pháp: STD Các cờ bị tác động: DF. 6. Lệnh: CLI Chức năng: Xoá giá trị cờ IF về 0 (IF=0). Cấm các ngắt cứng hoạt động, trừ các ngắt không che. Cú pháp: CLI Các cờ bị tác động: IF. 7. Lệnh: STI Chức năng: Đặt giá trị cờ IF bằng 1 (IF=1). Cấm các ngắt cứng hoạt động. Cú pháp: STI Các cờ bị tác động: IF. 8. Lệnh: HLT Chức năng: dừng máy. Cú pháp: HLT 9 Lệnh: NOP Chức năng: Không thực hiện gì Cú pháp: NOP Chú ý: Lệnh NOP rất có ý nghĩa khi CPU thực hiện các chu kỳ đợi và được xen vào một số chu kỳ lệnh trong quá trình thực hiện lệnh theo cơ chế pipeline. 1.4 TÓM TẮT Chương này đã trang bị cho sinh viên những kiến thức cơ bản để chuẩn bị cho các phần kế tiếp: Lập trình bằng Hợp ngữ. Chương này có ba phần chính: - Giới thiệu về cấu trúc bộ vi xử lý 8088. Đây là bộ vi xử lý khá đơn giản và dễ hiểu về mặt kiến trúc. Sơ đồ kiến trúc bao gồm hai khối chính: Khối giao diện BUS và khối thực hiện lệnh. Đồng thời, phần này cũng đề cập chi tiết chức năng của các thành phần bên trong bộ vi xử lý. - Một số chức năng của ngắt 21h. Đây là một ngắt quan trọng nhất của hệ điều hành MS DOS. Ngắt 21h cung cấp nhiều các chức năng khác nhau cho các nhà lập trình hệ thống. Phần này đã giới thiệu 14 chức năng thông dụng của ngắt 21h. Từ các chức năng phục vụ vào ra đối với kí tự, xâu kí tự cho đến các chức năng phục vụ cho thao tác các file và thư mục. - Tập lệnh của 8088 dạng hợp ngữ. Phần này trình bầy về một số lệnh thông dụng trong tập lệnh của 8088. Để tiện lợi cho người học lập trình ở các phần sau chúng tôi phân chia các lệnh ra thành các nhóm lệnh. Mỗi nhóm lệnh bao gồm một số lệnh thực hiện 1 số chức năng. 21
  23. Chương 1: Giới thiệu 1.5 CÂU HỎI VÀ BÀI TẬP Dưới đây là các câu hỏi dạng lựa chọn. Sinh viên sẽ lựa chọn một và chỉ một phương án trả lời đúng nhất cho mỗi câu hỏi Câu 1: Khối giao diện bus (BIU) và khối thực hiện lệnh (EU) giao tiếp với nhau thông qua: A. Hàng đợi lệnh B. Hệ thống bus trong C. Hệ thống bus trong và hàng đợi lệnh D. Không có liên hệ gì với nhau. Câu 2: Một trong những chức năng của thành phần điều khiển (CU) là: A. Tạo xung điều khiển B. Giải mã địa chỉ C. Chứa các thanh ghi D. Chứa các lệnh sắp được xử lý Câu 3: Các nhóm thanh ghi (16 bít) nào dưới đây có thể được chia làm 2 nửa thanh ghi 8 bít độc lập với nhau: A. AX,BX,DS B. SP,IP,CX C. ES, SS, DS D. DX,AX,CX Câu 4: Các nhóm thanh ghi (16 bít) nào dưới đây có thể được chia làm 2 nửa thanh ghi 8 bít độc lập với nhau: A. AX,BX,DS B. SP,IP,CX C. ES, SS, DS D. DX,AX,CX Câu 5: Các thanh ghi đoạn nào dưới đây trỏ vào các đoạn dữ liệu của cùng một chương trình .EXE: A. DS,ES B. DS,SS C. SS, SP D. DS Câu 6: Lệnh nào dưới đây là sai cú pháp: A. Mov AL,[BX+1] B. Mov [BX+1], AL C. Mov [AL],[BX+1] D. Mov [CX], 3 Câu 7: Lệnh nào dưới đây là sai cú pháp: 22
  24. Chương 1: Giới thiệu A. Mul [BX+5] B. Div [BX+5] C. Mul AL,3 D. Mul DX Câu 8: Lệnh nào dưới đây thực hiện việc xóa thanh ghi AX. A. AND AX,AX B. XOR AX,AX C. NOT AX D. MOV AX,[0000H] Câu 9: Lệnh nào dưới đây thường được sử dụng trước các lệnh nhảy có điều kiện. A. CMP B. MOV C. INT D. RET Câu 10 *: Xét đoạn chương trình dưới đây: Mov AH,08 Int 21h Mov DL,AL Inc DL Mov AH,02 Int 21h Đoạn chương trình trên thực hiện công việc nào dưới đây: A. Nhập một kí tự rồi in kí tự kế tiếp của kí tự đó ra màn hình B. Nhập một kí tự rồi in kí tự đó ra màn hình C. Nhập một xâu kí tự rồi in xâu đó ra màn hình D. Nhập 1 kí tự không hiện lên kí tự đó. 1.6 TÀI LIỆU THAM KHẢO 1. Văn Thế Minh. Kỹ thuật Vi xử lý. Nhà XB Giáo dục 1997. 2. Đặng Thành Phu. Turbo Assembler và Ứng dụng. NXB Khoa học và Kỹ thuật 1998. 3. Nguyễn Minh San. Cẩm nang Lập trình hệ thống (bản dịch). NXB Tổng cục Thống kê.2001. 23
  25. Chương 2: Lập trình bằng hợp ngữ CHƯƠNG 2: LẬP TRÌNH BẰNG HỢP NGỮ Chương này tìm hiểu về cách thức lập trình bằng hợp ngữ. Cách khai báo biến, hằng, khung chương trình, các cấu trúc lập trình, chương trình con và macro. 2.1 VIẾT VÀ THỰC HIỆN MỘT CHƯƠNG TRÌNH HỢP NGỮ 2.1.1 Cấu trúc lệnh và khai báo dữ liệu cho chương trình Một chương trình bao gồm tập hợp các lệnh và các khai báo dữ liệu sử dụng trong chương trình nhằm mục đích giải quyết một vấn đề. Phần này trình bày về cấu trúc một dòng lệnh và các qui tắc khai báo biến, hằng trong một chương trình Hợp ngữ. b. Cấu trúc dòng lệnh Dưới Đây là một dòng lệnh đầy đủ của chương trình Hợp ngữ,. Trên thực tế, một dòng lệnh cần tối thiểu hai trường: trường mã lệnh và trường toán hạng. Các trường khác không bắt buộc cần phải đầy đủ. Nhãn: Mã lệnh Toán hạng ; Chú giải Ví dụ: CongTiep: Add AL,[BX] ; cộng tiếp nội dung ô nhớ do thanh ghi BX ;trỏ tới vào AL Các giải thích cho các trường: Trường Mô tả Nhãn Nhãn có thể là nhãn dung cho lệnh nhảy, tên thủ tục hoặc tên biến. Khi chạy chương trình, các nhãn này sẽ được chương trình dịch gán cho 1 địa chỉ ô nhớ xác định. Nhãn có thể chứa từ 1 đến 32 kí tự, không chứa dấu cách và phải bắt đầu bằng các kí tự a,b,c ,z. Nhãn được kết thúc bằng dấu hai chấm (:) Mã lệnh Chứa lệnh thật (Opcode) hoặc giả lệnh (Pseudo-Opcode). Với các lệnh thật thì trường này chứa mã lệnh gợi nhớ (thường là dạng viết ngắn hoặc đầy đủ của một động từ trong tiếng Anh). Trong quá trình chạy chương trình, mã lệnh thật sẽ được chương trình dịch dịch ra mã máy. Đối với các giả lệnh, thì chương trình dịch không dịch chúng ra mã máy. Toán hạng Đối với các lệnh thật thì trường này chứa các toán hạng cho lệnh đó. Lệnh thật của 8088 có thể có 0,1 hoặc 2 toán hạng. Toán hạng trong các giả lệnh của 8088 thường chứa các thông tin khác nhau như xác định mô hình bộ nhớ sử dụng, kích thước ngăn xếp Chú giải Trường chú giải được bắt đầu bằng dấu chấm phẩy (;) để ghi những lời giải thích các lệnh của chương trình nhằm giúp cho người đọc chương trình một cách dễ hiểu hơn. 24
  26. Chương 2: Lập trình bằng hợp ngữ Khi thực hiện chương trình, phần giải thích (sau dấu ; ) sẽ bị bỏ qua. Ngoài ra, người ta cũng dùng trường này để ghi chu giải cho cả một đoạn chương trình, một chương trình con hay thâm chí là lời giới thiệu của bản quyền (copyright message) của người lập trình . Ví dụ: ; This program writen by X ; Last modified: 23/10/2006 ; This program is to delete a file c. Khai báo biến Các biến có thể được khai báo là ba kiểu dữ liệu khác nhau là: biến kiểu byte, biến kiểu từ (2 byte) và biến kiểu từ kép (4 byte). Biến kiểu byte: Dạng khai báo: Tên biến DB ? Ví dụ: X DB ? ;không có giá trị khởi đầu Y DB 4 ;giá trị khởi đầu là 4 Biến kiểu từ: Dạng khai báo: Tên biến DW ? Ví dụ: X DW ? ;không có giá trị khởi đầu Y DW 4 ; giá trị khởi đầu là 4. Biến kiểu từ kép: Dạng khai báo: Tên biến DD ? Ví dụ: X DD ? ;không có giá trị khởi đầu Y DD 4Fh ; giá trị khởi đầu là 4Fh Khai báo biến kiểu mảng Mảng là một dãy liên tiếp các phần tử có cùng kiểu byte hoặc từ. Ví dụ: A1 DB 1,2,3,4,5 Là khai báo một biến mảng tên là A1, gồm 5 byte trong bộ nhớ. Nội dung ô nhớ [A1] có giá trị là 1, [A1+1] có giá trị là 2, [A1+2] có giá trị là 3 Nếu muốn khai báo một mảng không cần khởi tạo giá trị ban đầu, ta khai báo như sau: A2 DB 100 DUP(?) 25
  27. Chương 2: Lập trình bằng hợp ngữ khai báo một mảng có 100 phần tử và các phần tử là kiểu byte. Khai báo sau là một mảng 100 phần tử kiểu byte và tất cả các phần tử được khởi tạo giá trị 0. A3 DB 100 DUP(0) Khai báo biến kiểu xâu kí tự Xâu kí tự là một mảng mà mỗi phần tử là một kí tự hay mã ASCII của kí tự. Xâu kí tự được kết thúc bởi kí tự ‘$’ (có mã ASCII là 24h). Ví dụ: Xau1 DB ‘Chào các bạn’,’$’; XuongDong DB 13,10,’$’;xâu chứa các kí tự xuống dòng và về đầu dòng Xau2 DB 36h,40h,’a’,’b’, ‘$’ ; xâu chứa cả mã ASCII và kí tự. d. Khai báo hằng Hằng có thể là kiểu số hoặc kiểu kí tự. Cú pháp khai báo hằng như sau: Tên hằng EQU Giá trị của hằng Ví dụ: CR EQU 0Dh ; Có thể sử dụng hằng để khai báo: Ví dụ: Chao EQU ‘Chào bạn’ LoiChao DB Chao,’$’ 2.1.2 Khung của chương trình Hợp ngữ Để thực hiện một chương trình dạng mã máy, hệ điều hành cấp phát một số vùng nhớ dành cho chương trình để chứa các mã lệnh, dữ liệu, và ngăn xếp. Phần này trình bày về các giả lệnh điều khiển đoạn dùng cho chương trình, khung của chương trình dạng .COM, khung của chương trình dạng .EXE, và cuối cùng là một số chương trình ví dụ đơn giản. a. Các giả lệnh điều khiển đoạn (segment directives) 1. Lệnh: MODEL Chức năng: Khai báo mô hình bộ nhớ cho một module Assembler. Lệnh này thường được đặt sau giả lệnh về khai báo loại CPU (CPU family) và được đặt trước tất cả các giả lệnh điều khiển đoạn khác. Cú pháp: .MODEL Trong đó kiểu kích thước bộ nhớ là một trong các kiểu sau: Kiểu kích thước Mô tả Tiny (hẹp) Mã lệnh và dữ liệu được gói vào cùng một đoạn. Kiểu này thường được dùng trong chương trình. COM 26
  28. Chương 2: Lập trình bằng hợp ngữ Small (nhỏ) Mã lệnh được gói vào trong một đoạn. Dữ liệu nằm trong một đoạn khác Medium (trung bình) Mã lệnh được gói vào trong một đoạn. Dữ liệu không gói gọn trong một đoạn. Compact (nén) Mã lệnh không gói vào trong một đoạn. Dữ liệu không gói gọn trong một đoạn. Large (lớn) Mã lệnh không gói vào trong một đoạn. Dữ liệu không gói gọn trong một đoạn. Không có mảng dữ liệu được khai báo nào lớn hơn 64KB Huge (rất lớn) Mã lệnh không gói vào trong một đoạn. Dữ liệu không gói gọn trong một đoạn. Các mảng dữ liệu được khai báo có thể lớn hơn 64KB. Ví dụ: .MODEL Small 2. Lệnh: STACK Chức năng: Khai báo kích thước đoạn ngăn xếp dùng trong chương trình. Đoạn ngăn xếp là một vùng nhớ để lưu các trạng thái hoạt động của chương trình khi có chương trình con. Cú pháp: .STACK Trong đó kích thước ngăn xếp là số byte dành cho ngăn xếp. Nếu không khai báo kích thước ngăn xếp thì chương trình sẽ tự động gán cho giá trị là 1024 (1 KB). Số này là khá lớn, thông thường khoảng 256 byte hay 100h là đủ. Ví dụ: .STACK 100h 3. Lệnh: DATA Chức năng: Khai báo đoạn dữ liệu cho chương trình. Đoạn dữ liệu chứa toàn bộ các khai báo hằng, biến của chương trình. Cú pháp: .DATA Ví dụ: .DATA CR EQU 0Dh LF EQU 0Ah LoiChao DB ‘Chào các bạn’,’$’ 4. Lệnh: CODE Chức năng: Khai báo đoạn mã lệnh chương trình. Đoạn mã chứa các dòng lệnh của chương trình. Cú pháp: .CODE Ví dụ: .CODE Mov AH,09 Mov Dx, Offset LoiChao . 27
  29. Chương 2: Lập trình bằng hợp ngữ b. Khung của chương trình Hợp ngữ để dịch ra dạng .EXE Dưới đây là khung của một chương trình hợp ngữ mà sau khi được dịch (compiled) và hợp dịch (linked) thì sẽ thành một file thực hiện được dạng .EXE. Sau khi sử dụng các giả lệnh điều khiển đoạn để khai báo mô hình bộ nhớ, kích thước ngăn xếp, đoạn dữ liệu và bắt đầu đoạn mã lệnh. Tất nhiên người lập trình có thể thay đổi mô hình bộ nhớ (có thể không phải là Small) hay kích thước ngăn xếp (một số khác, không phải là 100h) cho phù hợp với mục đích viết chương trình. Ta dùng nhãn Start và End Start để đánh dấu điểm bắt đầu và kết thúc đoạn mã lệnh dùng cho chương trình (tất nhiên người lập trình có thể dùng tên nhãn khác để đánh dấu, không bắt buộc phải dùng nhãn Start) .MODEL Small .STACK 100h .DATA ; Các khai báo hằng, biến ở đây .CODE Start: Mov AX,@Data Mov DS,AX ; Các lệnh của chương trình chính được viết ở đây Mov AH,4Ch Int 21h End Start ; Các chương trình con (nếu có) sẽ được viết ở đây. Hai lệnh: Mov AX,@Data Mov DS,AX Làm nhiệm vụ cho con trỏ DS trỏ tới đoạn chứa dữ liệu khai báo (Data). Hằng số @Data là tên của đoạn dữ liệu (thực chất hằng số này mang giá trị là địa chỉ của đoạn bộ nhớ cấp phát cho chương trình trong quá trình chạy chương trình ). Mà DS không làm việc trực tiếp với hằng số (không thể chuyển giá trị hằng số trực tiếp vào các thanh ghi đoạn), nên thanh ghi AX là biến trung gian để đưa giá trị @Data vào DS. Hai lệnh cuối của chương trình: Mov AH,4Ch Int 21h Làm nhiệm vụ kết thúc chương trình .EXE và trả lại quyền điều khiển cho hệ điều hành DOS. Nhắc lại rằng không giống như các hệ điều hành Windows 9x, 2K,XP là các hệ điều hành đa nhiệm. Hệ điều hành DOS là hệ điều hành đơn nhiệm (Single-task). Nghĩa là, tại một thời điểm, chỉ có một chương trình chiếm quyền điều khiển và tài nguyên của hệ thống. Ví dụ về một chương trình dạng .EXE đơn giản, chương trình Hello World. Chương trình thực hiện việc in ra màn hình một lời chào “Hello World” ; Chương trình này in ra màn hình lời chào Hello World 28
  30. Chương 2: Lập trình bằng hợp ngữ .MODEL Small .STACK 100h .DATA Msg db ‘Hello World’,’$’ .CODE Start: Mov AX,@Data Mov DS,AX ; cho DS trỏ đến đoạn Data Mov AH,09h ; Hàm 09, in ra 1 xâu kí tự Mov DX,Offset Msg ; Dx chứa địa chỉ offset của xâu Int 21h ; thực hiện chức năng in xâu Mov AH,4Ch ; Trở về và trả quyền điều khiển cho DOS Int 21h End Start c. Khung của chương trình Hợp ngữ để dịch ra dạng .COM Chương trình .COM ngắn gọn và đơn giản hơn so với các chương trình .EXE. Tất cả các đoạn ngăn xếp, dữ liệu, đoạn mã được gộp vào cùng một đoạn là đoạn mã. Nghĩa là, chương trình .COM được gói gọn trong một đoạn (việc dịch và thực hiện đối với chương trình .COM sẽ nhanh hơn các chương trình .EXE). Với các ứng dụng nhỏ mà mã lệnh và dữ liệu không vượt quá 64KB, ta có thể ghép luôn các đoạn ngăn xếp, dữ liệu và mã lệnh vào cùng với đoạn mã để tạo ra file dạng COM. Để dịch được ra file dạng .COM, chương trình nguồn phải tuân thủ theo khung dưới đây .MODEL Tiny .CODE Org 100h Jmp Start ; Các khai báo hằng, biến ở đây Start: ; Các lệnh của chương trình chính được viết ở đây Int 20h ; Các chương trình con (nếu có) sẽ được viết ở đây. End Start Khai báo mô hình kích thước sử dụng bộ nhớ luôn là Tiny. Ngoài ra, trong khung này không có lời khai báo đoạn ngăn xếp và đoạn dữ liệu. Lệnh đầu tiên trong đoạn mã là giả lệnh ORG 100h, dùng để gán địa chỉ bắt đầu cho chương trình tại 100h trong đoạn mã. Vùng dung lượng 256 byte đầu tiên được sử dụng cho đoạn mào đầu chương trình (Prefix Segment Program). Kế tiếp, người ta dung lệnh JMP để nhảy qua phần bộ nhớ được dùng cho khai báo. Ví dụ về một chương trình .COM đơn giản, chương trình Hello World. .MODEL Tiny .CODE Org 100h Jmp Start Msg db ‘Hello World’, ‘$’ 29
  31. Chương 2: Lập trình bằng hợp ngữ Start: Mov AH,09h ; Hàm 09, in ra 1 xâu kí tự Mov DX,Offset Msg ; Dx chứa địa chỉ offset của xâu Int 21h ; thực hiện chức năng in xâu Int 20h ; kết thúc chương trình, trở về DOS End Start 2.1.3 Tạo, dịch, hợp dịch và thực hiện chương trình Hợp ngữ Phần này trình bày về các bước để tạo, cách dịch, hợp dịch và thực hiện một chương trình hợp ngữ. Dưới đây là các bước phải được thực hiện tuần tự. Nghĩa là, bước thứ i không thể được thực hiện nếu bước trước nó (bước i-1) chưa được thực hiện thành công. Bước 1: Soạn chương trình nguồn Dùng bất kỳ trình soạn thảo nào như Nodepad, Turbo Pascal Editor, Turbo C Editor để tạo ra file văn bản chương trình. File chương trình phải có phần mở rộng là .ASM. Bước 2: Dịch Dùng một trong các chương trình dịch: MASM (Macro Assembler) hoặc TASM (Turbo Assembler) để dịch file .ASM thành file .OBJ. Nếu bước này có lỗi thì ta phải quay lại bước . Bước 3: Hợp dịch Dùng chương trình LINK hoặc TLINK để liên kết một hay nhiều file OBJ lại để thành một file .EXE, file có thể chạy được. Đối với chương trình có cấu trúc là file EXE thì bỏ qua bước Bước 4: Tạo file .COM Dùng chương trình EXE2BIN để dịch .EXE thành file .COM. Bước 5. Thực hiện chương trình vừa tạo. 30
  32. Chương 2: Lập trình bằng hợp ngữ (1)Tạo file chương trình .asm (2)Dùng TASM hoặc MASM để dịch ra file *.obj (3)Dùng trình liên kết LINK hoặc TLINK để hợp dịch thành file .EXE File .COM (4)Dùng EXE2BIN hoặc TLINK để dịch file EXE thành file COM (5)Chạy chương trình 2.2 CÁC CẤU TRÚC LẬP TRÌNH CƠ BẢN TRONG CHƯƠNG TRÌNH HỢP NGỮ Phần này trình bày về các cấu trúc lập trình cơ bản được sử dụng trong việc lập trình nói chung và lập trình hợp ngữ nói riêng. Các cấu trúc này thường được sử dụng để điều khiển một lệnh hoặc một khối lệnh. Đó là: - Cấu trúc tuần tự - Cấu trúc điều kiện IF-THEN - Cấu trúc điều kiện rẽ nhánh IF-THEN-ELSE - Cấu trúc CASE - Cấu trúc lặp xác định FOR-DO - Cấu trúc lặp WHILE-DO - Cấu trúc lặp REPEAT-UNTIL 31
  33. Chương 2: Lập trình bằng hợp ngữ 2.2.1 Cấu trúc tuần tự Cấu trúc tuần tự có mặt hầu hết tất cả các ngôn ngữ lập trình. Đây là cấu tríc thông dụng, đơn giản nhất, trong đó các lệnh được sắp xếp kế tiếp nhau hết lệnh này đến lệnh khác. Trong quá trình thực hiện chương trình các lệnh tuần tự được xử lý theo thứ tự của chúng. Bắt đầu từ lệnh đầu tiên cho đến khi gặp lệnh cuối cùng của cấu trúc thì công việc cũng được hoàn tất. Cấu trúc có dạng như sau: lệnh 1 lệnh 2 lệnh 3 . lệnh n Ví dụ: Cần tính tổng nội dung các ô nhớ có địa chỉ FFFAh, FFFBh, FFFCh, FFFDh rồi lưu kết quả vào thanh ghi AX. Để thực hiện việc này ta có thể sử dụng đoạn chương trình sau: Mov BX,FFFAh ; BX trỏ đến FFFAh Xor AX,AX ; Tổng =0 Add AL,[BX] ;cộng nội dung ô nhớ có địa chỉ FFFAh vào AX Inc BX ; BX trỏ đến FFFBh Add AL,[BX]; cộng nội dung ô nhớ có địa chỉ FFFBh vào AX Inc BX ; BX trỏ đến FFFCh Add AL,[BX]; cộng nội dung ô nhớ có địa chỉ FFFCh vào AX Inc BX ; BX trỏ đến FFFDh Add AL,[BX]; cộng nội dung ô nhớ có địa chỉ FFFDh vào AX Xong: ; ra khỏi cấu trúc 2.2.2 Cấu trúc IF THEN Đây là cấu trúc điều kiện (conditional statement) mà khối lệnh được thực hiện nếu nó thỏa mãn điều kiện. Cú pháp: IF THEN Cấu trúc này có thể được minh họa bằng sơ đồ khối sau đây: 32
  34. Chương 2: Lập trình bằng hợp ngữ sai Điều kiện đúng Khối lệnh Khối lệnh có thể gồm 1 hoặc nhiều lệnh. Trong hợp ngữ, để cài đặt cấu trúc IF THEN người ta thường sử dụng lệnh CMP (so sánh) và đi kèm theo sau là một lệnh nhảy có điều kiện. Ví dụ: Viết đoạn chương trình kiểm tra nếu thanh ghi AX>BX thì sẽ tính hiệu AX-BX và lưu kết quả vào thanh ghi AX. Dưới đây là đoạn chương trình thực hiện công việc đó: Cmp AX,BX ; so sánh AX và BX Jb Ketthuc ; nếu AX THEN ELSE Nếu điều kiện được thỏa mãn thì khối lệnh thứ nhất được thực hiện. Ngược lại, nếu điều kiện là sai thì khối lệnh thứ hai sẽ được thực hiện. Trong mọi trường hợp, một và chỉ một trong hai khối lệnh được thực hiện. 33
  35. Chương 2: Lập trình bằng hợp ngữ Đúng Sai Điều kiện Khối lệnh 1 Khối lệnh 2 Hình: cấu trúc IF THEN ELSE Trong cài đặt của cấu trúc IF THEN ELSE dạng hợp ngữ, thông thường người ta sử dụng một lệnh CMP, một lệnh nhảy có điều kiện và một lệnh nhảy không điều kiện. Ví dụ: Cho hai số được lưu vào thanh ghi AX và BX, tìm số lớn nhất và lưu kết quả vào thanh ghi DX. Dưới đây là đoạn chương trình thực hiện công việc đó. Cmp AX,BX ; so sánh AX và BX JG AXLonHon ; nếu AX>BX nhảy đến nhãn AXLonhon Mov DX,BX ; BX>=AX nên DX=AX Jmp Ketthuc ; nhảy đến nhãn Ketthuc AXLonHon: Mov DX,AX ;AX>BX nên DX=AX Ketthuc: 2.2.4 Cấu trúc CASE Cấu trúc CASE là cấu trúc lựa chọn để thực hiện một khối lệnh giữa nhiều khối lệnh khác. Cú pháp: CASE Giá trị 1: Khối lệnh 1 Giá trị 2: Khối lệnh 2 Giá trị n: Khối lệnh n END CASE 34
  36. Chương 2: Lập trình bằng hợp ngữ Biểu thức Giá trị 1 Giá trị 2 Giá trị n Khối lệnh 1 Khối lệnh 2 Khối lệnh n Để thực hiện cấu trúc CASE trong chương trình Hợp ngữ, ta phai kết hợp các lệnh CMP, lệnh nhảy có điều kiện, và lệnh nhảy không điều kiện. Ví dụ: Am db ‘Nhỏ hơn 0’,’$’ Duong db ‘Lớn hơn 0’,’$’ Khong db ‘Bằng không’,’$’ Sub AX,BX Sub AX,CX Cmp AX,0 Je Khong0 Jb Am0 Ja Duong0 Khong0: Mov AH,9 Mov DX,offset Khong Int 21h Jmp Ketthuc Am0: Mov AH,9 Mov DX,offset Am0 Int 21h Jmp Ketthuc Duong0: Mov AH,9 Mov DX,offset Duong0 Int 21h Ketthuc: 35
  37. Chương 2: Lập trình bằng hợp ngữ Tính hiệu AX-BX-CX và thông báo kết quả là âm, dương hay bằng không. 2.2.5 Cấu trúc lặp FOR-DO Đây là vòng lặp với số lần lặp đã biết trước. Cú pháp như sau: Count=số lần lặp Khối lệnh Count=Count-1 Count=0 đúng FOR Count (=Số lần lặp) DO Khối lệnh Khối lệnh sẽ được thực hiện Count lần. Để cài đặt cấu trúc này trong hợp ngữ người ta dùng thanh ghi CX để chứa Count và kết hợp với lệnh LOOP để duy trì vòng lặp. Mỗi lần lặp xong thì CX sẽ tự động giảm đi 1. Ví dụ: Tính tổng S=1+2+3+ .+100 Lưu kết quả vào thanh ghi AX. Mỗi lần thực hiện khối lệnh ta sẽ cộng vào AX một số. Lần thực hiện thứ i thì 100-i+1 sẽ được cộng vào AX. Như vậy số lần lặp sẽ là 100. Đoạn chương trình được viết như sau: Mov CX,100 ; khởi tạo số lần lặp Xor AX,AX ; AX=0 để chứa tổng Cong: Add AX,CX ; Cộng CX vào AX Loop Cong ; Lặp cho đến khi CX=0 ; ra khỏi vòng lặp, AX chứa tổng 36
  38. Chương 2: Lập trình bằng hợp ngữ 2.2.6 Cấu trúc lặp WHILE-DO Cú pháp: WHILE điều kiện DO Khối lệnh Trong cấu trúc lặp WHILE DO điều kiện được kiểm tra trước khi thực hiện khối lệnh. Nếu điều kiện đúng thì khối lệnh được thực hiện, còn điều kiện sai thì vòng lặp sẽ dừng. Số lần thực hiện khối lệnh chưa được biết trước. sai Điều kiện đúng Khối lệnh Để cài đặt cấu trúc này trong chương trình hợp ngữ, người ta thường dùng lệnh CMP để kiểm tra điều kiện và kết hợp mới một lệnh nhảy có điều kiện để thoát khỏi vòng lặp. Ví dụ: Nhập vào một số nguyên lớn hơn 0 và bé hơn 9 từ bàn phím. Kiểm tra xem có nhập đúng không?. Nếu nhập sai thì yêu cầu phải nhập lại. Lời giải Ta biết rằng số 0 có mã ASCII là 30h và số 9 có mã ASCII là 39h. Đoạn chương trình sẽ kiểm tra nếu kí tự gõ vào có mã ASCII bé hơn 30h hoặc lớn hơn 39h thì sẽ yêu cầu người dùng nhập lại kí tự khác. Dưới đây là đoạn chưong trình. Mov AH,01 ; nhập vào 1 kí tự Nhap: Int 21h Cmp AL,30h ; kí tự nhập vào ’9’ Ja Nhap ; nhập lại 37
  39. Chương 2: Lập trình bằng hợp ngữ Xong: Sub AL,30h ; Kí tự đã hợp lệ, đổi ra số 2.2.7 Cấu trúc lặp REPEAT-UNTIL Cú pháp: REPEAT Khối lệnh UNTIL Điều kiện Trong cấu trúc này, khối lệnh được thực hiện ít nhất một lần, sau đó mới kiểm tra điều kiện. Khối lệnh sẽ được lặp đi lặp lại cho đến khi điều kiện thỏa mãn. Khối lệnh sai Điều kiện đúng Để cài đặt cấu trúc này trong hợp ngữ, người ta thường dùng một lệnh CMP đi kèm với với một lệnh nhảy có điều kiện. Ví dụ: Tìm n nhỏ nhất sao cho 1+2+3 + +n >10000, lưu kết quả (số n) vào BX Lời giải: Ta cộng vào tổng (chứa trong AX) các số 1,2, mỗi lần tính tổng ta đều so sánh tổng đó với 10000, ta cộng cho đến khi AX>10000 thì ta dừng, số hạng cuối cùng của tổng chính là số n cần tìm. Đoạn chương trình được viết như sau: Mov CX,1 ; CX=1 Xor AX,AX ; AX=Tong=0 Cong: Add AX,CX ; Cộng CX vào AX Cmp AX ,10000 ; Tổng đã > 10000 chưa? Ja Xong ; đã lớn hơn, xong Inc CX ; Tăng CX lên 1 Jmp Cong ; Tiếp tục cộng Xong: Mov BX,CX ; lưu n vào BX 38
  40. Chương 2: Lập trình bằng hợp ngữ 2.3 CHƯƠNG TRÌNH CON VÀ MACRO 2.3.1 Chương trình con: cơ chế làm việc và cấu trúc a. Khái niệm: Chương trình con có mặt hầu hết các ngôn ngữ lập trình. Chương trình con rất có ý nghĩa trong lập trình có cấu trúc. Nó làm cho chương trình trở lên sáng sủa, dễ bảo trì hơn. Bên cạnh đó, nó còn có một ý nghĩa khác: một chương trình con được viết một lần nhưng được sử dụng (gọi đến) nhiều lần. Một cách định nghĩa đơn giản: chương trình con là một nhóm các lệnh được gộp lại phục vụ cho việc sử dụng nhiều lần thông qua tên và các tham số của chương trình con đó . Ví dụ: Một bài toán yêu cầu ta tính tổng của 3 số hạng được nhập từ bàn phím. Thay vì phải viết 3 đoạn chương trình (khá giống nhau) để lần lượt nhập từng số thì ta viết 1 chương trình con nhập vào 1 số rồi gọi nó 3 lần. b. Cơ chế làm việc của chương trình con Giả sử có một chương trình (chính) đang thực hiện như sau: Địa chỉ Lệnh 1F00 Mov AX,1 1F02 Mov BX,2FFF 1F04 Mov CX,2 1F06 Call TinhTong 1F09 Sub AX,BX 1F0B Add AX,CX 1F0D SHR AX,1 1F0F Jmp Done Chương trình con TinhTong được lưu ở 1 vùng nhớ khác Địa chỉ Lệnh FFD0 Mov DL,[BX] FFD2 Add AL,[BX] FFD4 Inc BX FFD6 Add AL,[BX] FFD8 Ret 39
  41. Chương 2: Lập trình bằng hợp ngữ Khi thực hiện đến lệnh Call TinhTong ở địa chỉ 1F06h, bộ xử lý sẽ thực hiện như sau: - Lưu địa chỉ của lệnh kế tiếp 1F09h vào ngăn xếp. - Nạp địa chỉ của lệnh đầu tiên của chương trình con FFD0h vào IP - Các lệnh của chương trình con được thực hiện cho đến khi gặp lệnh RET, chương trình con kết thúc và trả lại quyền điều khiển cho chương trình chính. - Địa chỉ 1F09h được lấy ra từ ngăn xếp rồi đặt vào IP. Lệnh tại địa chỉ 1F09h (Sub AX,BX) của chương trình chính sẽ được thực hiện. c. Cấu trúc của chương trình con Cấu trúc của một chương trình con có dạng như sau: PROC NEAR/FAR Các lệnh của chương trình con được viết ở đây RET ENDP Giải thích: - Lệnh điểu khiển PROC được sử dụng để khởi động chương trình con. Nhãn đứng trước toán tử PROC là tên của thủ tục. Sau toán tử PROC có lệnh điều khiển NEAR hoặc FAR để báo cho lệnh RET lấy địa chỉ quay về chương trình của nó trong ngăn xếp. - Nếu là NEAR thì chương trình con được gọi thì địa chỉ OFFSET (16 bít) được lấy từ ngăn xếp để gán cho thanh ghi IP. Nghĩa là, trong trường hợp này thì chương trình con và chương trình gọi nó ở trên cùng một đoạn (segment) - Nếu là FAR thì chỉ lấy địa chỉ SEGMENT và OFFSET trong ngăn xếp được lấy ra để gán cho thanh ghi CS và IP. Nghĩa là, trong trường hợp này thì chương trình con và chương trình gọi nó ở hai đoạn khác nhau. Để lệnh RET có đầy đủ thông tin để biết là phải nạp cả CS:IP hay chỉ nạp IP, có hai cách sau: - Thay vì sử dụng RET ta sử dụng lệnh RETN (near return) hoặc RETF (far return). - Dùng hai lệnh điều khiển PROC và ENDP. Lệnh ENDP sẽ đánh dấu kết thúc chương trình con. Nếu đi sau PROC là NEAR thì tất cả các lệnh RET trong chương trình con nằm trong PROC ENDP đều là RETN nằm chung một đoạn với chương trình gọi đến chương trình con này. Nếu đi sau PROC là FAR thì tất cả các lệnh RET trong chương trình con nằm trong PROC ENDP đều là RETF (nằm chung một đoạn với chương trình gọi đến chương trình con này). - Nếu cả chương trình chình và các chương trình con có kích thước nhỏ (tất cả mã lệnh không vượt quá 64KB) thì ta nên sử dụng chúng như là các thủ tục NEAR. Vì như thế chương trình sẽ được thực hiện nhanh hơn. - Khi sử dụng mô hình bộ nhớ, các giá trị NEAR là FAR cũng được gán ngầm định theo mô hình kích thước bộ nhớ. Chẳng hạn: nếu ta dùng .MODEL Tiny hoặc .MODEL Compact thì chương trình con sẽ được tự động xác định là NEAR. Còn nếu ta dùng 40
  42. Chương 2: Lập trình bằng hợp ngữ .MODEL Medium, .MODEL Large hoặc .MODEL Huge thì chương trình con sẽ được tự động xác định là FAR. - Trong trường hợp ta không có khai báo NEAR hoặc FAR sau lệnh PROC thì ngầm định là NEAR. 2.3.2 Truyền tham số Đây là phần rất quan trọng khi chương trình được thiết kế thành các chương trình con. Chúng được “giao tiếp” với nhau một cách trong suôt, lô gic trong quá trình thực hiện các chức năng của mình để tăng thêm tính tái sử dụng- một tính chất quan trọng của chương trình con. Dữ liệu phải được trao đổi từ chương trình gọi và chương trình con được gọi. Trong các ngôn ngữ bậc cao khác, cấu trúc chương trình con cho phép người lập trình khai báo danh sách tham số. Tuy nhiên, như ta thấy cấu trúc của một chương trình con hợp ngữ ta không thấy đi kèm với một danh sách tham số. Dưới đây ta sẽ xem xét tất cảc các vấn đề liên quan đến việc truyền tham số. a. Truyền giá trị tham số từ chương trình gọi sang chương trình được gọi - Truyền tham số thông qua các thanh ghi: đây là cách thức đơn giản và dễ thực hiện nhất, thường được sử dụng đối với các chương trình được viết thuần túy bằng hợp ngữ. Để thực hiện cách truyền tham số này ta chỉ cần đặt một giá trị nào đó vào thanh ghi ở chương trình gọi và sau đó chương trình con được gọi sẽ sử dụng giá trị ở thanh ghi này. - Truyền tham số thông qua các biến toàn cục: các biến toàn cục được khai báo trong chưong trình chính có tác dụng trong toàn bộ chương trình (cả chương trình chính và các chương trình con). Vì vậy ta có thể dùng nó để truyền giá trị giữa chưong trình chính và các chương trình con. Cách này khá phổ biến khi ta viết chương trình thuần túy bằng hợp ngữ hoặc phát triển chương trình hỗn hợp bằng hợp ngữ và các ngôn ngữ bậc cao khác. - Truyền tham số thông qua ngăn xếp: đây là phương pháp khá phức tạp. Tuy nhiên, cách này được sử dụng rất nhiều khi ta viết các module bằng hợp ngữ và các ngôn ngữ bậc cao khác rồi cho chúng liên kết (link) với nhau trong quá trình thực hiện. Cách này sẽ được đề cập chi tiết ở phần sau: kết nối chương trình hợp ngữ với các chương trình ngôn ngữ bậc cao. b. Truyền giá trị tham số từ chương trình được gọi lên chương trình gọi - Truyền tham số từ chương trình được gọi (chương trình con) lên chương trình gọi (chương trình chính) cũng theo ba cách: thông qua các thanh ghi, biến toàn cục và ngăn xếp. Trong trường hợp liên kết với ngôn ngữ bậc cao thì chương trình con được gọi (được viết bằng hợp ngữ) có thể chuyển giá trị lên chương trình gọi (được viết bằng ngôn ngữ bậc cao) bằng giá trị trả về (returned value). Để làm được điều này trong hợp ngữ thì giá trị trả về của chương trình con được gọi phải tuân thủ các qui cách sau: + Nếu giá trị trả về (tên hàm mang giá trị trả về) là 8 bít hoặc 16 bít thì giá trị đó phải được đặt vào thanh ghi AX của hàm trước khi quay về chương trình gọi nó. + Nếu giá trị trả về (tên hàm mang giá trị trả về) là 32 bít thì giá trị đó phải được đặt vào thanh ghi DX:AX của hàm trước khi quay về chương trình gọi nó. 41
  43. Chương 2: Lập trình bằng hợp ngữ - Lưu ý rằng số lượng các thanh ghi của máy tính là có hạn, nên ta không nên dùng quá nhiều thanh ghi cho việc chuyển giao các tham số. c. Vấn đề bảo vệ các thanh ghi Khác với lập trình với các ngôn ngữ bậc cao, khi lập trình hợp ngữ người lập trình hợp ngữ còn phải để ý đến việc bảo vệ các thanh ghi trong quá trình gọi các chương trình con. Ở các ngôn ngữ bậc cao, chương trình con không làm thay đổi giá trị của các biến của chương trình chính trừ khi ta chủ tâm làm việc đó. Trong các chương trình được viết bằng hợp ngũ thì ngược lại là rất hay xảy ra trường hợp là các giá trị của các biến trong chương trình chính được nạp vào các thanh ghi mà trong khi đó chương trình con cũng cần các thanh ghi này để thực hiện một công việc nào đó. Và như vậy thì chương trình con khi sử dụng thanh ghi có thể sẽ xóa giá trị trong thanh ghi mà chương trình chính đã đặt vào đó để sử về sau. Do vậy các giá trị đã được lưu vào trong thanh ghi cần phải được bảo vệ khi cần thiết. Có hai cách người ta hay dùng là: - Sử dụng các lệnh PUSH và POP: Khi bắt đâu một chương trình con, ta nên tiến hành lưu các giá trị của các thanh ghi mà chương trình con sẽ dùng đến vào ngăn xếp nhờ lệnh PUSH và trước khi ra khỏi chương trình con ta phải phục hồi lại các giá trị của các thanh ghi đó từ ngăn xếp nhờ lệnh POP. - Sử dụng theo một qui ước nhất quán (code convension): qui định sử dụng một số thanh ghi sử dụng cho chương trình chính và tất cả các chương trình con không được sử dụng đến các thanh ghi đó. 2.3.3 Chương trình gồm nhiều module Đó là chương trình gồm nhiều file, thích hợp cho các chương trình lớn và phức tạp. Chúng được dịch một cách độc lập nhưng được hợp dịch (link) với nhau khi chạy. Sau đây là những ưu điểm của việc viết chương trình gồm nhiều file: - Cho phép nhiều người lập trình cùng tham gia phát triển một chương trình lớn. - Dễ dàng cho việc sửa lỗi, khi dịch module nào phát hiện ra lỗi thì chỉ cần sửa và dịch lại module đó. - Mỗi module thường giải quyết một vấn đề ngắn gọn nên dễ tìm sai sót. Để chia xẻ các biến toàn cục hoặc các chương trình con được sử dụng chung giữa các module người ta sử dụng các lệnh điều khiển PUBLIC, EXTRN và GLOBAL. a. Lệnh điều khiển PUBLIC Chức năng: Lệnh điều khiển PUBLIC chỉ cho chương trình dịch hợp ngữ biết nhãn nào nằm trong module này được phép sử dụng ở các module khác. Cú pháp: PUBLIC tên nhãn Khai báo nhãn Trong đó tên nhãn có thể là: - Tên chương trình con - Tên biến - Tên hằng (theo sau bởi lệnh EQU) Ví dụ: 42
  44. Chương 2: Lập trình bằng hợp ngữ Nhãn là tên biến nhớ .DATA PUBLIC gTong, gSoHang, gMang, gMangLength gTong dd ? gSoHang dw ? gMangLength EQU 100 gMang db gMangLength DUP(?) Nhãn là tên của chương trình con .CODE PUBLIC gTinhTong, gTimMax gTinhTong PROC NEAR . gTinhTong ENDP ; gTimMax PROC FAR . gTimMax ENDP ; Chú ý: chương trình dịch hợp ngữ không phân biệt chữ hoa hay thường trong các nhãn. Tất cả các chữ đều hiểu như chữ hoa. Nếu muốn có sự phân biệt đó thì: - dùng tùy chọn /ml khi dịch cho tất cả mọi ký hiệu - dùng tùy chon /mx khi dịch cho các nhãn được khai báo PUBLIC, EXTRN, hoặc GLOBAL. b. Lệnh điều khiển EXTRN Chức năng: Lệnh điều khiển EXTRN báo cho chương trình dịch hợp ngữ biết nhãn nào đã được khai báo PUBLIC ở các module khác được sử dụng trong module này. Nói cách khác các nhãn đã được PUBLIC ở các module khác sẽ được sử dụng trong module này mà không cần khai báo lại nếu chúng được khai báo EXTRN. Cú pháp: EXTRN tên nhãn: kiểu Trong đó kiểu có các dạng như sau: Kiểu Giải thích ABS Giá trị tuyệt đối, dùng để khai báo các nhãn được xác định bởi EQU hoặc = BYTE Giá trị nhãn là 1 byte WORD Giá trị nhãn là 2 byte DWORD Giá trị nhãn là 4 byte FWORD Giá trị nhãn là 6 byte 43
  45. Chương 2: Lập trình bằng hợp ngữ QWORD Giá trị nhãn là 8 byte TBYTE Giá trị nhãn là 10 byte DATAPTR Con trỏ NEAR hoặc FAR phụ thuộc vào MODEL của bộ nhớ NEAR Chỉ chương trình con dạng khai báo NEAR FAR Chỉ chương trình con dạng khai báo FAR PROC Xác định nhãn là thủ tục; còn NEAR hoặc FAR phụ thuộc vào lệnh điều khiển .MODEL UNKNOWN Cho nhãn không biết kích cỡ Các kiểu dữ liệu theo sau nhãn được khai báo EXTRN phải xác định đúng, nếu không sẽ gây ra sai sót. Ví dụ: Ở module 1 có chương trình con được khai báo như sau: PUBLIC TinhTong TinhTong PROC FAR Ret TinhTong ENDP Ở module 2 sử dụng chương trình con TinhTong được khai báo trong module 1. .CODE EXTRN TinhTong: FAR Call TinhTong Để sử dụng EXTRN để khai báo cho chương trình dịch hợp ngữ biết những nhãn nào đã được khai báo PUBLIC ở phần trước được sử dụng trong module này. .DATA EXTRN gTong:DWORD, gSoHang:WORD, gMang:BYTE,gMangLength:ABS EXTRN TinhTong: NEAR, TimMax: FAR . Call TinhTong . Call TimMax c. Lệnh điều khiển GLOBAL Lệnh GLOBAL được hỗ trợ bởi chương trình dịch TASM (Turbo Assembler) của hẵng Borland. Lệnh điều khiển này còn có thể thay thế hai lệnh PUBLIC và EXTRN. Nếu ta khai báo GLOBAL cho các nhãn có kèm theo khai báo dạng nhãn thì GLOBAL trong trường hợp này sẽ 44
  46. Chương 2: Lập trình bằng hợp ngữ thay thế cho PUBLIC, còn khi khai báo nhãn đi sau GLOBAL mà chỉ xác định kiểu nhãn thì GLOBAL sẽ thay thế EXTRN. Ví dụ: .DATA GLOBAL gSoHang:WORD, gMang:BYTE Count DW ? .CODE GLOBAL TinhTong: NEAR, TimMax: FAR TimMax PROC FAR Call TinhTong Các nhãn TinhTong, TimMax được khai báo do đó lệnh điều khiển GLOBAL đối với các nhãn này có ý nghĩa như PUBLIC, còn các nhãn gSoHang, gMang chỉ nêu kiểu mà không khai báo thì GLOBAL đối với chúng là EXTRN. Một trường hợp vô cùng thuận lợi với việc sử dụng lệnh điều khiển GLOBAL là việc sử dụng GLOBAL trng file INCLUDE. Giả sử ta có một tập hợp các nhãn mà ta muốn sử dụng ở tất cả các module của chương trình gồm nhiều module. Ta có thể làm được như vậy nhờ việc gộp tất cả các nhãn vào file INCLUDE và sau đó đưa file này vào các module. Trong trường hợp này ta không thể sử dụng PUBLIC hoặc EXTRN vì EXTRN không thể làm việc được với các nhãn có xác định kích thước khai báo. Còn lệnh PUBLIC chỉ làm việc với các module trong đó các nhãn được khai báo mà không xác định kiểu. Do vậy, chỉ có GLOBAL là thỏ mãn cả hai điều kiện trên. Ví dụ về một chương trình nằm trên hai file (hai module) khác nhau. Với: - Module của chương trình chính là main.asm có chức năng xác định địa chỉ OFFSET của hai xâu kí tự, gọi chương trình con làm nhiệm vụ nối hai xâu kí tự đó lại và hiển thị xâu kết quả. - Module chương trình con là KetNoi.ASM làm nhiệm vụ kết nối hai xâu và xếp vào vùng nhớ kết quả. Dưới đây là chương trình chính: .MODEL SMALL .STACK 100h .DATA Xau1 DB “Hello”,0 Xau2 DB “Mr Bin”, 13,10,’$’,0 GLOBAL XauKQ:BYTE XauKQ DB 50 DUP (?) .CODE EXTRN KetNoi:PROC Start: Mov AX,@Data Mov DS,AX Mov AX,offset Xau1; AX chứa OFFSET của Xau1 45
  47. Chương 2: Lập trình bằng hợp ngữ Mov BX,offset Xau2; BX chứa OFFSET của Xau2 Call KetNoi ; nối hai xâu Mov DX,Offset XauKQ ; In ra màn hình Mov AH,9 Int 21h Mov AH,4Ch ; Trở về DOS Int 21h End Start Module của chương trình con KetNoi .MODEL SMALL .STACK 100h .DATA GLOBAL XauKQ:BYTE .CODE PUBLIC KetNoi KetNoi PROC Cld Mov DI, SEG XauKQ ; ES:DI trỏ đến xâu kq Mov ES,DI Mov DI, OFFSET XauKQ Mov SI,AX; DS:SI trỏ đến Xau1 Lap1: Lodsb ; lấy 1 kí tự đưa vào AL And AL,AL ; cho ZF=1 Jz DoKetNoi Stosb ; Lưu kí tự từ AL vào xâu Jmp Lap DoKetNoi: Mov SI,BX; DS:SI; trỏ đến Xau2 Lap2: Lodsb ; lấy kí tự đưa vào AL Stosb ; Cất kí tự vào XauKQ And AL,AL ; cho ZF=1 Jnz Lap2 ; giá trị khác 0 thì nhảy Ret ; trở về chương trình chính KetNoi ENDP END Để chạy hai module trên ta thực hiện theo các thao tác sau: Dịch hai module một cách tách biệt TASM Main TASM KetNoi Sau đó tiến hành hợp dịchTLINK Main+KetNoi ta sẽ có file Main.exe. 46
  48. Chương 2: Lập trình bằng hợp ngữ 2.3.4 Liên kết thủ tục vào một thư viện Trong quá trình lập trình, có thể một số thao tác sau ta cần thực hiện: - Đưa một khối lệnh vào các nơi khác nhau hoặc các module nguồn - Phân chia các nhãn gán (EQU) và MACRO giữa các phần khác nhau của một chương trình hoặc sử dụng lại chúng trong nhiều chương trình. - Viết một chương trình dài, xong không muốn chia nhỏ ra nhiều module vì phải dịch từng module rồi liên kết chúng với nhau song chương trình quá to không thể chứa trong một file. Để giải quyết các vấn đề trên, chương trình dịch của hợp ngữ có lệnh INCLUDE. Giả sử ta muốn tạo một file INCLUDE chứa khối lệnh mà ta muốn các module khác thêm vào khi dịch. Cú pháp: INCLUDE tên file (file chứa khối lệnh cần được đưa vào vị trí mà lệnh INCLUDE đang đứng) Cơ chế: Khi chương trình hợp ngữ gặp lệnh INCLUDE thì sẽ tìm đến đường dẫn chứa file đã được xác định sau INCLUDE và đưa toàn bộ khối lệnh mà tệp này chứa xen vào vị trí mà lệnh INCLUDE đang đứng của module chương trình. Hay nói cách khác nội dung của tệp INCLUDE được đặt vào đúng vùng nhớ của chương trình mà lệnh INCLUDE đang được xác định. Ví dụ: Giả sử có một chương trình trong file A.ASM có nội dung như sau: .CODE Mov BX,10 Add AX,BX INCLUDE B.ASM Sub AX,CX File B.ASM có nội dung như sau: Mov CX,3 Mov DX,4 Kết quả dịch chương trình A.ASM sẽ như sau: .CODE Mov BX,10 Add AX,BX Mov CX,3 Mov DX,4 Sub AX,CX Qua ví dụ trên ta thấy trong quá trình dịch chương trình A.ASM, khi đến dòng lệnh INCLUDE B.ASM 47
  49. Chương 2: Lập trình bằng hợp ngữ Thì chương trình dịch lấy tất cả các lệnh của B.ASM đặt vào vị trí mà lệnh INCLUDE đang đứng. ngoài ra các lệnh INCLUDE còn có tính chất lồng nhau với những mức khác nhau, có nghĩa là trong file INCLUDE có thể gọi 1 file INCLUDE khác. Dưới đây là cơ chế mà chương trình dịch tìm các file INCLUDE: • Nếu trong lệnh INCLUDE chỉ rõ tên ổ đĩa, đường dẫn, tên file thì chương trình dịch sẽ tìm theo sự xác định ở trên • Nếu trong lệnh INCLUDE chỉ xác định tên file thì chương trình dịch tiến hành tìm file này ở thư mục hiện thời. Trường hợp không tìm thấy thì sẽ tìm file đó ở thư mục được chỉ ra trong câu lệnh: TASM –i • Nếu không tìm thấy file INCLUDE trong tất cả các trường hợp trên thì chương trình dịch sẽ báo không tìm thấy file INCLUDE. 2.3.5 Macro Trước khi đi vào tìm hiểu có chế hoạt động, cách viết các macro ta hãy tìm hiểu một số lệnh thường được sử dụng để viết các macro. a. Các lệnh lặp và điều khiển điều kiện khi dịch Các lệnh lặp có chức năng là thực hiện một khối lệnh nhiều lần theo số lần lặp (do người lập trình đặt sẵn). Ta hãy xem xét một số lệnh lặp thường hay được sử dụng. 1. Lệnh: REPT Cú pháp: REPT Khối lệnh ENDM ; kết thúc lệnh lặp Ví dụ: REPT 100 DB ? ENDM Khi tiến hành dịch nó sẽ thực hiện khai báo như sau: DB ? DB ? DB ? ; 100 lần khai báo Trong trường hợp này giống như khai báo DB 100 Dup(?) Tuy nhiên, hai cách này cũng không hoàn toàn giống nhau. Ví dụ 2: REPT 100 DB Count Count= Count+1 48
  50. Chương 2: Lập trình bằng hợp ngữ ENDM Khi dịch ra sẽ được triển khai như sau: DB 0 DB 1 DB 99 2.Lệnh: IRP Chức năng: Lệnh này cho phép lặp một khối lệnh theo số lượng danh sách các tham số với sự thay đổi các giá trị của tham số trong khối lệnh. Cú pháp: IRP tên tham số Khối lệnh ENDM Ví dụ: IRP varX DB varX ENDM Khi dịch khối lệnh trên sẽ được dịch như sau: DB 0 DB 2 DB 10 3. Lệnh: IF và IFE Chức năng: Lệnh điều khiển IF báo cho chương trình dịch hợp ngữ biết phải thực hiện việc dịch khối lệnh khi giá trị của biểu thức khác 0. Cú pháp: IF Khối lệnh ENDIF Hoặc: IF Khối lệnh 1 ELSE Khối lệnh 2 ENDIF Ví dụ: IF is8086 ; nếu CPU là 8086 thì không cho phép lưu vào Mov AX,EFh ; một hằng số trực tiếp vào ngăn xếp Push AX ELSE Push EFh ; nếu không phải thì có thể ENDIF 49
  51. Chương 2: Lập trình bằng hợp ngữ Lệnh điều khiển IFE giống IF song việc dịch được tiến hành khi giá trị của biểu thức bằng 0 IFE 0 Khối lệnh ENDIF Luôn dịch khối lệnh bên trong. IFE và ENDIF 4. Lệnh: IFDEF và IFNDEF Chức năng: Lệnh điều khiển IFDEF báo cho chương trình dịch hợp ngữ biết phải thực hiện khối lệnh khi nhãn đã được khai báo. Cú pháp: IFDEF Nhãn Khối lệnh ENDIF Hoặc: IF Nhãn Khối lệnh 1 ELSE Khối lệnh 2 ENDIF Lệnh điều khiển IFNDEF giống lệnh trên nhưng với điều kiện ngược lại là chương trình dịch sẽ thực hiện khối lệnh nếu nhãn đã được khai báo. Ví dụ: Tránh việc khai báo hai lần giá trị khởi đầu của một biến varX DB 1 . IF varX Display “khai báo trùng hợp biến varX ” Err ELSE varX DB 10 ENDIF 5. Lệnh: IFB và IFNB Chức năng: Các lệnh này được dùng để kiểm tra việc truyền tham số cho macro. Chúng báo cho chương trình dịch hợp ngữ biết phải thực hiện khối lệnh nếu tham số là rỗng. Cú pháp: IFB Tham số Khối lệnh ENDIF Hoặc: IFB Tham số Khối lệnh 1 50
  52. Chương 2: Lập trình bằng hợp ngữ ELSE Khối lệnh 2 ENDIF Lệnh điều khiển IFNB giống lệnh trên nhưng với điều kiện ngược lại là chương trình dịch sẽ thực hiện khối lệnh nếu thông số không phải là dấu trống. 6. Lệnh: IFDIF và IFIDN Chức năng: Lệnh IFDIF báo cho chương trình dịch hợp ngữ biết phải thực hiện khối lệnh nếu hai tham số bằng nhau. Cú pháp: IFDIF Tham số1, Tham số 2 Khối lệnh ENDIF Hoặc: IFDIF Tham số1, Tham số 2 Khối lệnh 1 ELSE Khối lệnh 2 ENDIF Lệnh điều khiển IFIDN giống lệnh IFDIF nhưng với điều kiện ngược lại là chương trình dịch sẽ thực hiện khối lệnh nếu hai tham số phải là khác nhau. Chú ý: Tên tham số của 2 lệnh trên có phân biệt chữ hoa, chữ thường (Case sensitive) b. Cơ bản về Macro Macro bao gồm một tên đại diện và một khối lệnh. Khi chương trình dịch hợp ngữ gặp tên này ở đâu trong chương trình thì khối lệnh sẽ được dịch và đặt khối mã lệnh vào vị trí mà chương trình gọi tên macro. Về cơ chế thực hiện thì macro giống với file INCLUDE. Nghĩa là, mỗi lần chương trình dịch gặp tên của macro hay tên file INCLUDE thì toàn bộ khối lệnh được định nghĩa trong macro hay file INCLUDE sẽ được dịch và đặt vào vị trí lời gọi. Tuy nhiên, giữa file INCLUDE và MACRO có một số điểm khác nhau sau đây. Nhìn chung, macro mạnh hơn file INCLUDE vì: • Macro có thể truyền tham số. • Macro có thể chứa các nhãn cục bộ. • Việc dịch Macro diễn ra nhanh hơn vì macro là 1 phần của chương trình (không phải là file) nên không phải đọc từ đĩa ngoài. • Macro còn sử dụng một số lệnh điều khiển điều kiện hoặc lặp khi thực hiện. c. Khai báo và sử dụng Macro Trước khi sử dụng , ta phải khai báo macro theo cú pháp sau: Tên macro MACRO các tham số Thân macro ENDM 51
  53. Chương 2: Lập trình bằng hợp ngữ Ví dụ: Cong MACRO Xor AX,AX Add AX,BX Add AX,CX Add AX,DX ENDM Để sử dụng Macro này ta chỉ cần đưa tên của macro vào vị trí gọi. Mov BX,10 Mov CX,100 Mov DX,1000 Cong Chú ý: • Chương trình con thường được sử dụng khi cần tiết kiệm vùng nhớ vì với chương trình con thì các mã lệnh được dịch ra cho một nhiệm vụ nào đó chỉ có một lần và nó sẽ được gọi ở bất kỳ nơi nào trong chương trình khi cần đến. Tuy nhiên Macro được đánh giá là thực hiện nhanh hơn vì nó thực hiện một nhóm lệnh như 1 phần của chương trình gọi nó và không phải sử dụng lệnh CALL và RET. • Macro linh hoạt hơn so với chương trình con. Chúng sử dụng các lệnh điều khiển để trao đổi giữa tham trị và tham số hình thức. Nếu trong 1 chương trình có nhiều đoạn mã lệnh thực hiện các nhiệm vụ gần giống nhau thì macro rất hiệu quả. d. Trao đổi tham số Một macro có thể có 0,1 hoặc nhiều tham số. Macro ở ví dụ trên là macro không có tham số. Ta xem xét một số macro có tham số thông qua các ví dụ. Ví dụ: Dùng 1 macro để khai báo một mảng kiểu byte, có độ dài Length và tất cả các phần tử được gán giá trị ban đầu là Value. Mang MACRO Value, Length REPT Length DB Value ENDM Để sử dụng Macro ở trên, ta chỉ việc truyền các giá trị cho các tham số. Chẳng hạn, cần khởi tạo 10 phần tử và giá trị khởi tạo là 0 thì ta làm như sau: MangByte LABEL BYTE Mang 0,10 Các giá trị 0 và 10 được truyền vào tham số Value và Length khi Macro được sử dụng. 0 và 10 là tham số thực trong khi Value và Length là tham số hình thức. Khi macro được gọi thì các tham số hình thức được thay bới tham số thực. Nghĩa là: MangByte LABEL BYTE REPT 10 52
  54. Chương 2: Lập trình bằng hợp ngữ DB 0 ENDM 2.4 Chương trình ví dụ Ví dụ 0: Viết chương trình in ra nhập vào một kí tự nhưng in ra màn hình kí tự kế tiếp. Chẳng hạn, khi nhập vào kí tự ‘a’ thì mà hình lại hiện ra kí tự ‘b’. Bài giải Ta sử dụng hàm 08 của ngắt 21h để nhập 1 kí tự không hiện lên màn hình rồi sau đó dung hàm 02 để in kí tự kế tiếp (tăng mã ASCII lên 1) ra màn hình. .MODEL Tiny .CODE Org 100h Jmp Start Start: Mov AH,08h ; nhập 1 kí tự không hiện lên màn hình Int 21h Mov DL,AL ; chuyển mã ASCII của kí tự vào DL Inc DL ; DL chứa kí tự kế tiếp Mov AH,02h ; In ra màn hình Int 21h ; Int 20h ; trở về DOS End Start Ví dụ 1: Viết chương trình in ra 256 kí tự của bảng mã ASCII Bài giải Ta sử dụng một vòng lặp FOR-DO và dùng DL đê chứa mã ASCII của các kí tự trong bảng mã ASCII. CX chứa số kí tự cần in (256). Mỗi kí tự cách nhau bởi 1 dấu cách. Chương trình được viết theo khung của chương trình COM .MODEL Tiny .CODE Org 100h Jmp Start Start: Mov CX,256 ; số kí tự cần in Mov DL,0 ; kí tự đầu tiên Mov AH,2 ; hàm 2 ngắt 21h in ra 1 kí tự lên màn hình Tiep: Int 21h Mov BL,DL ; dùng BL để chứa tạm mã ASCII của kí tự Mov DL,32 Int 21 ; In dấu cách Mov DL,BL ; lấy lại kí tự in cuối cùng Inc DL ; sang kí tự tiếp theo Loop Tiep ; In kí tự kế tiếp 53
  55. Chương 2: Lập trình bằng hợp ngữ Int 20h ; trở về DOS End Start Ví dụ 2: Viết chương trình nhập vào một dãy các kí tự rồi hiển thị nó theo thứ tự ngược lại. Bài giải Ta có thể sử dụng ngăn xếp để giải quyết bài toán này. Mỗi khi có ký tự được nhập vào sẽ được PUSH vào ngăn xếp, sau khi nhập xong (bằng cách gõ Enter) thì các kí tự trong ngăn xếp sẽ được POP ra và hiển thị theo thứ tự ngược lại so với ban đầu. .MODEL small .STACK 100h .DATA NhapXau db ‘Nhap vao day ki tu: ’,’$’ InXau db ‘Day ki tu in ra theo thu tu nguoc lai la: ’,’$’ xuongdong db 13,10,’$’ .CODE Start: Mov AX,@Data Mov DS,AX Mov AH,9 Mov DX, offset NhapXau Int 21h ; in lời mời nhập xâu Xor CX,CX ; CX=0 Mov AH,1 ; Nhap ki tu DocVao: Int 21h Cmp AL,13 ; co phai Enter khong? JE ThoiDoc ; Neu là Enter, dung lai Push AX ; Cho vao ngan xep Inc CX ; Tang de dem so ki tu da nhap Jmp DocVao ThoiDoc: Mov AH,9 Mov DX, offset xuongdong Int 21h ;xuong dong va ve dau dong Mov DX, offset InXau Int 21h ; in lời mời in xâu InXau Mov AH,2 HienThi: Pop DX ; In tung ki tu trong ngan xep Int 21h Loop HienThi ; In ra cho den khi CX=0 Mov AH,4Ch ; Tro ve DOS Int 21h 54
  56. Chương 2: Lập trình bằng hợp ngữ End Start Ví dụ 3: Nhập vào hai số nguyên x và y (0 y ta lấy x trừ đi y, ngược lại ta lấy y trừ đi x và in dấu trừ trước kết quả. .MODEL small .STACK 100h .DATA stringX db ‘x= ’,’$’ stringY db ‘y= ’,’$’ xuongdong db 13,10,’$’ Hieu db ‘x-y = ’,’$’ X db ? Y db ? .CODE Start: Mov AX,@Data Mov DS,AX Mov AH,9 Mov DX, offset stringX Int 21h ; in xâu ‘x = ’ Call Nhap ; gọi chương trình con Nhập Mov x,AL Mov AH,9 Mov DX, offset xuongdong Int 21h ; in xâu xuống dòng và về đầu dòng Mov DX, offset xuongdong Int 21h ; in xâu ‘y = ’ Call Nhap ; gọi chương trình con Nhập Mov y, AL Mov AH,9 Mov DX, offset xuongdong Int 21h ; in xâu xuống dòng và về đầu dòng 55
  57. Chương 2: Lập trình bằng hợp ngữ Mov DX, offset xuongdong Int 21h ; in xâu ‘x-y = ’ Mov DL,x ; DL=x Cmp DL,y ; so sánh x với y Sub DL,y Call Inra ; gọi chương trình con Inra Jmp Ketthuc ; nhayr Jb Behon ; nhảy nếu x<y Mov AL,y Sub AL,x Mov AH,2 Mov DL,’-’ ; In dấu trừ trước kết quả Int 21h Mov DL,AL Call Inra ; gọi chương trình con Inra Ketthuc: Mov AH,4Ch Int 21h End Start ; ; chương trình con nhập, trả lại số nhập được trong AL ; Nhap Proc Mov AH,1 ; hàm 01 nhập vào 1 kí tự Nhaplai: Int 21h Cmp AL,30h ; nhỏ hơn kí tự ’0’ Jb NhapLai ; nhập lại Cmp AL,39h ; lơn hơn kí tự ‘9’ Ja NhapLai ; nhập lại Sub AL,30h ; đối mã ASCII sang số Nhap Endp ; ;chưong trình con In ra 1 số trong khoảng 0 9 trong DL ; Inra Proc Mov AH,2 ; in ki tự Add DL,30h ; đổi sang mã ASCII Int 21h Inra Endp ; 56
  58. Chương 2: Lập trình bằng hợp ngữ Ví dụ 4: Nhập vào một xâu kí tự rồi in xâu đó ra màn hình Bài giải: Đây là một bài tập không khó, tuy nhiên có một số vấn đề mà người học lập trình cần biết trước khi viết chương trình giải bài toán này. Đó là cấu trúc của vùng đệm (buffer) khi lưu trữ xâu kí tự. Chẳng hạn, xâu ‘Hello’ được lưu trữ trong vùng đệm như sau: Chứa độ Chứa độ Kí tự dài lớn dài thực Kí tự đầu kết thúc nhất của của tiên xâu xâu Xâu 255 5 H e l l o $ Byte đầu tiên của vùng đệm chứa độ dài lớn nhất của xâu, byte thứ 2 chứa độ dài thực. Xâu thực sự được chứa từ byte thứ 3 trở đi. Tuy nhiên, thông thường thì người dùng kết thúc việc nhập bằng phìm Enter có mã ASCII là 13 nhưng kí tự kết thúc xâu lại là $ nên chương trình phải xử lý việc này bằng cách sau: Lấy địa chỉ offset của xâu đem cộng với nội dung byte thứ hai rồi cộng với 2 thì sẽ trỏ đến byte cuối cùng của xâu đang chứa mã ASCII của Enter (13) rồi thay thế mã này bởi kí tự kết thúc xâu là ‘$’. Dưới đây là chương trình hợp ngữ: .MODEL Tiny .CODE Org 100h Jmp Start XauIn db ‘Nhap xau: ’,’$’ XauOut db ‘Xau vua nhap: ’,’$’ Xuongdong db 13,10,24h Buffer db 100 dup(?) ; Khai bao buffer Start: Mov AH,9 Mov DX, offset XauIn Int 21h Mov AH,0Ah Mov DX, offset Buffer Mov BX,DX ; BX va DX cung tro den Buffer Mov BYTE PTR[BX],100 ; Do dai lon nhat cua Int 21h Mov AH,9 Mov DX, offset Xuongdong ; xuong dong va ve dau dong Int 21h Mov DX, offset XauOut ; Xau kq 57
  59. Chương 2: Lập trình bằng hợp ngữ Int 21h Mov DX,BX ; BX,DX cung tro den Buffer Add BL,[BX+1] ; Cong vao do dai thuc cua xau vao BX Add BX,2 ; Tro den byte cuoi cung Mov BYTE PTR[BX],’$’ ; Thay the byte cuoi cung boi $ Add DX,2 ; bo qua hai byte dau Int 21h ; in xau ra Int 20h ; trở về DOS End Start Ví dụ 5: Viết chương trình tạo một thư mục với tên thư mục được nhập từ bàn phím. Bài làm Trước hết, ta phải nhập vào 1 xâu ký tự tên thư mục. Sau đó sử dụng hàm 39h để tạo thư mục. Kết thúc việc tạo thư mục ta kiểm tra cờ Carry (CF), nếu cờ Carry bằng 1 thì việc tạo thư mục đã bị lỗi, ngược lại là tạo thành công. Lệnh JC (Jump if Carry equals to 1) thực hiện việc đó. .MODEL Tiny .CODE Org 100h Jmp Start TenThuMuc db ‘Nhap ten thu muc: ’,’$’ OK db ‘Tao thu muc thanh cong’,’$’ NotOK db ‘Tao thu muc khong thanh cong’,’$’ Xuongdong db 13,10,24h Start: Mov AH,9 Mov DX, offset TenThuMuc Int 21h Mov AH,0Ah Mov DX, offset Buffer Mov BX,DX ; BX va DX cung tro den Buffer Mov BYTE PTR[BX],100 ; Do dai lon nhat cua Int 21h Mov AH,9 Mov DX, offset Xuongdong ; xuong dong va ve dau dong Int 21h Mov DX,BX ; BX,DX cung tro den Buffer Add BL,[BX+1] ; Cong vao do dai thuc cua xau vao BX Add BX,2 ; Tro den byte cuoi cung Mov BYTE PTR[BX],’$’ ; Thay the byte cuoi cung boi $ Add DX,2 ;bo qua hai byte dau, DX=ten thu muc Mov AH,39h ; ham tao thu muc Int 21h JC Error ; CF=1, bi loi Mov AH,9 58
  60. Chương 2: Lập trình bằng hợp ngữ Mov DX, offset OK ; thanh cong Int 21h Jmp Ketthuc Error: Mov AH,9 Mov DX, offset NotOK ; Khong thanh cong Int 21h Ketthuc: Int 20h ; trở về DOS End Start Ví dụ 6: Hãy định nghĩa trước một mảng các số nguyên trỏ bởi biến Mang. Hãy sắp xếp theo chiều tăng dần mảng số nguyên này rồi in kết quả lên màn hình. Bài làm Đây là một bài toán hay gặp khi học các ngôn ngữ lập trình. Ta sử dụng thuật giải sắp xếp chèn (INSERTION SORT) để giải bài toán này. Ý tưởng như sau: tìm phần tử lớn nhất của mảng rồi đặt phần tử đó vào cuối dãy, sau đó lại tiếp tục quá trình này với các phần tử còn lại. Tại mỗi lần tìm thì một phần tử được đặt đúng chỗ. Ở bước lặp thứ i có i phần tử được đặt đúng chỗ và ta chỉ cần tìm phần tử lớn nhất trong n-i phần tử còn lại. Ta tổ chức chương trình này thành một chương trình chính và một chương trình con. Chương trình con Exchange sẽ làm nhiệm vụ hoán đổi vị trí của hai phần tử của dãy. .MODEL small .STACK 100h .DATA Thongbao db ‘Day da sap xep: ’,’$’ xuongdong db 13,10,’$’ Mang db 8,4,3,1,2,5,1 Db ‘$’ .CODE Start: Mov AX,@Data Mov DS,AX Mov AH,9 Mov DX, offset Thongbao Int 21h ; in thongbao Mov DX, offset xuongdong Int 21h ; nhay xuong dong Mov BX,7 ; BX= so phan tu cua mang Mov DX, offset Mang ; DX tro vao mang Dec BX ; so vòng lặp bên ngoài Lap: Mov SI,DX ; SI trỏ vào đầu mảng Mov CX,BX ;Số lần lặp ở vòng lặp bên trong (tìm max) Mov DI, SI ;giả sử phần tử thứ 1 là max 59
  61. Chương 2: Lập trình bằng hợp ngữ Mov AL, [DI] ; AL= max TimMax: Inc SI ; phần tử kế tiếp Cmp [SI],AL ; phần tử mới > max? JB Tiep ; không lớn hơn max Mov DI,SI ; lớn hơn max, DI trỏ vào max Mov AL,[DI]; Đưa max vào AL Tiep: Loop TimMax Call DoiCho Dec BX JNZ Lap ; In Mang Mov BX,DX ; BX trỏ đến phần tử đầu tiên Mov CX,7 ; in cả 7 phần tử Mov AH,2 InMang: Mov DL,[BX] Add DL,30h Int 21h ; in ra Mov DL,32 ; in dấu cách giữa các phần tử cho dễ xem Int 21h Inc BX ; sang phần tử kế tiếp Loop InMang Ketthuc: Mov AH,4Ch Int 21h End Start ; ; chương trình con DoiCho ; DoiCho Proc Push AX Mov AL,[SI] XCHG AL,[DI] Pop AX Ret Nhap Endp ; 2.5 TÓM TẮT Chương này trình bày các vấn đề cơ bản của lập trình hợp ngữ: tạo và thực hiện một chương trình hợp ngữ, các cấu trúc lập trình cơ bản , chương trình con và Macro. 60
  62. Chương 2: Lập trình bằng hợp ngữ Trong phần viết và thực hiện một chương trình chúng ta đã tìm hiểu cấu trúc đầy đủ một lệnh hợp ngữ, cách thức khai báo hằng và biến. Phần này cũng trình bày khung của chương trình hợp ngữ, cách tạo, dịch và chạy một chương trình hợp ngữ. Công cụ quan trọng của các ngôn ngữ lập trình là các cấu trúc lập trình. Hợp ngữ chỉ hỗ trợ các lệnh nhảy (jump) và so sánh (compare) cho phép người lập trình cài đặt các cấu trúc này. Phần các cấu trúc lập trình cơ bản trình bày cú pháp, cách cài đặt các cấu trúc: tuần tự, điều kiện (IF-THEN), điều kiện phân nhánh (IF-THEN-ELSE), lựa chọn (CASE), các cấu trúc lặp xác định trước (FOR-DO) và lặp không xác định trước (WHILE-DO, REPEAT-UNTIL). Phần cuối cùng là các vấn đề liên quan đến chương trình con. Ngoài các vấn đề cơ bản liên quan đến chương trình con như: cơ chế, cấu trúc của chương trình con, cách thức truyền tham số, phần này cũng đề cập đến việc chia nhỏ một chương trình lớn thành các chương trình con và gói chúng vào các module hay thư viện nhờ các lệnh điều khiển PUBLIC, EXTRN, GLOBAL. Ngoài ra, cùng với chương trình con, Macro là một lựa chọn khi viết chương trình hợp ngữ. Macro không những thực hiện nhanh, mềm dẻo và khá hiệu quả. Chúng ta cũng đã khảo sát về các lệnh điều khiển hay được sử dụng trong các Macro, cách khai báo và sử dụng Macro. 2.6 BÀI TẬP Bài 1: Viết chương trình in ra màn hình 26 chữ cái ‘A’ ’Z’. Bài 2: Viết chương trình nhập vào 1 số nguyên n (0≤ n≤ 9), tính tổng S=1+2+3+ +n rồi in kết quả ra màn hình. Bài 3: Viết chương trình nhập vào hai số nguyên x và y (0≤ x,y ≤ 9), tính tổng x+y rồi in kết quả ra màn hình. Yêu cầu: Chương trình được tổ chức như sau: gồm 1 chương trình chính, 2 chương trình con hoặc 2 macro, 1 dùng để nhập vào 1 số và 1 dùng để in kết quả . Bài 4: Viết chương trình nhập vào từ bàn phím hai số nguyên a và b với 0 ≤ a,b ≤256. Tính tích của chúng và in kết quả ra màn hình. Yêu cầu chương trình phải được tổ chức thành các chương trình con hoặc macro. Bài 5: Viết chương trình nhập vào một tên file rồi xóa file đó. In ra màn hình thông báo xóa thành công hay không. Bài 6: Viết chương trình tạo một file backup từ một file văn bản nguồn. Tên của file văn bản được nhập vào từ bàn phím. 2.7 TÀI LIỆU THAM KHẢO 1. Văn Thế Minh. Kỹ thuật Vi xử lý. Nhà XB Giáo dục 1997. 2. Đặng Thành Phu. Turbo Assembler và Ứng dụng. NXB Khoa học và Kỹ thuật 1998. 3. Nguyễn Minh San. Cẩm nang Lập trình hệ thống (bản dịch). NXB Tổng cục Thống kê.2001. 61
  63. Chương 3: Các công cụ hỗ trợ CHƯƠNG 3. CÁC CÔNG CỤ HỖ TRỢ Phần này trình bày về trình tiện ích Debug, chương trình mô phỏng Emu8086, sự kết hợp chương trình hợp ngữ với ngôn ngữ bậc cao. Cuối cùng ta xem xét về các chương trình ngắt. 3.1 BỘ GỠ RỐI DEBUG 3.1.1 Tổng quan về Debug DEBUG là một trình tiện ích trợ giúp người lập trình tác động đến quá trình thực hiện một công việc (task) nào đó. Đồng thời Debug là một chương trình dùng để gỡ lỗi chương trình. Debug hỗ trợ cho người dùng các nhóm lệnh sau: Thao tác với bộ nhớ: - lệnh hiển thị nội dung ô nhớ (lệnh D) - lệnh sửa nội dung ô nhớ (lệnh E) - điền thông tin vào một vùng nhớ (lệnh F) - chuyển nội dung từ vùng nhớ này sang vùng nhớ khác (lệnh M). Thao tác với các files: - đặt tên file (lệnh N) - nạp nội dung một file vào bộ nhớ (lệnh L) - chạy file dang .COM hoặc .EXE (lệnh G) Truy cập đến các sector trên đĩa (lệnh L,W) Soạn thảo và thực hiện một chương trình hợp ngữ (lệnh A,G) Theo dõi quá trình thực hiện 1 chương trình - xem, sửa chữa trạng thái thanh ghi (lệnh R) - chạy từng bước (lệnh T, lệnh P) Thực hiện một số thao tác vào/ra đối với các thiết bị ngoại vi (lệnh I và O) Dịch ngược từ mã máy sang hợp ngữ (lệnh U) Tìm kiếm (lệnh S) 3.1.2 Sử dụng Debug a. Khởi động Debug Cách 1: Tại thư mục DOS: C:\DOS> gõ debug C:\DOS> debug Dấu nhắc của Debug là dấu – Cách 2: 62
  64. Chương 3: Các công cụ hỗ trợ Gõ C:\DOS> debug tenfile.exe Khi đó cả trình debug và chương trình người dùng (tenfile.exe) đều được đưa vào bộ nhớ RAM. b. Một số lưu ý - Địa chỉ của vùng nhớ (address): được thể hiện dưới dạng SEGMENT:OFFSET, chẳng hạn như: DS:0300 hoặc 9D0:01FF Nếu ở đoạn hiện tại, ta có thể chỉ cần dùng địa chỉ offset, ví dụ: 02FFh - Khoảng (range): thể hiện địa chỉ một vùng nhớ: address L value ví dụ: DS:1FF L 10 - Mỗi lệnh của Debug gồm 1 kí tự duy nhất - Giữa tên lệnh và tham số có ít nhất 1 dấu cách - Dùng dấu cách giống như dùng dấu phẩy (,) - Kết thúc 1 lệnh đang được thực hiện bằng Ctrl+C hoặc Ctrl+Break - Lệnh được thực hiện nếu gõ tên lệnh và gõ enter 3.1.3 Các lệnh của Debug 1. Lệnh: A Chức năng: Soạn thảo và dịch trực tiếp các lệnh hợp ngữ. Cú pháp: A [địa chỉ] Trong đó [địa chỉ] là địa chỉ offset của ô nhớ (dạng hexa) mà ta cần đặt mã lệnh vào. Ví dụ: -A 1FF0 xxxx:1FF0 mov AH,9 xxxx:1FF2 mov AH,9 - Nếu chưa xác định được địa chỉ ban đầu thì lệnh sẽ tự đông đưa vào địa chỉ offset 0100h - Nếu phát hiện lỗi trong một lệnh, Debug sẽ đưa ra thông báo ERROR và hiện lại địa chỉ ô nhớ có lỗi đó để người sử dụng sửa lại lệnh đó cho đúng - Muốn trở lại dấu nhắc Debug thì nhấn hai lần Enter - Lệnh A luôn sử dụng địa chỉ tuyệt đối, nghĩa là luôn đi kèm với 1 địa chỉ chính xác. 2. Lệnh: C Chức năng: So sánh nội dung của hai vùng nhớ. Cú pháp: C khoảng,địa chỉ 63
  65. Chương 3: Các công cụ hỗ trợ Trong đó khoảng bao gồm địa chỉ đầu tiên và địa chỉ cuối cùng của một vùng nhớ, địa chỉ là 1 địa chỉ offset bắt đầu của 1 vùng nhớ khác. Ví dụ: -C 100, 1FF, 500 Hoặc: -C 100 L 100, 500 300 3FF 100 1FF 3. Lệnh: D Chức năng: Hiển thị nội dung của một vùng nhớ. Cú pháp: D [khoảng |địa chỉ] Trong đó khoảng bao gồm địa chỉ đầu tiên và địa chỉ cuối cùng của một vùng nhớ, hoặc địa chỉ là 1 địa chỉ offset của 1 ô nhớ khác. Ví dụ: -D 100, 1FF Hoặc: -D 100 L 11 Nếu sử dụng lệnh: -D Thì nội dung các ô nhớ từ địa chỉ đã cho đến 128 byte kế tiếp sẽ được hiện lên. Nếu tiếp tục -D Thì nội dung 128 byte kế tiếp theo đó được hiện lên. 4. Lệnh: E Chức năng: Hiện nội dung ô nhớ và cho phép sửa nội dung ô nhớ. Cú pháp: 64
  66. Chương 3: Các công cụ hỗ trợ Cách 1: E Ví dụ: -E 01FF:0100 ‘ABC’9A Thì các ô nhớ từ 01FF:0100 đến 01FF:0103 sẽ lần lượt được điền các các giá trị là mã ASCII của A, B, C và giá trị 9A. Cách 2: E Thì nội dung của ô nhớ có địa chỉ trên sẽ hiện lên, ta có thể thay đổi giá trị mới và sau đó nếu muốn thay đổi nội dung của ô nhớ kế tiếp thì nhấn dấu cách (SPACE), còn nếu muốn dừng lại thì nhấn Enter để trở lại màn hình debug. Ví dụ: -E 01FF:0100 9A Nếu muốn sửa giá trị của ô nhớ này thì đưa vào giá trị mới vào rồi nhấn Enter đển hiện nội dung của ô nhơ kế tiếp 01FF:0101 và cứ tiếp tục như vậy cho đến khi nhấn Enter nếu muốn dừng. 5. Lệnh: F Chức năng: Nạp các giá trị trong danh sách vào một vùng nhớ. Cú pháp: F Ví dụ: -F 01FF:0100 L 4,‘ABC’9A Thì các ô nhớ từ 01FF:0100 đến 01FF:0103 sẽ lần lượt được điền các các giá trị là mã ASCII của A, B, C và giá trị 9A. 6. Lệnh: G Chức năng: Cho thực hiện một chương trình đang hiệu chỉnh. Việc thực hiện chương trình sẽ dừng lại khi đạt đến địa chỉ dừng. Sau đó’, hiển thị các thanh ghi và dòng lệnh thực hiện tiếp theo của chương trình Cú pháp: G [ ] [ ] Nếu gõ lệnh - G [ ] Thì chương trình sẽ được thực hiện từ địa chỉ đầu cho đên lệnh cuối cùng của chương trình. Nếu là chương trình con thì dừng lại khi gặp lệnh RET, nếu là macro thì dừng lại khi gặp lệnh ENDM. Nếu gõ lệnh - G Thì chương trình sẽ được thực hiện từ địa chỉ đã được nạp vào cặp thanh ghi CS:IP cho đến lệnh cuối cùng của chương trình 7. Lệnh: H Chức năng: Cộng và trừ hai giá trị hexa và hiển thị kết quả của tổng và hiệu lên màn hình. Cú pháp: H 65
  67. Chương 3: Các công cụ hỗ trợ Ví dụ: - H 10, 0f 1F, 01 Kết quả của tổng là 1F và của hiệu là 01 8. Lệnh: I Chức năng: Đọc và hiển thị giá trị của một cổng lên màn hình. Cú pháp: I Ví dụ: - I 1f 26 26 là giá trị đọc được từ cổng 1F. 9. Lệnh: L Chức năng: Chuyển nội dung 1 file hoặc nội dung sector của đĩa vào vùng nhớ. Cú pháp: Dạng 1: L [ ,[, ổ đĩa, sector, số sector]] Đọc số liệu từ sector đầu của ổ đĩa, với số lượng sector và bắt đầu từ địa chỉ được chỉ ra ở tham số thứ nhất. Ví dụ 1: - L 01FA:0100 1 0A 30 Đọc số liệu của 48 sector (30h) bắt đầu từ sector 0A của ổ đĩa B và vùng nhớ có địa chỉ 01FA:0100. (Các ổ đĩa được kí hiệu như sau: 0 là ổ đĩa A, 1 là ổ đĩa B, 2 là ổ đĩa C, 3 là ổ đĩa D). Nếu tên file được đặt tên bởi lệnh –N thì: - lệnh –L sẽ nạp nội dung của file vào vùng nhớ mặc định CS:0100 - lệnh –L sẽ nạp nội dung của file vào vùng nhớ có địa chỉ . Ví dụ 2: - N cong2so - L Nội dung của file cong2so sẽ được nạp vào vùng nhớ có địa chỉ đầu là CS:0100 Ví dụ 3: - N cong2so - L 03FF Nội dung của file cong2so sẽ được nạp vào vùng nhớ có địa chỉ đầu là CS:03FF 10. Lệnh: M Chức năng: Chuyển nội dung từ một vùng nhớ sang một vùng nhớ khác. Cú pháp: -M , Nếu trong khoảng và địa chỉ không xác định đoạn thì sẽ lấy DS là địa chỉ đoạn 66
  68. Chương 3: Các công cụ hỗ trợ Ví dụ nếu sử dung 1 trong 3 lệnh sau: - M 01FF:0100, 010E, 01FF:0200 - M 01FF:0100 L F, 01FF:0200 - M 01FF:0100 010E 01FF:0200 Thì 15 byte từ vùng nhớ có địa chỉ 01FF:0100 đến 01FF:010E sẽ được chuyển đến vùng nhớ có địa chỉ bắt đầu là: 01FF:0200 11. Lệnh: N Chức năng: Đặt tên cho file. Lệnh này thường được đi kèm với các lệnh –L và –W dùng tên file đó . Cú pháp: -N .EXE (hoặc .COM) Ví dụ: - N Tenfilemoi.exe - L 12. Lệnh: O Chức năng: Đưa một byte dữ liệu ra cổng. Cú pháp: -O , Ví dụ: - O 02F, 20 13. Lệnh: Q Chức năng: Thoát khỏi Debug và trở về DOS. Cú pháp: -Q 14. Lệnh: R Chức năng: Hiển thi và sửa đổi nội dung các thanh ghi. Cú pháp: -R [thanh ghi | F] Có các trường hợp sau: - R hiển thị và sửa đổi nội dung các thanh ghi. - RAX hiển thị nội dung của thanh ghi AX và cho phép sửa nội dung đó, ví dụ: AX 101A. Nếu không muốn thay đổi giá trị thì nhấn Enter, còn muốn sửa giá trị mới thì nhập giá trị mới vào rồi nhấn Enter. Muốn hiển thị nội dung của các thanh ghi kế tiếp (BX, CX, DX) thì nhấn dấu cách (SPACE). - R F hiện và sửa nội dung của thanh ghi cờ. 15. Lệnh: S 67
  69. Chương 3: Các công cụ hỗ trợ Chức năng: Tìm trong vùng nhớ xác định bởi khoảng các kí tự trong danh sách. Cú pháp: -S , Nếu khoảng không xác định thì đoạn ngầm định là thanh ghi DS. Ví dụ 1: - S 01FF:0100 0110 20 Hoặc: - S 01FF:0100 L 10 20 Thì sẽ tìm các ô nhớ có nội dung bằng 20h trong vùng nhớ từ 01FF:0100 đến 01FF:0110. Kết quả là tất cả địa chỉ của các ô nhớ có nội dung bằng 20 thì sẽ được hiển thị, chẳng hạn có 3 ô nhớ có nội dung bằng 20 thì màn hình sẽ liệt kê như sau: 01FF:0100 01FF:0104 01FF:0105 Ví dụ 2: - S 01FF:0100 0110 ‘ABC’2E Sẽ tìm 4 ô liên tiếp chứa giá trị là mã ASCII của A, B, C và 2E. 16. Lệnh: T Chức năng: Thực hiện một hay nhiều lệnh bắt đầu từ địa chỉ CS:IP hoặc từ địa chỉ được chỉ ra ở dấu = , và hiển thị trạng thái toàn bộ các thanh ghi sau mỗi lệnh. Cú pháp: -T [= địa chỉ][, số lệnh] - địa chỉ là địa chỉ của lệnh đầu tiên sẽ thực hiện - số lệnh được thực hiện trong chế độ này. Có các trường hợp sau: - T lệnh tại địa chỉ CS:IP sẽ được thực hiện. - T 10 10 lệnh bắt đầu từ địa chỉ CS:IP sẽ được thực hiện. Trạng thái của tất cả các thanh ghi sau mỗi lệnh sẽ được hiện ra 1 cách liên tục. - T=2FF,10 sẽ thực hiện 16 lệnh bắt đầu từ lệnh tại địa chỉ CS:02FF. 17. Lệnh: P Chức năng: Giống lệnh T nhưng thực hiện cả 1 chương trình con. Cú pháp: -P [= địa chỉ][, số lệnh] 18. Lệnh: U Chức năng: Dịch ngược các lệnh dưới dạng mã máy nằm trong vùng nhớ sang dạng hợp ngữ và hiển thị địa chỉ, mã máy và lệnh dạng gợi nhớ lên màn hình. Cú pháp: -U [khoảng][địa chỉ] Có các trường hợp sau: 68
  70. Chương 3: Các công cụ hỗ trợ - U các lệnh nằm trong vùng nhớ sẽ được dịch ngược. - U dịch ngược bắt đầu từ địa chỉ cho đến 128 byte kế tiếp. - U dịch ngược bắt đầu từ địa chỉ CS:IP đến 128 byte kế tiếp. 19. Lệnh: W Chức năng: Ghi dữ liệu lên đĩa. Cú pháp: -W [địa chỉ [,ổ đĩa, sector đầu, số sector] Dữ liệu trong vùng nhớ bắt đầu từ địa chỉ ghi lên ổ đĩa vào sector đầu tiên cho đến sector cuối do số sector xác định. Ví dụ: - N cong2so.exe - W 01FF:0200, 1, 2A,5 Dữ liệu từ vùng nhớ xác định bởi 01FF:0200 được ghi vào ổ đĩa B từ sector 2A và ghi vào 5 sector với tên file là cong2so.exe. 3.2 CHƯƠNG TRÌNH MÔ PHỎNG EMU8086 Hiện nay, hầu hết các desktop tại các phòng thực hành tại Việt nam chạy hệ điều hành 32- bit như windows XP, NT, 2000 thì người lập trình không thể gọi ngắt bằng chương trình người dùng được. Thay vì gọi ngắt, các hệ điều hành 32-bit cung cấp một tập các hàm giao diện lập trình ứng dụng gọi là API (Application Progammable Interface) cho phép người lập trình gọi hàm. Để người mới học lập trình hệ thống có thế lập trình với các ngắt mà không bị giới hạn bởi phiên bản khác nhau của các hệ điều hành của Microsoft thì cách tốt nhất là học lập trình trên môi trường mô phỏng Emulator 8086. Phần này chúng tôi giới thiệu về phần mềm mô phỏng CPU 8086 của công ty phần mềm Emu8086 Inc., phiên bản 2.58. Các phiên bản mới hơn có thể được download tại địa chỉ của trang web: www.emu8086.com. 3.2.1 Các chức năng soạn thảo, dịch và thực hiện chương trình. Dưới đây là màn hình cho phép người sử dụng viết một chương trình hợp ngữ hoặc chạy thử một ví dụ có sẵn: 69
  71. Chương 3: Các công cụ hỗ trợ New: tạo một chương trình mới, khi đó người dùng sẽ được hỏi xem sẽ tạo file chương trình dạng nào: COM, EXE, BIN hay BOOT . Open: mở một file chương trình nguồn hợp ngữ. Samples: Liệt kê các file chương trình mẫu có sẵn do chương trình mô phỏng cung cấp. Save: Lưu file chương trình nguồn Compile: dịch file chương trình nguồn Emulate: cho phép thực hiện chương trình nguồn. Các trạng thái của quá trình thực hiện chương trình được hiển thị trên màn hình mô phỏng dưới đây. Calculator: người dùng có thể nhập 1 vào một biểu thức với các số là: có dấu, số dạng word hoặc số dạng byte để tính toán. Kết quả tính toán được hiển thị một trong các dạng số thập phân, nhị phân, hexa hoặc số bát phân (cơ số 8). Convertor: Bộ chuyển đổi gữa các cơ số. Emu8086 hỗ trợ chuyển đổi giữa các cơ số 16 thành cơ số 10 có dấu hoặc không dấu. Chuyển đổi từ cơ số 8 thành thành cơ số 2 (nhị phân). Một mã ASCII gồm 2 số hexa cũng có thể được chuyển đổi thành thập phân hoặc nhị phân. 3.2.2 Chức năng mô phỏng quá trình thực hiện chương trình. Dưới đây là màn hình mô phỏng trạng thái thực hiện một chương trình. 70
  72. Chương 3: Các công cụ hỗ trợ Các chức năng chính: Hình: Màn hình mô phỏng chương trình Load: tải chương trình. Trước khi thực hiện thì chương trình sẽ được tải vào trong bộ nhớ. Chương trình có thể ở dạng các file thực hiện được như EXE, COM, BIN, BOOT hoặc dưới dạng file nguồn ASM. Reload: người dùng có thể tải lại 1 chương trình. Single Step: chạy chương trình theo chế độ từng lệnh. Với chế độ này, người dùng có thể quan sát trạng thái các thanh ghi, bộ nhớ trong Run: chế độ chạy tất cả các lệnh trong chươn trình. Trên màn hình, người dùng có thể quan sát trạng thái các thanh ghi và đoạnh bộ nhớ sử dụng cho đoạn mã lệnh của chương trình. Phần registers mang nội dung của các thanh ghi trong đó các thanh ghi AX,BX,CX và DX được chia làm 2 nửa. phân cao (H) và phần thấp (L). Ngoài ra, ta có thể xem nội dung các thanh ghi đoạn, con trỏ lệnh, ngăn xếp Phần bộ nhớ lưu trữ đoạn mã chương trình. Địa chỉ đoạn (dạng hexa) được lưu trong thanh ghi CS. Danh sách địa chỉ offset được hiển thị dưới các dạng hexa và thập phân. Ngoài ra, người dùng có thể có thể xem: • kết quả hiển thị lên mà hình (nhắp chuột vào nút User Screen). • mã nguồn của chương trình (nhắp chuột vào nút Actual Source). • trạng thái ALU (nhắp chuột vào nút ALU). 71
  73. Chương 3: Các công cụ hỗ trợ • nội dung của ngăn xếp (nhắp chuột vào nút Stack). • nội dung của thanh ghi cờ (nhắp chuột vào nút FLAG) 3.2.3 Các chương trình mẫu. Emu8086 cung cấp cho người dùng 54 chương trình mẫu. Chúng rất có ích cho người học lập trình hợp ngữ. Từ các chương trình đơn giản như Hello world cho đến một số chương trình thao tác với một số thiết bị ngoại vi điển hình như màn hình, máy in Để chạy thử các chương trình mẫu này, người dùng nhắp chuột vào nút Samples/ More Samples để chọn ra một file chương trình để chạy thử. Dưới đây là các giải thích cho 1 một số chương trình mẫu. Chương trình Calculate SUM. Chương trình tính tổng các phần tử trong một mảng V1 đã được định nghĩa trước và lưu kết quả vào biến V2. Dưới đây là nội dung chương trình (lời giải thích được dịch ra tiếng Việt): #make_BIN# ; Tính tổng các phần tử trong mảng V1 ; Lưu kết quả vào biến V2. ; Số phần tử của mảng: MOV CX, 5 ; AL chứa tổng các phần tử: MOV AL, 0 ; BX là chỉ số của mảng: MOV BX, 0 ; Tính tổng: Tong: ADD AL, V1[BX] ; có thể thay đổi giá trị của mảng ; đặt giá trị phần tử bằng chỉ số MOV V1[BX], BL ; phần tử kế tiếp: INC BX ; lặp cho đến khi CX=0: ; tính tổng của tất cả các phần tử LOOP Tong ; lưu kết quả vào biến V2: MOV V2, AL HLT ; Khai báo biến: V1 DB 4, 3, 2, 1, 0 V2 DB 0 Ở chương trình trên có một số điểm khác so với các chương trình ta thường thấy. Lệnh đầu tiên của chương trình là #make_BIN#. Chương trình sẽ được viết dưới dạng file binary. 72
  74. Chương 3: Các công cụ hỗ trợ Các biến của chương trình được khai báo ở phần cuối. Chương trình tính tính tổng hai số nguyên được nhập từ bàn phím nằm trong khoảng [- 32768 32767] rồi in kết quả ra mà hình. Các file chương trình liên quan: calc.asm và emu8086.inc Trong file emu8086.inc chứa một số chương trình con và macro được gọi từ calc.asm. Dưới đây là chương trình calc với các lời giải thích đã được viết lại bằng tiếng Việt. ; Đây là chương trình nhập vào 2 số nguyên ; trong khoảng [-32535, 32536] từ người dùng ; Tính tổng của chúng ; rồi in kết quả ra màn hình ; chương trình dạng COM #make_COM# include 'emu8086.inc' ORG 100h ; Nhảy qua đoạn khai báo biến, hằng JMP START ; khai báo biến: num DW ? START: ; Nhập vào số thứ nhất: CALL PTHIS DB 13, 10, 'Calculation Range: [-32768 32767]', 13, 10 DB 13, 10, 'Enter first number: ', 0 ; Gọi chương trình con scan_num để nhập 1 số, kết quả trả lại ; là một số được lưu trong thanh ghi CX CALL scan_num ; Lưu số thứ nhất vào biến num: MOV num, CX ; nhập vào số thứ 2: CALL PTHIS msg2 DB 13, 10, 'Enter second number: ', 0 CALL scan_num ; cộng các số: ADD num, CX JO overflow ; In kết quả bằng chương trình con PTHIS CALL PTHIS DB 13, 10, 'The sum is: ', 0 MOV AX, num CALL print_num JMP exit ; xử lý lỗi tràn: overflow: 73