Scite 调试 |
|
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.b
和 p->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+U
和 Alt+D
分别对应于“向上”和“向下”级别。
这个有用的(可能也是独一无二的)功能是通过在 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
支持。)
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 函数。