# Challenge
***Rax:**
* 1 trong các thanh ghi đa dụng và là thanh ghi trung tâm
* Ðuợc dùng để lưu kết quả trung gian của các phép toán,số học,logic
***Lệnh"mov":**
* Là lệnh sao chép dữ liệu,không thay đổi giá trị gốc(*tham chiếu*) mà chỉ copy từ nguồn sang đích
* Cú pháp: **mov destination,source** (*destination là đích đến của giá trị;source là nguồn lấy*)
* **Source**:có thể là thanh ghi(*rax,rdi,....*);ô nhớ(*gía trị tại 1 address nào đấy*) hoặc 1 hằng số
* **Destination**:thanh ghi,ô nhớ
* Không thể truyền trực tiếp từ ô nhớ sang ô nhớ mà phải qua thanh ghi trung gian
* Mov không tham chiếu dữ liệu (*khi thêm hay bớt giá trị tại source thì destination cũng k bị thay đổi*)
+**Ex1:**`mov rax,10` (*rax lúc này chứa giá trị 10*)
+**Ex2:** `mox rdi,rsi` (*rdi lúc này mang giá trị mà rsi đang chứa*)
+**Ex3:** `mov rax,[rbx]` (*lấy giá trị tại ô nhớ rbx trỏ đến đưa vào rax*)
+**Ex4:**`mov [133700],[123400]` ->***(Sai)***
```
mov rax,[123400]
mov [133700],rax
```
->***(Đúng)***
***Syscall:**
* Là cách mà user mode gọi các chức năng của hệ điều hành(*kernal mode*)
* Các thao tác như in ra màn hình,đọc file,tạo tiến trình,thoát chương trình đều qua syscall
* Là cầu nối giữa chương trình và kernal
* **-Cách thức hoạt động:**
* +Cpu sẽ chuyển từ user mode sang kernal mode
* +Dựa trên số hiệu syscall trong rax kernal sẽ biết mình cần làm gì
* +Các tham số được truyền qua các thanh ghi (theo ABI quy uớc)
* +Kernal thực hiện và trả kết quả về trong rax
* **-Quy uớc truyền tham số(linux x86-64):**
* +Thanh ghi rax sẽ hiểu syscall
* **+Các thanh ghi chứa tham số:**
* rdi:tham số 1
* rsi:tham số 2
* rdx:tham số 3
* r10:tham số 4
* r8:tham số 5
* r9:tham số 6
* **-1 vài syscall hay dùng:**
* | **Syscall** | **Số hiệu (RAX)** | **Chức năng**|
| -------- | ------------- | ---------------------- |
| `read` | 0 | Đọc từ file/thiết bị |
| `write` | 1 | Ghi ra file/thiết bị |
| `open` | 2 | Mở file |
| `close` | 3 | Đóng file |
| `exit` | 60 | Thoát chương trình |
| `fork` | 57 | Tạo tiến trình con |
| `execve` | 59 | Chạy chương trình khác |
***Syscall"exit":**
* Truyền vào **rax** giá trị **60** để thực hiện;sau dó thêm mã thoát vào rdi để khi gõ **"echo$?"** sẽ ra mã thoát
* Nếu không nhập mã thoát thì kernal sẽ lấy giá trị rác bất kì làm mã thoát điều này không ảnh huởng gì nhưng **tốt nhất vẫn nên nhập**
***Quy trình xây dựng file thực thi(*executable*):**
* Viết code **assembly(.s)**
* Dùng lệnh **'as'** để assemble với cú pháp: **as ----64 ten_file.s -o ten_file.o** (***----64 dùng để assemble cho cấu trúc 64bit***)
* Dùng lệnh **'ld'** để link thành executable với cú pháp:
* **ld ten_file.o -o ten_file**
* -**Chaỵ thử với cú pháp:**
* **./ten_executable**
* **echo $?(dùng để hiển thị mã thoát)**
**Lệnh "cat":**
Là viết tắt của concatenate(nối)
Có tác dụng đọc và in nội dung file ra màn hình hoặc nối file, ghi file
Cú pháp: cat[option] [name_file]
(có thể bỏ qua option)
Các chức năng cơ bản:
cat file.txt
-> in toàn bộ nội dung của file ra màn hình
cat file1.txt file 2.txt
->in ra màn hình nội dung của file 1 và tiếp sau đó là file 2
cat file1 file2 > file3
->gộp nội dung file 1 và 2 vào file mới là file 3(nếu có sẵn nội dung sẽ bị ghi đè)
cat file 4 >> file 3
->thêm file 4 vào cuối file 3(không ghi đè)
cat > file
->sẽ tạo 1 file mới(gõ nội dung cho file và khi muốn kết thúc nhấn ctrl+d để lưu và end)
1 số option cần biết:
| Tuỳ chọn | Ý nghĩa |
| -------- | -------------------------------------------------------------------------------------- |
| `-n` | Đánh số dòng toàn bộ |
| `-b` | Đánh số dòng, bỏ qua dòng trống |
| `-s` | Gộp các dòng trống liên tiếp thành 1 dòng trống |
| `-E` | Hiển thị ký tự `$` ở cuối mỗi dòng |
| `-T` | Hiển thị tab bằng ký tự `^I` |
| `-A` | Hiển thị tất cả ký tự không in được (như `^M`, `^L`, …) |
| `-v` | Hiển thị ký tự không in được (tương tự `-A` nhưng không hiển thị newline/tab đặc biệt) |
# **Kiến trúc máy tính:**
Là cách thiết kế và tổ chức cách thành phần bên trong máy tính để chúng có thể cùng nhau hoạt động và thực hiện lệnh
* **Logic gate:**
Là khối cơ bản trong mạch điện tử số, thực hiện phép toán boolean(true/false) dựa trên tín hiệu đầu vào 0 1 để đưa ra đầu ra
0 (false) trong mạch điện được gán cho điện áp thấp (low)
1(true) trong mạch điện được gán cho điện áp cao (high)
3 loại cổng logic cơ bản là and,not,or
Từ 3 cổng cơ bản trên ta có thể mở rộng ra các cổng cơ bản khác như:
NAND(Not And):là phủ định của And
NOR(Not Or):là phủ định của or
XOR(exclusive Or):giống nhau(0 0)=false,khác nhau =true
XNOR:giống nhau =true,khác nhau=false
* Máy tính gồm các phần chính:cpu,mem,bus,thiết bị ngoại vi:
* -Cpu(bộ xử lý trung tâm):là bộ não của máy tính có nhiệm vụ điều khiển hoạt động của toàn hệ thống,xử lý dữ liệu và thực hiện lệnh
+Cấu trúc của cpu gồm:
* ALU:thực hiện các phép toán số học,logic
* CU:điều khiển quá trình lấy lệnh,giải mã và thi hành lệnh
* register(thanh ghi):bộ nhớ tốc độ cao trong cpu để lưu trữ dữ liệu tạm thời
* -Memory(bộ nhớ):dùng để lưu trữ dữ liệu và chương trình
+Gồm bộ nhớ chính và phụ
* Chính gồm:
+Ram:đọc ghi rất nhanh nhưng mất dữ liệu khi tắt nguồn
+Rom:chỉ đọc,chứa mã khởi động(firmware)
-Bộ nhớ phụ là HDD,SSD có thể lưu trữ lâu dài
-input là thiết bị nhập như chuột, bàn phím,...
-output là thiết bị xuất như màn hình,máy in,...
-bus(tuyến truyền dữ liệu) là hệ thống dây dẫn hoặc kênh truyền tín hiệu để truyền dữ liệu,địa chỉ và tín hiệu điều khiển giữa các thành phần trong máy tính(cpu<->ram<->output)
+Bus là con đường mà dữ liệu chạy qua giữa các bộ phận trong máy tính
+Gồm 3 loại:
* Bus dữ liệu:truyền dữ liệu
* Bus địa chỉ:xác định vị trí ô nhớ
* Bus điều khiển:gửi tín hiệu điều khiển
-Ngoài ra còn có cache:1 bộ nhớ cực nhanh nằm ngay trong cpu(hoặc rất gần)dùng để lưu tạm dữ liệu và lệnh mà cpu thường xuyên cần truy cập
+Nó giống như bộ nhớ đệm giúp cpu đỡ phải chờ ram vì ram rất chậm(chậm hơn cpu hàng trăm lần)
+Khi cpu cần dữ liệu nó sẽ truy cập cache trước,nếu có sẵn nó sẽ lấy ngay,còn không có thì nó sẽ vào ram để tìm và nạp vào cache
# Assembly
Là bước trung gian giữa ngôn ngữ mã máy và những ngôn ngữ lập trình bậc cao hơn như c,c++,python,...
**Cấu tạo của 1 lệnh trong asm:**
`sentence=verb+noun`
* Sentence:là instruction(lệnh)
* Verb:động từ mô tả hành động-mnemonic(mov,jmpp,call,...),giống như tiếng việt di chuyển ở asm là mov
- Mnemonic là tên viết tắt đại diện cho 1 hành động (move=mov;divide=div;subtract=sub)
1 số mnemonic cần biết:
| Mnemonic | Nghĩa tiếng Anh | Hành động | Ví dụ |
| -------- | --------------- | ---------------------------------- | -------------- |
| `mov` | move | Sao chép dữ liệu | `mov rax, rbx` |
| `add` | add | Cộng hai giá trị | `add rax, 5` |
| `sub` | subtract | Trừ | `sub rax, rbx` |
| `mul` | multiply | Nhân | `mul rbx` |
| `div` | divide | Chia | `div rcx` |
| `push` | push onto stack | Đưa vào stack | `push rax` |
| `pop` | pop from stack | Lấy từ stack | `pop rbx` |
| `cmp` | compare | So sánh hai giá trị (thiết lập cờ) | `cmp rax, rbx` |
| `jmp` | jump | Nhảy đến địa chỉ khác | `jmp label` |
| `call` | call | Gọi hàm | `call func` |
| `ret` | return | Trả về từ hàm | `ret` |
* Noun:là đối tượng bị tác động-operand(trong 1 instruction có thể có 1 hay nhiều operand thậm chí không có operand nào tùy thuộc vào verb)

# Data
* **Binary**
-Là hệ đếm dùng 2 ký tự 0 và 1 để biểu diễn all
-Máy tính được chọn hệ nhị phân ngôn ngữ là vì nó cực kì đơn giản dễ hiểu dễ nhận biết với 0(false/không có điện) và 1(true/có điện)
-Thực chất assembly chỉ là ngôn ngữ có thể đọc hiểu được của binary
-Bit là đơn vị nhỏ nhất trong data
- Nó chỉ có 2 trạng thái là 0 và 1 với mỗi số/trạng thái ứng với 1 bit
ex:2bit(00 01 10 11),3bit(111,000,101,...)
-Byte là đơn vị đo cơ bản nhất trong data vì hầu hết các cpu và ram đều dùng đến đơn vị này
- 1byte=8bit
- 1 bit có 2 lựa chọn vậy nên 1 byte gồm 256 tổ hợp sẽ được sinh ra và đây cũng chính là bảng mã ascii mở rộng,với mỗi mã có thể là kí tự viết thường, viết hoa, chữ số,kí tự đặc biệt,thậm chí là những âm thanh.Từ 256 tổ hợp này gần như ta có thể tạo ra tất cả mọi thứ
- 1 kí tự có kích thước 1 byte và 1 điểm ảnh rgb thì là 3 byte
Bộ nhớ máy tính dc chia thành các ô nhớ
với mỗi ô lưu 1 byte,mỗi byte sẽ có 1 địa chỉ riêng,khi lưu 1 biến dữ liệu của nó sẽ chiếm nhiều ô liên tiếp
* Ngoài hệ 2 thì để làm gọn dãy biểu diễn binary mà vẫn tương đương hoàn toàn thì ngta dùng hệ 8 và 16
-Hệ 8: hợp lệ các chữ số từ 0-7 với mỗi nhóm 3 bit của hệ nhị phân sẽ bằng 1 số hệ 8 và thường được dùng trong unix/linux
-Hệ 16:dùng 16 kí hiệu với 10 kí hiệu đầu tiên là 0-9 và các kí hiệu còn lại là chữ cái in hoa từ A-F với 1 kí hiệu ở hệ 16=4 bit ở hệ nhị phân
* Bảng so sánh 3 hệ:
| Hệ | Cơ số | Ký hiệu | Số lượng chữ số | Nhóm bit tương ứng | Dùng ở đâu |
| :------------ | :---: | :------- | :-------------: | :----------------: | :----------------------- |
| Nhị phân | 2 | 0–1 | dài | 1 bit | CPU, mạch điện |
| Bát phân | 8 | 0–7 | ngắn hơn | 3 bit | Unix file permission |
| Thập lục phân | 16 | 0–9, A–F | ngắn gọn nhất | 4 bit | Hiển thị địa chỉ, opcode |
-Hệ 16 là hệ được dùng nhiều nhất vì 1byte=2 chữ số hex(hệ 16) mà dữ liệu thì có thể gồm rất rất nhiều byte nên khi hiển thị ở dạng hex thì nó sẽ là ngắn gọn nhất
-Ngoài bit và byte thì còn có các bậc kích thước dữ liệu khác
+Nibble=1hex=4 bit
+Các bậc cao hơn như Word,double word,quad word thì sẽ tùy thuộc vào cấu trúc nhưng hầu hết các máy tính hiện nay là 64bit nên
- 1 word=8byte
- 1 double word=16 byte
-1 quad word=32 byte
| Kiến trúc CPU | Kích thước 1 word | Ghi chú |
| --------------------------------------- | ----------------- | ---------------------------------------------- |
| **8-bit** (rất cũ, như Intel 8080, Z80) | 1 byte (8 bit) | Mỗi lần CPU xử lý 1 byte |
| **16-bit** (như Intel 8086) | 2 byte (16 bit) | Gọi là **word = 2 bytes** |
| **32-bit** (như Intel 80386, ARMv7) | 4 byte (32 bit) | **word = 4 bytes**, **double word = 8 bytes** |
| **64-bit** (như x86_64, ARMv8) | 8 byte (64 bit) | **word = 8 bytes**, **double word = 16 bytes** |
+Còn 1 bậc khác là half word: đơn giản thì nó chỉ là 1/2 của word thôi nhưng không được sử dụng nhiều ở các kiến trúc cpu khác trừ 32bit
-Tuy nhiên không phải lúc nào word cx là 8 byte vì trong asm word có nghĩa là 2 byte:
| Nghĩa | Dùng trong đâu | Ví dụ |
| ----------------------------------------- | ----------------------------- | ---------------------------------- |
| **Word kiến trúc (CPU word size)** | Dùng trong lý thuyết hệ thống | “64-bit processor” → word = 8 byte |
| **Word ngữ pháp Assembly (operand size)** | Dùng trong ngôn ngữ ASM | `mov word ptr ...` → word = 2 byte |
Cách phân biệt:

Trong asm ta có:
```
mov byte ptr [rbx], 1 ;ghi 1 byte
mov word ptr [rbx], 1 ; ghi 2 byte
mov dword ptr [rbx], 1 ; ghi 4 byte
mov qword ptr [rbx], 1 ; ghi 8 byte
```
-Trong tất cả bậc trên thì word chính là đơn vị dữ liệu cơ bản mà cpu xử lý trong 1 chu kỳ tức nó là độ rộng tự nhiên của 1 thanh ghi và bus dữ liệu của cpu
-Cho 1 giá trị ở hệ 16:0x12345678 (đây là 1 số 32 bit).Ta có 1 số hệ 16(hex) tương ứng vs 1 nibble(1/2byte) mà 1 ô nhớ chỉ lưu trữ 1 byte nên trong mỗi ô chứa sẽ chứa 2 số hex.Mỗi byte ô nhớ đều sẽ có 1 địa chỉ riêng và trong tất cả các hệ thì vị trí nhỏ nhất(bit thấp nhất) sẽ là vị trí ngoài cùng bên phải và vị trí sẽ tăng dần từ phải sang trái
| Byte | Giá trị | Vị trí (tính từ phải qua trái) | Trọng số | Vai trò |
| ------ | ------- | ------------------------------ | -------- | ------------------------------------------------- |
| Byte 3 | 0x12 | cao nhất | × 2²⁴ | **Byte cao nhất (Most Significant Byte – MSB)** |
| Byte 2 | 0x34 | | × 2¹⁶ | |
| Byte 1 | 0x56 | | × 2⁸ | |
| Byte 0 | 0x78 | thấp nhất | × 2⁰ | **Byte thấp nhất (Least Significant Byte – LSB)** |
-Endianness:1 khái niệm cực kỳ quan trọng,nó mô tả thứ tự sắp xếp các byte của 1 giá trị nhiều byte khi lưu vào bộ nhớ
+Khi lưu cpu cần quyết định byte nào lưu trước,byte nào lưu sau trong ram
+Gồm 2 loại chính:
- Little endian:lưu byte bé nhất(lsb) ở địa chỉ bé nhất
-Big endian:lưu byte lớn nhất(msb) ở địa chỉ bé nhất
-Little Endian: dễ cho tính toán số học, vì byte thấp nhất nằm đầu tiên → dễ truy cập và cộng dồn.
→ Được dùng bởi Intel, AMD (x86, x86_64).
-Big Endian: dễ cho đọc/ghi dữ liệu mạng hoặc file nhị phân, vì giống cách ta đọc số từ trái sang phải.
→ Dùng trong một số CPU PowerPC, SPAR
So sánh:
| Đặc điểm | **Little Endian** | **Big Endian** |
| -------------------- | -------------------------- | -------------------- |
| Byte thấp lưu ở | Địa chỉ nhỏ | Địa chỉ lớn |
| Dễ đọc với con người | ❌ Khó đọc | ✅ Dễ đọc |
| Dễ xử lý trong CPU | ✅ Dễ (Intel chọn kiểu này) | ❌ Khó hơn |
| Dùng phổ biến ở | x86, x64, ARM (default) | Mạng, PowerPC, SPARC |
| Tên gọi vui | “Nhỏ trước” | “Lớn trước” |
-Overflow:là tình trạng mà kết quả của 1 phép tính vượt quá giới hạn biểu diễn của số bit được cấp
+Chẳng hạn bộ nhớ chỉ dc cấp phát 4 bit nghĩa là ta chỉ có thể biểu diễn từ 0-15 nếu cộng thêm 1 thì kết quả sẽ là 1 số 5 bit nhưng cpu chỉ có 4 bit nên sẽ dẫn tới overflow.Lúc này giá trị của bit thứ 5 sẽ dc bỏ đi và giữ lại 4 bit cuối là 0000 để lưu.
->Khi overflow diễn ra thì giá trị sẽ quay về 0
-Ngược lại với overflow là hiện tượng khi giá trị đang là 0 mà bị trừ đi 1 thì lúc này sẽ có 2 trường hợp xảy ra
+Với số có dấu(signed interger) thì giá trị sẽ chạy từ -128 đến 127 nên khi giá trị đang từ 0 trừ đi 1 thì sẽ là -1
+Với số không dấu(unsigned interger) thì giá trị sẽ chạy từ 0-255 nên khi lấy giá trị 0 trừ đi 1 thì sẽ trả về giá trị lớn nhất
- Máy tính không hiểu lỗi nên vẫn sẽ thực hiện phép tính theo module bình thường và sẽ quay ngược lại thành số lớn nhất
# Register
- Là những ô nhớ siêu nhanh nằm bên trong cpu(bộ nhớ trong của cpu) nhanh hơn Ram hàng trăm lần
- Mỗi kiến trúc máy tính thì có số lượng register khác nhau nhưng thường là trong khoảng 10-20 register đa dụng với kiến trúc x86-64 thì là 16 register đa dụng
- Đặc điểm:
| Đặc điểm | Mô tả |
| -------------------- | ------------------------------------- |
| **Tốc độ** | Rất nhanh, thường chỉ 1 chu kỳ CPU |
| **Kích thước nhỏ** | 8, 16, 32 hoặc 64 bit |
| **Số lượng ít** | Mỗi CPU chỉ có vài chục thanh ghi |
| **Chức năng cụ thể** | Mỗi thanh ghi thường có vai trò riêng |
-Có khả năng lưu dữ liệu tạm thời,địa chỉ ô nhớ,trạng thái chương trình
-Kích thước của register thường sẽ giống 1 kích thước 1 word của cấu trúc(ví dụ như cấu trúc 64 bit thì kích thước register sẽ là 8 byte)
-Cấu tạo của thanh ghi rax

-Cấu tạo của thanh ghi thường gồm 5 phần:
+Phần 8bit:
- 8bit low: tất cả các register đều có 8 bit low(với rax là al)
- 8bit high:có vài register có còn lại thì không có(với rax là ah)
+Phần 16 bit,32 bit và 64:tất cả register đều có (với rax 16 bit là ax,32 bit là eax,64 bit là rax)
-Cấu tạo của register còn nói lên lịch sử phát triển của nó qua các kiến trúc từ lúc còn là 8 bit đến 64 bit như hiện nay
-Cấu trúc 1 số thanh ghi đa dụng:
| 64-bit | 32-bit | 16-bit | 8-bit thấp | 8-bit cao | Vai trò truyền thống |
| ------ | -------- | -------- | ---------- | --------- | --------------------------- |
| RAX | EAX | AX | AL | AH | Accumulator (tích lũy) |
| RBX | EBX | BX | BL | BH | Base register |
| RCX | ECX | CX | CL | CH | Counter (đếm vòng lặp) |
| RDX | EDX | DX | DL | DH | Data (nhập/xuất, phép chia) |
| RSI | ESI | SI | SIL | — | Source index |
| RDI | EDI | DI | DIL | — | Destination index |
| RBP | EBP | BP | BPL | — | Base pointer (stack frame) |
| RSP | ESP | SP | SPL | — | Stack pointer (đỉnh stack) |
| R8–R15 | R8D–R15D | R8W–R15W | R8B–R15B | — | Thêm trong 64-bit mode |
-Ta có thể truyền dữ liệu vào từng thành phần nhỏ hơn của register(vd:truyền dữ liệu vào al hay ah của rax)
-32 bit caveat(lưu ý khi truyền dữ liệu vào thanh ghi 32 bit):khi ghi vào thanh 32 bit tất cả các bit cao sẽ bị chuyển về 0
- Lỗi này chỉ xảy ra ở thanh 32 bit, điều này có thể dẫn tới bug logic
```
mov rax, 0xFFFFFFFFFFFFFFFF ; RAX = FFFFFFFFFFFFFFFFh
mov eax, 0x12345678 ; ghi 32 bit thấp
```
-Thông thường ta sẽ nghĩ giá trị trong rax lúc này là:
```
RAX = FFFFFFFF12345678h
```
-Nhưng thật ra giá trị lưu trong rax sẽ là:
```
RAX = 0000000012345678h
```
-Nên tối ưu code bằng cách chỉ dùng 64 bit hoặc 32 bit và tránh mix với các thanh ghi có bit thấp hơn trừ khi cần thiết
-Máy tính không được mặc định ở bất kì kiểu dữ liệu số không dấu hay số có dấu,máy tính không lưu dấu mà chỉ lưu bit
-Cùng 1 dãy bit giống nhau nhưng khi hiểu theo kiểu không dấu và có dấu thì lại khác nhau hoàn toàn
-Nếu muốn máy tính hiểu đó là số không dấu hay có dấu thì mỗi loại dữ liệu đều có các câu lệnh riêng
| Kiểu lệnh | Mục đích | Ví dụ |
| ------------ | ----------------------------- | ----------------------- |
| **Unsigned** | So sánh, chia, nhân không dấu | `div`, `cmp` + `ja/jb` |
| **Signed** | Cho số âm/dương | `idiv`, `cmp` + `jg/jl` |
-Khi dùng các lệnh của kiểu dữ liệu nào máy tính sẽ hiểu và tính toán theo kiểu dữ liệu đó
-Extending data(mở rộng data):khi làm việc với các thanh ghi nhỏ và muốn truyền dữ liệu từ thanh ghi nhỏ sang thanh ghi lớn hơn thì cpu cần phải mở rộng giá trị đó để phù hợp với kích thước mới
-Ví dụ muốn truyền dữ liệu 0xF0 từ thanh 8 bit(al) sang thanh 32 bit(eax) thì cpu cần mở rộng nó thành dạng 32 bit để phù hợp với thanh ghi 32 bit
-Có 2 cách mở rộng dữ liệu:
| Kiểu mở rộng | Tên tiếng Anh | Ý nghĩa |
| ------------------ | ------------- | ---------------------------------------------------------- |
| **Zero extension** | Zero-extend | Điền thêm các bit 0 ở phần cao |
| **Sign extension** | Sign-extend | Điền thêm bit dấu (bit cao nhất của dữ liệu cũ) ở phần cao |
-Với ví dụ trên thì khi ta dùng kiểu zero-extend dữ liệu sẽ dc lưu vào thanh 32 bit có dạng:
```
movzx eax, al ; MOV with Zero-Extend
```
-Ta có al=0xF0(240) ->eax=0x000000F0
-Khi dùng sign-extend:
```
movsx eax, al ; MOV with Sign-Extend
```
al=0xF0(-16) ->eax=0xFFFFFFF0
-Khi dùng movsx thì máy tính sẽ hiểu đây là 1 số có dấu
* Mở rộng với các kích thước khác:
| Lệnh | Ý nghĩa | Ví dụ |
| --------------- | ---------------------------------------------------------- | ----------------------------------------------- |
| `movzx eax, al` | zero-extend 8 → 32 bit | AL=0x7F → EAX=0x0000007F |
| `movsx eax, al` | sign-extend 8 → 32 bit | AL=0xF0 → EAX=0xFFFFFFF0 |
| `movzx rax, ax` | zero-extend 16 → 64 bit | AX=0x1234 → RAX=0x0000000000001234 |
| `movsx rax, ax` | sign-extend 16 → 64 bit | AX=0xF234 → RAX=0xFFFFFFFFFFFFF234 |
| `cdqe` | convert doubleword to quadword (sign-extend `EAX` → `RAX`) | nếu EAX = 0xFFFFFFFF → RAX = 0xFFFFFFFFFFFFFFFF |
-Phải quan tâm đến cách mở rộng vì khi chuyển dữ liệu từ thanh ghi nhỏ sang thanh ghi lớn mà không mở rộng đúng cách có thể gây ra tính toán sai và lỗi logic
* Ngoài các register đa dụng trên còn có 1 số register đặc biệt khác:
| Loại | Ví dụ | Chức năng chính |
| ------------------- | ------------------ | -------------------------- |
| Index/pointer | RSI, RDI, RBP, RSP | Quản lý vùng nhớ, stack |
| Instruction pointer | RIP | Con trỏ lệnh |
| Flags | RFLAGS | Cờ điều kiện |
| Segment | CS, DS, SS… | Phân vùng bộ nhớ (legacy) |
| SIMD | XMM, YMM | Xử lý vector (SSE/AVX) |
# Memory
-Register thì rất mắc và số lượng có hạn vì vậy cần phải có 1 nơi để lưu trữ dữ liệu và truy cập nhanh khi cần.Không nơi nào khác mà chính là bộ nhớ hệ thống
-Ram là 1 dãy rất dài các ô nhớ,mỗi ô có 1 địa chỉ duy nhất và được lưu dưới dạng 1 dãy số hệ nhị phân,nhưng khi hiển thị cho chúng ta thấy nó sẽ được chuyển thành dạng 1 dãy số hệ 16(làm ngắn gọn và dễ đọc hơn)
-Với mỗi địa chỉ thì chỉ lưu trữ dc 1 ô nhớ có kích thước 1 byte vậy nên khi lưu dữ liệu thì dữ liệu sẽ được phân thành từng byte và lưu vào các ô nhớ liên tiếp nhau
-Khi đọc 1 địa chỉ cpu biết nó phải đọc bắt đầu từ ô địa chỉ đó,nếu lệnh bảo đọc 8 byte thì sẽ đọc ô nhớ địa chỉ và 7 ô liền sau nó
:::info
ex: `mov rax,0x0002`
cpu sẽ đọc từ 0x0002->0x0009 và gọp lại thành 1 dữ liệu hoàn chỉnh
:::
+Vậy địa chỉ sẽ là điểm mà cpu bắt đầu đọc chứ không phải vùng nhớ của toàn bộ dữ liệu
-cpu có thể cùng lúc đọc nhiều ô nhớ nên điều này không quá làm cản trở tốc độ đọc dữ liệu của cpu
:::spoiler 127TB địa chỉ khả dụng
* Tên địa chỉ được lưu dưới dạng nhị phân với mỗi 1 số 0 hoặc 1 có giá trị 1 bit.Hiện nay kiến trúc chính được sử dụng là kiến trúc 64 bit nên tên địa chỉ chỉ có thể dài tối đa 64 số với mỗi số sẽ có 2 trường hợp nên số lượng địa chỉ tối đa có thể tạo ra là 2 mũ 64 địa chỉ với mỗi địa chỉ có thể lưu trữ 1 byte nên dung lượng tối đa có thể lưu trữ là 16 exabyte
* Tuy nhiên 64 bit đấy chỉ là 64 bit địa chỉ logic còn trên thực tế thì chưa máy nào gắn đủ ram để dùng 2 mũ 64 byte và thực tế phần cứng chỉ dùng 48 bit địa chỉ hợp lệ nghĩa là 2 mũ 48 byte địa chỉ khả dụng=256 TB(128TB dùng cho user space và 128 dùng cho kernel space) và thực chất cũng chỉ là 127TB xấp xỉ 128TB nên mới có câu nói như trên
:::
-2 mũ 64 là số lượng địa chỉ tối đa mà cpu có thể biểu diễn chứ không phải lượng ram vật lý mà bạn có thể cắm vào(máy bạn có 16gb ram nhưng vẫn nhìn thấy vùng địa chỉ rộng 127TB)
-Process(tiến trình):hiểu đơn giản là ta có 1 file notepad được lưu trong ổ đĩa C khi ta double click vào nó thì sẽ hiển thị ra màn hình máy tính 1 chương trình cho ta note và chương trình hiển thị ra để ta note đấy chính là process
-Khi chạy tiến trình thì hệ điều hành sẽ cấp phát cho nó 1 không gian địa chỉ ảo,mỗi process sẽ tưởng tượng ra 1 ảo ảnh bộ nhớ riêng và nghĩ rằng ram là của riêng nó nên dung lượng của không gian địa chỉ ảo có thể lên đến 256TB
-Nhưng thực tế chỉ phần dữ liệu đang dùng mới được ánh xạ vào ram thật
-Memory layout:các mà vùng nhớ chia nhỏ và sắp xếp để sử dụng,nó mô tả cấu trúc,vị trí và mối quan hệ giữa các vùng nhớ
-Mối quan hệ giữa process mem và mem layout:
| Thuật ngữ | Nghĩa |
| ------------------ | -------------------------------------------------------------------------------------------------- |
| **Process memory** | Toàn bộ vùng nhớ mà tiến trình sử dụng trong RAM (bao gồm code, data, heap, stack, thư viện, v.v.) |
| **Memory layout** | **Cách sắp xếp, tổ chức** các vùng đó trong không gian địa chỉ ảo của tiến trình |
-“Process memory” là nội dung thực tế (RAM, dữ liệu, code đang chạy).
-“Memory layout” là bản thiết kế (layout) mô tả vùng nào nằm ở đâu, làm gì.
* Stack
-Là 1 vùng bộ nhớ tạm thời dùng để lưu biến cục bộ,địa chỉ quay lại,tham số khi gọi hàm
+Biến cục bộ:là biến được khai báo bên trong 1 hàm (không nhất thiết phải là hàm main),khối lệnh và chỉ tồn tại trong phải vi mà nó được khai báo
```
void hello(){
int x=10; // x là biến cục bộ trong hàm hello
cout<<x<<endl;}
int main(){
int n;cin>>n; //n là biến cục bộ trong hàm main
for(int i=0;i<=n;i++){ //i là biến cục bộ trong khối lệnh for
cout<<i<<" ";}
}
```
+Địa chỉ quay lại:là 1 dãy hex biểu diễn địa chỉ ô nhớ nơi chứa lệnh tiếp theo sau khi gọi hàm trong chương trình
- Chẳng hạn như đoạn code phía trên nếu như ta gọi hàm hello trong main thì nó sẽ thực hiện hết lệnh ở hàm hello nhưng sau khi thực hiện xong nó phải biết quay lại đâu để thực hiện câu lệnh tiếp
- Trước khi nhảy vào hàm được gọi để thực hiện các lệnh thì cpu sẽ lưu địa chỉ của lệnh tiếp tạm thời trên stack
- Sau khi thực thi hàm xong cpu sẽ quay lại lệnh đấy tiếp tục thực hiện
- Trong asm thì khi gọi hàm (ex:call sayhello) thì sẽ push địa chỉ câu lệnh tiếp theo lên stack và jump đến hàm đã gọi;khi trong hàm đang thực thi lệnh return(ret) thì cpu sẽ pop địa chỉ đấy khỏi stack và jump về đấy
- Vậy nếu sau nó không còn lệnh: thì chắc chắn sẽ vẫn có lệnh return của hàm main, cpu sẽ push địa chỉ của lệnh return và thao tác với stack như bth
+Tham số khi gọi hàm:trước khi gọi hàm các tham số của hàm sẽ được push vào stack theo thứ tự từ phải phải sang trái
- Cơ chế của stack cũng như sắp xếp 1 chồng dĩa vậy cái nào sắp vào sau cùng sẽ được lấy ra trước vậy nên khi push stack sẽ theo thứ tự từ phải sang trái để truyền đúng giá trị cho tham số
-Tiếp theo khi gọi hàm cpu sẽ push địa chỉ quay lại lên stack
-Đến đầu hàm thì compiler sẽ tự động thêm đoạn đầu hàm như sau:
```
push rbp
mov rbp, rsp
sub rsp, X
```
-push rbp sẽ đẩy old rbp(stack frame của main) vào stack
+Thứ mà được gọi là old rbp kia khi được lưu vào stack thì chỉ là địa chỉ mốc cho toàn bộ frame của hàm
-Truyền rsp vào rbp, sau khi truyền vào sẽ tạo ra 1 stack frame mới
+ rbp là 1 thanh ghi được thiết lập sẵn để khi được rsp(thanh ghi luôn chỉ vào top của stack) truyền vào thì rbp sẽ được cpu chọn làm mốc và thiết lập lại các địa chỉ trong stack theo rbp
-Trừ đi X byte để lưu biến cục bộ(nếu có)
Hình ảnh minh họa stack sau khi thực hành những lệnh trên
```
Địa chỉ cao ↑
────────────────────────────
| Tham số 2 | [rbp+16]
| Tham số 1 | [rbp+8]
| Return address | [rbp+0x8?] hoặc [rbp+8]
| Old RBP | [rbp] ← RBP (mốc frame)
| Biến cục bộ 1 | [rbp-8]
| Biến cục bộ 2 | [rbp-16]
| ... |
| | ← RSP (sau khi trừ)
────────────────────────────
Địa chỉ thấp ↓
```
- Trong khi thực thi hàm thì tham số sẽ được truy cập và gán thông qua ***rbp + offset***
+ "offset"là khoảng cách bao nhiêu byte từ dữ liệu cần truy cập so với mốc;cơ chế thiệt lập địa chỉ của rbp cũng giống rsp nghĩa là khi thêm địa chỉ mới vào thì địa chỉ tại dữ liệu đấy sẽ giảm đi vậy nên "+ offset" chỉ những dữ liệu nằm dưới old rbp (mốc),"- offset" thì là những dữ liệu nằm trên (thường sẽ là biến cục bộ)
-Sau khi thực thi hàm xog trước khi return thì compiler cũng sẽ tự thêm các lệnh sau vào:
```
mov rsp, rbp ; khôi phục RSP về mốc gốc
pop rbp ; khôi phục base pointer cũ (của caller)
ret ; quay về return address
```
-lệnh đầu đưa con trỏ của stack quay trở lại vị trí của rsp hay vị trí của old rbp đã push vào stack trước đó;để khôi phục lại như ban đầu và các địa chỉ sẽ được thiết lập theo rsp như nguyên bản
-lệnh tiếp theo sẽ đẩy rbp ra(xóa lun mốc)
-Lệnh cuối sẽ pop địa chỉ quay lại và quay về main
:::spoiler Tại sao biến cục bộ lại lưu phía trên mốc(old rbp)?
-Biến cục bộ chỉ tốn tại trong hàm, cpu chỉ thực sự đi vào trong hàm khi đã thiết lập mốc
-Khi sắp kết thúc hàm thì sẽ chuyển từ rbp về rsp lúc này cũng là lúc hàm đã kết thúc, nhảy về lại main thì khi này con trỏ stack dg trỏ đến gốc và vì biến cục bộ lúc này nằm ở vị trí cao hơn nghĩa là ngoài phạm vi khả dụng của stack nên các biến cục bộ trên cũng coi như cùng lúc bị xóa khỏi stack
:::
* Heap (đống) là vùng nhớ dùng để cấp phát động (dynamic allocation) — nghĩa là vùng nhớ mà chương trình yêu cầu tại runtime, chứ không cố định khi biên dịch
-Cấp phát động là khi chương trình tự quyết định cần bao nhiêu bộ nhớ khi đang chạy, chứ không phải khi biên dịch
+1 vài trường hợp ta không thể biết trước rằng cần dùng bao nhiêu bộ nhớ để lưu trữ đủ đủ dữ liệu;chẳng hạn khi muốn khai báo 1 mảng gồm n phần tử rồi dùng vòng lặp để nhập vào từng phần tử thì cú pháp hợp lệ là phải dùng cấp phát động chứ k phải int a[n]
- Thật ra không phải khi int a[n] sẽ xảy ra lỗi vì 1 vài chương trình mở rộng vẫn chạy được nó nhưng đó không phải tính năng của c++ tiêu chuẩn và nếu có chạy được thì vùng nhớ sẽ được lưu trên stack và chỉ có kích thước vài MB nên nếu n lớn thì sẽ dẫn tới lỗi stack overflow
- Cách cấp phát động trong C++ và C:
- Với C: có 3 cách để khai báo cấp phát động:
- Dùng malloc để khởi tạo 1 vùng nhớ rỗng với cú pháp:
`ptr = (castType*) malloc(size);`
:::spoiler Chi tiết
-ptr là khai báo kiểu pointer
-casetype là ép kiểu dữ liệu của malloc(malloc là 1 hàm mà khi gọi thì sẽ trả về 1 con trỏ có kiểu dữ liệu void và trỏ đến địa chỉ đầu tiên của vùng nhớ được cấp phát trên heap);ép kiểu thì chỉ thường dùng trên c++ vì khi trả về con trỏ có kiểu void thì trong c++ sẽ k thể tự ép thành các kiểu phù hợp dễ dẫn đến lỗi,nhưng khi dùng malloc trong c++ thì phải đảm bảo hiểu thật rõ để tránh gây ra lỗi;tốt hơn hết nên dùng malloc cho C thì C sẽ tự động ép kiểu của con trỏ từ void thành 1 kiểu dữ liệu phù hợp với dữ liệu được trỏ đến nên ép kiểu là không cần thiết với C
-malloc(size) là kích thước bộ nhớ cần cấp phát
vd:`int *p = (int*) malloc(5*sizeof(int))`
cấp phát 5 ô nhớ mỗi ô nhớ 4 byte
:::
:::spoiler calloc
-Là hàm để cấp phát động trên heap giống malloc nhưng không khởi tạo 1 vùng nhớ với các địa chỉ chứa giá trị rác như malloc mà sẽ khởi tạo 1 vùng nhớ với giá trị = 0
-Cú pháp:`ptr = (castType*)calloc(n, size);`
với ptr và casttype vẫn giống malloc chỉ khác rằng calloc có 2 ẩn là số lượng phần tử và kích thước của từng phần tử;thật ra thì dù 2 ẩn nhưng kết cục vẫn sẽ tạo ra bộ nhớ có kích thước (n * size) hệt malloc
vd:
```
int n = 5;
int *a = (int*) calloc(n, sizeof(int));
```
-đoạn code trên sẽ cấp phát bộ nhớ cho 5 phần tử int và đặt giá trị cho nó là 0
-Thường được dùng khi việc với mảng logic(mảng 0 1),bảng tần suất(tất cả ban đầu đều là 0) hoặc đơn giản chỉ mún bộ nhớ được tạo trông sạch sẽ
:::
:::spoiler realloc
-Là hàm dùng để thay đổi kích thước vùng nhớ đã được cấp phát từ trước bằng malloc hay calloc
Cú pháp: `ptr = realloc(ptr, n);`
vd:
```
int *a=malloc(3*sizeof(int)); //cấp phát vùng nhớ cho 3 số int(12byte)
//giờ muốn thành 5 phần tử int thì cấp phát thêm 8 byte cho 2 số int
a=realloc(a,5*sizeof(int)); //thay đổi lại kích thước vùng nhớ tăng lên thành 20 byte
```
-Trường hợp đặc biệt là khi con trỏ được khai báo bằng 'NULL' `int *a=NULL;`;lúc này realloc sẽ có chức năng như malloc
-Tuy nhiên không phải lúc nào cũng thay đổi kích thước thành công;khi được gọi thì realloc sẽ kiểm tra xem vùng nhớ cũ có thể mở rộng tại chỗ được không(đủ khoảng không gian trống liền kề trên heap )nếu đủ thì sẽ mở rộng tại chỗ,còn nếu không thì sẽ xét ở những khoảng trống khác trên heap nếu đủ thì sẽ cấp phát vùng nhớ mới tại đấy bằng cách copy dữ liệu ở vùng cũ sang vùng mới, giải phóng vùng cũ và trả về địa chỉ con trỏ mới,nếu xảy ra lỗi ở các bước trên(thường sẽ ở bước tìm và cấp phát vùng mới) thì dẫn đến realloc thất bại
-Nguyên nhân thất bại:
+ 1)Không còn đủ bộ nhớ trống:khi máy hết Ram hoặc khi yêu cầu mở rộng vùng nhớ quá lớn sẽ dẫn đến thất bại
+ 2)Phân mảnh bộ nhớ:ngay khi tổng dung lượng còn trống trên heap lớn hơn kích thước vùng nhớ cần mở rộng nhưng lại không có đủ vùng trống liền kề thì vẫn dẫn đến thất bại;điều này diễn ra khi trên heap chứa nhiều vùng nhớ khác nhau chưa được free mà khoảng trống liền kề nhau giữa các vùng nhớ lại không đủ để mở rộng tại chỗ hay cấp phát vùng nhớ mới thì sẽ dẫn đến thất bại
+ 3)Tham số sai hoặc lỗi logic:khi tham số size là 0 thì ta sẽ nghĩ nó bị lỗi nhưng thật ra nó sẽ free vùng nhớ và trả về null như 1 lệnh free bình thường;hoặc nếu tham số vùng nhớ cần thay đổi kích thước là 1 vùng nhớ không hợp lệ thì chương trình sẽ bị crash
-Khi thất bại realloc sẽ trả về null,vùng nhớ cũ sẽ vẫn giữ nguyên và hợp lệ,dữ liệu cũ sẽ không bị mất trừ khi(gán đè lên con trỏ cũ)
`vd:ptr = realloc(ptr, new_size); // ❌ nguy hiểm nếu thất bại`
-Khi gán đè và nếu thất bại realloc trả về null,sẽ mất lun con trỏ cũ mà không free được dẫn tới memory leak
-Cách phòng tránh mất dữ liệu khi realloc thất bại:
```
int *tmp = realloc(ptr, new_size);
if (tmp != NULL) {
ptr = tmp; // chỉ gán lại khi realloc thành công
} else {
printf("Khong du bo nho de mo rong!\n");
}
```
:::
-Trong c++:
+Xin 1 ô nhớ:`pointer = new type(initial_value);`
.Nếu không có value trên trong thì sẽ xin cấp phát 1 ô nhớ mà chưa gán giá trị
+Xin nhiều ô nhớ:` pointer = new type[size];`
:::info
-Trong new thì kiểu của con trỏ phải giống kiểu của dữ liệu xin cấp phát
-Khi xin nhiều ô nhớ thì phải dùng `delete[]ptr` nếu không thì sẽ chỉ giải phóng phần tử đầu tiên
:::
* BSS:là nơi chứa các biến toàn cục hoặc tĩnh mà chưa được khởi tạo giá trị cụ thể hoặc giá trị = 0
-Biến tĩnh là biến có chữ static phía trước,nó chỉ được sai báo 1 lần và có thể dùng với tất cả kiểu dữ liệu
* Data segment:là nơi chứa các biến toàn cục hoặc tĩnh mà đã được khởi tạo giá trị cụ thể
```
int a; // global, chưa khởi tạo → nằm ở BSS
int b = 5; // global, có khởi tạo → nằm ở Data segment
int main() {
static int c; // static, chưa khởi tạo → nằm ở BSS
}
```
* Test segment:vùng bộ nhớ của process dùng để chứa mã lệnh của chương trình;đây là nơi các câu lệnh trong file .cpp, .c, .asm... sau khi biên dịch được nạp vào để CPU chạy.
-Các dòng code sẽ được biên dịch thành dạng assembly và từ đó biến thành các dãy byte nhị nhân và thứ được lưu trong text segment chính là các byte nhị phân đó
* Memory endianess:khi lưu trữ 1 dữ liệu nhiều byte vào memory thì dữ liệu đấy sẽ được tách ra và sắp xếp theo little endianess
* Address calculation:là quá trình àm cpu sẽ xác định chính xác địa chỉ thực tế ô nhớ để truy cập đến
-Công thức tổng quát tính địa chỉ:`[base + index*scale + displacement]`
+Base: là thanh ghi cơ sở nơi bắt đầu của vùng nhớ cần tính
+Index:thanh ghi chỉ mục dùng để duyệt qua địa chỉ của 1 phần tử được yêu cầu trong mảng
+Scale:kích thước phần tử tại địa được lấy
+disp: là giá trị offset nghĩa là khi ta đã chọn được 1 mốc địa chỉ nhưng địa chỉ ta muốn lại nằm trên hoặc dưới địa chỉ mốc đấy thì ta phải + hoặc - offset để chọn được đúng địa chỉ mà ta muốn
-Lệnh"**LEA(load effective address)**":là lệnh tính toán địa chỉ mà không đọc lệnh tại đó
-Ứng dụng của lea:
| Mục đích | Cách dùng LEA |
| ---------------------------- | ---------------------------------------------- |
| 📦 Lấy địa chỉ biến | `lea rax, [rbp - 8]` |
| 🔢 Tính nhanh phép nhân/cộng | `lea eax, [ebx + ebx*4]` |
| 📊 Tối ưu vòng lặp | Cập nhật con trỏ/mảng bằng `LEA` thay vì `ADD` |
| 🧭 Tính offset trong struct | `lea rdx, [rcx + offsetof(struct, field)]` |
So sánh mov và lea:
| Đặc điểm | `MOV` | `LEA` |
| ----------------------- | ------------------- | ------------------------------------------ |
| Đọc bộ nhớ | ✅ Có | ❌ Không |
| Ghi vào thanh ghi | ✅ | ✅ |
| Lưu giá trị hay địa chỉ | Giá trị tại địa chỉ | Chính địa chỉ |
| Dùng cho | Lấy dữ liệu | Tính toán địa chỉ, offset, nhân cộng nhanh |
* Rip relative addressing:là 1 cách tính địa chỉ dựa trên vị trí lệnh đang thực thi với RIP là thanh ghi lưu địa chỉ của lệnh tiếp theo
vd:`mov rax, [rip + 0x10]`
-Nếu địa chỉ của lệnh trên là 0x400100 mà lệnh trên có độ dài 8 byte thì địa chỉ lệnh tiếp theo sẽ là 0x400108 và lệnh lại bảo cộng thêm 10 thì địa chỉ thực tế mà cpu sẽ truy cập là 0x400118
+Độ dài lệnh:các lệnh sẽ có độ dài khác nhau từ 1-15 byte tùy thuộc vào độ mở rộng của lệnh đó thì độ dài sẽ càng lớn
-Sau khi tính toán thì địa chỉ thực tế mà cpu truy cập sẽ có thể là 1 lệnh khác hoặc dữ liệu
-Vì khi chương trình hoặc thư viện được load vào bất kỳ vị trí nào trong bộ nhớ, các địa chỉ tuyệt đối sẽ thay đổi.
→ Nếu dùng RIP-relative, ta không cần biết địa chỉ tuyệt đối, chỉ cần “tương đối so với lệnh hiện tại”.
-Có thể kết hợp với lea để tính toán địa chỉ cụ thể vì code có thể chạy được ở bất kì vị trí nào trong bộ nhớ tuy nhiên rip relative chỉ tồn tại trong 64 bit
+Địa chỉ tuyệt đối:là địa chỉ thật trong bộ nhớ vật lý hoặc ảo;là địa chỉ đầy đủ mà cpu dùng để truy cập dữ liệu
vd:0x7FFDE000 là địa chỉ tuyệt đối (absolute),nó chỉ tới một vị trí cố định trong không gian địa chỉ của process.
-Khi code viết kiểu này, nó phụ thuộc hoàn toàn vào việc dữ liệu nằm đúng ở địa chỉ đó.
Nếu chương trình được load vào vị trí khác → địa chỉ đó không còn đúng nữa → lỗi truy cập bộ nhớ (segfault).
+Địa chỉ tương đối:là địa chỉ được tính toán dựa trên cột mốc chứ không cố định thường sẽ biểu diễn bằng "mốc+offset";cpu sẽ tính địa chỉ thực trong lúc chạy chứ không cần biết trước địa chỉ tuyệt đối
-Một process không luôn được load vào cùng chỗ,mỗi khi hệ điều hành chạy chương trình:Nó cấp phát vùng nhớ ảo riêng biệt cho process đó,tuỳ vào tình trạng RAM, ASLR (Address Space Layout Randomization), thư viện động...
→ vùng nhớ của chương trình được đặt vào chỗ khác nhau mỗi lần.
-Với mỗi lần khởi chạy(mở và thoát process) thì địa chỉ được cấp phát cho process sẽ khác nhau nên khi dùng địa chỉ tuyệt đối để trỏ đến địa chỉ lưu dữ liệu thì sẽ bị sai nên 64 bit ưu tiên dùng rip relative
So sánh:
| Loại địa chỉ | Biểu diễn | Phụ thuộc vị trí load? | Ví dụ | Dùng ở đâu |
| ------------------------ | ----------------------- | ---------------------- | ----------------------------- | -------------------------------- |
| **Tuyệt đối (absolute)** | Giá trị địa chỉ cố định | ✅ Có | `[0x401000]` | Code tĩnh, hệ thống nhúng |
| **Tương đối (relative)** | Base + offset | ❌ Không | `[rip + offset]`, `[rbp - 8]` | Ứng dụng hiện đại, thư viện động |
* Writing immediate value:
-Immediate value:là giá trị được ghi trực tiếp trong mã lệnh mà không nằm trong thanh ghi hay ô nhớ nào cả
-Ta có thể ghi immediate value vào thanh ghi hoặc ô nhớ;khi ghi vào ô nhớ thì nên là ô nhớ được có địa chỉ được thanh ghi trỏ đến hoặc là ô nhớ có địa chỉ tương đối(rip relative) để đảm bảo an toàn
-Hạn chế của immediate value là không thay đổi được giá trị trong lúc runtime
-Trên pwn.co thì các địa chỉ mà chall yêu cầu đã được cho sẵn kích thước nên khi ta truyền immediate value vào với mov sẽ vẫn bình thường nhưng khi ta tự code trên terminal riêng thì khi mún truyền dữ liệu đến 1 địa chỉ bất kỳ thì ta cần phải cho biết địa chỉ đó kích thước bao nhiêu đề truyền dữ liệu vào và mở rộng sao cho hợp lí
-Khi dùng mov thì sẽ có 2 ẩn 1 là dữ liệu cần truyền 2 là địa chỉ được truyền(địa chỉ ở đây là địa chỉ của ô nhớ bắt đầu lưu trữ dữ liêu);1 trong 2 cái trên cần phải có ít nhất 1 cái có kích thước cụ thể thì mới có thể dùng mov được
+Với thanh ghi: nếu thanh ghi là địa chỉ được truyền vào và dữ liệu là 1 số chưa biết kích thước thì nếu dữ liệu cần truyền vào là số dương thì phải dùng movzx để mở rộng kiểu zero extend cho phù hợp còn nếu dữ liệu là 1 số âm thì phải dùng movsx để mở rộng theo kiểu số có dấu;còn với trường hợp giá trị thanh ghi là thứ cần được truyền vào còn nơi đến là 1 địa chỉ chưa biết kích thước thì địa chỉ sẽ tự lấy kích thước theo thanh ghi;còn trường hợp cả 2 ẩn đều là thanh ghi nhưng nếu cùng kích thước thì vẫn sẽ dùng mov bình thường còn nếu 2 ẩn là 2 thanh ghi khác kích thước thì sẽ dẫn đến lỗi
+Ta có địa chỉ mà thanh ghi trỏ đến cũng sẽ là 1 địa chỉ không biết kích thước và 1 immediate value cũng là không biết kích thước;cpu không có khả năng nhớ về kích thước của địa chỉ nên với mỗi lần mov dữ liệu mà 2 ẩn k biết kích thước thì ta sẽ phải khai báo lại kích thước cho nó dù trước đó có khai báo kích cho nó hay chưa
# System call
| Mục | Tóm tắt |
| ------------------ | ------------------------------------------------ |
| **Định nghĩa** | Cầu nối giữa user mode và kernel mode |
| **Thực hiện** | Qua lệnh `syscall` (hoặc `int 0x80` trên x86 cũ) |
| **Truyền tham số** | Bằng các thanh ghi (rdi, rsi, rdx, ...) |
| **Trả về kết quả** | Trong `rax` |
| **Ví dụ** | `write()`, `open()`, `exit()` |
| **Mục tiêu** | Cho phép truy cập tài nguyên hệ thống an toàn |
Lợi ích:Bảo mật:
✅Bảo mật:ngăn chương trình truy cập trực tiếp phần cứng.
✅ Ổn định: kernel kiểm soát toàn bộ truy cập tài nguyên.
✅ Độc lập phần cứng: chương trình chỉ cần gọi API, kernel lo phần còn lại.
✅ Chuẩn hóa: mọi ngôn ngữ lập trình đều có thể gọi system call thông qua thư viện.
Cấu trúc của 1 số lệnh cơ bản:
`write(1,buffer,count)`
với 1 là file descriptor(đầu ra hoặc file cần ghi)
buffer là địa chỉ chứa vùng nhớ dữ liệu cần ghi( là con trỏ trỏ đến vùng nhớ chứa dữ liệu)
count là số byte muốn ghi
-Để thực hiện lệnh write trước tiên ta phải truyền cho rax là 1 vì 1 là mã số của lệnh write,truyền tham số thứ nhất cho rdi,tham số 2 cho rsi,3 cho rdx sau đó syscall thì sẽ thực hiện lệnh thành công
Với read:
read(0,buffer,count)
với cách làm tương tự chỉ khác mã số của read là 0
Với exit:mã số của lệnh exit là 60 và ta có thể truyền mã thoát(exit code) cho rdi
-VD:`Cho 1 dữ liệu có 8 byte ở địa chỉ 1337000 hãy đọc dữ liệu trên ghi nó ra và exit chương trình với mã thoát là 42`
:::spoiler code giải
mov rax,0
mov rdi,0
mov rsi,1337000
mov rdx,8
syscall
mov rax,1
mov rdi,1
mov rsi,1337000
mov rdx,8
syscall
mov rax,60
mov rdi 42
syscall
Trên linux gõ echo$? để hiện mã thoát
:::
*String agrument(đối số chuỗi):là con trỏ trỏ đến mảng kí tự;để in ra chuỗi chứa mảng kí tự thì cũng write như bình thường nhưng trong C chuỗi kí tự thường có kí tự /0(null) để đánh dấu kết thúc chuỗi nó chiếm 1 byte
Vd:"Hello Hackers" là 1 chuỗi có 13 byte nếu ta write với count là 13 hay 14 thì vẫn in ra chuỗi giống nhau vì kernal không quan tâm đến /0
*Constant agrument thật ra là những tham số nhưng nó được ghi thẳng ra bằng 1 hằng số chứ không phải là 1 biến
vd:write(1,1337000,3)
thì 1 và 3 là 2 constant agrument nhưng nếu thay 2 số đấy bằng 1 biến thì nó là giá trị có thể thay đổi
# Control flow
*Control flow jump:là hành động cpu thay đổi giá trị của rip để nhảy đến 1 địa chỉ khác trong bộ nhớ thay vì tiếp tục lệnh kế theo thứ tự
Bình thường:lệnh 1 → lệnh 2 → lệnh 3 → lệnh 4
Khi có jump:lệnh 1 → lệnh 2 → (jump đến) lệnh 8 → lệnh 9 …
-Cpu lun có 1 thanh ghi rip chứa địa chỉ của lệnh tiếp theo cần được thực thi khi thực heienj lệnh jump hay các lệnh điều khiển khác cpu sẽ phải thay đổi giá trị của rip
-Jump xảy ra khi gặp các lệnh như:jmp, je, jne, call, ret, loop, int, syscall,...
-Jump có 2 loại chính là:
+Unconditional jump:cpu sẽ nhảy luôn đến đấy mà không cần điều kiện gì cả;các lệnh sau sẽ nhảy trực tiếp:jmp,call, ret, int, syscall

+Conditional jump:chỉ nhảy khi 1 điều kiện cụ thể là đúng

Các loại flag:
| Cờ | Ý nghĩa | Thiết lập khi |
| -- | ------------- | ------------------------------------ |
| ZF | Zero Flag | kết quả phép toán = 0 |
| SF | Sign Flag | bit dấu (âm) = 1 |
| CF | Carry Flag | có “mượn” hoặc “nhớ” trong phép toán |
| OF | Overflow Flag | tràn số có dấu |
Các lệnh nhảy có điều kiện thường gặp:
| Lệnh | Nhảy khi | Ý nghĩa |
| ------------- | ------------------- | -------------------------- |
| `je` / `jz` | ZF = 1 | bằng nhau |
| `jne` / `jnz` | ZF = 0 | khác nhau |
| `jg` / `jnle` | ZF = 0, SF = OF | lớn hơn (có dấu) |
| `jl` / `jnge` | SF ≠ OF | nhỏ hơn (có dấu) |
| `ja` / `jnbe` | CF = 0, ZF = 0 | lớn hơn (không dấu) |
| `jb` / `jc` | CF = 1 | nhỏ hơn (không dấu) |
| `jge` | SF = OF | lớn hơn hoặc bằng (có dấu) |
| `jle` | ZF = 1 hoặc SF ≠ OF | nhỏ hơn hoặc bằng (có dấu) |
Loại jump đặc biệt(indirect jump):nhảy đến địa chỉ được chứa trong thanh ghi hoặc bộ nhớ chứ không cố định trong code
:::warning

:::
*Looping:là vòng lặp nghĩa là cpu sẽ thực thi 1 đoạn mã lặp lại nhiều lần cho đến khi thỏa mãn điều kiện
Cấu trúc:
| Thành phần | Chức năng |
| ----------------------------- | ------------------------------------------- |
| **Khởi tạo (Initialization)** | Thiết lập giá trị ban đầu (ví dụ `i = 0`) |
| **Điều kiện (Condition)** | Kiểm tra xem có nên tiếp tục vòng lặp không |
| **Cập nhật (Update)** | Thay đổi biến điều khiển sau mỗi lần lặp |
| **Thân vòng lặp (Body)** | Các lệnh được thực hiện lặp đi lặp lại |
Ví dụ: code C:
```
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
```
Tương ứng với code asm:
```
mov eax, 0 ; i = 0
loop_start:
cmp eax, 5 ; so sánh i và 5
jge loop_end ; nếu i >= 5 thì nhảy ra ngoài
; ---- Thân vòng lặp ----
; (printf được gọi ở đây)
inc eax ; i++
jmp loop_start ; quay lại đầu vòng
loop_end:
```
-Cấu lệnh trước cmp sẽ tạo ra cờ và lệnh jge sẽ đi kiểm tra cờ để biết nó đúng hay sai mà thực hiện bước nhảy và 2 lệnh này không nhất thiết phải kề nhau nhưng giữa chúng phải không có lệnh nào làm thay đổi cờ
-Mỗi lệnh conditional jump chỉ quan tâm đến 1 số loại cờ cụ thể chứ không quan tâm tất cả