Scite 调试

lua-users home
wiki

scite-debug 简介

scite-debug 完全用 SciTE Lua 编写,兼容标准 5.1。唯一绝对必要的 C 代码是 *spawner*,它捕获交互式命令行调试器(如 GDB),并允许 scite-debug 向其写入命令。输出被定向到指定的全局函数,使用适当的主线程技术;在 Windows 上,我从调试器线程向 wndproc 传递消息,在 GTK 上使用异步输出机制。在这两个平台上,我们都通过整行获取调试器输出,这意味着在调试发出提示的交互式程序时存在限制。但是,在 Windows 方面,我们要求为调试的程序提供一个单独的控制台窗口,这将程序输入和输出与调试器输出分开,并允许调试 Windows 控制台应用程序。

调试目标完全有可能没有调试符号。当我们只对调试程序加载的共享库感兴趣时,就会出现这种情况。因此,我们通常会在这些库中设置断点,但 GDB 必须被哄骗以接受这一点。如果 debug.target 以 '[n]' 开头,那么 GDB 将被提供一个任意的小符号文件(由 stubby.so|.dll 提供)。此时,它很乐意接受任何未解析的断点请求作为 *待处理*。(请注意,您需要 GDB 的 vs 6 版本才能做到这一点;如果您使用的是 MinGW,如果您仍在运行 vs 5,请下载更新的 GDB。)

可以使用 debug.environment 属性为调试目标设置环境变量。这是一个用分号分隔的 VAR=VAL 对列表。

典型的俘获调试器会话需要设置断点,并使用 视图|参数 指定的参数启动程序运行。在程序运行之前设置的任何断点都会被写入配置文件;在 GDB 的情况下,它被称为 'prompt.cmd'。此文件也是根据我们的喜好进一步配置调试器的机会。例如,对于 GDB,我们设置一个带换行符的提示符,关闭输出分页等。

在断点处停止需要一个独特的模式,该模式与调试器输出匹配,并且可以提取文件和行。GDB 有一种特殊模式 (-f),它被 Emacs 使用,它以这种格式打印出任何断点的完整路径:(26)(26)<path>:<line>,其中 (26) 是由十进制 26 表示的 ASCII 字符。这提供了一个非常独特的目标模式。其他调试器并不那么容易被自动驱动,我们必须将就。

一旦我们进入“断点”模式,当然可以设置和取消设置断点。

任何调试器命令都可以从输出窗口输入。例如,您可以使用“watch var”在 GDB 中设置变量监视。

一个常见的任务是评估表达式。Alt-I(检查)将报告光标处表达式的值。scite-debug 足够智能,知道 a.bp->c->x 是完整的表达式,但如果选择了表达式,它将使用该表达式。工具提示评估使用相同的启发式方法和相同的调试器命令,但将输出重定向到工具提示而不是 SciTE 输出窗口。任何命令的输出都可以重定向到任意函数,这为更图形化的界面打开了有趣的机会。(但是,这将需要一段时间。)

对于 C/C++,scite-debug 会注意到一个表达式被评估为指针,并将尝试取消引用该指针。也就是说,如果 GDB 报告值为 $11 = (A *)0xFFF23EE,那么 scite-debug 将尝试评估 *$11 并收集结果。

另一个对 C++ 调试有用的功能是某些标准模式的自动简化,特别是 std::string,它将提取字符指针值。这些规则被递归地应用,因此包含 std::string 值的结构将以更人性化的形式呈现。请注意,这些简化在很大程度上取决于所使用的精确实现!目前仅支持 g++,因此模式相当稳定。这是一个用户自定义的好地方。

scite-debug 通常知道如何解释堆栈跟踪;双击所需级别将使您进入该帧并将您置于相应的源代码行。可以使用 Ctrl+Alt+S 显式显示堆栈跟踪,并在发生错误时尽可能自动呈现。Alt+UAlt+D 分别对应于“向上”和“向下”级别。

集成 Lua 和 C/C++ 调试

这个有用的(可能也是独一无二的)功能是通过在 GDB 中的进程中运行 clidebug Lua 调试器来实现的。clidebug 现在有了“GDB 模式”,它模仿 GDB 命令集和输出。因此,无论我们在 GDB 还是 clidebug 中中断,luagdb 都将带我们到源代码中的那个位置。

有两种可能的情况;第一种情况是宿主程序本身是 Lua(或其近亲),并且理解 -e 和 -l 命令行选项;这是默认情况。第二种情况是宿主程序是嵌入 Lua 的程序。debug.target 变量必须同时指示宿主程序和 Lua 脚本名称。一些示例可以说明这一点。

# the host is Lua on the path, and has no debugging symbols.
debug.target=[n]:gdb;lua;mytest.lua

# the host is SciTE - the [h] indicates that it isn't Lua
debug.target=[n]:gdb;SciTE[h];/home/steve/{{scite-debug}}/extman.lua

# a debug version of Lua.
debug.target=:gdb;/home/steve/lua-5.1.3/src/lua;/home/steve/tests/testlfs.lua

如果宿主程序不是 Lua,那么您必须在要调试的脚本中自己初始化 clidebug。在此代码段中,将路径替换为适当的位置。

local path = ';/home/steve/{{scite-debug}}/lua_clidebugger/?.'
package.path = package.path .. path .. 'lua'
package.cpath = package.cpath .. path .. 'so'
WIN=false
GDB=true
require "debugger"
io.stdout:setvbuf("no")
pause('debug')

调试像 SciTE 这样的 Windows 上的 GUI 程序需要特殊的构建,至少如果您想调试 Lua 脚本的话。通常 GUI 程序('subsystem:windows')没有标准输入和输出,但它们可以重建。在 SciTE 的情况下,我用 '-lgdi32 -lcommctl32' 替换了 '-mwindows';生成的程序带有难看的黑色控制台,但调试器会隐藏它。

(另一个仅针对 GUI 应用程序调试 Lua 的选项是 remDebug,它也受 scite-debug 支持。)

单步进入 C/C++

luagdb 使从 Lua 代码单步进入 C 扩展成为可能。为此,有必要在“call”调试器事件上找到要进入的特定 C 函数的地址。一个小扩展 dbgl 查询 Lua 内部并返回此地址。它还提供了一个方便的函数来设置断点,称为 debug_break()(此技巧需要一个对挂起的断点没有问题的 GDB 版本)。clidebug 以不同的前缀打印出此地址,并调用 dbgl.debug_break(),这将使我们进入 GDB。luagdb 获取地址并对地址发出 GDB 'info line' 查询,以查看该地址是否有任何调试信息可用。如果有,它将在该函数的开头设置一个临时断点。无论哪种方式,它都会发出一个“continue”命令,以便我们继续执行。我们缓存了此结果,因此我们不必连续调用“info line”,但我们仍然在程序每次从 Lua 单步进入 C/C++ 时设置临时断点。这确保了始终可以单步跳过 C 函数。

一旦您进入 C/C++ 端,您可以通过简单地继续(Alt+R)继续在 Lua 中单步执行。

这两个仍然是两个独立的调试会话,因此存在一些限制。在运行的程序中设置断点可能会导致有趣的问题,至少在 Windows 上是这样。没有集成的调用堆栈。

dbgl.c 必须找出 Lua C 函数的对应地址。

static int c_addr (lua_State *L)
{
    char buff[40];
    CallInfo *ci;
    Closure* cl = NULL;
    for (ci = L->ci - 1; ci > L->base_ci; ci--) {
      if (! f_isLua(ci)) {  // C function!
        cl = clvalue(ci->func);	
      	break;
      }
    }
    if (cl == NULL) {
      lua_pushnil(L);
    } else {
      void *fun = cl->c.f;    
      sprintf(buff,"0x%X",fun);
      lua_pushstring(L,buff);
    }
    return 1;
}

这需要一些关于 Lua 内部机制的知识,并且需要 lstate.h,它不是公共 API 的一部分。(因此,在重建 dbgl.c 时,您需要提供一个指向 Lua 头文件的完整集的包含路径。)我们向下查看闭包堆栈以找到我们正在调用的 C 函数。


另请参阅:[项目页面]
最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2008 年 3 月 11 日下午 12:40 GMT (差异)