如何在 Docker 容器内的 Linux Perf 工具中启用调试符号?

12

我正在使用基于“ubuntu”标签的Docker容器,并且无法使Linux Perf工具显示调试符号。

以下是我演示问题的步骤。

首先,我启动一个容器,这里使用交互式shell。

$ docker run -t -i ubuntu:14.04 /bin/bash

然后从容器提示符处安装Linux性能工具。
$ apt-get update
$ apt-get install -y linux-tools-common linux-tools-generic linux-tools-`uname -r`

我现在可以使用perf工具了。我的内核版本是3.16.0-77-generic

现在,我将安装gcc,编译一个测试程序,并尝试在perf record下运行它。

$ apt-get install -y gcc

我将测试程序粘贴到test.c文件中:
#include <stdio.h>

int function(int i) {
    int j;
    for(j = 2; j <= i / 2; j++) {
        if (i % j == 0) {
            return 0;
        }
    }
    return 1;
}

int main() {
    int i;
    for(i = 2; i < 100000; i++) {
        if(function(i)) {
            printf("%d\n", i);
        }
    }
}

然后进行编译,运行并报告:
$ gcc -g -O0 test.c && perf record ./a.out && perf report

输出结果大致如下:
  72.38%  a.out  a.out              [.] 0x0000000000000544
   8.37%  a.out  a.out              [.] 0x000000000000055a
   8.30%  a.out  a.out              [.] 0x000000000000053d
   7.81%  a.out  a.out              [.] 0x0000000000000551
   0.40%  a.out  a.out              [.] 0x0000000000000540

这段代码没有符号,即使可执行文件有符号信息。

在容器外进行相同的一般步骤可以正常工作,并显示类似于以下内容:

96.96%  a.out  a.out             [.] function 
0.35%  a.out  libc-2.19.so       [.] _IO_file_xsputn@@GLIBC_2.2.5
0.14%  a.out  [kernel.kallsyms]  [k] update_curr
0.12%  a.out  [kernel.kallsyms]  [k] update_cfs_shares
0.11%  a.out  [kernel.kallsyms]  [k] _raw_spin_lock_irqsave                 

在主机系统中,我已经通过切换为root用户并执行以下命令打开了内核符号:

$ echo 0 > /proc/sys/kernel/kptr_restrict 

如何使容器化版本正常工作并显示调试符号?

2个回答

13

使用-v /:/host标志运行容器,并在容器中使用--symfs /host标志运行perf report可以解决这个问题:

 96.59%  a.out  a.out              [.] function
  2.93%  a.out  [kernel.kallsyms]  [k] 0xffffffff8105144a
  0.13%  a.out  [nvidia]           [k] 0x00000000002eda57
  0.11%  a.out  libc-2.19.so       [.] vfprintf
  0.11%  a.out  libc-2.19.so       [.] 0x0000000000049980
  0.09%  a.out  a.out              [.] main
  0.02%  a.out  libc-2.19.so       [.] _IO_file_write
  0.02%  a.out  libc-2.19.so       [.] write

为什么它不能直接工作的原因之一?从perf script输出的结果可以看出:

...
           a.out    24 3374818.880960: cycles:  ffffffff81141140 __perf_event__output_id_sample ([kernel.kallsyms])
           a.out    24 3374818.881012: cycles:  ffffffff817319fd _raw_spin_lock_irqsave ([kernel.kallsyms])
           a.out    24 3374818.882217: cycles:  ffffffff8109aba3 ttwu_do_activate.constprop.75 ([kernel.kallsyms])
           a.out    24 3374818.884071: cycles:            40053d [unknown] (/var/lib/docker/aufs/diff/9bd2d4389cf7ad185405245b1f5c7d24d461bd565757880bfb4f970d3f4f7915/a.out)
           a.out    24 3374818.885329: cycles:            400544 [unknown] (/var/lib/docker/aufs/diff/9bd2d4389cf7ad185405245b1f5c7d24d461bd565757880bfb4f970d3f4f7915/a.out)
...

注意 /var/lib/docker/aufs 路径。这是来自主机的路径,容器中不存在,您需要帮助 perf report 定位它。这可能是因为 mmap 事件由 perf 在任何控制组之外跟踪,并且 perf 不尝试重新映射这些路径。

另一个选择是在主机端运行 perf,如sudo perf record -a docker run -ti <container name>。但此处必须进行系统范围的集合(使用 -a 标志),因为容器由 Docker daemon 进程产生,该进程不在我们运行的 Docker 客户端工具的进程层次结构中。


2
谢谢,那正是它。 此外,为了获取内核符号,您还需要添加: --kallsyms=/proc/kallsyms - Nathan Whitehead
3
如果有人需要的话:更简单的方法是使用docker run命令并加上参数-v /var/lib/docker/:/var/lib/docker,这样perf就不需要任何特殊参数来正确解析符号。 - Nathan Whitehead

8

另一种无需更改容器运行方式(因此可以对已经运行的进程进行分析)的方法是使用bindfs在主机上挂载容器根目录:

bindfs /proc/$(docker inspect --format {{.State.Pid}} $CONTAINER_ID)/root /foo

然后以 perf report --symfs /foo 的形式运行 perf report。

你需要在全局范围内运行 perf record,但可以将其限制为仅收集特定容器的事件:

perf record -g -a -F 100 -e cpu-clock -G docker/$(docker inspect --format {{.Id}} $CONTAINER_ID) sleep 90

bindfs命令/proc/$(docker inspect --format {{.State.Pid}} $CONTAINER_ID)/root /foo会导致/foo指向主机文件系统(主机的root文件夹)。为什么呢?谢谢! - Eduardo Lucio
我在RHEL上遇到了同样的问题;请注意,Eduardo已经提出了一个(未回答的)问题https://dev59.com/T6rka4cB1Zd3GeqPaTlc - Radim Vansa
问题现在已经有答案,并且这个问题在 bindfs 1.13.10 中已经修复。 - leopold.talirz
似乎这个方法不适用于containerd。 - Min Zhou
@MinZhou 可以确认一下吗?你找到了其他关于 containerd 的方法吗? - mR.aTA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接