Cách Xây Dựng Một Công Cụ Gỡ Lỗi Lua Một Cách Tinh Tế - nói dối e blog

Cách Xây Dựng Một Công Cụ Gỡ Lỗi Lua Một Cách Tinh Tế

Gần đây tôi có dịp hỗ trợ nhóm phát triển một dự án của công ty trong việc rà soát mã nguồn phía client. Dù các dự án của chúng tôi sử dụng engine đồ họa khác nhau như ejoy2d hay Unity3d, ngôn ngữ lập trình chính trong quá trình phát triển đều là Lua. Điều này đồng nghĩa với việc các lập trình viên hàng ngày đều phải làm việc trực tiếp với Lua.

Cá nhân tôi luôn có chút ác cảm với phương pháp phát triển quanh quẩn trong vòng lặp thử nghiệm - sửa lỗi - kiểm thử lại. Theo quan điểm của tôi, một chương trình tốt nên được kiểm tra logic ngay trong quá trình viết code, thông qua việc nhận diện các “mùi code” (code smell) tiềm ẩn - những dấu hiệu cho thấy độ phức tạp đang vượt ngưỡng kiểm soát. Khi gặp phải tình huống này, giải pháp tối ưu nhất là tái cấu trúc code thay vì dựa dẫm vào công cụ debug.

Khi gặp lỗi thực tế, tôi cho rằng lập trình viên nên dành thời gian phân tích kỹ lưỡng mã nguồn, hình dung các kịch bản lỗi có thể xảy ra, thay vì vội vàng chạy thử code để quan sát trạng thái hệ thống. Tuy nhiên, khi chứng kiến các bạn trong nhóm phải liên tục khởi động client Unity chỉ để chờ vài dòng log debug, tôi nhận ra rằng một công cụ gỡ lỗi phù hợp có thể cải thiện đáng kể hiệu suất làm việc.

Lua cung cấp một hệ thống API debug rất toàn diện, tuy nhiên lại thiếu vắng một công cụ debug chuẩn. Đây không phải lần đầu tiên tôi xây dựng công cụ debug cho Lua - blog của tôi đã ghi lại nhiều phiên bản khác nhau qua các năm, phiên bản gần nhất cách đây 3 năm. Điều đặc biệt là các phiên bản trước thường chỉ được sử dụng vài ngày rồi bị bỏ xó. Lần này, tôi quyết định thử nghiệm một hướng tiếp cận hoàn toàn mới.

Phiên bản hiện tại mới chỉ ở giai đoạn đầu, tập trung xây dựng kiến trúc nền tảng. Tôi quan niệm một công cụ debug tinh tế không nên can thiệp sâu vào hệ thống đang được debug, đặc biệt là khi cần theo dõi việc sử dụng bộ nhớ hay hoạt động của garbage collector. Các phiên bản trước đây thường tích hợp trực tiếp code debug vào máy ảo (VM) đang chạy ứng dụng, khiến ranh giới giữa công cụ debug và ứng dụng gốc trở nên mờ nhạt.

Lần này, tôi muốn tách biệt hoàn toàn: công cụ debug sẽ chạy trên một VM độc lập, giao tiếp với ứng dụng thông qua một giao diện API. Cách tiếp cận này mở ra nhiều khả năng thú vị như xây dựng giao diện đồ họa, tích hợp web server để debug qua trình duyệt, hay đơn giản là xuất log theo dõi trạng thái mà không cần sửa đổi code gốc. Thay vì thêm các lệnh print vào code ứng dụng, chúng ta có thể viết module debug riêng biệt, sử dụng các hàm theo dõi trạng thái một cách lập trình hóa.

Cốt lõi của thiết kế mới này là khái niệm “điểm quan sát” (probe point). Khác với breakpoint truyền thống, điểm quan sát không dừng chương trình mà sẽ kích hoạt một hàm xử lý trong công cụ debug (chạy trên VM độc lập). Tại mỗi điểm quan sát, chúng ta có thể:

  • Ghi log trạng thái hệ thống vào file
  • Thực hiện hành động dựa trên điều kiện trạng thái
  • Chuyển sang chế độ debug tương tác khi cần

Có hai cách thiết lập điểm quan sát:

  1. Hardcoded: Nhúng trực tiếp vào code ứng dụng
  2. Dynamic: Sử dụng Lua Hook để thêm vào lúc runtime

Ví dụ điển hình là tích hợp điểm quan sát vào vòng lặp chính của game. Khi không cần debug, hệ thống sẽ bỏ qua các điểm này. Khi cần thiết, chúng ta kích hoạt điểm quan sát để thiết lập các hook debug sâu hơn.

Mã nguồn ban đầu đã được đăng tải lên GitHub, sẽ tiếp tục được hoàn thiện trong thời gian tới. Việc kỳ vọng một công cụ debug đồ họa đẹp mắt trong thời gian ngắn là không thực tế, trừ khi có sự tham gia của các lập trình front-end (ví dụ: xây dựng web server để debug qua trình duyệt).

Một điểm thú vị trong thiết kế là cơ chế truy cập dữ liệu giữa hai VM độc lập. Khi công cụ debug và ứng dụng chạy trên hai VM khác nhau, làm thế nào để truy xuất các bảng (table) trong VM ứng dụng?

Giải pháp tôi thiết kế là tạo một cấu trúc C (được đóng gói dưới dạng userdata), lưu trữ đường dẫn tham chiếu đến đối tượng Lua mà không giữ reference trực tiếp. Khi cần truy xuất một biến tại điểm quan sát, hệ thống sẽ ghi lại toàn bộ quá trình truy cập:

  • Lấy biến local từ stack frame
  • Truy xuất upvalue
  • Tìm kiếm trong bảng toàn cục
  • Duyệt các trường con của table

Dữ liệu này được đóng gói thành một userdata, cho phép công cụ debug tái tạo chính xác trạng thái của đối tượng cần quan sát mà không gây ảnh hưởng đến hệ thống chính.

0%