如何用LuaJIT定义C函数?

Epi*_*oM2 5 c lua luajit

这个:

local ffi = require "ffi"

ffi.cdef[[
  int return_one_two_four(){
    return 124;
  }
]]

local function print124()
  print(ffi.C.return_one_two_four())
end

print124()
Run Code Online (Sandbox Code Playgroud)

抛出错误:

Error: main.lua:10: cannot resolve symbol 'return_one_two_four': The specified procedure could not be found.
Run Code Online (Sandbox Code Playgroud)

我对C有一种温和的把握,并希望在一些事情上使用它的一些优点,但我在LuaJIT的FFI库中找不到很多例子.它似乎cdef只用于函数声明而不是定义.如何在C中创建函数然后在Lua中使用它们?

Hen*_*nke 11

LuaJIT是一个Lua编译器,但不是C编译器.您必须首先将C代码编译到共享库中.例如用

gcc -shared -fPIC -o libtest.so test.c
luajit test.lua
Run Code Online (Sandbox Code Playgroud)

与文件test.ctest.lua如下.

test.c

int return_one_two_four(){
    return 124;
}
Run Code Online (Sandbox Code Playgroud)

test.lua

local ffi = require"ffi"

local ltest = ffi.load"./libtest.so"

ffi.cdef[[
int return_one_two_four();
]]

local function print124()
    print(ltest.return_one_two_four())
end

print124()
Run Code Online (Sandbox Code Playgroud)

Wandbox上的实例

LuaJIT内的JIT

在该问题的评论中,有人提到了在机器代码中编写函数并在Windows上的LuaJIT中执行它们的解决方法.实际上,通过在LuaJIT中实际实现JIT,在Linux中也是如此.在Windows上,您只需将操作码插入字符串,将其转换为函数指针并调用它,由于页面限制,在Linux上也是如此.在Linux上,内存可写或可执行,但不能同时进行,因此我们必须以读写模式分配页面,插入程序集然后将模式更改为读 - 执行.为此,只需使用Linux内核函数来获取页面大小和映射内存.但是,如果你犯了最小的错误,就像其中一个操作码中的拼写错误一样,该程序将会出现段错误.我正在使用64位汇编,因为我使用的是64位操作系统.

重要提示:在您的机器上执行此操作之前,请检查中的幻数<bits/mman-linux.h>.它们在每个系统上都不一样.

local ffi = require"ffi"

ffi.cdef[[
typedef unsigned char uint8_t;
typedef long int off_t;

// from <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t length);
int mprotect(void *addr, size_t len, int prot);

// from <unistd.h>
int getpagesize(void);
]]

-- magic numbers from <bits/mman-linux.h>
local PROT_READ     = 0x1  -- Page can be read.
local PROT_WRITE    = 0x2  -- Page can be written.
local PROT_EXEC     = 0x4  -- Page can be executed.
local MAP_PRIVATE   = 0x02 -- Changes are private.
local MAP_ANONYMOUS = 0x20 -- Don't use a file.

local page_size = ffi.C.getpagesize()
local prot = bit.bor(PROT_READ, PROT_WRITE)
local flags = bit.bor(MAP_ANONYMOUS, MAP_PRIVATE)
local code = ffi.new("uint8_t *", ffi.C.mmap(ffi.NULL, page_size, prot, flags, -1, 0))

local count = 0
local asmins = function(...)
    for _,v in ipairs{ ... } do
        assert(count < page_size)
        code[count] = v
        count = count + 1
    end
end

asmins(0xb8, 0x7c, 0x00, 0x00, 0x00) -- mov rax, 124
asmins(0xc3) -- ret

ffi.C.mprotect(code, page_size, bit.bor(PROT_READ, PROT_EXEC))

local fun = ffi.cast("int(*)(void)", code)
print(fun())

ffi.C.munmap(code, page_size)
Run Code Online (Sandbox Code Playgroud)

Wandbox上的实例

如何找到操作码

我看到这个答案引起了一些兴趣,所以我想添加一些我一开始很难的东西,即如何找到你想要执行的指令的操作码.有一些在线资源,尤其是英特尔®64和IA-32架构软件开发人员手册,但没有人想要浏览数以千计的PDF页面,只是为了了解如何操作mov rax, 124.因此,有些人制作了列出指令和相应操作码的表,例如http://ref.x86asm.net/,但查找表中的操作码也很麻烦,因为mov根据目标和来源,甚至可以有许多不同的操作码操作数是.所以我做的是我写一个简短的汇编文件,例如

mov rax, 124
ret
Run Code Online (Sandbox Code Playgroud)

您可能想知道,为什么没有函数,也没有像segment .text我的汇编文件中那样的东西.好吧,既然我不想连接它,我可以把所有这些都留下来并节省一些打字.然后使用它组装它

$ nasm -felf64 -l test.lst test.s
Run Code Online (Sandbox Code Playgroud)

-felf64选项告诉汇编器我正在使用64位语法,-l test.lst我希望在文件中列出生成的代码test.lst.该列表看起来类似于:

$ cat test.lst
     1 00000000 B87C000000              mov rax, 124
     2 00000005 C3                      ret
Run Code Online (Sandbox Code Playgroud)

第三列包含我感兴趣的操作码.只需将它们分成1个字节的单位并将它们插入到程序中,即B87C000000成为0xb8, 0x7c, 0x00, 0x00, 0x00(十六进制数字在Lua中幸运地不区分大小写,我更喜欢小写).