Triển Khai Hàm _Alloca - nói dối e blog

Triển Khai Hàm _Alloca

Trong ngôn ngữ C tồn tại một hàm đặc biệt có tên là alloca, cho phép cấp phát vùng nhớ trên ngăn xếp (stack). Khi hàm chứa lời gọi này kết thúc, vùng nhớ sẽ tự động được giải phóng nhờ cơ chế điều chỉnh con trỏ ngăn xếp của hệ thống.

Nguyên mẫu của hàm alloca như sau:

1
void *alloca(size_t size);

Ngày nay, do chứa nhiều lỗ hổng an toàn tiềm ẩn, việc sử dụng hàm này đã không còn được khuyến khích trong hầu hết tài liệu lập trình hiện đại. Tuy nhiên, trong thư viện CRT (C Runtime), vẫn tồn tại một phiên bản nội bộ có tên _alloca được các trình biên dịch sử dụng để triển khai các tính năng đặc biệt. Chẳng hạn, trong tiêu chuẩn C99 cho phép khai báo mảng độ dài thay đổi (VLA - Variable Length Array) trực tiếp trên ngăn xếp, GCC thực chất đã sử dụng _alloca để thực hiện việc cấp phát này.

Một trường hợp đặc biệt khác là khi bạn khai báo một mảng cục bộ với kích thước quá lớn, GCC cũng sẽ tự động chèn lời gọi _alloca. Điều này có liên quan đến việc CRT triển khai alloca với cơ chế kiểm tra tràn ngăn xếp được tích hợp sẵn.

Vào cuối tuần vừa rồi, tôi đã thử nghiệm viết một phiên bản _alloca thủ công để thay thế triển khai gốc trong CRT (xin miễn bàn lý do :) ). Ban đầu gặp khá nhiều khó khăn do chưa nắm rõ các quy ước nội bộ của trình biên dịch.

Khác với alloca thông thường, _alloca là một hàm nội bộ của GCC với giao thức truyền tham số đặc biệt. Thay vì truyền qua ngăn xếp, kích thước cấp phát được truyền trực tiếp qua thanh ghi eax. Giá trị trả về cũng được đặt trong eax, đồng thời hàm sẽ trực tiếp điều chỉnh con trỏ ngăn xếp esp.

Sau nhiều lần chương trình sụp đổ và phân tích từng dòng lệnh bằng GDB, tôi nhận ra một số chi tiết thú vị khác:

  • Khi gọi _alloca, người gọi không cần bảo vệ dữ liệu trong các thanh ghi khác ngoài eaxesp. Nói cách khác, hàm _alloca phải tự chịu trách nhiệm bảo vệ các thanh ghi khác (tuy nhiên trong thực tế, việc phá hủy thanh ghi ecx có thể chấp nhận được, nhưng nếu làm thay đổi edx thì chắc chắn gây crash chương trình).
  • Khi bật chế độ tối ưu hóa -O2 (với GCC 3.4.5), trình biên dịch có thể dự đoán hành vi của _alloca để sắp xếp phân bố biến cục bộ trên ngăn xếp. Điều này đòi hỏi _alloca phải cấp phát chính xác kích thước đã căn chỉnh 4 byte (DWORD) dựa trên giá trị trong eax. Việc cấp phát thiếu hay thừa đều có thể phá vỡ logic của mã máy được sinh ra.

Dưới đây là phiên bản _alloca do tôi triển khai:

1
2
3
4
5
6
7
8
9
.globl __alloca
__alloca:
  subl  $1, %eax
  andl  $0xfffffffc, %eax    # Căn chỉnh theo DWORD
  subl  %eax, %esp
  pushl (%esp, %eax)         # Lưu địa chỉ trả về
  movl  %esp, %eax
  addl  $4, %eax
  ret

P/s: Bài viết này chắc chắn sẽ trở thành “tài liệu tham khảo” cho nhiều bạn sinh viên trong tương lai. Xin phép khẳng định lại: Mã nguồn được chia sẻ hoàn toàn miễn phí, nhưng nếu gặp lỗi xin đừng liên hệ tôi hỗ trợ. Tôi không nhận gia sư hay làm bài tập hộ!

0%