Здравствуйте, гость ( Вход | Регистрация )

> Luajit неочевидные особенности.
DarkMaster
сообщение 8.6.2024, 9:28
Сообщение #1


***********

Модератор UOPilot
Сообщений: 9.735
Регистрация: 2.12.2008
Группа: Супермодераторы
Наличность: 29635
Пользователь №: 11.279



Тема будет оченя вялотекущая, тем не менее иногда сталкиваюсь с неожиданным поведением среды. Подобные заметки могут быть кому-то полезны, например, мне, склероз не дремлет.
Код
local kernel32 = ffi.load("kernel32")
local threadId = ffi.new("DWORD[1]")
    local threadHandle = kernel32.CreateThread(nil, 0, ffi.cast("THREAD_START_ROUTINE", async), ffi.cast("LPVOID", HintStruct), 0, threadId)

Вызов kernel32.CreateThread и C.CreateThread на самом деле не эквивалентен. В частности в этом случае вызов через C.CreateThread шансово приводит к крашу. Причины я не знаю, но факт на лицо. В качесте домыслов через C.CreateThread подтягивается функция принадлежащая самому lua, а не конкретной либе.



Zerobane отладка.
Код
while 1 do
    local a = false
    if  a then
        print("a == true")
    end
end

Очевидно, что цикл будет крутиться вечно, а условие if a then никогда не выполнится. Ставим brake, меняем через отладчик значение a = true. Условие if a then все равно не будет выполнятся. При том, что а действительно будет равна true. Дело в том, что jit создал bypass, т.к. посчитал, что a константа и никогда не будет изменена. Фактически он никогда и не проверял ее значение, а просто делал безусловный переход пропуская блок if end. В качестве решения можно использовать конструкцию:
Код
local f = function(new_var) end
do
    local var = false
    f = function(new_var)
        if new_var then
            var = new_var
        end
        return var
    end
end

while 1 do
    local a = f(false)
    if  a then
        print("a == true")
    end
end

Данная заглушка позволит выполнить отладку с минимальными вмешательствами в код.

Сообщение отредактировал DarkMaster - 8.6.2024, 9:29


--------------------
Скрипты UOPilot под заказ.
Консультации по UOpilot 15$/час.
Услуги Lua разработчика (не пилот, проекты, постоянка)
Disсоrd:
Kov____
Пользователь в офлайнеDelete PostОтправить личное сообщение
Вернуться в начало страницы
+Ответить с цитированием данного сообщения
 
Ответить в эту темуОткрыть новую тему
Ответов
DarkMaster
сообщение 25.6.2024, 1:46
Сообщение #2


***********

Модератор UOPilot
Сообщений: 9.735
Регистрация: 2.12.2008
Группа: Супермодераторы
Наличность: 29635
Пользователь №: 11.279



Запускаем функцию в отдельном потоке и lua state.
Код
lua_state, func_pointer, thread_id = func_to_new_thread_and_state(my_function, data_to_function)
lua_state - новый созданный lua_state
func_pointer - указатель на функцию внутри нового lua_state.
thread_id - id потока.
my_function - функция либо дамп функци (string.dump(func)) для запуска. Ваша функция не должна ссыласться на upvalue.
data_to_function - C указатель на данные которые будут переданы в функцию.

Необходимо четко понимать, что lua_state полностью новый экземпляр lua. Никакие ранее объявленные переменные, подгруженные модули через require или ffi.load, глобальные переменные и т.д. не будут доступны в новом экземпляре (state). Если что-то нужно передать, то сделать это можно только в виде передачи указателя на данные. Передавать можно соответственно только C данные. Передача в качестве данных для функции в виде типов lua либо функций lua в новый поток недопустимы - lua не является потоко-безопасным - вы получите краш. При завершении основного потока (скрипта), новый поток так же будет завершен автоматически.

func_to_new_thread_and_state.lua
Код
local ffi = require("ffi")
local C = ffi.C

ffi.cdef[[
    typedef struct lua_State lua_State;
    lua_State *luaL_newstate(void);
    void luaL_openlibs(lua_State *L);
    int luaL_loadbuffer(lua_State *L, const char *buff, size_t sz, const char *name);
    int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);
    const char *lua_tolstring(lua_State *L, int idx, size_t *len);
    unsigned long long int strtoull(const char* str, char** endptr, int base);

    typedef int BOOL;
    typedef void* HANDLE;
    typedef void* LPVOID;
    typedef unsigned int DWORD;
    typedef int (__stdcall *THREAD_START_ROUTINE)(LPVOID lpThreadParameter);
    HANDLE __stdcall CreateThread(
        void* lpThreadAttributes,
        size_t dwStackSize,
        THREAD_START_ROUTINE lpStartAddress,
        LPVOID lpParameter,
        DWORD dwCreationFlags,
        DWORD* lpThreadId
    );
    BOOL __stdcall CloseHandle(
        HANDLE hObject
    );
    void Sleep(unsigned int dwMilliseconds);
]]

local kernel32 = ffi.load("kernel32")


-- Входящие данные подразумеваются, как
-- 32 битный или 64 битный указатель,
-- т.е. вида:
-- 0x12345678 либо 0x123456789abcdef
-- префикс 0x не удалять!
-- Основная задача данного кода -
-- хранение полученных указателей.
-- Встроенный tonumber невозможно
-- корректно использовать, т.к.
-- он возвращает double и произойдет
-- потеря точности при получении x64.
-- Подключать msvcrt ради _strtoui64
-- имхо перебор, а пачка вызовов
-- tonumber существенно медленнее.
local char_arr_to_int64 = function(str)end
do
    ffi.cdef[[
        typedef unsigned int size_t;
        size_t strlen(const char *s);
    ]]
    local char_values = {
         [48] = 0x0,  -- Значение символа '0'
         [49] = 0x1,  -- Значение символа '1'
         [50] = 0x2,  -- Значение символа '2'
         [51] = 0x3,  -- Значение символа '3'
         [52] = 0x4,  -- Значение символа '4'
         [53] = 0x5,  -- Значение символа '5'
         [54] = 0x6,  -- Значение символа '6'
         [55] = 0x7,  -- Значение символа '7'
         [56] = 0x8,  -- Значение символа '8'
         [57] = 0x9,  -- Значение символа '9'
         [65] = 0xA,  -- Значение символа 'A'
         [66] = 0xB,  -- Значение символа 'B'
         [67] = 0xC,  -- Значение символа 'C'
         [68] = 0xD,  -- Значение символа 'D'
         [69] = 0xE,  -- Значение символа 'E'
         [70] = 0xF,  -- Значение символа 'F'
         [97] = 0xA,  -- Значение символа 'a'
         [98] = 0xB,  -- Значение символа 'b'
         [99] = 0xC,  -- Значение символа 'c'
        [100] = 0xD,  -- Значение символа 'd'
        [101] = 0xE,  -- Значение символа 'e'
        [102] = 0xF,  -- Значение символа 'f'
    }
    char_arr_to_int64 = function(str)
        local len = C.strlen(str)
        local char_array = ffi.new("unsigned char[8]")

        -- 18 символов (64 бита)
        if len == 18 then
            for i = 0, 7 do
                char_array[i] = char_values[str[16-i*2]]*0x10 +
                                char_values[str[17-i*2]]
            end
        else -- 32 бита
            for i = 0, 3 do
                char_array[i] = char_values[str[8-i*2]]*0x10 +
                                char_values[str[9-i*2]]
            end
        end

        -- Получаем 64-битное целое число из массива unsigned char
        local ptr_uint64_t = ffi.cast("unsigned long long*", char_array)

        return ptr_uint64_t[0]
    end
end

local func_to_new_thread_and_state = function(f, p_opt)end
do
    local init = string.dump(
        function(f)
            local ffi = require "ffi"
            ffi.cdef[[
                typedef void* LPVOID;
                typedef int (__stdcall *THREAD_START_ROUTINE)(LPVOID lpThreadParameter);
            ]]
            f = tostring(ffi.cast("THREAD_START_ROUTINE", f)):gsub("^.-0x", "0x", 1)
            return f
        end
    )

    func_to_new_thread_and_state = function(f, p_opt)
        local lua_state = C.luaL_newstate()
        C.luaL_openlibs(lua_state)

        -- Передача функции возможна только в виде дампа.
        if type(f) == "function" then
            f = string.dump(f)
        end

        C.luaL_loadbuffer(lua_state, init, #init, "=init")
        C.luaL_loadbuffer(lua_state, f, #f, "=f")
        C.lua_pcall(lua_state, 1, 1, 0)

        local raw = C.lua_tolstring(lua_state, -1, nil)
        local pllu = char_arr_to_int64(raw)

        f = ffi.cast("THREAD_START_ROUTINE", pllu)

        local threadId = ffi.new("DWORD[1]")
        local threadHandle = kernel32.CreateThread(nil, 0, f, ffi.cast("LPVOID", p_opt), 0, threadId)
        kernel32.CloseHandle(threadHandle)

        return lua_state, f, threadId[0]
    end
end

return func_to_new_thread_and_state

пример вызова
Код

local ffi = require("ffi")
local C = ffi.C
ffi.cdef[[
    void lua_close (lua_State *L);
]]

-- Подгружаем модуль.
local func_to_new_thread_and_state = require"func_to_new_thread_and_state"

-- Пример функции передаваемой в новый поток.
local my_function = function(data_pointer)
    -- Все нужные require должны
    -- быть явно вызваны заново.
    local ffi = require("ffi")
    local C = ffi.C

    -- cdef так же должны быть объявлены заново.
    -- Виндовый wait. Просто для примера.
    ffi.cdef[[
        void Sleep(unsigned int dwMilliseconds);
    ]]

    -- Приводим указатель к типу.
    -- Если передается указатель на структуру,
    -- то структура так же явно должна быть
    -- объявлена повторно в cdef.
    local data = ffi.cast("int*", data_pointer)

    local f = io.open("d:\\1.txt", "wb")
    local timeout = os.clock() + 1
    while timeout > os.clock() do
        C.Sleep(100)
        f:write(tostring(data[0]), "\r\n")
    end
    f:close()
    return 0
end

-- Объявляем C массив int на 1 элемент,
-- присваиваем значение элементу равное 123.
local data_to_function = ffi.new("int[1]", 123)

-- Пример вызова функции
local lua_state, func_pointer, thread_id = func_to_new_thread_and_state(my_function, data_to_function)

-- Верент lua_state, ссылку на функцию новом потоке,  id потока.
print(lua_state, func_pointer, thread_id)


-- Пример дальнейшего кода который
-- будет выполняться в основном потоке
-- без ожидания завершения выполнения
-- переданной функции.
local timeout = os.clock() + 2
while timeout > os.clock() do
    C.Sleep(100)
    print"1"
end

-- Завершаем существование lua_state,
-- если он нам больше не нужен.
C.lua_close(lua_state)



Сообщение отредактировал DarkMaster - 25.6.2024, 4:32


--------------------
Скрипты UOPilot под заказ.
Консультации по UOpilot 15$/час.
Услуги Lua разработчика (не пилот, проекты, постоянка)
Disсоrd:
Kov____
Пользователь в офлайнеDelete PostОтправить личное сообщение
Вернуться в начало страницы
+Ответить с цитированием данного сообщения



Ответить в эту темуОткрыть новую тему
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 

- Текстовая версия | Версия для КПК Сейчас: 27.6.2025, 14:47
Designed by Nickostyle