Làm Thế Nào Để Chèn Và Thực Thi Đoạn Mã Lua Trong Lúc Chạy Chương Trình
Gần đây mình muốn thêm một bộ gỡ lỗi trực tuyến vào dự án skynet nhằm hỗ trợ debug các dịch vụ viết bằng Lua.
Lua không cung cấp sẵn bộ debug trực tiếp, nhưng lại có một hệ thống API debug đầy đủ chức năng. Thông thường, ta có thể chèn lệnh debug.debug() vào mã nguồn để truy cập môi trường tương tác, nơi cho phép nhập bất kỳ lệnh Lua nào. Tất nhiên, bạn cũng có thể kích hoạt nó thông qua debug hook.
Tuy nhiên phương pháp tương tác này tồn tại một hạn chế: Lua sử dụng hàm load để biên dịch chuỗi đầu vào thành hàm Lua rồi thực thi. Điều này khiến đoạn mã nhập vào không thể truy cập trực tiếp các biến cục bộ và upvalue trong ngữ cảnh hiện hành.
Muốn đọc/ghi các biến cục bộ hoặc upvalue, ta phải dùng các hàm như debug.getlocal, điều này khá bất tiện.
Liệu có cách nào tạo một phiên bản dostring nâng cao, cho phép đoạn mã thực thi có cùng ngữ cảnh với nơi gọi nó không?
Có thể thực hiện được, nhưng đòi hỏi một số thủ thuật đặc biệt.
Thay vì tải trực tiếp đoạn mã cần chạy, ta sẽ xây dựng một môi trường giả lập tương ứng. Ví dụ nếu có hai biến cục bộ a và b, ta thêm dòng local a,b vào đầu chuỗi mã. Sau đó trước khi thực thi, gán giá trị thực tế vào các biến này.
Vì đã biết những biến nào được tiêm vào, sau khi chạy xong ta có thể đọc lại giá trị và cập nhật lại vào môi trường hiện tại.
Đối với upvalue, việc xử lý đơn giản hơn nhiều.
Từ phiên bản Lua 5.2 trở đi, debug.upvaluejoin cho phép liên kết upvalue hiện hành với hàm cần chạy, không cần cập nhật thủ công sau đó.
Riêng các tham số biến đổi dạng … cần xử lý kỹ càng hơn. Bạn phải đọc từng giá trị rồi truyền cho hàm tiêm vào một cách chính xác.
Nghe thì tưởng đơn giản, nhưng khi triển khai sẽ khá phức tạp. Mình đã xây dựng một phiên bản tham khảo, bạn có thể dùng hàm run dưới đây để thực thi chuỗi mã với ngữ cảnh hoàn toàn giống nơi gọi nó - như thể mã được chèn trực tiếp vào vị trí đó vậy. Lưu ý: Nếu ngữ cảnh hiện tại không tham chiếu đến upvalue nào đó, dù có thể nhìn thấy ở nơi khác, đoạn mã tiêm vào cũng không thể truy cập được. Trong trường hợp này, bạn cần truyền đúng level để chuyển đến tầng ngữ cảnh phù hợp.
Với hàm này, ta có thể sử dụng như sau:
|
|
Kết quả chạy sẽ như sau:
|
|
Bạn thấy đấy, đoạn mã được chèn vào hàm f có thể truy cập các biến cục bộ a,b và upvalue uv mà hàm f tham chiếu, đồng thời thay đổi giá trị của chúng. Các tham số biến đổi … cũng được truy cập chính xác thông qua toán tử …
Mình đã đăng mã triển khai hàm run trên gist.
Nếu dùng run trong debug hook, cần truyền level thích hợp (thường là 1). Phiên bản này không tối ưu hiệu năng (muốn hiệu năng cao nên viết lại bằng C), khuyến cáo dùng cho mục đích gỡ lỗi tương tác chứ không áp dụng trong môi trường sản phẩm.