gperftools 无法识别文件

gperftools failing to identify files

有没有办法避免 Google Performance Tools 将文件列为“??:?”,也就是说,无法找到哪个文件包含它正在报告的函数?如何确定哪个库包含被调用的函数?

$ env LD_PRELOAD="/usr/lib/libprofiler.so.0" \
   CPUPROFILE=output.prof python script.py
$ google-pprof --text --files /usr/bin/python output.prof 
Using local file /usr/bin/python.
Using local file output.prof.
Removing _L_unlock_13 from all stack traces.
Total: 433 samples
 362  83.6%  83.6%      362  83.6% dtrsm_ ??:?
  58  13.4%  97.0%       58  13.4% dgemm_ ??:?
   1   0.2%  97.2%        1   0.2% PyDict_GetItem /.../Objects/dictobject.c
   1   0.2%  97.5%        1   0.2% PyParser_AddToken /.../Parser/parser.c
...

我的目标是能够在具有许多已编译 C 扩展模块的 python 包中分析 C 代码。在上面的玩具示例中,我该怎么做才能找到 "dtrsm_" 的定义位置?如果有多个加载的库包含同名函数,有什么方法可以判断调用的是哪个版本?

只是 Google 有问题的函数名称。您在上面显示的那些是在 LAPACK 中定义的。 dtrsm 用于求解矩阵方程。 dgemm 用于矩阵相乘。

您需要知道的是 1) 为什么要调用它们,以及 2) 矩阵有多大。

为了找出调用它们的原因,我所做的只是检查各个堆栈样本,as here

矩阵大小很重要的原因是如果它们很小,这些 LAPACK 例程实际上可以花费相对大部分的时间来对输入进行分类,例如通过调用函数 LSAME。

C/C++ 如果相同的 pre-processed 源文件(例如 #includes 扩展)包含相同符号的重复定义,则无法编译。 (请注意,在 C++ 的情况下,根据 compiler-specific 方案,符号被破坏以合并参数签名以便于重载函数,否则无法区分。)

链接器只关心未解析的符号(因此应该没有什么可以阻止多个库同时调用它们各自的 internally-defined 具有重合名称的函数)。如果一个文件调用一个已声明但未定义的函数,并且多个可用的库实现了该符号,那么链接器可以自由选择(比如 search-path 中的优先级)替换哪个版本。(顺便说一下,这是相同的gperftools 或 hpctoolkit 等分析器能够注入自身并改变另一个应用程序的正常行为的机制。)

由于不同的库映射到不同的内存页,因此应该可以(从内存地址)识别哪个库包含函数的执行版本。事实上,GNU 调试器可以识别包含代码的库,即使它无法命名函数。

$      gdb python
(gdb)  run -c "from numpy import *; linalg.inv(random.random((1000,1000)))"
CTRL-C
(gdb)  backtrace
#0 0x00007ffff5ba9df8 in dtrsm_ () from /usr/lib/libblas.so.3
...
#3 0x00007ffff420df83 in ?? () from /.../numpy/linalg/_umath_linalg.so

Linux(或者更确切地说是 GNU C 库)提供了 "backtrace" 调用(用于从调用堆栈获取指针列表),以及用于自动转换的 "backtrace_symbols" 调用每个指向描述性字符串的指针,例如:

"/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7fc429929ec5]"

Gperftools 可以(根据对 github 镜像的查询判断)调用通用 "backtrace",但 "backtrace_symbols" 它 "forks out to pprof to do the actual symbolizing"。这是一个 fairly-epic perl 脚本,看起来很可能是“??”来自。

至关重要的是,google-pprof 试图报告定义函数的 source-file(和 line-number),而不是包含 machine-code 的 binary-file (通常在堆栈跟踪中引用)。它调用 "nm" 实用程序。在我的系统上,似乎(通过 运行ning "nm -l -D")libblas 与 libc 和 python 二进制文件不同,已去除此类调试符号(大概是为了优化),解释了结果。

回答最初的问题:call-stack 样本应该明确明确地指定调用的是哪个版本。这些可能可以使用几个月前在 google-pprof 中添加的选项来转储,或者(对于 time-intensive 函数)可以通过使用 gdb 手动重采样来粗略确定。 (甚至可以想象,可以调整 g-pprof 以在其输出摘要中明确识别二进制文件路径。)或者可以 运行 "nm" (和 grep)候选 binaries/libraries (其中 short-list 可以通过 运行ning "strings" 在探查器的原始输出上获得,以及其他方法)。如果源代码可访问(对于 grep)或者库很流行(在网络上),那么当然(根据 Mike Dunlavey),查询函数名称可能是最简单的。理论上“??:?”可以通过仔细重新编译有问题的 objects.

来解决