首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Lua 腳本死循環偵測與強制中止機制實作

     編輯:關於C語言

Lua 腳本死循環偵測與強制中止機制實作

嵌入 Lua 腳本的宿主程式面臨一大風險:使用者編寫的腳本可能包含死循環,導致整個應用程式凍結。由於停機問題不可判定,無法靜態分析所有死循環。工業界通用方案是執行時間監控 + 超時強制中止。本文實作一個跨執行緒的 Lua 腳本超時保護機制。

核心架構設計

  • 主執行緒:執行使用者腳本,透過 lua_pcall 捕獲錯誤
  • 監控執行緒:定期檢查腳本執行時間,超時則注入 debug hook 中止腳本
  • 同步機制:使用原子計數器區分「執行中」與「閒置」狀態

關鍵資料結構

lua_State* g_lua = nullptr;           // Lua 虛擬機狀態
volatile unsigned g_begin = 0;         // 腳本開始執行時間 (ms)
volatile long g_counter = 0;           // 執行計數器:奇數=執行中,偶數=閒置
volatile long g_check = 0;             // 監控執行緒採樣時的計數器快照

核心實作

1. 安全執行腳本入口

int run_user_script(int nargs, int nresults, int errfunc) {
    g_begin = GetTickCount();              // 記錄開始時間
    _InterlockedIncrement(&g_counter);     // 計數器變奇數 → 標記執行中
    
    int err = lua_pcall(g_lua, nargs, nresults, errfunc);
    
    _InterlockedIncrement(&g_counter);     // 計數器變偶數 → 標記結束
    return err;                            // 正常返回或捕獲 luaL_error
}

關鍵點:必須使用 lua_pcall 而非 lua_call,才能捕獲 luaL_error 拋出的異常。

2. 監控執行緒:週期性超時檢查

void check_script_timeout() {
    g_check = g_counter;                   // 快照當前計數器
    
    // 若非執行中 (偶數),直接返回
    if ((g_check & 0x1) == 0) return;
    
    // 超過 1000ms 判定超時
    if (GetTickCount() - g_begin > 1000) {
        int mask = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
        lua_sethook(g_lua, timeout_hook, mask, 1);
    }
}

3. Debug Hook:強制中止腳本

void timeout_hook(lua_State* L, lua_Debug* ar) {
    lua_sethook(L, nullptr, 0, 0);         // 移除 hook,避免重複觸發
    
    // 雙重檢查:確認仍在執行同一個腳本
    if (g_check == g_counter) {
        luaL_error(L, "script timeout");   // 拋出 Lua 錯誤,被 pcall 捕獲
    }
}

關鍵設計細節解析

為什麼用計數器奇偶性而非布林旗標?

避免「檢查旗標 → 旗標變更 → 執行動作」的競態條件。原子增量操作保證:

  • 主執行緒:even → odd → even
  • 監控執行緒採樣 g_check 時,若為奇數則確定在執行中
  • Hook 執行時再次比對 g_check == g_counter,排除 hook 注入延遲導致的誤判

為什麼 Hook 要設定所有遮罩?

LUA_MASKCOUNT 配合 count=1 確保每條 VM 指令都會觸發 hook,最快偵測到超時並中止。若僅用 LUA_MASKLINE,死循環若無換行則永不觸發。

單執行緒替代方案(效能較差)

若不想多執行緒,可在主迴圈中定期呼叫 check_script_timeout(),但 hook 會頻繁觸發影響效能。適用於低效能需求場景。

完整編譯提醒

  • 需連結 Lua 庫與平台原子操作庫 (Windows: kernel32.lib,Linux: -lpthread)
  • GetTickCount() 為 Windows API,跨平台請改用 std::chrono::steady_clock
  • Lua 5.1+ 相容,5.4 建議改用 lua_sethook 新版 API

總結

此機制以 ~1ms 精度 偵測死循環,零侵入式 不需修改使用者腳本,透過 lua_pcall + lua_sethook + 原子計數器三重保障,實現生產級的 Lua 腳本沙箱超時保護。實務部署建議:超時閾值可配置、記錄超時堆疊資訊供事後分析、考慮記憶體/CPU 配額限制作為纵深防禦。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved