Bài 4: Machine-Level Programming

1. Thanh ghi (Registers)

1.1 Kiến trúc IA32 — 8 thanh ghi 32 bit

IA32 có 8 thanh ghi 32 bit. Mỗi thanh ghi có thể truy cập ở nhiều kích thước khác nhau (32-bit, 16-bit, 8-bit cao, 8-bit thấp):

Thanh ghi 32-bit16-bit8-bit cao8-bit thấpMục đích
%eax%ax%ah%alKết quả (Accumulator)
%ecx%cx%ch%clCounter
%edx%dx%dh%dlData
%ebx%bx%bh%blBase
%esi%siSource Index
%edi%diDestination Index
%esp%spStack Pointer
%ebp%bpBase Pointer

1.2 Kiến trúc x86-64 — 16 thanh ghi 64 bit

x86-64 mở rộng các thanh ghi cũ lên 64 bit và bổ sung 8 thanh ghi mới:

  • Các thanh ghi cũ: %rax, %rbx, %rcx, %rdx, %rsi, %rdi, %rsp, %rbp
  • Thanh ghi mới: %r8 đến %r15
  • Mỗi thanh ghi 64-bit có thể truy cập phần 32-bit thấp (ví dụ: %r8d), 16-bit (%r8w), và 8-bit (%r8b)
  • %rbp trong x86-64 trở thành thanh ghi mục đích chung (không còn bắt buộc làm base pointer nữa)

2. Chuyển dữ liệu — mov

2.1 Các suffix của lệnh mov

Suffix quyết định số byte được chuyển:

SuffixKích thướcVí dụ
movb1 bytemovb $123, %al
movw2 bytesmovw %ax, %bx
movl4 bytesmovl %eax, %ebx
movq8 bytes (x86-64)movq %rax, %rbx
movTự độngDùng được với tất cả

2.2 Các kiểu toán hạng (Operands)

Immediate  →  $0x400, $-533        ; Hằng số, tiền tố $
Register   →  %eax, %esi           ; Giá trị trong thanh ghi
Memory     →  (%eax), 0x100        ; Giá trị tại địa chỉ bộ nhớ

2.3 Bảng tổ hợp toán hạng hợp lệ của movl

SourceDestVí dụC tương đương
ImmRegmovl $0x4, %eaxtemp = 4;
ImmMemmovl $-147, (%eax)*p = -147;
RegRegmovl %eax, %edxtemp2 = temp;
RegMemmovl %eax, (%edx)*p = temp;
MemRegmovl (%eax), %edxtemp = *p;

3. Các chế độ địa chỉ bộ nhớ

3.1 Dạng tổng quát

D(Rb, Ri, S)   →   Mem[Reg[Rb] + S * Reg[Ri] + D]

Trong đó:

  • D: Hằng số dịch chuyển (displacement) — 1, 2, hoặc 4 bytes
  • Rb: Base register — bất kỳ thanh ghi nào
  • Ri: Index register — bất kỳ thanh ghi nào trừ %esp/%rsp
  • S: Scale — 1, 2, 4, hoặc 8

3.2 Các dạng đặc biệt

DạngCông thứcVí dụ
(Rb, Ri)Mem[Reg[Rb] + Reg[Ri]](%eax, %ecx)
D(Rb, Ri)Mem[Reg[Rb] + Reg[Ri] + D]4(%eax, %ecx)
(Rb, Ri, S)Mem[Reg[Rb] + S*Reg[Ri]](%eax, %ecx, 4)
(,Ri, S)Mem[Reg[Ri] * S](, %ecx, 8)
D(Rb)Mem[Reg[Rb] + D]8(%ebp)

4. Lệnh leal — Load Effective Address

4.1 Cú pháp và tác dụng

leal Src, Dst

leal tính địa chỉ theo biểu thức, rồi gán giá trị địa chỉ đó vào Dstkhông đọc bộ nhớ.

Hai công dụng chính:

  1. Tính địa chỉ: p = &x[i]
  2. Tính toán biểu thức dạng x + k*i + d (compiler hay dùng để tối ưu nhân/cộng)

4.2 Ví dụ: Compiler tối ưu phép nhân

int mul12(int x) {
    return x * 12;
}

Thay vì dùng imull $12, %eax, compiler sinh ra:

leal (%eax, %eax, 2), %eax    ; %eax = x + 2*x = 3x
sall $2, %eax                  ; %eax = 3x << 2 = 12x

5. Các phép tính toán học và logic

5.1 Lệnh 2 toán hạng

LệnhPhép tínhGhi chú
addl Src, DestDest = Dest + Src
subl Src, DestDest = Dest - Src
imull Src, DestDest = Dest * Src
sall Src, DestDest = Dest << SrcCòn gọi là shll
sarl Src, DestDest = Dest >> SrcArithmetic — giữ dấu (sign bit)
shrl Src, DestDest = Dest >> SrcLogical — điền 0 vào bit cao
xorl Src, DestDest = Dest ^ Src
andl Src, DestDest = Dest & Src
orl Src, DestDest = Dest | Src

5.2 Lệnh 1 toán hạng

LệnhPhép tính
incl DestDest = Dest + 1
decl DestDest = Dest - 1
negl DestDest = -Dest
notl DestDest = ~Dest (đảo toàn bộ bit)

6. Bài tập có lời giải

Bài tập 1 — Trace giá trị thanh ghi và bộ nhớ

Cho trước:

Thanh ghiGiá trịĐịa chỉGiá trị
%eax0x1000x1000xF9
%ecx0x10x1040x11
%edx0x30x1080x15
0x10C0xAB

Bài tập 2 — Lệnh nào gán %ebx = 2?

Cho trước: %eax = 0x1, %ebx = 0x2, %ecx = 0x1, %edx = 0x2

Bộ nhớ: 0x100 = 0x1, 0x104 = 0x2, 0x108 = 0x1

A. movl %eax, %ebx          ; %ebx = %eax = 0x1  → ❌
B. movl 2, %ebx             ; Sai cú pháp (thiếu $) → ❌ không hợp lệ
C. addl %eax, %ebx          ; %ebx = 0x2 + 0x1 = 0x3 → ❌
D. imull %eax, %ebx         ; %ebx = 0x2 * 0x1 = 0x2 → ✅
E. movb $2, %bl             ; 8 bit thấp của %ebx = 2; %ebx = 0x2 → ✅
F. movl (%edx,%eax,2), %ebx ; địa chỉ = 0x2 + 2*0x1 = 0x4 → không hợp lệ ở đây
G. movl 1(%eax), %ebx       ; địa chỉ = 0x1 + 1 = 0x2, đọc Mem[0x2] → không xác định
H. addl (%ecx), %ebx        ; %ebx += Mem[0x1] → không xác định

Bài tập 3 — Dịch assembly sang C

; x tại (%ebp+8), y tại (%ebp+12), z tại (%ebp+16)
movl 12(%ebp), %eax    ; %eax = y
xorl 8(%ebp), %eax     ; %eax = y ^ x
sall $5, %eax          ; %eax = (y^x) << 5
incr %eax              ; %eax = ((y^x)<<5) + 1   [lưu ý: đây là lỗi đánh máy, phải là incl]
subl 16(%ebp), %eax    ; %eax = ((y^x)<<5) + 1 - z

Bài tập 4 — Điền assembly tương ứng với C

int fun2(int x, int y, int z) {
    int t1 = y - x;
    int t2 = 6 * z;
    return t1 ^ t2;
}
movl 16(%ebp), %edx         ; %edx = z
movl 12(%ebp), %eax         ; %eax = y
subl 8(%ebp),  %eax         ; %eax = y - x  (t1)
leal (%edx,%edx,2), %edx    ; %edx = z + 2z = 3z
addl %edx, %edx             ; %edx = 3z + 3z = 6z  (t2)
xorl %edx, %eax             ; %eax = t1 ^ t2  → kết quả
ret

Bài tập 5 — Decode assembly

; x tại (%ebp+8), y tại (%ebp+12)
movl 8(%ebp),  %eax     ; %eax = x
subl 12(%ebp), %eax     ; %eax = x - y
sarl $31, %eax          ; %eax = (x-y) >> 31  → toàn bit 0 hoặc toàn bit 1 (mask)
movl %eax,   %edx       ; %edx = mask
andl 12(%ebp), %eax     ; %eax = mask & y
notl %edx               ; %edx = ~mask
andl 8(%ebp),  %edx     ; %edx = ~mask & x
orl  %edx,     %eax     ; %eax = (mask & y) | (~mask & x)

Bài tập 6 — Viết lệnh assembly

Tác vụCác cách viết
Tăng %eax lên 1incl %eax hoặc leal 1(%eax), %eax hoặc addl $1, %eax
Nhân %ecx với 4imull $4, %ecx hoặc sall $2, %ecx hoặc leal (,%ecx,4), %ecx
%ebx = %eax + 12 (chỉ địa chỉ)leal 12(%eax), %ebx
Trừ %edx từ ô nhớ 0x104subl %edx, 0x104
Giữ 4 bit thấp của %ecxandl $0xF, %ecx
Đọc 2 byte từ -4(%eax) vào %cxmovw -4(%eax), %cx

Bài tập 7 — Tính 10a + 4

a ở địa chỉ 0x102, %ebx = 0x100. Điền assembly:

movl 0x102, %eax               ; %eax = a
leal (%eax,%eax,4), %eax       ; %eax = a + 4a = 5a
leal 2(%eax), %eax             ; %eax = 5a + 2
leal (%eax,%eax), %eax         ; %eax = 2*(5a+2) = 10a + 4

Bài tập 8 — Tính 6a + b + 4 trong 2 lệnh

Cho %eax = a, %ebx = b:

; Cách 1:
leal 2(%eax,%eax,2), %eax      ; %eax = 2 + 3a
leal (%ebx,%eax,2), %eax       ; %eax = b + 2*(3a+2) = 6a + b + 4 ✅

; Cách 2:
leal 4(%ebx,%eax,4), %ebx      ; %ebx = b + 4a + 4
leal (%ebx,%eax,2), %eax       ; %eax = (b+4a+4) + 2a = 6a + b + 4 

Bài tập 9 — Tính (x+y)² / 2

x8(%ebp), y12(%ebp):

movl 8(%ebp),  %eax    ; %eax = x
addl 12(%ebp), %eax    ; %eax = x + y
imull %eax, %eax       ; %eax = (x+y)²
sarl $1, %eax          ; %eax = (x+y)² >> 1 = (x+y)²/2 (lấy phần nguyên)

Bài tập 10 — Tìm lỗi trong assembly

Hàm C cần chuyển đổi:

int func5(char* str) {
    int a = str[0] - '0';  // '0' = 0x30 = 48
    int b = str[1] - '0';
    return a + b;
}

Assembly có lỗi:

movl  8(%ebp), %eax      ; (1) %eax = địa chỉ str  ✅
movl  (%eax),  %al       ; (2) %al = str[0]  ⚠️ suffix không khớp
subl  $0x48,   %eax      ; (3) str[0] - '0'  ⚠️ dùng %eax thay vì %al
mov   1(%eax), %bh       ; (4) str[1] → vào %bh ❌ địa chỉ sai
subl  $'0,     %ebx      ; (5) - '0' ❌ cú pháp sai
addl  %ebx,    %eax      ; (6) a + b  ⚠️ size không nhất quán

Bài tập Bonus — Phân tích đoạn assembly ẩn

; x tại (%ebp+8), n tại (%ebp+12)
movl 12(%ebp), %ecx    ; (1) %ecx = n
movl  8(%ebp), %edx    ; (2) %edx = x
xorl %eax, %eax        ; (3) %eax = 0
addl $1, %eax          ; (4) %eax = 1
sall %ecx, %eax        ; (5) %eax = 1 << n   ← LỖI
subl $1, %eax          ; (6) %eax = (1<<n) - 1
andl %edx, %eax        ; (7) %eax = x & ((1<<n)-1)

7. Tổng kết — Sơ đồ tư duy

Assembly cơ bản
├── Thanh ghi
│   ├── IA32: %eax..%ebp (32-bit)
│   └── x86-64: %rax..%r15 (64-bit)
├── Chuyển dữ liệu
│   ├── mov{b,w,l,q} — suffix khớp với kích thước thanh ghi
│   └── Không có Mem→Mem trực tiếp
├── Địa chỉ bộ nhớ
│   └── D(Rb, Ri, S) = Mem[Rb + S*Ri + D]
├── leal — tính địa chỉ/biểu thức, không đọc bộ nhớ
└── Phép tính
    ├── 2 toán hạng: add, sub, imul, sal, sar, shr, xor, and, or
    └── 1 toán hạng: inc, dec, neg, not