Open BigMadDonkey opened 6 months ago
我看了下代码 首先 GetInfo 和 Normalized 都是来源于 debug.getInfo 的返回,所以这个数据是实时取到的。
lastRunFunction的赋值是在 function this.real_hook_process(info) 这个函数中,它会记录当前用户函数的 getInfo 信息以及 event(call/line/return) 状态
Formated 数据来源于 lastRunFunction["source"],这个数据是最后一次进入 debug.sethook 设置的钩子函数时获取到的. 理论上来讲,这两个数据应该是一致的,预期的结果如下
GetInfo: @c:/Users/xxx/Desktop/luaTest2/ae.lua
Normalized: c:/users/xxx/desktop/luatest2/ae.lua
Formated: ae.lua
我另外对比了下 3.2.0和3.3.1的 LuaPanda.lua 代码,处理协程的部分确实有所修改 3.2.0是在调试器启动连接后对协程进行了hook
--协程调试
if coroutineCreate == nil and type(coroutine.create) == "function" then
this.printToConsole("change coroutine.create");
coroutineCreate = coroutine.create;
coroutine.create = function(...)
local co = coroutineCreate(...)
table.insert(coroutinePool, co);
--运行状态下,创建协程即启动hook
this.changeCoroutineHookState();
return co;
end
else
this.printToConsole("restart coroutine");
this.changeCoroutineHookState();
end
而3.3.1是在 LuaPanda.lua 被加载时就进行了协程的hook(其实就是重写了协程的创建函数,在创建的协程上加个钩子)
这里我的建议是在 this.debug_hook 函数末尾打印下 getinfo 获取到的信息,看是否符合预期。因为Formated数据其实是从这里取的。 我最近也会再测试下协程的hook
我看了下代码 首先 GetInfo 和 Normalized 都是来源于 debug.getInfo 的返回,所以这个数据是实时取到的。
lastRunFunction的赋值是在 function this.real_hook_process(info) 这个函数中,它会记录当前用户函数的 getInfo 信息以及 event(call/line/return) 状态
Formated 数据来源于 lastRunFunction["source"],这个数据是最后一次进入 debug.sethook 设置的钩子函数时获取到的. 理论上来讲,这两个数据应该是一致的,预期的结果如下
GetInfo: @c:/Users/xxx/Desktop/luaTest2/ae.lua Normalized: c:/users/xxx/desktop/luatest2/ae.lua Formated: ae.lua
我另外对比了下 3.2.0和3.3.1的 LuaPanda.lua 代码,处理协程的部分确实有所修改 3.2.0是在调试器启动连接后对协程进行了hook
--协程调试 if coroutineCreate == nil and type(coroutine.create) == "function" then this.printToConsole("change coroutine.create"); coroutineCreate = coroutine.create; coroutine.create = function(...) local co = coroutineCreate(...) table.insert(coroutinePool, co); --运行状态下,创建协程即启动hook this.changeCoroutineHookState(); return co; end else this.printToConsole("restart coroutine"); this.changeCoroutineHookState(); end
而3.3.1是在 LuaPanda.lua 被加载时就进行了协程的hook(其实就是重写了协程的创建函数,在创建的协程上加个钩子)
这里我的建议是在 this.debug_hook 函数末尾打印下 getinfo 获取到的信息,看是否符合预期。因为Formated数据其实是从这里取的。 我最近也会再测试下协程的hook
感谢回复,下周我会去公司环境下试验一下的 到时候更新一下状态
@stuartwang 问题似乎解决了,还真是coroutine hook没挂上的问题,是我们内部框架的改动,导致连接调试器的位置之前有些地方创建了可复用的协程(用完之后会被缓存在pool中),后面再用到协程时用的是这些在没挂上hook时创建的可复用协程,导致hook不上。保证debugger最先require就好了。非常感谢!
不过,我尝试在硬断点的位置查看lastRunFunction["source"]
似乎复现不出formatted和getInfo不同的情况了,目前不知道这种情况的具体诱因。
另外我还有个疑问:debugger源码中设置coroutine hook的位置会判断是否有hookLib
,为什么hookLib
存在时就不用coroutine hook了呢?如果hookLib.lua_set_hookstate
就可以对所有的协程都hook,那么这个由于“调试器连接晚于部分可复用协程创建”导致的无法hook的问题,应该只会发生在useCHook:false
(不使用CHook)的情况下,不会影响使用CHook时的调试,但是我使用旧的写法在创建可复用协程后再连接调试器,即使设置了useCHook:true
还是没有停在断点上。
不过,我尝试在硬断点的位置查看
lastRunFunction["source"]
似乎复现不出formatted和getInfo不同的情况了,目前不知道这种情况的具体诱因。
按照最初的设计想法,formatted 就是规范化后的路径,只是 lastRunFunction["source"] 恰好有这个数据,就拿过来用了,理论上二者应该是能匹配上的。我觉得之前出现二者不匹配的问题还是和协程有关,调试器关于协程的处理可能测试还不够充分,导致以某些特定场景下的异常。
另外我还有个疑问:debugger源码中设置coroutine hook的位置会判断是否有hookLib,为什么hookLib存在时就不用coroutine hook了呢?
指的是如下代码中的 if hookLib == nil then
判断对吧。我的理解是,lua 层的 debug.sethook ([thread,] hook, mask [, count])
接口只针对特定的协程设置钩子函数,每个协程都要单独设置。而luac接口 void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);
是可以进程中包括协程函数一起设置钩子。所以 coroutineCreate 时如果使用的是 hookLib ,就无须对每个协程单独设置了。(这里时间比较久了,我记得是这样的,当然也可以写Demo验证如上规则。如果有错误欢迎指出)
function this.replaceCoroutineFuncs()
if hookLib == nil then
if coroutineCreate == nil and type(coroutine.create) == "function" then
this.printToConsole("change coroutine.create");
coroutineCreate = coroutine.create;
coroutine.create = function(...)
local co = coroutineCreate(...)
table.insert(coroutinePool, co);
--运行状态下,创建协程即启动hook
this.changeCoroutineHookState(co, currentHookState);
return co;
end
end
end
end
如果hookLib.lua_set_hookstate就可以对所有的协程都hook,那么这个由于“调试器连接晚于部分可复用协程创建”导致的无法hook的问题,应该只会发生在useCHook:false(不使用CHook)的情况下,不会影响使用CHook时的调试,但是我使用旧的写法在创建可复用协程后再连接调试器,即使设置了useCHook:true还是没有停在断点上。
这里我们的理解是一致的,hookLib.lua_set_hookstate 是调用了luac接口进行hook的,理论上可以hook包括协程在内的所有函数。如果没有停止,有以下可能性
目前想到的就是这些,有问题欢迎交流
很抱歉隔了这么久才回复...因为当时暂时解决了调试的问题,又短时间内看不出协程调试的端倪,所以就先搁置去忙其他工作了。最近又发现有时协程中断点不生效(时有时无,通常在VSCode侧,已被执行过的代码行尝试增删断点几次会修复这个问题),所以又研究了一下,发现协程hook可能还是有些问题。
之前说问题是由于我这边使用了可复用协程,协程pool的创建先于require LuaPanda
导致有一部分pool的协程没有执行被修改的coroutine.create
进而没挂上hook,但是修改完之后发现还是有时会有上述问题。我分析了一下情况(useCHook:true, attachMode):
首先Unity进入PlayMode,require LuaPanda并开始监听(并override coroutine.create
),然后框架创建可复用协程,由于此时没有连接调试器,所以hookLib == nil
,因此这时创建的协程都加了lua侧的debug.setHook
,并且currentHookState
是disconnect。
后面运行过程中连接调试器,调用changeHookState
,但此时hookLib
已经获取,因此后续给协程修改hookState时不走lua侧的逻辑改为走cHook,这导致已有的可复用协程同时被加了lua侧的hook和CHook,我猜测这可能是导致异常状况的原因。
后续再从VScode添加/删除断点,有时就能修复,这个我还不太清楚是什么原因。
不过,如果先在VSCode侧运行调试,然后Unity进PlayMode,这样创建协程时hookLib
已经是存在的,就不会有这种情况了,我自测这样做协程中都是能正常断点断到的。
先打开调试再进PlayMode时,还发现了一个比较奇怪的地方
按我理解这里不应该有最底下一行log的,但是似乎coroutine.create还是被修改了。
但change coroutine.create
的log却又没有。这一点我也很疑惑,暂时没看出来怎么回事
但是有一点是可以确定的,那就是hookLib是有可能先为nil后不为nil的,这就可能导致对同一个协程hook的操作方式先后有不同。我个人觉得是否使用hookLib,亦即是否useCHook,至少应该是每一次Unity PlayMode都固定的,最好在开始确定好,然后在整个Unity play过程中不修改吧?感觉放到launch.json里配置未必是好做法,应该很少有这种需要动态切换hook方式的使用场景吧。
如果用attachMode去调试一个在连接调试器前打开的协程,这个协程就可能先以lua方式hook,再被CHook。
Describe the bug 之前我用LuaHelper插件 + LuaPanda 3.2.0 在项目中进行调试,持续使用了1年左右,都十分正常(我在配置launch.json时
useCHook
为false
所以跟VSCode的升级可能没关系)。最近发现在编辑器里插入的红点断点总是不生效,参照FAQ尝试用硬断点然后执行LuaPanda.testBreakpoint
发现输出如图所示:并且硬断点有时会停在我们框架内扩展的可复用coroutine的逻辑附近而不是调用
LuaPanda.BP()
的位置。之后尝试改用最新的LuaPanda插件 + LuaPanda3.3.1,但是红点断点仍然不生效,但在不生效位置加硬断点之后,
LuaPanda.testBreakpoint
输出变成下图的样子:Desktop (please complete the following information):
Additional context 其实我推测可能与项目框架内对coroutine的修改有关,但是我一时实在是看不出来,故想请教一下开发者,3.2.0版本testBreakpoint中formatted 路径正常(但还是停不到断点上),而LuaPanda3.3.1 formatted显示为coroutine,可能导致这种情况的原因是什么? 十分感谢!