Khi lập trình với C++ hay Assembly, việc hiểu rõ các Calling Convention (quy ước gọi hàm) là cực kỳ quan trọng, bởi nó quyết định cách tham số được truyền vào hàm, cách quản lý stack cũng như cách các thanh ghi được lưu trữ trước và sau khi gọi hàm, giúp giảm lỗi, tối ưu hiệu suất và hỗ trợ quá trình debug hiệu quả.
Calling Convention định nghĩa cách hàm nhận tham số và trả về giá trị; mỗi quy ước có cách tổ chức stack và sử dụng thanh ghi khác nhau, ảnh hưởng trực tiếp đến tính tương thích giữa các module và hiệu suất chương trình.
Là các thanh ghi mà hàm gọi (callee) có thể sử dụng tự do và làm thay đổi giá trị; nếu caller cần giữ nội dung của chúng sau khi gọi hàm, caller phải chủ động lưu (push) trước và khôi phục (pop) sau.
1 2 3 4 5 6 7 8
; Caller lưu EAX, ECX, EDX trước khi gọi hàm vì callee có thể thay đổi push eax push ecx push edx call SomeFunction ; có thể clobber EAX, ECX, EDX pop edx pop ecx pop eax
Là các thanh ghi mà hàm được gọi (callee) phải giữ nguyên giá trị ban đầu; nếu callee muốn sử dụng chúng, nó phải lưu (push) giá trị cũ lên stack và khôi phục (pop) trước khi trả về.
1 2 3 4 5 6 7 8 9
SomeFunction: push ebx push esi push edi ; ... function body sử dụng EBX, ESI, EDI ... pop edi pop esi pop ebx ret
Đỉnh stack +-----------------+ | Tham số a | <-- ESP (push a) +-----------------+ | Tham số b | <-- ESP+4 (push b) +-----------------+
Khi thực hiện call add: CPU tự động đẩy Return Address lên stack
1 2 3 4 5 6 7 8
Đỉnh stack +-----------------+ | Return Address | <-- ESP+0 +-----------------+ | Tham số a | <-- ESP+4 +-----------------+ | Tham số b | <-- ESP+8 +-----------------+
Bên trong hàm add (với frame prologue push ebp; mov ebp, esp):
1 2 3 4 5 6 7 8 9 10 11
+-----------------+ | Local variables | +-----------------+ | old EBP | <- [EBP] +-----------------+ | Return Address | <- [EBP+4] +-----------------+ | Tham số a | <- [EBP+8] +-----------------+ | Tham số b | <- [EBP+12] +-----------------+
; Windows x64: cấp phát 32 byte Shadow Space + 8 byte để căn chỉnh 16-byte boundary sub rsp, 40 ; 32 (Shadow Space) + 8 (padding) mov rcx, 1 ; Tham số 1 (RCX) mov rdx, 2 ; Tham số 2 (RDX) call SomeFunction ; Gọi hàm, callee có thể dùng Shadow Space add rsp, 40 ; Khôi phục stack
Giải thích:
Shadow Space (32 byte): Theo chuẩn Microsoft x64 ABI, caller phải cấp 32 byte trên stack trước khi call để callee sử dụng làm home space cho bốn tham số đầu truyền qua thanh ghi.
Padding 8 byte: Sau khi cấp Shadow Space, rsp cần chia hết cho 16 để đáp ứng yêu cầu căn chỉnh của ABI (đồng bộ với SSE và tối ưu CPU). Vì 32 byte Shadow Space không làm thay đổi modulo 16 của rsp (32 mod 16 = 0), nhưng lệnh call sẽ đẩy Return Address (8 byte) lên stack, làm lệch căn chỉnh. Thêm 8 byte padding trước lệnh call đảm bảo Return Address sẽ nằm ở địa chỉ chia hết cho 16.
ESP (Stack Pointer): luôn trỏ đến đỉnh stack dùng cho push/pop, thay đổi sau mỗi thao tác.
EBP (Base Pointer): làm mốc cố định để truy cập biến cục bộ và tham số, không đổi trong suốt hàm.
1 2 3 4 5 6 7 8
push ebp mov ebp, esp ; EBP = ESP sub esp, 0x10 ; cấp phát local mov dword ptr [ebp-4], 0 ; local var x mov dword ptr [ebp-8], 1 ; local var y mov esp, ebp ; khôi phục stack pop ebp ret