bugly崩溃排查2:luajit编译调试

382 阅读4分钟

luajit是什么

简单说,是lua语言的另一个实现版本,唯一区别的是,加入了jit功能。

JIT是just in time的缩写,也就是即时编译。

编译luajit

前置说明

luac文件是加密后的代码,并不是luac的代码才会在luajit模式下运行,加密和luajit是2个任何不相关的东西,不要混淆,这是一个理解的误区。

如果使用了luajit的库,那么lua代码才会在luajit模式下运行,因为luajit编译出来的是lua51.dll,和lua官方编译出来的文件名是一致的。

代码是在luajit的情况下崩溃的,所以必须让项目的lua代码运行在luajit模式下,为了方便测试,我选择了使用win32进行编译测试。

win32编译

luajit的win32编译非常简单,作者已经帮我们写好了bat编译脚本,切换到 Visual Studio Command Prompt

image.png

输入msvcbuild.bat很顺利就在当前目录下生成了lua51.lib

msvcbuild.bat的解读

不使用msvcbuid.bat,自己搭建luajit的vs项目,这篇文章可以参考,编译脚本我们还是需要好好理解下,有助于我们了解更多的细节。

  1. 先编译了minilua.c,链接生成了minilua.exe

    cl /nologo /c /O2 /W3 /D_CRT_SECURE_NO_DEPRECATE 
        /D_CRT_STDIO_INLINE=__declspec(dllexport)__inline 
        host\minilua.c
    link /nologo /out:minilua.exe minilua.obj
    
  2. 生成src/host/buildvm_arch.h,host/.gitignore里面忽略了这个文件,也印证了这个文件是需要动态生成的

    minilua ..\dynasm\dynasm.lua -LN -D WIN -D JIT -D FFI -o host\buildvm_arch.h vm_x86.dasc
    

    image.png

  3. 编译buildVM相关的源码,生成buildVM.exe,这里用到了上一步生成的buildvm_arch.h文件,不过对于cmake来说,它只关心c文件,不会影响cmake的编写

    cl /nologo /c /O2 /W3 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_STDIO_INLINE=__declspec(dllexport)__inline /arch:SSE2 /I "." /I ..\dynasm host\buildvm*.c
    link /nologo /out:buildvm.exe buildvm*.obj
    
  4. 使用buldVM.exe,生成了一些中间文件有obj.h.lua,也就是-o参数后边的文件

    buildvm -m peobj   -o lj_vm.obj
    buildvm -m bcdef   -o lj_bcdef.h    lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_buffer.c
    buildvm -m ffdef   -o lj_ffdef.h    lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_buffer.c
    buildvm -m libdef  -o lj_libdef.h   lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_buffer.c
    buildvm -m recdef  -o lj_recdef.h   lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_buffer.c
    buildvm -m vmdef   -o jit\vmdef.lua lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_buffer.c
    buildvm -m folddef -o lj_folddef.h  lj_opt_fold.c
    
  5. 编译lj_\*.clib_\*.c,链接生成lua51.dll,最关键的一步之前的所有准备都是为这一步准备的

    cl  /nologo /c /O2 /W3 
        /D_CRT_SECURE_NO_DEPRECATE 
        /D_CRT_STDIO_INLINE=__declspec(dllexport)__inline 
        /arch:SSE2 /MD /DLUA_BUILD_AS_DLL 
        lj_*.c 
        lib_*.c
    link /nologo /release /DLL /out:lua51.dll lj_*.obj lib_*.obj
    
  6. 编译luajit.c,链接生成luajit.exe

    cl /nologo /c /O2 /W3 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_STDIO_INLINE=__declspec(dllexport)__inline /arch:SSE2 luajit.c
    link /nologo /release /out:luajit.exe luajit.obj lua51.lib
    
  7. 最后就是一些清理工作了,至此生成的文件为lua51.lib lua51.dll luajit.exe

编译luajit可能遇到的报错

手动搭建vs项目编译luajit时,你一定会遇到这个问题:

2>lib_jit.obj : error LNK2019: 无法解析的外部符号 _lj_vm_cpuid,函数 _jit_cpudetect 中引用了该符号
2>lib_math.obj : error LNK2019: 无法解析的外部符号 _lj_vm_floor,函数 _random_seed 中引用了该符号
2>lj_vmmath.obj : error LNK2001: 无法解析的外部符号 _lj_vm_floor

lj_vm_xx好多函数都是在lj_vm.h中定义的,发现的确是没有lj_vm_cpuid的实现,luajit快的一个原因就是lj_vm_xx之类的函数都是用反汇编实现的,这也导致你就没有办法通过vs进行完整的编译luajit,只能通过官方提供的msvcbuild.bat进行编译。

调试luajit的解决办法

通过msvcbuild.bat编译出来debug版本,然后将lua51.dlllua51.liblua51.pdb(这个非常重要,能够调试跳转到源码全部靠他)文件提供给exe使用,最后是勉强跑起来了

命令参数debug无法通过cmake设置,只能自己手动设置了

image.png

现在也能跳转到lua_cpcall了,但是像lj_vm_cpcall这个就无法跳转了,只能从堆栈中查看反汇编看到的结果:

image.png

image.png

不过经过这番折腾,勉强可以跑起来了luajit,并且在对应的源码位置打断点,是可以进行调试的

尝试触发lj_meta_chach

image.png

简单测试了下,目前发现调用该函数是在lua_close的时候,游戏关闭或者被强行退出,也会有可能触发该函数,但是崩溃就有点无法理解了

也有可能是lua gc触发的,也有可能是我的lua脚本太简单,看来还是需要放到项目中进行验证。

至此,我们已经大致了解编译调试luajit的办法了,下一步就是放到cocos2dx中实际跑起来。