LuaJIT源码分析(一)搭建调试环境
众所周知,LuaJIT是一个针对lua编程语言的即时编译器(JIT,Just-In-Time Compiler),它执行lua代码的速度相当的快。而网络上关于LuaJIT源码的资料相当地少,只能自己开一个坑,从头开始阅读了。
万事开头难。想要分析源码,第一步肯定是要先搭建一个可以调试源码的环境出来。然而,就连这个第一步,网络上可参考的内容也很有限,大概是LuaJIT的编译本身也比较复杂吧。
先checkout下源代码,官方git如下:
git clone https://luajit.org/git/luajit.git
github上也有一个镜像地址。
我们准备使用visual studio进行调试,LuaJIT提供了使用msvc编译的脚本msvcbuild.bat,它位于src目录下。浏览这个脚本,可以发现LuaJIT的编译分为三个部分,首先是build出一个minilua,它是lua原生代码的一个子集,用来判断当前平台是32位还是64位,并执行lua脚本并生成平台相关的指令;然后是buildvm,用来生成各种库函数的映射;最后编译lua的各种lib,然后生成最终的LuaJIT。这个脚本需要在Visual Studio Command Prompt环境下执行,根据注释它还有4个编译选项:
@rem Open a "Visual Studio Command Prompt" (either x86 or x64).
@rem Then cd to this directory and run this script. Use the following
@rem options (in order), if needed. The default is a dynamic release build.
@rem
@rem nogc64 disable LJ_GC64 mode for x64
@rem debug emit debug symbols
@rem amalg amalgamated build
@rem static static linkage
这里我们编译并不需要带上这些选项。另外,脚本在编译结束时,会执行一些清理工作,把中间生成的代码,以及编译产生的obj都会删除掉。而我们调试时其实是希望保留这些代码的,那么就要在脚本中把这部分逻辑给注释掉:
@REM @del *.obj *.manifest minilua.exe buildvm.exe
@REM @del host\buildvm_arch.h
@REM @del lj_bcdef.h lj_ffdef.h lj_libdef.h lj_recdef.h lj_folddef.h
以Visual Studio 2022为例,我们找到64位的command prompt:
单击它,然后cd到LuaJIT的src目录,运行编译脚本msvcbuild.bat:
编译很快,如果一切顺利,会在编译结束时看到成功build的日志:
在src目录下也能看到build的luajit.exe,双击运行,它就是我们要的lua虚拟机。
然后,我们在src同级目录下新建一个目录,用来存放vs工程,在vs中新建一个empty console C++工程,然后把src目录的头文件和源文件都添加进去。
执行F5调试,此时可能会遇到一个报错:
error C4996: ‘strerror’: This function or variable may be unsafe. Consider using strerror_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
这是因为LuaJIT源码中使用strerror这个C API,而它是不安全的,编译器更推荐使用strerror_s,所以报错提示了。不过这里我们并不打算修改源码,而是直接在工程里添加_CRT_SECURE_NO_WARNINGS
宏:
再来一次,编译能通过了,但是到链接的时候,又报错了:
从报错可以看出是有函数被重复定义了,既然都指向ljamalg.obj,那么先去它对应的源文件ljamalg.c一探究竟,原来它直接include了一堆源文件进来了:
我们回到msvcbuild.bat,可以看到ljamalg.c这个文件只有在开启了amalg选项时才会被编译进来:
@if "%1"=="amalg" goto :AMALGDLL
...
:AMALGDLL
%LJCOMPILE% %LJDYNBUILD% ljamalg.c
而我们正常编译没有带这个选项,这个文件实际上是不参与build过程的,因此我们要把这个文件从vs里排除掉。那么这个amalg选项是做什么的呢?这一点LuaJIT的官网上有提:
The build system has a special target for an amalgamated build, i.e.
make amalg
. This compiles the LuaJIT core as one huge C file and allows GCC to generate faster and shorter code. Alas, this requires lots of memory during the build. This may be a problem for some users, that’s why it’s not enabled by default. But it shouldn’t be a problem for most build farms. It’s recommended that binary distributions use this target for their LuaJIT builds.
简而言之,该选项会将 LuaJIT 核心编译为一个巨大的 C 文件,并允许编译器生成更快、更短的代码。
去除ljamalg.c之后,我们再次尝试,然后很不幸链接依旧会失败:
我们回忆一下LuaJIT的build过程,vm中各种库函数的映射是通过buildvm完成的,它本身并没有对应的源代码,不过它生成的目标文件倒是在src目录下,这也是在bat中完成的:
buildvm -m peobj -o lj_vm.obj
这个目标文件中应该包含这里需要引用的各种函数。那么我们在vs中手动添加进来:
再次尝试,LuaJIT终于可以启动了。默认我们debug是没有带上参数的,所以它是从stdin接收参数的。
在main函数的入口加上断点,再次执行F5,也能正常调试了:
好了,这样就可以愉快地阅读LuaJIT源码了。
Reference
[1] LuaJIT
[2] 什么是 LuaJIT?为什么 Apache APISIX 选择了 LuaJIT?
[3] 浅入浅出LuaJIT