问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
can't find this res : Lua/Data/.lua
UnityEngine.DebugLogHandler:Internal_Log(LogType, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:LogError(Object)
XXXXLibrary.AssetManager:LoadUnityAsset(String) (at X:\XXXX\XXXX\Project\client\Assets\XXXXXX\Scripts\Resource\AssetManager.cs:247)
XXXXLibrary.AssetManager:LoadAsset(String) (at X:\XXXX\XXXX\Project\client\Assets\XXXXXX\Scripts\Resource\AssetManager.cs:44)
LuaMgr:LoadLua(String, String&, Boolean) (at X:\XXXX\XXXX\Project\client\Assets\Scripts\XLua\LuaMgr.cs:94)
LuaMgr:CustomLoader(String&) (at X:\XXXX\XXXX\Project\client\Assets\Scripts\XLua\LuaMgr.cs:166)
XLua.StaticLuaCallbacks:LoadFromCustomLoaders(IntPtr) (at X:\XXXX\XXXX\Project\client\Assets\XLua\Src\StaticLuaCallbacks.cs:786)
XLua.LuaDLL.Lua:lua_pcall(IntPtr, Int32, Int32, Int32)
XLua.DelegateBridge:PCall(IntPtr, Int32, Int32, Int32) (at X:\XXXX\XXXX\Project\client\Assets\XLua\Src\DelegateBridge.cs:138)
XL

从 Android Logcat 输出的日志中可以看到 Lua 文件加载失败。

环境

  • Unity 2018.4.25f1
  • xLua 2.1.14
  • Windows 7
  • Android 9

日志显示不全

模拟器与 Android 真机都存在单条日志的缓存大小太小,导致无法输出完整的出错堆栈

logcat 命令的选项与开发者选项中的日志缓冲区大小都是指总体的缓冲区大小,而不是指单条消息的缓冲区大小

这里提到是内核里面定义的单条消息大小,要想修改只能重新编译 Android 系统。最好尝试其他手机看看是否能输出全日志。

将上面的日志(包括文件结尾的换行符)保存为文本文件后,发现大小正好是 1024 字节。也就是说这条消息大小与内核定义的单条消息大小完全一致。

查看完整日志

因为手头没有单条日志长度限制较大的手机,因此尝试在游戏内使用 In-game Debug Console 插件输出日志查看。具体介绍见文:

无法点击日志按钮

但发现游戏启动时无法点击日志按钮打开界面,猜测是 UGUI EventSystem 组件被关掉了,检查代码后发现果然如此。

游戏在启动时将 EventSystem 组件关闭,是为了防止与后面实例化的 UI 管理器中携带的 EventSystem 组件冲突,但是关掉的太早了,完全可以在刚刚实例化 UI 管理器前关掉。

完整的调用堆栈

终于拿到完整的调用堆栈了,这样可以确认问题出在游戏初始化的哪一个阶段:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
can't find this res : Lua/Data/.lua
UnityEngine.Debug:LogError(Object)
XXXXLibrary.AssetManager:LoadUnityAsset(String) (at X:/XXXX/XXXX/Project/client/Assets/XXXXXX/Scripts/Resource/AssetManager.cs:247)
XXXXLibrary.AssetManager:LoadAsset(String) (at X:/XXXX/XXXX/Project/client/Assets/XXXXXX/Scripts/Resource/AssetManager.cs:44)
LuaMgr:LoadLua(String, String&, Boolean) (at X:/XXXX/XXXX/Project/client/Assets/Scripts/XLua/LuaMgr.cs:94)
LuaMgr:CustomLoader(String&) (at X:/XXXX/XXXX/Project/client/Assets/Scripts/XLua/LuaMgr.cs:166)
XLua.StaticLuaCallbacks:LoadFromCustomLoaders(IntPtr) (at X:/XXXX/XXXX/Project/client/Assets/XLua/Src/StaticLuaCallbacks.cs:786)
XLua.LuaDLL.Lua:lua_pcall(IntPtr, Int32, Int32, Int32)
XLua.DelegateBridge:PCall(IntPtr, Int32, Int32, Int32) (at X:/XXXX/XXXX/Project/client/Assets/XLua/Src/DelegateBridge.cs:138)
XLua.DelegateBridge:_Gen_Delegate_Imp21(Object[]) (at X:/XXXX/XXXX/Project/client/Assets/Scripts/XLua/Gen/DelegatesGensBridge.cs:519)
XX.LuaObject:Call(String, Object[]) (at X:/XXXX/XXXX/Project/client/Assets/Scripts/XLua/XX.cs:152)
XX.Luainterface:CallLuaScript(String, String, Boolean&, Object&) (at X:/XXXX/XXXX/Project/client/Assets/Scripts/XLua/Luainterface.cs:46)
<_InitGame>d__59:MoveNext0 (at X:/XXXX/XXXX/Project/client/Assets/Scripts/UI/Update/UIUpdate.cs:1347)
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

查看 Lua 调用堆栈

从上面的 C# 调用堆栈上只能看到 C# 代码,Lua 调用堆栈在 XLua.LuaDLL.Lua:lua_pcall 中,并不能直接看到。

测试 Lua 钩子

想到 Lua 提供了钩子方法可以截获方法调用或返回等操作:

先编写代码测试钩子是否可以正确工作,然后将其放到项目中测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function trace(event)
    local functionName = debug.getinfo(2,"n").name
    if functionName ~= 'require' then
        return
    end

    local stackInfo = debug.traceback()
    print(stackInfo)
end

function main()
    debug.sethook(trace, "c")
end

尝试使用 Lua hook 在每次 require 方法调用时打印调用堆栈

接入项目中测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
LUA: stack traceback:
    Lua/main:109: in function 'trace'
    [C]: in function 'require'
    Lua/Common:3508: in field 'GetXXXXXXXConfig'
    Lua/Config/ConstsConfig:15: in main chunk
    [C]: in function 'require'
    Lua/ConfigData:130: in method 'Init'
    Lua/main:55: in function <Lua/main:44>
UnityEngine.DebugLogHandler:Internal_Log(LogType, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Log(Object)
XLua.StaticLuaCallbacks:Print(IntPtr) (at X:\XXXX\XXXX\Project\client\Assets\XLua\Src\StaticLuaCallbacks.cs:629)
XLua.LuaDLL.Lua:lua_pcall(IntPtr, Int32, Int32, Int32)
XLua.DelegateBridge:PCall(IntPtr, Int32, Int32, Int32) (at X:\XXXX\XXXX\Project\client\Assets\XLua\Src\DelegateBridge.cs:138)
XLua.DelegateBridge:__Gen_Delegate_Imp21(Object[]) (at X:\XXXX\XXXX\Project\client\Assets\Scripts\XLua\Gen\DelegatesGensBridge.cs:519)
XX.LuaObject:Call(String, Object[]) (at X:\XXXX\XXXX\Project\client\Assets\Scripts\XLua\XX.cs:152)
XX.LuaInt
can't find this res : Lua/Data/.lua
UnityEngine.DebugLogHandler:Internal_Log(LogType, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:LogError(Object)
XXXXLibrary.AssetManager:LoadUnityAsset(String) (at X:\XXXX\XXXX\Project\client\Assets\XXXXXX\Scripts\Resource\AssetManager.cs:247)
XXXXLibrary.AssetManager:LoadAsset(String) (at X:\XXXX\XXXX\Project\client\Assets\XXXXXX\Scripts\Resource\AssetManager.cs:44)
LuaMgr:LoadLua(String, String&, Boolean) (at X:\XXXX\XXXX\Project\client\Assets\Scripts\XLua\LuaMgr.cs:94)
LuaMgr:CustomLoader(String&) (at X:\XXXX\XXXX\Project\client\Assets\Scripts\XLua\LuaMgr.cs:166)
XLua.StaticLuaCallbacks:LoadFromCustomLoaders(IntPtr) (at X:\XXXX\XXXX\Project\client\Assets\XLua\Src\StaticLuaCallbacks.cs:786)
XLua.LuaDLL.Lua:lua_pcall(IntPtr, Int32, Int32, Int32)
XLua.DelegateBridge:PCall(IntPtr, Int32, Int32, Int32) (at X:\XXXX\XXXX\Project\client\Assets\XLua\Src\DelegateBridge.cs:138)
XL

通过输出 Lua 调用堆栈后可以看到出错的地方。

找到出错代码

1
2
3
4
5
    if CS.UnityEngine.Application.isEditor then
        return require 'Lua/Data/shop'
    else
        return require 'Lua/Data/' .. config.shop
    end

分析问题

仔细看这段代码可以发现非编辑器下,感觉是一个字符串拼接后调用 require 方法的代码。

但实际上并不是,这里先执行的是 require 'Lua/Data/',获取结果后再与后面的 config.shop 进行字符串拼接。

重现问题

将编辑器分支代码 require 后的表改为使用 .. 字符串连接:

1
2
3
4
5
    if CS.UnityEngine.Application.isEditor then
        return require 'Lua/Data/' .. 'shop'
    else
        return require 'Lua/Data/' .. config.shop
    end

然后就可以在 Unity 编辑器下重现问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Assets/XXXX/Resources/Lua/Lua/Common.lua.txt:3511: module 'Lua/Data/' not found:
    no field package.preload['Lua/Data/']
    no such builtin lib 'Lua/Data/'
    no such file 'Lua/Data/' in CustomLoaders!
    no such resource 'Lua/Data/.lua'
    no file '/usr/local/share/lua/5.3/Lua/Data/.lua'
    no file '/usr/local/share/lua/5.3/Lua/Data//init.lua'
    no file '/usr/local/lib/lua/5.3/Lua/Data/.lua'
    no file '/usr/local/lib/lua/5.3/Lua/Data//init.lua'
    no file './Lua/Data/.lua'
    no file './Lua/Data//init.lua'
    no file '/usr/local/lib/lua/5.3/Lua/Data/.so'
    no file '/usr/local/lib/lua/5.3/loadall.so'
    no file './Lua/Data/.so'
    no such file 'Lua/Data/.lua' in streamingAssetsPath!
    stack traceback:
    [C]: in function 'require'
    Assets/XXXX/Resources/Lua/Lua/Common.lua.txt:3511: in field 'GetXXXXXXXConfig'
    Assets/XXXX/Resources/Lua/Lua/Config/ConstsConfig.lua.txt:15: in main chunk
    [C]: in function 'require'
    Assets/XXXX/Resources/Lua/Lua/ConfigData.lua.txt:130: in method 'Init'
    Assets/XXXX/Resources/Lua/Lua/main.lua.txt:55: in function <Assets/XXXX/Resources/Lua/Lua/main.lua.txt:44>
    UnityEngine.Debug:LogError(Object)
    XX.Debugger:LogError(Object) (at Assets/Scripts/Core/Manager/Debugger.cs:185)
    XX.LuaObject:Call(String, Object[]) (at Assets/Scripts/XLua/XX.cs:157)
    XX.LuaInterface:CallLuaScript(String, String, Boolean&, Object[]) (at Assets/Scripts/XLua/LuaInterface.cs:46)
    <_InitGame>d__59:MoveNext() (at Assets/Scripts/UI/Update/UIUpdate.cs:1347)
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) (at /Users/builduser/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)

at XLua.LuaEnv.ThrowExceptionFromError (System.Int32 oldTop) [0x00040] in /Volumes/XXXX/XXXX/client/Assets/XLua/Src/LuaEnv.cs:441
    at XLuaGenDelegateImpl0.__Gen_Delegate_Imp22 (System.Object[] args) [0x00057] in <187f87a25a174f1896c8299190e68c6b>:0
    at XX.LuaObject.Call (System.String func, System.Object[] args) [0x00032] in /Volumes/XXXX/XXXX/client/Assets/Scripts/XLua/XX.cs:152
    UnityEngine.Debug:LogError(Object)
    XX.Debugger:LogError(Object) (at Assets/Scripts/Core/Manager/Debugger.cs:185)
    XX.LuaObject:Call(String, Object[]) (at Assets/Scripts/XLua/XX.cs:158)
    XX.LuaInterface:CallLuaScript(String, String, Boolean&, Object[]) (at Assets/Scripts/XLua/LuaInterface.cs:46)
    <_InitGame>d__59:MoveNext() (at Assets/Scripts/UI/Update/UIUpdate.cs:1347)
    UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) (at /Users/builduser/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)

修复问题

使用括号将 require 方法的参数包围起来。然后在真机上测试,确定问题已修复。

问题总结

这是一个运算符与方法调用的优先级问题,正确的方法是使用带括号的方法调用。

对于需要长期维护的项目,建议将 Lua 代码内所有 require 方法都改为括号调用的形式,可以使用正则表达式进行批量查找替换。

感想

刚开始解决问题时是由于提交后打包出的问题,上次成功的打包与出错的打包之间提交并不多,想要使用二分法查找出错的提交。

先尝试回滚最后的提交打包,发现问题解决。仔细检查最后的代码并没有发现任何问题,担心问题是由多方面共同作用导致的。

不要想,而要看

这是书籍《调试九法》里介绍的一个原则,想要修复一个问题,必须要看到如何出错的。这篇文章的主要目的就是为了分享解决问题的思路。

因此才会想到上面介绍的 Lua 钩子方法。能看到是如何出错的,问题也就基本解决了。

最后,强烈推荐 书籍推荐:调试九法 - 狂飙