Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[求助] LuaPanda不能停在红点断点 硬断点可以生效 #184

Open
BigMadDonkey opened this issue May 14, 2024 · 4 comments
Open

Comments

@BigMadDonkey
Copy link

Describe the bug
之前我用LuaHelper插件 + LuaPanda 3.2.0 在项目中进行调试,持续使用了1年左右,都十分正常(我在配置launch.json时 useCHookfalse所以跟VSCode的升级可能没关系)。最近发现在编辑器里插入的红点断点总是不生效,参照FAQ尝试用硬断点然后执行LuaPanda.testBreakpoint发现输出如图所示:
image
并且硬断点有时会停在我们框架内扩展的可复用coroutine的逻辑附近而不是调用LuaPanda.BP()的位置。

之后尝试改用最新的LuaPanda插件 + LuaPanda3.3.1,但是红点断点仍然不生效,但在不生效位置加硬断点之后,LuaPanda.testBreakpoint输出变成下图的样子:
image

Desktop (please complete the following information):

  • OS: macOS
  • VSCode v1.89.1
  • LuaPanda Version 3.2.0 and 3.3.1
  • Framework xlua

Additional context
其实我推测可能与项目框架内对coroutine的修改有关,但是我一时实在是看不出来,故想请教一下开发者,3.2.0版本testBreakpoint中formatted 路径正常(但还是停不到断点上),而LuaPanda3.3.1 formatted显示为coroutine,可能导致这种情况的原因是什么?
十分感谢!

@stuartwang
Copy link
Collaborator

我看了下代码
首先 GetInfo 和 Normalized 都是来源于 debug.getInfo 的返回,所以这个数据是实时取到的。

image

lastRunFunction的赋值是在 function this.real_hook_process(info) 这个函数中,它会记录当前用户函数的 getInfo 信息以及 event(call/line/return) 状态

image

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(其实就是重写了协程的创建函数,在创建的协程上加个钩子)
image


这里我的建议是在 this.debug_hook 函数末尾打印下 getinfo 获取到的信息,看是否符合预期。因为Formated数据其实是从这里取的。
我最近也会再测试下协程的hook

@BigMadDonkey
Copy link
Author

我看了下代码
首先 GetInfo 和 Normalized 都是来源于 debug.getInfo 的返回,所以这个数据是实时取到的。

image

lastRunFunction的赋值是在 function this.real_hook_process(info) 这个函数中,它会记录当前用户函数的 getInfo 信息以及 event(call/line/return) 状态

image

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(其实就是重写了协程的创建函数,在创建的协程上加个钩子)
image


这里我的建议是在 this.debug_hook 函数末尾打印下 getinfo 获取到的信息,看是否符合预期。因为Formated数据其实是从这里取的。
我最近也会再测试下协程的hook

感谢回复,下周我会去公司环境下试验一下的 到时候更新一下状态

@BigMadDonkey
Copy link
Author

@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还是没有停在断点上。

@stuartwang
Copy link
Collaborator

stuartwang commented May 21, 2024

不过,我尝试在硬断点的位置查看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包括协程在内的所有函数。如果没有停止,有以下可能性

  1. 因为某些原因chook并没有实际加载上,仍然用的是luahook (BP() 中使用LuaPanda.getInfo()可以看到实际是否加载)
  2. lua 5.4.6 版本中发现使用 lua_getinfo(L, "Slf", ar) 取到的函数起止行号有错。调试器会根据起止行号做性能优化,这个错误导致无法正确识别有断点的函数,导致无法停止。 关于在5.4下使用luapanda #167
  3. 其他可能性包括chook下路径处理异常。我觉得这种可能性不大,为了统一路径处理,c也是把路径交给lua层处理的,为例避免频繁调用还做了缓存。

目前想到的就是这些,有问题欢迎交流

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants