Вызов lua калбека через sol внутри обработчика хука без обработки C++ исключений

Tema05

Известный
Автор темы
1,531
501
Вот упрощённый вариант частей моей dll-ки. Я ставлю хук на функцию игры и из lua устанавливаю калбек, который будет вызываться из оригинального C++ калбека. Всё отлично работает, но есть проблема с обработкой ошибок.
C++:
// установка lua калбека для вызова из хука
void set_callback(sol::this_state ts, sol::unsafe_function func)
{
    gHandler = func;
}

// вызывается при подключении библиотеки из lua через require
sol::table open(sol::this_state ts)
{
    sol::state_view lua(ts);
    installHook();
    module.set_function("set_callback", &set_cb);
    return module;
}

extern "C" __declspec(dllexport) int luaopen_lib(lua_State* L) {
    return (sol::c_call<decltype(&open), &open>)(L);
}



sol::unsafe_function gHandler; // тут будет хранится функция lua калбека
kthook::kthook_simple<bool(__thiscall*)(void*)> mHookedFunc; // объект хука

// функция установки C++ хука
void installHook() {
    mHookedFunc.set_dest(0x123456); // адрес функции в игре на которую нужно поставить хук
    mHookedFunc.set_cb(handleHookedFunc); // калбек, который будет срабатывать при вызове функции
    mHookedFunc.install() // устанавливает хук
}

// калбек захуканной функции
bool handleHookedFunc(const kthook::kthook_simple<bool(__thiscall*)(void*)>& hook, void* ptr)
{
    gHandler(); // вызов lua калбека. Если он пройдёт с ошибкой управление текущему потоку не вернётся и код ниже не выполнится
    return hook.call_trampoline(ptr); // трамплин на оригинальную функцию
}
Lua:
local lib = require 'lib'
lib.set_callback(function()
    error("LUA ERROR") -- условно какая-то ошибка.
end)
Если внутри lua калбека происходит какая-то ошибка (у меня искуственно при помощи error("CRASH")) то lua скрипт закономерно должен крашнуться. Где-то внутри вызова gHandler() выполнится lua_error (через sol) и ошибка дойдёт до lua. Но, проблема в том что в этом случаи управление потоку не будет возвращено и весь код после вызова неудачно выполненной lua функции gHandler() не отработает. Соответственно в представленном варианте return hook.call_trampoline(ptr); не выполнится, а это нарушает хука и игра разумеется умирает.

Я могу заменить sol::unsafe_function на sol::protected_function, тогда sol обернёт вызов lua функции в pcall, который отловит ошибку в lua калбеке и трамплин выполнится. Но, в этом случаи lua скрипт не крашнется. Ошибка была отловлена и "проглочена" при помощи pcall, в C++ мы её дальше никак не обрабатываем. А это мне не подходит, я хочу получать краш lua скрипта при ошибках в lua калбеке.

Если добавить проброс пойманной ошибки через throw это ни к чему хорошему не приведёт. Оригинальный C++ калбек хука handleHookedFunc выполняется непосредственно в том же потоке что и захуканная функция, т.е. в потоке игры, а это значит что исключение пробрасывается внутрь игры, это если не брать в расчёт что трамплин, который идёт позже также не вызывается. Игра также умирает.
C++:
bool handleHookedFunc(const kthook::kthook_simple<bool(__thiscall*)(void*)>& hook, void* ptr)
{

    sol::protected_function_result result = gHandler(); // вызов lua калбека. Если он пройдёт с ошибкой управление текущему потоку не вернётся и код ниже не выполнится
    if (!result.valid())
    {
        sol::error err = result;
        throw err;
    }
    return hook.call_trampoline(ptr); // трамплин на оригинальную функцию
}

Теперь смотрим на moonloader. Там через addEventHandler можно установить калбека на всякие события, например "onReceivePacket" (событие отправки пакета данный на сервер). Эти события по сути также хуки каких-то функций/методов в игре. И внутри этих lua калбеков ошибки обрабатываются так как я хочу. Они крашат lua скрипт, при этом это никак не мешает работе игры, несмотря на то что вызов lua калбека идёт из хука.
Lua:
addEventHandler('onReceivePacket', function(id, bs)
    error('LUA ERROR')
end)

Как мне реализовать такую же обработку ошибок как в moonloader? Чтобы ошибки внутри lua калбека крашили скрипт, а не игнорировались, но не ломали хук из которого они вызываются?
 

вайега52

Eblang головного мозга
Модератор
2,891
2,868
Вот упрощённый вариант частей моей dll-ки. Я ставлю хук на функцию игры и из lua устанавливаю калбек, который будет вызываться из оригинального C++ калбека. Всё отлично работает, но есть проблема с обработкой ошибок.
C++:
// установка lua калбека для вызова из хука
void set_callback(sol::this_state ts, sol::unsafe_function func)
{
    gHandler = func;
}

// вызывается при подключении библиотеки из lua через require
sol::table open(sol::this_state ts)
{
    sol::state_view lua(ts);
    installHook();
    module.set_function("set_callback", &set_cb);
    return module;
}

extern "C" __declspec(dllexport) int luaopen_lib(lua_State* L) {
    return (sol::c_call<decltype(&open), &open>)(L);
}



sol::unsafe_function gHandler; // тут будет хранится функция lua калбека
kthook::kthook_simple<bool(__thiscall*)(void*)> mHookedFunc; // объект хука

// функция установки C++ хука
void installHook() {
    mHookedFunc.set_dest(0x123456); // адрес функции в игре на которую нужно поставить хук
    mHookedFunc.set_cb(handleHookedFunc); // калбек, который будет срабатывать при вызове функции
    mHookedFunc.install() // устанавливает хук
}

// калбек захуканной функции
bool handleHookedFunc(const kthook::kthook_simple<bool(__thiscall*)(void*)>& hook, void* ptr)
{
    gHandler(); // вызов lua калбека. Если он пройдёт с ошибкой управление текущему потоку не вернётся и код ниже не выполнится
    return hook.call_trampoline(ptr); // трамплин на оригинальную функцию
}
Lua:
local lib = require 'lib'
lib.set_callback(function()
    error("LUA ERROR") -- условно какая-то ошибка.
end)
Если внутри lua калбека происходит какая-то ошибка (у меня искуственно при помощи error("CRASH")) то lua скрипт закономерно должен крашнуться. Где-то внутри вызова gHandler() выполнится lua_error (через sol) и ошибка дойдёт до lua. Но, проблема в том что в этом случаи управление потоку не будет возвращено и весь код после вызова неудачно выполненной lua функции gHandler() не отработает. Соответственно в представленном варианте return hook.call_trampoline(ptr); не выполнится, а это нарушает хука и игра разумеется умирает.

Я могу заменить sol::unsafe_function на sol::protected_function, тогда sol обернёт вызов lua функции в pcall, который отловит ошибку в lua калбеке и трамплин выполнится. Но, в этом случаи lua скрипт не крашнется. Ошибка была отловлена и "проглочена" при помощи pcall, в C++ мы её дальше никак не обрабатываем. А это мне не подходит, я хочу получать краш lua скрипта при ошибках в lua калбеке.

Если добавить проброс пойманной ошибки через throw это ни к чему хорошему не приведёт. Оригинальный C++ калбек хука handleHookedFunc выполняется непосредственно в том же потоке что и захуканная функция, т.е. в потоке игры, а это значит что исключение пробрасывается внутрь игры, это если не брать в расчёт что трамплин, который идёт позже также не вызывается. Игра также умирает.
C++:
bool handleHookedFunc(const kthook::kthook_simple<bool(__thiscall*)(void*)>& hook, void* ptr)
{

    sol::protected_function_result result = gHandler(); // вызов lua калбека. Если он пройдёт с ошибкой управление текущему потоку не вернётся и код ниже не выполнится
    if (!result.valid())
    {
        sol::error err = result;
        throw err;
    }
    return hook.call_trampoline(ptr); // трамплин на оригинальную функцию
}

Теперь смотрим на moonloader. Там через addEventHandler можно установить калбека на всякие события, например "onReceivePacket" (событие отправки пакета данный на сервер). Эти события по сути также хуки каких-то функций/методов в игре. И внутри этих lua калбеков ошибки обрабатываются так как я хочу. Они крашат lua скрипт, при этом это никак не мешает работе игры, несмотря на то что вызов lua калбека идёт из хука.
Lua:
addEventHandler('onReceivePacket', function(id, bs)
    error('LUA ERROR')
end)

Как мне реализовать такую же обработку ошибок как в moonloader? Чтобы ошибки внутри lua калбека крашили скрипт, а не игнорировались, но не ломали хук из которого они вызываются?
Я подозреваю, что обертка sol'а над луа функциями в случае ошибки кидает исключение, которое можешь попробовать поймать внутри C++ каллбека на хук
 

Tema05

Известный
Автор темы
1,531
501
Я подозреваю, что обертка sol'а над луа функциями в случае ошибки кидает исключение, которое можешь попробовать поймать внутри C++ каллбека на хук
Оно не кидает исключение. При ошибке с sol::unsafe_function там идёт longjmp из-за lua_error и управление уйдёт, даже если в try catch завернуть, а при sol::protected_function исключение не передаётся, только статус и текст ошибки если есть.

кстати, наверное тебе лучше использовать std::function, т.к. он более безопасный, но при этом, насколько помню, не оборачивает в pcall
std::function (видимо ты имел ввиду sol::function) это и есть sol::unsafe_function или sol::protected_function в зависимости от наличия #define SOL_NO_EXCEPTIONS 1
1749827502523.png


Я когда дебаггером пытался разобраться как оно в moonloader работает заметил, что иногда скрипт может крашнуться одновременно из-за нескольких ошибок. Что по идеи невозможно из-за однопоточного устройства lua или происходит из-за гонки потоков. Причём это не одно и то же событие и разные. Значит moonloader как-то в отдельных потоках или корутиных выполняет все эти события. Это скорее всего и позволяет обрабатывать ошибки на месте без потери управления потоком
1749829034866.png
 
Последнее редактирование:
  • Вау
Реакции: вайега52