[您也可以查看此文档的 单页版本。]

调试 Subversion

使用 SVN_DBG 调试

SVN_DBG 调试工具是一个 C 预处理器宏,它将调试输出发送到 stdout(默认)或 stderr,同时不会干扰 SVN 测试套件。

它提供了一种替代调试器(如 gdb)的方案,或者提供额外的信息来帮助使用调试器。在无法使用调试器的情况下,它可能特别有用。

svn_debug 模块包含两个调试辅助宏,它们将调用的文件:行和类似 printf 的参数打印到 #SVN_DBG_OUTPUT stdio 流(默认情况下为 #stdout)

SVN_DBG( ( const char *fmt, ...) ) /* double braces are neccessary */
SVN_DBG_PROPS( ( apr_hash_t *props, const char *header_fmt, ...) )

控制 SVN_DBG 输出

  • 只要 svn 使用 --enable-maintainer-mode 配置,SVN_DBG 就处于启用状态。
  • SVN 测试套件会自动关闭 SVN_DBG 输出,要手动抑制输出,请在 shell 环境中将 SVN_DBG_QUIET 变量设置为 1。
  • 完成后,请确保从您提交的任何代码以及您发送到列表的任何补丁中删除所有 SVN_DBGSVN_DBG_PROPS 宏实例。(又称:不要忘记在病人身上留下手术刀!)

SVN_DBG 宏定义和代码位于

显示 SVN_DBG 宏用法的示例补丁

Index: subversion/libsvn_fs_fs/fs_fs.c
===================================================================
--- subversion/libsvn_fs_fs/fs_fs.c     (revision 1476635)
+++ subversion/libsvn_fs_fs/fs_fs.c     (working copy)
@@ -2303,6 +2303,9 @@ get_node_revision_body(node_revision_t **noderev_p
 
   /* First, try a cache lookup. If that succeeds, we are done here. */
   SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
+  SVN_DBG(("Getting %s from: %s\n", 
+           svn_fs_fs__id_unparse(id),
+           is_cached ? "cache" : "disk"));
   if (is_cached)
     return SVN_NO_ERROR;

显示 SVN_DBG_PROPS 宏用法的示例补丁

Index: subversion/svn/proplist-cmd.c
===================================================================
--- subversion/svn/proplist-cmd.c	(revision 1475745)
+++ subversion/svn/proplist-cmd.c	(working copy)
@@ -221,6 +221,7 @@ svn_cl__proplist(apr_getopt_t *os,
                                       URL, &(opt_state->start_revision),
                                       &rev, ctx, scratch_pool));
+      /*  this can be called with svn proplist  --revprop -r <rev> */
+      SVN_DBG_PROPS((proplist,"The variable apr_hash_t *proplist contains: "));
       if (opt_state->xml)
         {
           svn_stringbuf_t *sb = NULL;

调试服务器

调试 DAV 服务器

'mod_dav_svn.so' 包含主要的 Subversion 服务器逻辑;它作为 mod_dav 中的一个模块运行,而 mod_dav 作为 httpd 中的一个模块运行。如果 httpd 可能使用动态共享模块,您可能需要在设置 mod_dav_svn 中的断点之前 设置断点等待(在 ~/.gdbinit 中)。或者,您可以启动 httpd,中断它,设置断点,然后继续

   % gdb httpd
   (gdb) run -X
   ^C
   (gdb) break some_func_in_mod_dav_svn
   (gdb) continue

-X 开关等效于 -DONE_PROCESS 和 -DNO_DETACH,它们分别确保 httpd 作为单个线程运行并保持连接到 tty。它一启动,就会坐下来等待请求;这时您就可以按下 Ctrl+C 并设置断点。

您可能需要查看 Apache 的运行时日志

   /usr/local/apache2/logs/error_log
   /usr/local/apache2/logs/access_log

以帮助确定可能出现的问题以及设置断点的位置。

或者,在工作副本中运行 ./subversion/tests/cmdline/davautocheck.sh --gdb 将使用该工作副本中的 mod_dav_svn 启动 httpd。然后,您可以针对它运行单独的 Python 测试:./basic_tests.py --url=https://127.0.0.1:3691/.

调试 ra_svn 客户端和服务器,在 Unix 上

ra_svn 中的错误通常会以以下其中一条神秘的错误消息表现出来

  svn: Malformed network data
  svn: Connection closed unexpectedly

(第一条消息也可能意味着数据流在隧道模式下被用户点文件或钩子脚本破坏;请参阅 问题 #1145。)第一条消息通常意味着您必须调试客户端;第二条消息通常意味着您必须调试服务器。

使用带有 --disable-shared --enable-maintainer-mode 的构建来调试 ra_svn 最容易。使用后一个选项,错误消息将准确地告诉您在何处设置断点;否则,请在 marshal.c:vparse_tuple() 的末尾查找返回“格式错误的网络数据”错误的行号。

要调试客户端,只需在 gdb 中调出它,设置断点,然后运行有问题的命令

  % gdb svn
  (gdb) break marshal.c:NNN
  (gdb) run ARGS
  Breakpoint 1, vparse_tuple (list=___, pool=___, fmt=___, 
    ap=___) at subversion/libsvn_ra_svn/marshal.c:NNN
  NNN                                 "Malformed network data");

有一些有用的信息

  • 回溯将准确地告诉您哪个协议交换失败。

  • "print *conn" 将显示连接缓冲区。read_buf、read_ptr 和 read_end 表示读取缓冲区,它可以显示编组器正在查看的数据。(由于 read_buf 通常在 read_end 处没有以 0 结尾,因此请小心不要错误地假设缓冲区中存在垃圾数据。)

  • 格式字符串决定了编组器预期看到的内容。

要调试以守护进程模式运行的服务器,请在 gdb 中启动它,设置断点(通常,客户端上的“连接意外关闭”错误表示服务器上的“格式错误的网络数据”错误,尽管它也可能表示核心转储),并使用“-X”选项运行它以服务单个连接。

  % gdb svnserve
  (gdb) break marshal.c:NNN
  (gdb) run -X

然后运行有问题的客户端命令。从那里开始,就像调试客户端一样。

调试隧道模式下的服务器比较麻烦。您需要在 svnserve 的 main() 顶部附近添加类似“{ int x = 1; while (x); }”的内容,并将生成的 svnserve 放入服务器上的用户路径。然后启动操作,在服务器上使用 gdb 附加到进程,执行“set x = 0”,并根据需要逐步执行代码。

跟踪网络流量

跟踪客户端和服务器之间的网络流量有助于调试。有多种方法可以进行网络跟踪(此列表并不详尽)。

在进行网络跟踪时,您可能需要禁用压缩——请参阅 servers 配置文件中的 http-compression 参数。

使用 Wireshark 进行网络跟踪

使用 Wireshark(以前称为“Ethereal”)来窃听对话。

首先,确保在同一个 Wireshark 会话中捕获之间,您点击了“清除”,否则来自一个捕获(例如,HTTP 捕获)的过滤器可能会干扰其他捕获(例如,ra_svn 捕获)。

假设您已清除,那么

  1. 下拉“捕获”菜单,然后选择“捕获过滤器”。

  2. 如果调试 http://(WebDAV)协议,则在弹出的窗口中选择“HTTP TCP 端口 (80)”(这应该会导致过滤器字符串“tcp port http”)。

    如果调试 svn://(ra_svn)协议,则选择“新建”,为新过滤器命名(例如,“ra_svn”),并在过滤器字符串框中键入“tcp port 3690”。

    完成后,单击“确定”。

  3. 再次转到“捕获”菜单,这次选择“接口”,然后单击相应接口旁边的“选项”(您可能需要选择接口“lo”,即“环回”,假设服务器将在与客户端相同的机器上运行)。

  4. 通过取消选中相应的复选框来关闭混杂模式。

  5. 单击右下角的“开始”按钮以启动捕获。

  6. 运行您的 Subversion 客户端。

  7. 操作完成后,单击停止图标(以太网接口卡上的红色 X)(或捕获->停止应该可以)。现在您有一个捕获。它看起来像一个巨大的行列表。

  8. 单击协议列进行排序。

  9. 然后,单击第一行相关行以选择它;通常这只是第一行。

  10. 右键单击,然后选择跟踪 TCP 流。您将看到 Subversion 客户端 HTTP 转换的请求/响应对。

以上说明特定于 Wireshark 的图形版本(版本 0.99.6),不适用于称为“tshark”的命令行版本(对应于“tethereal”,从 Wireshark 被称为 Ethereal 的时候开始)。

使用 socat 进行网络跟踪

另一种选择是在 Subversion 客户端和服务器之间设置一个日志代理。一个简单的方法是使用 socat 程序。例如,要记录与 svnserve 实例的通信,请运行以下命令

socat -v TCP-LISTEN:9630,reuseaddr,fork TCP4:localhost:svn

然后使用 svn://127.0.0.1:9630/ 的 URL 基地址运行您的 svn 命令;socat 将从端口 9630 转发流量到正常的 svnserve 端口(3690),并将所有双向流量打印到标准错误,并在其前面加上 < 和 > 符号以显示流量方向。

要从加密的 https:// 连接记录可读的 HTTP,请运行一个使用 TLS 连接到服务器的 socat 代理

socat -v TCP-LISTEN:9630,reuseaddr,fork OPENSSL:example.com:443

然后将其用作普通 http:// 连接的代理

svn ls http://example.com/svn/repos/trunk \
    --config-option servers:global:http-proxy-host=localhost \
    --config-option servers:global:http-proxy-port=9630 \
    --config-option servers:global:http-compression=no

socat 代理记录纯 HTTP,而与服务器的所有网络流量都使用 TLS 加密。

使用 http-proxy 进行网络跟踪

如果您正在调试 http 客户端/服务器设置,可以使用网络调试代理,如 CharlesFiddler

设置此类代理后,您需要配置 Subversion 以使用该代理。这可以通过 servers 配置文件中的 http-proxy-hosthttp-proxy-port 参数来完成。您也可以使用这些选项在命令行中指定代理 --config-option 'servers:global:http-proxy-host=127.0.0.1' --config-option 'servers:global:http-proxy-port=8888'

跟踪内存泄漏

我们使用 APR 池,因此严格意义上来说,我们不太可能出现内存泄漏;我们分配的所有内存最终都会被清理。但有时某个操作会消耗比预期更多的内存;例如,检出大型源代码树不应该比检出小型源代码树消耗更多内存。当这种情况发生时,通常意味着我们正在从生命周期过长的池中分配内存。

如果您有喜欢的内存泄漏跟踪工具,您可以使用 `--enable-pool-debug` 配置(这将使每个池分配使用自己的 `malloc()`),安排在操作中途退出,然后使用该工具。如果没有,这里还有另一种方法。

  • 使用 `--enable-pool-debug=verbose-alloc` 配置。确保重新构建所有 APR 和 Subversion,以便每个分配都获得文件和行信息。

  • 运行操作,将 stderr 管道到一个文件。希望您有足够的磁盘空间。

  • 在文件中,您将看到许多类似以下内容的行:

        POOL DEBUG: [5383/1024] PCALLOC (      2763/      2763/      5419) \
        0x08102D48 "subversion/svn/main.c:612"                             \
        <subversion/libsvn_subr/auth.c:122> (118/118/0)
       

    您最关心的应该是第十个字段(带引号的字段),它提供了创建此分配池的文件和行号。转到该文件和行,并确定池的生命周期。在上面的示例中,`main.c:612` 表示此分配是在 svn 客户端的顶层池中进行的。如果此分配在操作过程中重复多次,则表明存在内存泄漏的来源。第十一个字段(带方括号的字段)提供了分配本身的文件和行号。