是否可以在包含对C函数的引用的lua字节码字符串上调用loadstring?

dav*_*gan 7 lua serialization binary-data love2d

我们正在使用Love2d Lua游戏引擎向Lua公开图形API.我们正在尝试序列化一个包含游戏世界所有保存游戏数据的巨型哈希表.这个哈希包括一些函数,其中一些函数调用Love2d C函数.

为了序列化散列中的函数,我们使用string.dump,并使用loadstring将它们加载回来.这适用于纯Lua函数,但是当我们尝试序列化然后加载回调用包装的C函数(如Love2d api中的函数)的函数时,loadstring返回nil.

考虑以下简单程序,通过Love2d的图形引擎将"hello,world"绘制到屏幕上:

function love.load()
    draw = function()
        love.graphics.print('hello, world', 10, 10)
    end
end
function love.draw()
    draw()
end
Run Code Online (Sandbox Code Playgroud)

我们希望能够做到这一点:

function love.load()
    draw_before_serialize = function()
        love.graphics.print('hello, world', 10, 10)
    end

    out = io.open("serialized.lua", "wb")
    out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])')
    out:close()

    require "serialized"
end
function love.draw()
    draw()
end
Run Code Online (Sandbox Code Playgroud)

这样做会写入磁盘上的Lua文件,其中包含混合的非编译Lua和Lua字节码,如下所示:

draw = load([[^[LJ^A^@      
       @main.lua2^@^@^B^@^B^@^D^E^B^B4^@^@^@%^A^A^@>^@^B^AG^@^A^@^Qhello, world 
       print^A^A^A^B^@^@]])
Run Code Online (Sandbox Code Playgroud)

此方法适用于不调用C模块的Lua函数.我们认为这是问题,因为这个例子确实有效:

function love.load()
    draw_before_serialize = function()
        print('hello, world')
    end

    out = io.open("serialized.lua", "wb")
    out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])')
    out:close()

    require "serialized"
end
function love.draw()
    draw()
end
Run Code Online (Sandbox Code Playgroud)

它不是调用Love2d图形方法,而是对控制台进行打印.

经过更多测试后,我们很困惑地发现这个例子确实有效:

function love.load()
    draw_before_serialize = function()
        love.graphics.print('hello, world', 10, 10)
    end

    draw = load(string.dump(draw_before_serialize))
end
function love.draw()
    draw()
end
Run Code Online (Sandbox Code Playgroud)

这里我们实际上没有将函数写出到磁盘,而只是转储它然后立即加载它.我们认为也许罪魁祸首不是用二进制写模式标志set("wb")写出数据,但由于我们在Linux上这个标志没有效果.

有任何想法吗?

ste*_*ett 6

我认为问题在于字符串的格式化.Nicol Bolas可能对围绕你的字节码转储的[[]]引号有所了解,但这指出了一个更大的问题; 字节代码实际上可以是任何东西,但是您将它视为可以写入文本文件并从文本文件读取的普通字符串.您的上一个演示演示了此问题,您可以在其中加载转储的字符串,而无需将其写入文件.

样的实施串行器的表,其中包括功能,你想要做什么,我想,但我也认为它坏(当然,我不能让它正确的工作,反正...).无论如何,它是在正确的轨道上.您需要格式化字节码,然后将其写入文件.

我确信有更好的方法,但这有效:

1.    binary = string.dump(some_function)
2.    formatted_binary = ""
3.    for i = 1, string.len(binary) do
4.        dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0')
5.        formatted_binary = formatted_binary .. dec
6.    end
Run Code Online (Sandbox Code Playgroud)

这循环遍历字节码中的每个字符,将它们格式化为转义字节(每个字符串都包含一个代码,如"\ 097",插值后将转义为"a").

这个样本的第4行有点密集所以我会把它分解.第一,

binary:sub(i, i)
Run Code Online (Sandbox Code Playgroud)

从字符串中拉出第i个字符.然后

binary:sub(i, i):byte()
Run Code Online (Sandbox Code Playgroud)

返回第i个字符的ascii整数表示.然后我们用它格式化它

("\\%3d"):format(binary:sub(i, i):byte())
Run Code Online (Sandbox Code Playgroud)

例如,如果角色是"a",它会给我们一个像"\ 97"这样的字符串.但这不能正常逃脱,因为我们需要"\ 097",所以我们用gsub替换""和"0".gsub返回结果字符串和已执行的替换次数,因此我们只取第一个返回值并将其放在"dec"中.我不确定为什么"%3d"格式默认不会替换"0"的空格...哦.

然后,为了执行格式化的二进制字符串,我们需要将其转义并将结果传递给"load".Lua中的怪物[[]]引号不会像""那样逃脱......实际上我不确定它们是否会完全逃脱.那么为了创建一个可执行的Lua字符串,它将返回一个将执行"some_function"中的任何操作的函数,我们这样做:

executable_string = 'load("' .. formatted_binary .. '")'
Run Code Online (Sandbox Code Playgroud)

好的 - 所以把所有这些放在一起,我想我们可以让你的测试用例如下:

  1 function love.load()
  2     draw_before_serialize = function()
  3         love.graphics.print('hello, world', 10, 10)
  4     end
  5 
  6     binary = string.dump(draw_before_serialize)
  7     formatted_binary = ""
  8     for i = 1, string.len(binary) do
  9         dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0')
 10         formatted_binary = formatted_binary .. dec
 11     end
 12     
 13     out = io.open("serialized.lua", "wb")
 14     out:write('draw = load("' .. formatted_binary .. '")')
 15     out:close()
 16     
 17     require "serialized"
 18 end 
 19 function love.draw()
 20     draw()
 21 end
Run Code Online (Sandbox Code Playgroud)

当我用Love运行时,我得到一个OpenGL屏幕,角落里印有"hello world".生成的文件"serialized.lua"包含以下内容:

draw = load("\027\076\074\001\000\009\064\109\097\105\110\046\108\117\097\084\000\000\004\000\004\000\008\009\002\002\052\000\000\000\055\000\001\000\055\000\002\000\037\001\003\000\039\002\010\000\039\003\010\000\062\000\004\001\071\000\001\000\017\104\101\108\108\111\044\032\119\111\114\108\100\010\112\114\105\110\116\013\103\114\097\112\104\105\099\115\009\108\111\118\101\001\001\001\001\001\001\001\002\000\000")
Run Code Online (Sandbox Code Playgroud)