Tự học lập trình Assembly – Bài 1: Bước đầu với lập trình Assembly trên vi xử lý Intel 8086/8088

tu hoc Assembly bai 1

BƯỚC ĐẦU VỚI LẬP TRÌNH ASSEMBLY TRÊN VI XỬ LÝ 8088/8086

Giới thiệu về Hợp ngữ

Hợp ngữ (Assembly) là một ngôn ngữ lập trình cấp thấp, nó thực chất là dạng gợi nhớ (Mnemonic), hay dạng kí hiệu, của ngôn ngữ máy.

Như đã biết, lệnh ngôn ngữ máy là một dãy các con số 0, 1 nên rất khó đọc và khó lập trình, vì thế các nhà thiết kế vi xử lý đã đưa ra tập lệnh hợp ngữ gần với ngôn ngữ tự nhiên hơn nên dễ đọc và dễ lập trình hơn. Tuy vậy, các lệnh hợp ngữ vẫn giao tiếp với phần cứng máy tính một cách rất chặt chẽ, nhờ đó việc tiếp cận với lập trình hợp ngữ giúp chúng ta hiểu rõ hơn về kiến trúc và tổ chức hoạt động của máy tính.

Ngoài ra nó còn giúp chúng ta thấy rõ hơn mối quan hệ giữa các thành phần chức năng bên trong máy tính và hệ điề hành. Có thể nói ngược lại là, việc tìm hiểu và lập trình trên hợp ngữ giúp chúng ta hiểu rõ hơn về kiến trúc máy tính, tổ chức hoạt động bên trong máy tính và hệ điều hành.

Trong giới hạn của tài liệu này chúng ta chỉ tìm hiểu tập lệnh hợp ngữ của các vi xử lý họ Intel 8088/8086, để  lập trình chạy trên các máy IBM-PC: Sử dụng họ vi xử lý này và hoạt động trong sự phối hợp với hệ điều hành MS_DOS.

Một trong những đặc điểm của hợp ngữ là chương trình viết trên nó có kích thước nhỏ hơn và tốc độ nạp/thực hiện chương trình nhanh hơn so với viết (chương trình cùng chức năng) trên các ngôn ngữ lập trình bậc cao.

Ngoài ra, hầu hết các ngôn ngữ lập trình bậc cao hiện nay đều cho phép viết (“nhúng”) mã lệnh hợp ngữ trong nó. Điều này giúp người lập trình khai thác tối đa thế mạnh của các ngôn ngữ lập trình, hợp ngữ rất mạnh trong các thao tác can thiệp sâu vào các thành phần bên trong hệ thống, trong khi đó ngôn ngữ bậc cao mạnh trong các thao tác xử lý dữ liệu và thiết kế giao diện. Như vậy sẽ là rất thuận lợi nếu sử dụng ngôn ngữ bậc cao để viết chương trình xử lý thông tin hệ thống, khi đó nhiệm vụ truy xuất hệ thống (thanh ghi, bộ nhớ, cổng vào/ra, thiết bị,…) để lấy dữ liệu sẽ được giao cho các đoạn mã lệnh hợp ngữ được nhúng trong chương trình này.

Hợp ngữ hỗ trợ 2 chế độ tương tác hệ thống: (1) Nhập trực tiếp từng lệnh/đoạn lệnh vào bộ nhớ rồi cho phép thực hiện ngay trên bộ nhớ mà không cần qua bước biên dịch chương trình. Chương trình gỡ rối Debug (đi kèm hệ điều hành MS_DOS: Debug.exe) là một trong những chương trình hỗ trợ chế độ này cho hợp ngữ 16 bít; (2) Viết chương trình hợp ngữ, rồi sau đó sử dụng các chương trình biên dịch để dịch nó sang chương trình thực thi (dạng EXE hoặc COM) và cho thực hiện chương trình này.

Hiện nay có hai loại trình biên dịch được sử dụng để biên dịch chương trình hợp ngữ (từ tập lệnh hợp ngữ của các vi xử lý họ Intel) sang chương trình thực thi: Trình biên dịch hợp ngữ 16 bít, MASM (Macro Assembler), được sử dụng để dịch thành các chương trình chạy trên nền hệ điều hành 16 bít MS_DOS; Trình biên dịch hợp ngữ 32 bít, MASM32 (Macro Assembler 32 bít), được sử dụng để dịch thành các chương trình chạy trên nền hệ điều hành 32 bít MS_Windows. Trong thực tế, để chuyển một chương trình hợp ngữ sang dạng chương trình thực thi EXE 16 bít hoặc COM 16 bít thì cần phải có sự hỗ trợ của chương trình tiện ích của hệ điều hành MS_DOS: Link (Link.exe) và EXE2Bin (EXE2Bin.com). 

Chương trình hợp ngữ 16 bít sử dụng hệ thống các ngắt mềm (Interrupt) của BIOS và DOS như là thư viện lập trình của nó, trong khi đó chương trình hợp ngữ 32 bít sử dụng tập hàm API làm thư viện lập trình của nó.

Biến – Hằng trong chương trình hợp ngữ

Biến và hằng

Biến và hằng (hằng có tên) trong chương trình hợp ngữ có tính chất, mục đích sử dụng, kiểu dữ liệu, quy tắc đặt tên, quy tắc gán giá trị,… tương tự như biến và hằng trong các ngôn ngữ lập trình bậc cao khác. Biến trong chương trình hợp ngữ chỉ có các kiểu dữ liệu là: Byte, Word, Doubleword,… và hằng trong chương trình hợp ngữ có thể là số, kí tự hoặc một xâu kí tự.    

Khi viết chương trình hợp ngữ chúng ta cần quan tâm đến địa chỉ của biến trong bộ nhớ. Một biến được khai báo trong chương trình sẽ được hệ thống gán cho một địa chỉ trong bộ nhớ (khi chương trình được nạp vào bộ nhớ để hoạt động). Cụ thể: mỗi biến trong chương trình sẽ được định vị tại một địa chỉ xác định trong bộ nhớ, và các biến được khai báo liên tiếp nhau trong chương trình (từ trên xuống dưới) sẽ được định vị tại các địa chỉ liên tiếp nhau trong bộ nhớ (từ offset thấp đến offset cao). Nhờ đó, nếu chương trình xác định được địa chỉ của một biến nào đó thì nó dễ dàng có được địa chỉ và nội dung của các biến khác trong chương trình.

Khác với biến, hằng trong chương trình hợp ngữ không được cấp phát bộ nhớ để lưu trữ, tức là, nơi nào trong chương trình chứa trên hằng thì sẽ được trình biên dịch thay bằng giá trị của nó một cách trực tiếp.

Hợp ngữ cung cấp các toán tử giả để định nghĩa/khai báo dữ liệu: DB (định nghĩa byte), DW (định nghĩa word), DD (định nghĩa doubleword),…. Và toán tử EQU để khai báo hằng. Biến có thể được khai báo ở đầu hoặc ở cuối chương trình. Trong khi đó, hằng có thể khai báo ở bất kỳ nơi đâu trong chương trình, khi đó ta có thể sử dụng toán tử dấu “=” để gán giá trị cho hằng.     

Khai báo biến – hằng

Cú pháp khai báo:            

  • a:        <Tên biến>    DB       <Trị khởi tạo>         
  • b:        <Tên biến>    DW      <Trị khởi tạo>         
  • c:         <Tên biến>    DD      <Trị khởi tạo>         
  • d:        <Tên hằng>   EQU    <Hằng số>

Trường hợp a được sử dụng để khai báo biến kiểu byte, trường hợp b được sử dụng để khai báo biến kiểu word, trường hợp c được sử dụng để khai báo biến kiểu doubleword, trường hợp d được sử dụng để khai báo hằng. <Trị khởi tạo> có thể một hoặc nhiều giá trị, nó có thể là một số, một kí tự hoặc một xâu kí tự, và cũng có thể là một dấu hỏi chấm (“?”). <Hằng số> có thể là một số, một kí tự hay một xâu kí tự.

Ví dụ 1:           

  • Spt                 DB      0
  • KiTu               DB      ‘a’
  • TieuDe           DB      ‘Tin hoc’
  • SoNguyen      DW     ?
  • DaySo           DD      1020, 1345, 2389, 5763

Trong ví dụ trên, hai biến Spt và Kitu đều là biến kiểu byte, kích thước 1byte. Biến TieuDe cũng là biến kiểu byte nhưng gồm 7 byte ô nhớ liên tiếp (kích thước 7 byte), mỗi byte chứa 1 kí tự ASCII. Biến SoNguyen là biến kiểu word, chưa được gán giá trị khởi tạo. Biến DaySo là biến kiểu doubleword, gồm 4 phần tử có giá trị lần lượt (từ thấp đến cao) là: 1020, 1345, 2389, 5763.

Ví dụ 2:         

  • LF                  EQU                0Ah
  • TB                  EQU                ‘Cong nghe Thong tin’
  • TieuDe            DB                  TB

Khai báo trên cho thấy, có thể khởi tạo giá trị ban đầu cho biến thông qua một hằng đã được định nghĩa trước.

Ví dụ 3:          TenKhoa        DB      ‘Cong nghe Thong tin’, 0Ah, 0Dh, ‘$’

Khai báo biến TenKhoa cho thấy, có thể khai báo một biến mà trong đó bao gồm cả số, kí tự và xâu kí tự, đây là biến kiểu byte, gồm 22 byte.    

Ví dụ 4:          SoPT   DW     2345h

Biến SoPT ở trên là một biến word, trong trường hợp này byte thấp của nó nhận giá trị 45h, byte cao nhận giá trị 23h, nhưng byte thấp định vị tại địa chỉ SoPT, byte cao định vị tại địa chỉ SoPT + 1.  

Trong hợp ngữ, một dãy các byte hay word liên tiếp nhau trong bộ nhớ có thể xem là một mảng (mảng byte hay mảng word). Biến DaySo trong ví dụ 1 ở trên có thể được xem là một mảng word gồm 4 phần tử. Giá trị của các phần tử trong mảng có thể  được xác định thông qua tên biến và chỉ số tương ứng (địa chỉ). Cụ thể:
DaySo[0] = 1020; DaySo[2] = 1345; DaySo[4] = 2389; DaySo[6] = 5763.

Hợp ngữ cho phép sử dụng toán tử DUP để khai báo một biến dạng mảng mà trong đó gồm nhiều phần tử có cùng giá trị khởi tạo. Dạng sử dụng toán tử DUP là m Dup (n): gồm m phần tử có cùng giá trị khởi tạo là n.

Ví dụ 5:         MangSN         DW     23, 45, 50 Dup (0), 12

Như vậy, biến MangSN được xem là một mảng word gồm 53 phần tử, hai phần tử đầu tiên nhận giá trị lần lượt là 23 và 45, 50 phần tử tiếp theo nhận cùng giá trị 0 và phần tử cuối cùng nhận giá trị 12.

Trong ví dụ 1 ở trên: Các biến được khai báo ở đây sẽ được định vị tại các địa chỉ liên tiếp nhau trong bộ nhớ. Nếu biến Spt được định vị tại địa chỉ offset 100 trong đoạn nhớ dữ liệu thì các biến tiếp theo sẽ được định vị tại các offset sau đó. Cụ thể: Biến KiTu bắt đầu tại offset 101, biến TieuDe bắt đầu tại offset 102, biến SoNguyen định vị tại offset 109, biến DaySo bắt đầu tại offset 111 (xem hình sau):

100101102103104105106107108109111113115117
0aTin_Hoc1020134523895763
(dòng trên là địa chỉ offset của biến, dòng dưới là các ô nhớ chứa giá trị của các phần tử trong biến)

Điều cần quan tâm ở đây là, có thể truy xuất đến giá trị của một phần tử trong biến này thông qua tên của một biến khác. Ví dụ: Spt[0] = 0, TieuDe[0] = ‘T’, TieuDe[1] = ‘’i, DaySo[0] = 1020, DaySo[6] =  5763,… nhưng cũng có thể
Spt[2] = KiTu[1] = ‘T’, KiTu[5] = ‘h’, DaySo[-5] = ‘h’, TieuDe[11] = 1345,…  

(Còn tiếp)

0 0 Bình chọn
Đánh giá bài viết
Đăng ký
Nhận thông báo cho
guest
0 Góp ý
Phản hồi nội tuyến
Xem tất cả bình luận