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

来源:互联网 发布:怎样经营淘宝 编辑:程序博客网 时间:2024/05/16 17:28

这一个月忙得昏天黑地。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");27432744     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_V142748     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 #else2752     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 个。为了使用批处理,设置如下:

~/.gdbinitset breakpoint pending onset print pretty onset print array on

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

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

sh# cat /tmp/gdb.commandsb mod_cache.c:mod_cache_handle_memory_storageb response.c:http_response_prepareb response.c:763r -D -f /etc/lighttpd.confwhile 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.outNo 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:215215handler_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:763763switch(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:23502350{$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:266266PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)Value returned is $15 = HANDLER_GO_ONProgram 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 退出。

原创粉丝点击