🌱 Stack Overflow là gì?
Khi lập trình với các ứng dụng nhúng với bộ nhớ nhỏ, chúng ta thường gặp 1 số sự cố không đáng có và có thể gây lỗi chương trình (chương trình chạy sai hoặc "chết"). Một trong số những sự cố có nguyên nhân do bộ nhớ hạn hẹp của Vi điều khiển, điển hình là Heap và Stack, 2 sự cố ở mình muốn nói đến ở đây là Heap Overflow và Stack Overflow. Ở bài viết này mình sẽ giới thiệu về Stack Overflow.
👉 Bộ nhớ Stack là gì?
Stack là vùng nhớ dành cho các biến local, địa chỉ của bộ đếm chương trình. Trong ứng dụng nhúng với tài nguyên giới hạn, phần mềm nếu thiết kế không tốt có thể dẫn đến tràn stack , từ đó gây ra những lỗi với hiện tượng khó xác đinh lúc runtime (như đã nói ở trên là chương trình chạy sai hoặc bị "chết").
➤ Để tránh việc này, việc nên làm khi lập trình là không khai báo một mảng local quá lớn và không dùng đệ quy trong các ứng dụng nhúng. Tuy nhiên, trong một hệ thống phần mềm phức tạp viết bởi nhiều người và không thể kiểm soát được mã nguồn viết bởi người khác, nhu cầu kiểm tra hệ thống phần mềm hiện tại đã chiếm bao nhiêu tài nguyên Stack là điều cần thiết.
- Ví dụ một dự án porting một framework sang 1 board nhúng mà chúng ta không chắc chắn được chúng có đủ tài nguyên để chạy được hay không thì việc kiểm tra Stack chính là kiểm tra tính khả thi của dự án
- Khi gặp bug hiện tượng lạ lúc run-time, điều đầu tiên nghĩ đến là stack đã bị tràn dẫn đến những undefined behavior và lúc đó cũng cần check lại Stack đang có.
👉 Nguyên nhân chính gây ra tràn Stack
👉 Kiểm tra Stack hiện đang được dùng
Có nhiều cách để kiểm tra Stack hiện đang sử dụng:
- Sử dụng các chương trình tính toán Stack ví dụ như call_graph, thường được tích hợp vào IDE hoặc chạy standlone. Chúng sẽ dựa vào việc cách các hàm gọi nhau để tính ra điểm sâu nhất của stack, hàm nào là điểm sâu nhất ..... Tuy nhiên cách này sẽ không tính toán được nếu lời gọi hàm của chúng ta thông qua các con trỏ hàm. Một số IDE hỗ trợ các #pragma calls để chúng ta chỉ thị cho chương trình tính stack biết là các hàm nào sẽ được gọi. Tuy nhiên, nếu các ngắt gọi thì vẫn không tính toán được.
- Fill bộ nhớ với 1 pattern cố định ví dụ như 0xDEADBEEF và sau đó kiểm tra từ đỉnh stack xuống, giá trị đầu tiên khác với pattern cố định là 0xDEADBEEF trên thì đó nhiều khả năng là điểm sâu nhất của stack mà chương trình hiện giờ đạt tới. (chú ý: không chắc chắn đó là điểm sâu nhất của stack ví dụ như khi khai báo một mảng rất lớn mà không khởi tạo hay thay đổi giá trị của mảng đó thì dù kích thước mảng có thể vượt quá stack nhưng các pattern cố định lưu ở vùng nhớ đó vẫn không thay đổi).
- Để bảo vệ tràn Stack, các Vi điều khiển có ngoại vi MPU (Memory Protection Unit) thường cấu hình một vùng biên bảo vệ trên đỉnh stack là vùng nhớ read-only. Khi chương trình cố gắng ghi vào vùng đó sẽ có Exception và chúng ta phát hiện được thời điểm Stack đã bị tràn để có phương pháp giải quyết hiệu quả.
Nhìn chung có khá nhiều cách từ đơn giản đến phức tạp để ước lượng stack cần sử dụng trong ứng dụng. Và chúng ta nên tìm cách phù hợp để áp dụng vào ứng dụng của mình.
Chúc các bạn học tập tốt 😊