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-bit | 16-bit | 8-bit cao | 8-bit thấp | Mục đích |
|---|---|---|---|---|
%eax | %ax | %ah | %al | Kết quả (Accumulator) |
%ecx | %cx | %ch | %cl | Counter |
%edx | %dx | %dh | %dl | Data |
%ebx | %bx | %bh | %bl | Base |
%esi | %si | — | — | Source Index |
%edi | %di | — | — | Destination Index |
%esp | %sp | — | — | Stack Pointer |
%ebp | %bp | — | — | Base 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) %rbptrong 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:
| Suffix | Kích thước | Ví dụ |
|---|---|---|
movb | 1 byte | movb $123, %al |
movw | 2 bytes | movw %ax, %bx |
movl | 4 bytes | movl %eax, %ebx |
movq | 8 bytes (x86-64) | movq %rax, %rbx |
mov | Tự động | Dù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
| Source | Dest | Ví dụ | C tương đương |
|---|---|---|---|
| Imm | Reg | movl $0x4, %eax | temp = 4; |
| Imm | Mem | movl $-147, (%eax) | *p = -147; |
| Reg | Reg | movl %eax, %edx | temp2 = temp; |
| Reg | Mem | movl %eax, (%edx) | *p = temp; |
| Mem | Reg | movl (%eax), %edx | temp = *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ặc8
3.2 Các dạng đặc biệt
| Dạng | Công thức | Ví 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, Dstleal tính địa chỉ theo biểu thức, rồi gán giá trị địa chỉ đó vào Dst — không đọc bộ nhớ.
Hai công dụng chính:
- Tính địa chỉ:
p = &x[i] - 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 = 12x5. Các phép tính toán học và logic
5.1 Lệnh 2 toán hạng
| Lệnh | Phép tính | Ghi chú |
|---|---|---|
addl Src, Dest | Dest = Dest + Src | |
subl Src, Dest | Dest = Dest - Src | |
imull Src, Dest | Dest = Dest * Src | |
sall Src, Dest | Dest = Dest << Src | Còn gọi là shll |
sarl Src, Dest | Dest = Dest >> Src | Arithmetic — giữ dấu (sign bit) |
shrl Src, Dest | Dest = Dest >> Src | Logical — điền 0 vào bit cao |
xorl Src, Dest | Dest = Dest ^ Src | |
andl Src, Dest | Dest = Dest & Src | |
orl Src, Dest | Dest = Dest | Src |
5.2 Lệnh 1 toán hạng
| Lệnh | Phép tính |
|---|---|
incl Dest | Dest = Dest + 1 |
decl Dest | Dest = Dest - 1 |
negl Dest | Dest = -Dest |
notl Dest | Dest = ~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 ghi | Giá trị | Địa chỉ | Giá trị |
|---|---|---|---|
%eax | 0x100 | 0x100 | 0xF9 |
%ecx | 0x1 | 0x104 | 0x11 |
%edx | 0x3 | 0x108 | 0x15 |
0x10C | 0xAB |
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 địnhBà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 - zBà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ả
retBà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 1 | incl %eax hoặc leal 1(%eax), %eax hoặc addl $1, %eax |
Nhân %ecx với 4 | imull $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ớ 0x104 | subl %edx, 0x104 |
Giữ 4 bit thấp của %ecx | andl $0xF, %ecx |
Đọc 2 byte từ -4(%eax) vào %cx | movw -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 + 4Bà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
x ở 8(%ebp), y ở 12(%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ánBà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