gdb set $var and while loop----http://www.roczhou.com/blog/?tag=gdb

这一个月忙得昏天黑地。C 学习、lighttpd 项目、saunit 项目、syslog 推进,还有日常维护任务…

先把调试 lighttpd 的过程中学到的一点 gdb 有关的内容记录一下。在 gdb 中可以使用 set 设置一个变量,方便调试的时候打印出值,以免每次都要输入一长串的类型转换操作。如下:

(gdb) p ((data_array *) du)->value
$20 = (array *) 0x3caf930
(gdb) set da = (data_array *) du;
No symbol "da" in current context.
(gdb) set $da = (data_array *) du;
Invalid character ';' in expression.
(gdb) set $da = (data_array *) du 
// $ 符号是必须的,否则会认为是代码里面的变量
(gdb) p $da->value
$21 = (array *) 0x3caf930
(gdb) p $da->value->data[0]
$22 = (data_unset *) 0x3caf130

(gdb) p $da->value->data[0]->type 
$25 = TYPE_STRING
(gdb) p *((data_string*) $da->value->data[0])->value
$27 = {
  ptr = 0x3caf1b0 "Cache-Control", 
  used = 14, 
  size = 64, 
  ref_count = 0
}

如果想循环打印一个数组里面的内容,可以使用 while。前面 这里 也说过使用 while。那个还有点问题,应该加上 end:

(gdb) set $i = 0
(gdb) while($i p con->response.headers->data[$i]
 >set $i++
 >end
$564 = (data_unset *) 0x1bbb5680
$565 = (data_unset *) 0x1bbb59d0
$566 = (data_unset *) 0x1bbb60d0
$567 = (data_unset *) 0x1bbb61f0
$568 = (data_unset *) 0x1bbb6460
$569 = (data_unset *) 0x1bbb5220
$570 = (data_unset *) 0x1bbb5340

(gdb) set $i = 0
(gdb) while($i p ((data_string *) con->response.headers->data[$i])->key->ptr
 >set $i++
 >end
$573 = 0x1bbb5f70 "Date"
$574 = 0x1bbb3ff0 "Server"
$575 = 0x1bbb4bd0 "Last-Modified"
$576 = 0x1bbb7510 "ETag"
$577 = 0x1bbb3d30 "Accept-Ranges"
$578 = 0x1bbb3dd0 "Content-Length"
$579 = 0x1bbb7030 "Content-Type"

(gdb) set $i = 0
(gdb) while($i p ((data_string *) con->response.headers->data[$i])->value->ptr
 >set $i++
 >end
$580 = 0x1bbb3fa0 "Sat, 21 Aug 2010 13:58:07 GMT"
$581 = 0x1bbb4b80 "Apache/2.2.3 (Red Hat)"
$582 = 0x1bbb74c0 "Wed, 14 Jul 2010 10:52:28 GMT"
$583 = 0x1bbb7560 "\"11e3cd7-1f22-c8229300\""
$584 = 0x1bbb3d80 "bytes"
$585 = 0x1bbb3e20 "7970"
$586 = 0x1bbb7080 "text/html; charset=UTF-8"

以上代码都来自于 lighttpd 源码。version 1.4.26。

gdb batch commands && cscope

昨天在跟 lighttpd 的 mod_cache 模块,在 plugins_call_handle_subrequest() 的时候调用的是 mod_cache.c 的 mod_cache_handle_memory_storage() 函数。但我在用实际数据跟踪的时候(html 和带 ? 的页面请求),都没有看到该函数真正执行到里面去:

如果是已经缓存了页面,根本就看不到 subrequest 部分被调用;如果删除缓存的文件,或者设置了不缓存的文件,虽然会进入 subrequest 部分,但因为此时 con->use_cache_file 标志为 0,则在函数刚进入的时候就会返回。那么,这个函数还有什么作用呢?

用日志法和 trace 法,包括在 gdb 里面动态跟踪,有一个问题就是:只能看到执行了的部分。至于没有执行到的部分,以及为什么没有被执行到,就不清楚了。比如这里,即使我在 mod_cache.c:mod_cache_handle_memory_storage 处设置断点,但因为 cache 住的时候根本就不会走到这个函数,所以我还是搞不清楚为啥没有走到这一步。

这时候只能从源代码入手,查找哪个函数调用了 mod_cache.c:mod_cache_handle_memory_storage。利用前面的 trace 法和 debug 日志,可以看出是在 response.c 中调用了,但并不确定是否还有其他地方调用了。此时可首先利用 cscope 分析源代码。

cscope link

配置好 cscope 后,在 vim 中使用

 :cs find c mod_cache_handle_memory_storage

查找所有调用了它的函数。但会发现找不到。使用

 :cs find c handle_subrequest

也不行。因为这里涉及到了回调函数,以及宏定义。似乎 scope 对这些支持还不是很好:

2740 int mod_cache_plugin_init(plugin *p) {
2741     p->version = LIGHTTPD_VERSION_ID;
2742     p->name = buffer_init_string("cache");
2743
2744     p->init = mod_cache_init;
2745     p->handle_uri_clean = mod_cache_uri_handler;
2746     p->handle_docroot = mod_cache_docroot_handler;
2747 #ifdef LIGHTTPD_V14
2748     p->handle_response_start = mod_cache_handle_response_start;
2749     p->handle_response_filter = mod_cache_handle_response_filter;
2750     p->handle_subrequest = mod_cache_handle_memory_storage;
2751 #else
2752     p->handle_response_header = mod_cache_handle_response_start;
2753     p->handle_filter_response_content = mod_cache_handle_response_filter;
......
src/mod_cache.c

另外,src/plugin.c 中定义了一些函数的宏 PLUGIN_TO_SLOT(相当于作者偷了一把懒, hehe …):

...
265 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean)
266 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw)
267 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done)
268 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close)
269 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)
270 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start)
271 PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_RESPONSE_START, handle_response_start) 
...
src/plugin.c

这时候就只能利用之前对代码的整体分析的理解才能明白上述关系,这样我们才能找到实际调用函数 plugin.c:plugins_call_handle_subrequest,再利用 cscope 往上回溯到 response.c:http_response_prepare –> connection.c:connection_state_machine(此时最好把 Tlist 打开,这样很容易看到回溯后所在的函数位置)。

connection.c:connection_state_matchine
    v (con->state: case CON_STATE_HANDLE_REQUEST)
  response.c:http_response_prepare
      v
    plugin.c:plugins_call_handle_subrequest
        v
      [callback] plugin *p->handle_subrequest

这里我们没明白的就是随着状态的变化(con->state),response.c:http_response_prepare 分别是在什么地方返回——这个函数比较长,return 的点也比较多。如果我在这个函数处设置一个断点,然后一次次地在这个断点出打印出我关心的信息,当然也可以,但效率比较低,也比较累。此时,可以使用 gdb 提供的批处理方法:-batch 和 -x 参数。

总的来说,gdb 的命令比较多,大概统计了一下,超过 600 个。为了使用批处理,设置如下:

~/.gdbinit
set breakpoint pending on
set print pretty on
set print array on

breakpoint pending 是在动态加载的模块(如 mod_cache)中设置断点时比较有用,否则 gdb 要提示是否设置,而在 batch 模式下则不可用,会导致退出。当然,这条指令也可以写到 gdb 的批处理脚本中。

下面看看这个批处理脚本:

sh# cat /tmp/gdb.commands
b mod_cache.c:mod_cache_handle_memory_storage
b response.c:http_response_prepare
b response.c:763
r -D -f /etc/lighttpd.conf
while 1
    p "[INFO] con->state"
    p  con->state
    p "[INFO] *con->uri.path"
    p  *con.uri.path
    p "[INFO] con->use_cache_file"
    p  con->use_cache_file
    p "[INFO] finish"
    fin
    c

这里 response.c:763 是我关心的行,但每次在 response.c:http_response_prepare 停住并打印我所希望知道的一些变量。这里还打印了一些 [INFO] 信息,方便阅读。

运行:

[Intranet root@oplive-test2 /root]
#gdb -batch -x /tmp/gdb.commands lighttpd | tee /tmp/gdb.out
No source file named mod_cache.c.
Breakpoint 1 (mod_cache.c:mod_cache_handle_memory_storage) pending.
Breakpoint 2 at 0x4081a0: file response.c, line 215.
Breakpoint 3 at 0x4081cd: file response.c, line 763.

Breakpoint 2, http_response_prepare (srv=0xfb90010, con=0xfbdc120) at response.c:215
215	handler_t http_response_prepare(server *srv, connection *con) {
$1 =   "[INFO] con->state"
$2 = CON_STATE_HANDLE_REQUEST
$3 =   "[INFO] *con->uri.path"
$4 = {
  ptr = 0x0, 
  used = 0, 
  size = 0, 
  ref_count = 0
}
$5 =   "[INFO] con->use_cache_file"
$6 = 0
$7 =   "[INFO] finish"
Breakpoint 3, http_response_prepare (srv=0xfb90010, con=0xfbdc120) at response.c:763
763		switch(r = plugins_call_handle_subrequest(srv, con)) {

Breakpoint 1, mod_cache_handle_memory_storage (srv=0xfb90010, con=0xfbdc120, p_d=0xfba5170) at mod_cache.c:2350
2350	{
$8 =   "[INFO] con->state"
$9 = CON_STATE_HANDLE_REQUEST
$10 =   "[INFO] con->uri.path"
$11 = {
  ptr = 0xfc301a0 "/blog/", 
  used = 7, 
  size = 64, 
  ref_count = 0
}
$12 =   "[INFO] *con->use_cache_file"
$13 = 0
$14 =   "[INFO] finish"
0x0000000000417eaa in plugins_call_handle_subrequest (srv=0xfb90010, con=0xfbdc120) at plugin.c:266
266	PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)
Value returned is $15 = HANDLER_GO_ON

Program received signal SIGSEGV, Segmentation fault.
0x0000003094c78d60 in strlen () from /lib64/libc.so.6
$16 =   "[INFO] con->state"/tmp/gdb.commands:15: Error in sourced command file:

No symbol "con" in current context.

以上为没有 cache 住的情况(文件不存在或过期)。

上面高亮的片段显示了 response.c:http_response_prepare 的各次返回点。

这个脚本的问题是,在 cache 住的时候,虽然使用了 -batch 参数,但是它不知道应该在何时退出,因为 while 的表达式一直为真。只能 CTRL-C 退出。

在Visual Studio Code (VSCode) 中遇到 "unable to start debugging, Unexpected GDB output" 错误,并伴随着类似信息 "d:/xx/: No such file or directory",通常意味着GDB(GNU调试器)无法找到指定的工作目录 (`d:/xxx`)。这可能是由于路径错误、文件不存在或者VSCode配置的问题。 解决这个问题的步骤可以包括: 1. **检查路径**:确保 `d:/xxx` 路径下的文件确实存在。如果不存在,你需要创建它或者修改调试配置中的工作目录。 2. **更新VSCode配置**:检查你的launch.json 文件,这是VSCode调试配置的主要文件,确认`program` 和 `cwd`(当前工作目录)设置是否正确。比如: ``` { "name": "C++ Debug", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}", "args": [], "cwd": "${workspaceFolder}", ... } ``` 确保`cwd`指向的是程序实际运行的文件夹,而不是临时的或不存在的路径。 3. **环境变量**:检查GDB命令行是否有相关的环境变量设置,如`GDB_PATH`或`PWD`,它们可能影响了GDB对目录的理解。 4. **清除缓存**:重启VSCode并尝试清除调试器的缓存,有时旧的配置可能导致问题。可以在用户设置里搜索 "debugger.gdbpath" 或 "debuggerPath" 来清理相关配置。 5. **更新GDB版本**:有时候,特定版本的GDB与VSCode集成可能出现兼容性问题,你可以试试升级或降级GDB。 如果以上都排查无果,建议查看VSCode的官方文档、社区论坛或开发者帮助中心,寻找更多解决方案。如果你有具体的VSCode配置或项目结构,可以提供更详细的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值