昨天在跟 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 退出。