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