<center> <h1>
TASK 1 <small>Mentor : lekiet</small>
</h1></center>
# Thanh ghi
Trong Assembly, máy tính không có khái niệm các biến như ngôn ngữ bật cao. Nó sẽ thao tác qua các thanh ghi và bộ nhớ để xử lí dữ liệu.

## Thanh ghi dữ liệu

EAX, EBX, ECX, EDX là các thanh ghi dữ liệu 32bit ( thay E = R như RAX, RBX... là các thanh ghi 64bit sau này).
Không có hậu tố hay tiền tố sẽ là 16 bit
- AX ( A = Accumulator : Tích lũy) : Thanh ghi này được dùng trong nhập xuất, sử dụng trong hầu hết instruction số học.
- BX (B = Base): Thanh ghi này để lưu các địa chỉ
- CX (C = Count) : Thanh ghi này để đếm vòng lặp.
- DX (D = Data) : Giống như 1 thanh ghi `temp` , dùng chung với AX để tính toán.
Với tiền tố `E` như EAX,EBX sẽ là lưu trữ 32 bit, `R` như RAX, RBX sẽ là 64 bit.
Hậu tố H và L tương ứng với `High` and `Low`. Với H sẽ là lấy 8 bit cao trong chuẩn 16 bit (`1101 1001` thì sẽ là `1101`). Còn L thì là 8 bit thấp ( `1101 1001` sẽ là `1001`).
## Thanh ghi con trỏ

- SP ( Stack Pointer): Con trỏ ngăn xếp này lưu địa chỉ cuối cùng của phần tử lưu vào stack.

- BP ( Base Pointer) : Hỗ trợ biến tham chiếu tới chương trình con.
- IP ( Instruction Pointer): Trỏ đến địa chỉ tiếp theo của chương trình thực thi. Kết hợp thanh ghi `CS` ( Code Segment) có tác dụng lưu toàn bộ chương trình code, các phân đoạn mã với `IP` để có thể tạo logic câu lệnh tiếp theo.
## Thanh ghi chỉ số

- SI ( Source Index): Dùng lưu giá trị địa chỉ nguồn cho phép toán với sâu.
- DI ( Destination Indix): Dùng lưu giá trị đíchc đến cho phép tính toán với sâu.
Thường thì sẽ có mấy format đơn giản như
```asm
mov si, user_input
mov di, buffer
copy + encode
cmp buffer, key
```
## Thanh ghi cờ
Flags Register là các thanh ghi kết hợp với Pointer Register để điều khiển chương trình. Do là Assembly không có các kiểu lệnh `IF/ELSE, WHILE, DO/WHILE...` nên phải dùng kiểu kết hợp này so sánh, lệnh nhảy các thứ.

- CF : Cờ nhớ, nó nhớ 0 hoặc nhớ 1 dùng cho số không dấu, nó phản ánh việc tràn số không dấu ( Ví dụ `cmp a,b` thì nó là `a-b` nếu a>=b thì CF = 0, và ngược lại).
- PF : Bit chẵn lẻ, đếm số lượng bit 1 nếu chẵn thì PF = 1, và ngược lại ( `0010 1000` có 2 bit 1, nên là chẵn => PF = 1 ). Nó cũng thường dùng để kiểm tra lỗi
- AF : Cờ điều chỉnh chứa giá trị nhớ khi chuyển từ bit có trọng số 3 lên bit có trọng số 4.
- ZF : Cờ không, khi kết quả của phép tính bằng 0 hoặc so sánh bằng nhau thì `ZF = 0`, ngược lại nếu kết quả khác 0 hay so sánh không bằng nhau thì `ZF =1`
- SF : Cờ dấu, nó dùng để xác định số là âm hay dương, dựa vào MSB ( bit ngoài cùng bên phải), nếu số âm thì SF = 1, dương SF = 0.
- OF : Cờ tràn bằng 1 khi kết quả phép tính ( Có dấu) lớn hơn đích đến của nó. Đại loại là khi phép toán sai như khi +1 vào 127 trong thanh ghi 8bit, 128> 127 tràn rồi thì OF = 1
- DF : Cờ hướng, xác định hướng cho việc xoay hoặc so sánh gì đó, 0 là trái, 1 là phải
- TF : Cờ bẩy dùng để debug, đưa cờ bẩy vào để có thể thực hiện chương trình từng bước .
- IF : Cờ ngắt được sử dụng khi cần ngắt ngoài, kiểu như nhập input thì ngắt chương trình chờ nhập rồi mới xử lí. IF = 1 thì ngắt, IF = 0 thì chạy.
# Instruction
Instruction (lệnh) trong Assembly là những lệnh bậc thấp mà máy có thể hiểu được, nó giống mấy lệnh trong thư viện C++ này nọ nhưng bậc thấp nhất.
| Lệnh | Tiếng Anh | Thực hiện | Ví dụ |
|:------:|:---------------:|:---------------------:|:----------------------------------------------------:|
| `mov` | move | Sao chép | `mov rax, rbx` |
| `add` | add | Cộng 2 giá trị | `add rax, 36` |
| `sub` | subtract | Trừ | `sub rax, 18` |
| `mul` | multiply | Nhân | `mul rbx` |
| `div` | divide | Chia | `div rbx` |
| `push` | push onto stack | Đẩy dữ liệu vào stack | `push rax` |
| `pop` | pop form stack | Lấy dữ liệu stack | `pop rax` |
| `cmp` | compare | so sánh giá trị | `cmp rax, rbx` |
| `jmp` | jump | Nhảy đến địa chỉ khác | `jmp func ...(code bỏ qua)....func (code chạy tiếp)` |
| `dec` | decrease | giảm 1 | `dec rax` |
| `inc` | increase | tăng 1 | `inc rax ` |
| `lea` | load effective address | Lấy địa chỉ, không lấy dữ liệu | `lea eax, [rbp-4]` |
| `call` | call | Gọi hàm | `call func1000041` |
| `ret` | return | Hàm trả về | `ret` |
Assembler: Chuyển .asm thành .o (Object file)
`nasm -f elf64 file.asm -o file.o`
Linker (ld): Chuyển .o thành file thực thi (Executable)
`ld file.o -o file
`
Phân loại ra
| Column 1 | Column 2 |
|:---------------------------------- |:----------------------- |
| Arithmetic ( tính toán) | add, sub, imul, div |
| Bitwise | xor, and, or, shl, sal |
| Control | cmp, test, jmp, je, jne |
| Function | call, ret |
| Data movement ( di chuyển dữ liệu) | mov, lea, push, pop |
Lệnh nhảy còn nhiều loại ([Em tham khảo ở đây](https://www.philadelphia.edu.jo/academics/qhamarsheh/uploads/Lecture%2018%20Conditional%20Jumps%20Instructions.pdf))

1. Nhảy nếu bằng 0 / bằng nhau
2. Nhảy nếu `khác` 0 / `khác` nhau
3. Nhảy nếu cờ Carry được set
4. Nhảy nếu cờ Carry `không` được set
5. Nhảy nếu cờ Overflow được set
6. Nhảy nếu cờ Overflow `không` được set
7. Nhảy nếu cờ Signed được set ( Số âm)
8. Nhảy nếu cờ Signed `không` được set ( >= 0)
9. Nhảy nếu cờ Parity được set
10. Nhảy nếu cờ Parity `không` được set

11. Nhảy nếu lớn hơn / Không ít hơn hoặc bằng ( như nhau mà nhỉ)
12. Nhảy nếu lớn hơn hoặc bằng / Không nhỏ hơn
13. Nhảy nếu `nhỏ` hơn / Không lớn hơn hoặc bằng
14. Nhảy nếu nhỏ hơn hoặc bằng / Không lớn hơn
15. Phần sau là `> , >=, <, < =`
# STACK
Stack là ngăn xếp, nó được thực hiện theo quy luật LIFO (Last In, First Out). Giống như việc đặt từng quyển sách thành chồng, rồi lấy ra từ trên xuống dưới.
Nó được quản lí bởi 1 thanh ghi là SP (Stack Pointer) trỏ đến đỉnh stack và BP ( Base Pointer) trỏ đến đáy stack để xác định các tham số, biến cục bộ của hàm.
## Cấu trúc

- Đỉnh stack được trỏ bởi thanh ghi `ESP`, đó là nơi thực thi các lệnh như lấy `pop` và đẩy `push`.
- Địa chỉ các ô nhớ trong stack càng lên cao càng giảm
- Khung ngăn xếp ( stack frame): Mỗi hàm sẽ tạo ra 1 bộ khung riêng để lưu các giá trị tham số, biến cục bộ, giá trị return các thứ
## Thao tác
`push` >> Đẩy dữ liệu vô stack : ESP sẽ giảm đi 4/8 bit đối với hệ thống 32/64 bit. >>> Càng lên cao càng giảm địa chỉ, mỗi lần giảm 4-8 bit tùy vào 32bit hay 64bit
`pop` >> Lấy giá trị từ stack ra : ESP sẽ tăng 4/8 bit đối với hệ thống 32/64 bit. >>< Càng xuống thấp địa chỉ stack càng giảm.
- Khởi tạo stack : `push rbp` để đưa địa chỉ rbp vào stack, sau đó `mov rbp, rsp` để lưu địa chỉ trỏ đỉnh stack vào rbp >>> xác lập được phần đáy của stack, `tham chiếu cố định của hàm`.
- Dọn dẹp stack : Phục hồi ESP bằng cách `mov esp, rbp` do rbp lưu giá trị esp ban đầu. Phục hồi RBP bằng cách `pop rbp` vì lúc đầu mình push .
- Trở về hàm gọi : `ret` để return, sủi khỏi stack và về hàm gọi và chạy tiếp chương trình
# Endianness
Gồm `LITTEL EDIAN` và `BIG EDIAN`
* Littel là đọc theo kiểu LSB ( Least Significant Byte - LSB)

Ví dụ `0x12345678` sẽ được dịch thành `78` `56` `34` `12`
Trong đó `78` là `LSB`.

* Big là đọc theo kiểu MSB ( Most Significant Byte - MSB)

Ví dụ : `0x12345678` sẽ được dịch thành `12` `34` `56` `78`
Trong đó `12` là `MSB`
Trong kiến thức này thì chủ yếu quan tâm đến việc MÁY TÍNH sẽ đọc dữ liệu theo kiểu ngược từ dưới lên ( Littel Edian)
# Calling conention
Ở mức độ máy thì CPU không biết khái niệm về hàm, nó chỉ biết `nhảy (jmp), lưu địa chỉ, đọc ghi bộ nhớ, thanh ghi`
Để nhiều đoạn mã compiler khác nhau, module khác nhau có thể giao tiếp với nhau, gọi được nhau thì cần quy ước chung là calling convention. Nó là 1
contract giữa `callee` và `calller`.
## cdecl
| Convention | Nền tảng | Truyền | Trả về | Người dọn | Thanh ghi cần bảo toàn |
| ---------- | -------- | ------ | ------ | --- | -------- |
| cdecl | Linux/Win x86 | Stack | EAX | Caller | EBX, EDI ESI, EBP |
Các tham số đường truyền vào stack, thứ tự từ PHẢI → TRÁi.
```c
func(a,b,c)
-Trong máy sẽ thực hiện :
>>> push c
>>> push b
>>> push a
>>> call func()
```
> Mục đích là để đưa a vào đầu stack, giúp dễ lấy tham số hơn.
Người dọn stack sẽ là `caller`, sau khi kết thúc lệnh call, người gọi cần dùng ESP ( stack pointer) để loại bỏ các tham số đã push vào.
Theo quy ước thì các thanh ghi EBX, EDI, ESP, EBP cần phải dọn sạch trước khi `ret`
```c
int sum(int a, int b) {
return a + b;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; --- Bên phía Người gọi (Caller) ---
push 2 ; Tham số b
push 1 ; Tham số a
call _sum ; Gọi hàm
add esp, 8 ; DỌN STACK (2 tham số * 4 bytes)
; Lúc này kết quả nằm trong EAX
; --- Bên phía Hàm được gọi (Callee) ---
_sum:
push ebp ; Bảo toàn EBP
mov ebp, esp ; Thiết lập stack frame
mov eax, [ebp + 8] ; Lấy tham số a
add eax, [ebp + 12] ; Cộng thêm b vào EAX (trả về)
pop ebp ; Khôi phục EBP
ret ; Quay về (không dọn stack ở đây)
```
## stdcall
giống `cdecl` nhưng người dọn stack là `callee`
```a
_sum@8: ; Tên hàm thường có thêm @ + số bytes tham số
push ebp
mov ebp, esp
mov eax, [ebp+8]
add eax, [ebp+12]
pop ebp
ret 8 ; QUAN TRỌNG: Tự cộng ESP thêm 8 để dọn dẹp 2 tham số
```
```a
push 2
push 1
call _sum@8
; Không có lệnh "add esp, 8" ở đây!
```
## fastcall
2 tham số đầu tiên sẽ được lưu vào thanh ghi, các tham số từ thứ 2 trở đi sẽ đưa vào stack.
Sử dụng thanh ghi vào để tăng tốc độ xử lí dữ liệu
| Convention | Nền tảng | Truyền | Trả về | Người dọn | Thanh ghi cần bảo toàn |
| ---------- | -------- | ------ | ------ | --- | -------- |
| fastcall | Win x86 | ECX > EDX > Stack | EAX | Callee | EBX, EDI ESI, EBP |
Ví dụ như 1 hàm sum
```c
__fastcall void Sum(int a, int b, int c, int d)
```
main sẽ là cộng a b c d lại
asm nó như này
```c
push 4 ; Tham số 'd' (vào Stack)
push 3 ; Tham số 'c' (vào Stack)
mov edx, 2 ; Tham số 'b' (vào EDX)
mov ecx, 1 ; Tham số 'a' (vào ECX)
call Sum
Sum:
push ebp
mov ebp, esp
; Tính toán: EAX = ECX + EDX + [EBP+8] + [EBP+12]
pop ebp
ret 8 ; <--- CALLEE DỌN: Tự cộng ESP thêm 8 bytes (của c và d)
```
---
---
---
---
# CODE
# Code hello
<div style="border-left:5px solid #9C27B0; background:#F3E5F5; padding:10px;">
<b></b>
```c
section .text
global _start
_start:
;syscall viết
mov rax, 1 ; syscall gọi write
mov rdi, 1 ; 1 là out
mov rsi, hello_msg ; Địa chỉ của chuỗi cần in
mov rdx, hello_msg_len ; Độ dài chuỗi (tính bằng byte)
syscall ; Gọi syscall
;syscall sủi
mov rax, 60 ; syscall gọi exit
mov rdi, 0 ; 0 = thành công
syscall ; Sủi
section .data
hello_msg: db "Hello new KCSC's member, Welcome to REVERSE TEAM", 10,\
"HERE IS YOUR FLAG: KCSC{Hello_World}" ; db (Define Byte): Chuỗi và ký tự xuống dòng
hello_msg_len: equ $ - hello_msg ; equ: Hằng số tính bằng (Vị trí hiện tại - Vị trí bắt đầu)
```

</div> ```
# Cộng 2 số bình thường
- Assembly không biết `int`, `unsigned int`, `long long` ,`unsigned long long`... là gì cả. Mọi thứ đều là byte trong bộ nhớ
- Khi nhập input vào, assembly quy ước nó là mã `ASCII`

Do đó nhập 1 từ bàn phím vào, thứ được lưu trong bộ nhớ sẽ là 48, hay 0x30. Để có số 1 thì phải `sub 48` để có được`SỐ 1`.
- Vậy để cộng 2 số, cần chuyển 2 số đó từ `kí tự` thành số bằng cách `trừ đi '0'`, sau đó cộng 2 giá trị lại, và lấy kết quả đó `cộng '0'` sẽ ra được kí tự mang giá trị tổng ( Có vẻ mình diễn đạt hơi khó hiểu rồi...)
INPUT → ASCII ('1' '2' '3' '\n') → buffer → stoi → cộng → itos → in ra.
```asm=
section .data
;nhập số thứ nhất
msg1 db "Nhap so thu 1: ", 0
msg1_len equ $ - msg1 ; Độ dài chuỗi msg1
;nhập số thứ hai
msg2 db "Nhap so thu 2: ", 0
msg2_len equ $ - msg2 ; Độ dài chuỗi msg2
; kết quả
msgkq db "Tong la: ", 0
msgkq_len equ $ - msgkq ; Độ dài chuỗi msgkq
section .bss
num1 resb 30 ; xin 30 byte cho bộ nhớ , này là số 1
num2 resb 30 ; số 2
buf resb 30 ; chứa chuỗi kết quả
section .text
global _start
_start:
;thông báo nhập số đầu
mov rax, 1 ; write
mov rdi, 1 ; xuất
mov rsi, msg1 ; địa chỉ chuỗi
mov rdx, msg1_len ; độ dài chuỗi
syscall
; nhận input số đầu tiên
mov rax, 0 ; read
mov rdi, 0 ; vào
mov rsi, num1 ; buffer nhận dữ liệu
mov rdx, 30 ; số byte tối đa nhận
syscall
;RAX = số byte đã đọc (bao gồm '\n') vì
dec rax ; **GIẢI THÍCH 1**
mov byte [num1 + rax], 0
mov rbx, num1 ; RBX trỏ tới chuỗi
call _stoi ; _stoi là hàm chuyển string thành int, tí làm sau
mov r8, rax ; Lưu giá trị số thứ nhất vào R8
; số thứ 2, comment cũng giống số 1 thoai
; write
mov rax, 1
mov rdi, 1
mov rsi, msg2
mov rdx, msg2_len
syscall
;read
mov rax, 0
mov rdi, 0
mov rsi, num2
mov rdx, 30
syscall
dec rax
mov byte [num2 + rax], 0
mov rbx, num2
call _stoi
; Giờ cộng 2 số lại
add rax, r8 ;RAX = num1 + num2(dejavu r8 chứa num1, rax chứa num 2)
;----IN KẾT QUẢ----
mov rbx, buf ; RBX trỏ tới buf kết quả
call _itos ; Gọi hàm chuyển int -> string, tí nữa viết sau.
;in thông báo kết qủa
mov rax, 1
mov rdi, 1
mov rsi, msgkq
mov rdx, msgkq_len
syscall
;in kết quả
mov rax, 1
mov rdi, 1
mov rsi, buf
mov rdx, rcx
syscall
;Thoát
mov rax, 60 ; exit
xor rdi, rdi ; 0 là hoàn thành
syscall
;----viết các lệnh call_
_stoi: ;STRING TO INT ***GIẢI THÍCH 2***
xor rax, rax ; reset thanh ghi
xor rsi, rsi ; reset luôn
.next:
mov dl, [rbx + rsi] ; Lấy ký tự hiện tại
cmp dl, 0 ; Nếu gặp NULL → kết thúc
je .done ; Tí tạo tí tạo
imul rax, rax, 10 ; result = result * 10
sub dl, '0' ; ASCII '0'..'9' → số 0..9
add rax, rdx ; cộng chữ số vào kết quả
inc rsi ; sang ký tự tiếp theo
jmp .next
.done:
ret ; quay về địa chỉ đã call
;Khi này rax chứa kết quả đã cộng
_itos: ; INT TO STRING
mov rcx, 0 ; RCX (counter) đếm số chữ số
; ***GIẢI THÍCH 3***
.convert:
xor rdx, rdx
mov rbx, 10
div rbx ; RAX = RAX / 10, RDX = phần dư
add dl, '0' ; số → ASCII
push rdx ; lưu vô stack
inc rcx
test rax, rax ; Bật cờ ZF
jnz .convert ; còn chữ số thì tiếp tục
mov rbx, buf ; RBX trỏ lại buffer
.printloop:
pop rdx ; lấy chữ số theo thứ tự đúng
mov [rbx], dl
inc rbx
loop .printloop ; lặp RCX lần
ret
```
KẾT QUẢ

.
.
.
.
.
.
*Giải thích 1* :
- Ví dụ input : '1' '2' '3' '\n'
- num1 ( có 30 byte ô nhớ ) ban đầu là : `00 00 00 00 ... ` → `31 32 33 0A 00 00 ...` Lúc này RAX đọc được 4 byte, ta `dec rax` để giảm còn 3.
- `[num1 + rax]` là trỏ đến 1 ví trị trong mang thứ `rax` , khi này `num1 + 3` là trỏ đến `0A` ( tính từ 0 ), sửa đó `mov 0` vào patching giá trị `\n` thành `NULL`
.
.
.
.
.
.
*Giải thích 2* : (tiếp tục với dữ liệu 1 2 3 đã chuyển đổi )
- Dèjavu là `mov rbx, num1`, rbx trỏ tới mảng num1 chứa chuỗi đầu tiên
- Reset thanh ghi để không dính giá trị rác
- Tạo vòng lặp duyệt từng kí tự, trừ nó với `'0'` để cho ra dạng int của chuỗi. Dùng thanh ghi RDX (Data) để lưu trữ cho tiện, sài DL (phần low) cho nó nhanh vì có 1 byte mà
- dl = [num1 + 0] = 0x31 (Tức là '1' trong Ascii)
- cmp dl , 0 : So sánh với NULL, nếu NULL thì `je.done` là hàm kết thúc việc chuyển đổi , còn khác 0 thì chạy tiếp
- Sau đó thì biến đổi như này

- Mỗi lần biến đổi xong, tăng rsi lên 1, để trỏ từng kí tự trong mảng.
- Sau đó `jmp .next` kiểm tra xem NULL chưa, chưa NULL thì lặp tiếp.
.
.
.
.
.
.
*Giải thích 3*:
.convert
- Trước tiên thì reset thanh ghi `RDX`
- Giải ngược từng số + `'48'` vô để ra ACSII kí tự, đưa kí tự vào `stack`.
- `inc RCX` vì mỗi lần đưa vào stack là 1 kí tự, tăng số counter để tí lặp bấy nhiêu lần lại cho việc trả KẾT QUẢ.
- Bật cờ ZF, `rax AND rax` để kiểm tra, rax = 0 thì làm tiếp, còn không thì lặp `.convert`
Ví dụ kết quả là 1 2 3, lưu vào stack là 3 2 1

.printloop:
- Trỏ rbx vô mảng buf ( mảng lưu kết quả).
- Như hình trên thì pop từng số từ `stack` `rdx` ra xong lưu vào từng ô nhớ của rbx ( trỏ tới buf).
- Tăng địa chỉ trỏ lên rồi lặp đến khi sạch `stack` và về `ret`
# CỘNG TRÀN BAO NHIÊU CŨNG ĐƯỢC
```a
section .data
msg1 db "Nhap so thu 1: ", 0
msg1_len equ $ - msg1
msg2 db "Nhap so thu 2: ", 0
msg2_len equ $ - msg2
msgkq db "Tong la: ", 0
msgkq_len equ $ - msgkq
newline db 10
section .bss
num1 resb 100 ; Tăng lên 100 byte ô để lưu
num2 resb 100
buf resb 101
section .text
global _start
_start:
; --- Nhập số thứ nhất ---
mov rax, 1
mov rdi, 1
mov rsi, msg1
mov rdx, msg1_len
syscall
mov rax, 0
mov rdi, 0
mov rsi, num1
mov rdx, 100
syscall
; Lưu độ dài số 1 vào R12 và bỏ ký tự xuống dòng (\n)
dec rax
mov r12, rax
; --- Nhập số thứ hai ---
mov rax, 1
mov rdi, 1
mov rsi, msg2
mov rdx, msg2_len
syscall
mov rax, 0
mov rdi, 0
mov rsi, num2
mov rdx, 100
syscall
; Lưu độ dài số 2 vào R13 và bỏ ký tự xuống dòng (\n)
dec rax
mov r13, rax
; SET các thanh ghi
mov rsi, r12
dec rsi ; RSI trỏ vào ký tự cuối cùng của num1
mov rdi, r13
dec rdi ; RDI trỏ vào ký tự cuối cùng của num2
mov r10, 0 ; R10 lưu số NHỚ
mov rcx, 0 ; RCX đếm số chữ số đã PUSH vào Stack
.loop_add:
xor rax, rax
add rax, r10 ; Cộng số NHỚ từ hàng trước vào tổng hiện tại
mov r10, 0 ; Reset số nhớ
; Lấy chữ số num1 nếu còn
cmp rsi, 0
jl .check_num2
movzx rdx, byte [num1 + rsi] ;movzx để an toàn số không dấu
sub rdx, '0' ; Chuyển ASCII sang số
add rax, rdx
dec rsi
.check_num2:
; Lấy chữ số num2 nếu còn
cmp rdi, 0
jl .calc
movzx rdx, byte [num2 + rdi]
sub rdx, '0' ; Chuyển ASCII sang số
add rax, rdx
dec rdi
.calc:
; BƯỚC QUAN TRỌNG: Chia 10 để lấy "Viết" và "Nhớ"
mov rdx, 0
mov rbx, 10
div rbx ; RAX = Thương (Nhớ), RDX = Dư (Viết)
mov r10, rax ; Lưu lại số Nhớ cho lượt sau
add rdx, '0' ; Chuyển số dư về ASCII
push rdx ; ĐƯA VÀO STACK ĐỂ ĐẢO CHIỀU
inc rcx ; Tăng biến đếm chữ số
; Điều kiện lặp: Còn số 1 HOẶC còn số 2 HOẶC còn số Nhớ
cmp rsi, 0
jge .loop_add
cmp rdi, 0
jge .loop_add
cmp r10, 0
jg .loop_add
; --- LẤY TỪ STACK RA VÀ LƯU VÀO BUFFER ---
mov r14, rcx ; Lưu tổng số chữ số vào R14 để in
mov rbx, buf ; Trỏ RBX vào buffer kết quả
.pop_loop:
pop rax ; Lấy chữ số ra (đúng thứ tự từ trái sang phải)
mov [rbx], al
inc rbx
loop .pop_loop
; --- IN KẾT QUẢ ---
; In chữ "Tong la: "
mov rax, 1
mov rdi, 1
mov rsi, msgkq
mov rdx, msgkq_len
syscall
; In chuỗi số trong buffer
mov rax, 1
mov rdi, 1
mov rsi, buf
mov rdx, r14 ; Độ dài là số lần đã Pop
syscall
; In newline cho đẹp
mov rax, 1
mov rdi, 1
mov rsi, newline
mov rdx, 1
syscall
; Thoát chương trình
mov rax, 60
xor rdi, rdi
syscall
```
