c++ - 如何将 etrace 与动态库一起使用以按时间顺序跟踪 C++ 中的函数调用?

标签 c++ c perl macros profiling

背景:

我有一个大型模拟工具,我需要了解它的逻辑行为。为了做到这一点,如果我有函数调用的时间顺序,我会得到最大的帮助,作为一个最小的工作示例。

我在网上找到了很多工具,例如 CygProfileretrace .在找到解决方案时我变得非常痛苦,以至于我开始遵循最疯狂的解决方案,即在调试器中使用“step into”。如果您有一个小程序但没有完整的模拟工具,这是一个不错的选择。


问题:

我面临的一个问题是上述解决方案最初是针对C的。并且它们在编译时会生成一个静态文件 ( *.o )。另一方面,仿真工具生成一个共享库(.so)。我对较低级别的东西知之甚少,所以当我尝试链接它们时似乎失败了。

我专门看了etrace documentation ,它说:

To see how to modify ptrace.c to work with a dynamic library, look at the example2 directory. The sources there also create a stand-alone executable, but the PTRACE_REFERENCE_FUNCTION macro is defined just as it would be for a dynamic library.

如果您查看存储库,example 中的文件没有区别和 example2文件夹。只是多了一个.h文件在 example2 .

另一方面,如果您查看 src/ptrace.c上面写着:

When using ptrace on a dynamic library, you must set the PTRACE_REFERENCE_FUNCTION macro to be the name of a function in the library. The address of this function when loaded will be the first line output to the trace file and will permit the translation of the other entry and exit pointers to their symbolic names. You may set the macro PTRACE_INCLUDE with any #include directives needed for that function to be accesible to this source file.

下面是注释代码:

/* When using ptrace on a dynamic library, the following must be defined:
#include "any files needed for PTRACE_REFERENCE_FUNCTION"
#define PTRACE_REFERENCE_FUNCTION functionName
`*/

问题:

本质上问题如下:如何使用etrace有动态库吗?

我需要#include 任何文件吗?

To trace a stand-alone program, there is no need to #include any additional file. Just link your code against ptrace.c and use the -finstrument-functions option as a compile option for gcc. This should do it.

如何将通过 makefile 构建的 C++ 代码链接到 ptrace.c

最后说明:如果有人能容忍我的无知并为我的问题提供分步解决方案,我将不胜感激。


更新 1:

我设法将与etrace相关的库添加到模拟工具中,并且运行良好。

但是,(可能是因为脚本太旧,或者不适合与 C++ 一起使用)使用 perl script 时出现以下错误etrace 默认提供

Hexadecimal number > 0xffffffff non-portable"

可能这会稍微改变这个问题的性质,在这一点上将其更多地转变为与 perl 相关的问题。

如果这个问题解决了,希望etrace将处理一个复杂的项目,我将提供详细信息


更新 2:

我接受了@Harry 的建议,我相信这在大多数项目中都适用。但是在我的情况下,我从 perl 脚本中得到以下信息:

Use of uninitialized value within %SYMBOLTABLE in list assignment at etrace2.pl line 99, <CALL_DATA> line 1.

\-- ???
|   \-- ???
\-- ???
|   \-- ???
|   |   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???

由于自动生成的 makefile,我使用了 LD_PRELOAD为 etrace.so 加载共享库,我得到如下:

gcc -g -finstrument-functions -shared -fPIC ptrace.c -o etrace.so -I <path-to-etrace>

我在工具中创建了虚拟 etrace.h:

#ifndef __ETRACE_H_
#define __ETRACE_H_

#include <stdio.h>

void Crumble_buy(char * what, int quantity, char * unit);


void Crumble_buy(char * what, int quantity, char * unit)
{
    printf("buy %d %s of %s\n", quantity, unit, what);
}

#endif

并使用了Crumble_buy对于#defineetrace.h对于#include .

最佳答案

修复 Perl 脚本

Hexadecimal number > 0xffffffff non-portable"

这是来自 hex 的警告,因为它正在检测可能不可移植的值(大于 32 位)。

在脚本的最顶部,添加以下内容:

use bigint qw/hex oct/;

编写此工具时,我怀疑人们使用的是 32 位机器。您可以使用带有 -m32 标志的 32 位编译程序,但如果您如上所述更改 perl 脚本,则不需要这样做。

注意,如果您使用的是 Mac,则不能像在脚本中使用 mknod 那样来创建管道;您需要使用不带参数的 mkfifo

在 Linux 上,添加上述 bigint 修复程序有效。然后你需要从同一个目录运行这两个命令,我使用 example2:

../src/etrace.pl crumble
# Switch to a different terminal
./crumble

我在 Mac 和 Linux 上得到了这个

\-- main
|   \-- Crumble_make_apple_crumble
|   |   \-- Crumble_buy_stuff
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   \-- Crumble_prepare_apples
|   |   |   \-- Crumble_skin_and_dice
|   |   \-- Crumble_mix
|   |   \-- Crumble_finalize
|   |   |   \-- Crumble_put
|   |   |   \-- Crumble_put
|   |   \-- Crumble_cook
|   |   |   \-- Crumble_put
|   |   |   \-- Crumble_bake

关于动态库...

加载动态库时,目标文件中的地址不是运行时将使用的地址。 etrace 所做的是从您指定的 header 中获取函数名称。例如,对于 example2,这将是以下内容:

#include "crumble.h"
#define PTRACE_REFERENCE_FUNCTION Crumble_buy

然后您将编辑 makefile 以确保可以找到头文件:

CFLAGS = -g -finstrument-functions -I.

注意添加的include -I.。来自 header 的符号地址(在我们的例子中,Crumble_buy)用于计算目标文件和实际地址之间的偏移量;这允许程序计算正确的地址以找到符号。

如果您查看 nm 的输出,您会得到如下内容:

0000000100000960 T _Crumble_bake
00000001000005b0 T _Crumble_buy
0000000100000640 T _Crumble_buy_stuff
00000001000009f0 T _Crumble_cook

左边的地址是相对的,也就是说,在运行时,这些地址实际上是变化的。 etrace.pl 程序将这些存储在这样的哈希中:

$VAR1 = {
          '4294969696' => '_Crumble_bake',
          '4294969424' => '_Crumble_put',
          '4294970096' => '_main',
          '4294969264' => '_Crumble_mix',
          '4294970704' => '_gnu_ptrace_close',
          '4294967296' => '__mh_execute_header',
          '4294968752' => '_Crumble_buy',
          '4294968896' => '_Crumble_buy_stuff',
          '4294969952' => '_Crumble_make_apple_crumble',
          '4294969184' => '_Crumble_prepare_apples',
          '4294971512' => '___GNU_PTRACE_FILE__',
          '4294971504' => '_gnu_ptrace.first',
          '4294970208' => '_gnu_ptrace',
          '4294970656' => '___cyg_profile_func_exit',
          '4294970608' => '___cyg_profile_func_enter',
          '4294969552' => '_Crumble_finalize',
          '4294971508' => '_gnu_ptrace.active',
          '4294969840' => '_Crumble_cook',
          '4294969088' => '_Crumble_skin_and_dice',
          '4294970352' => '_gnu_ptrace_init'
        };

注意前导下划线,因为这是在使用 clang 的 Mac 上。在运行时,这些地址不正确,但它们的相对偏移量是正确的。如果您可以计算出偏移量是多少,则可以调整在运行时获得的地址以找到实际符号。执行此操作的代码如下:

 if ($offsetLine =~ m/^$REFERENCE_OFFSET\s+($SYMBOL_NAME)\s+($HEX_NUMBER)$/) {
    # This is a dynamic library; need to calculate the load offset
    my $offsetSymbol  = "_$1";
    my $offsetAddress = hex $2; 

    my %offsetTable = reverse %SYMBOLTABLE;

    print Dumper(\%offsetTable);
    $baseAddress = $offsetTable{$offsetSymbol} - $offsetAddress;
    #print("offsetSymbol == $offsetSymbol\n");
    #print("offsetAddress == $offsetAddress\n");
    #print("baseoffsetAddress == $offsetAddress\n");
    $offsetLine = <CALL_DATA>;
  } else {
    # This is static
    $baseAddress = 0;
  }

这就是 #define PTRACE_REFERENCE_FUNCTION Crumble_buy 行的用途。 ptrace 中的 C 代码正在使用该 MACRO,如果已定义,则首先输出该函数的地址。然后它计算偏移量,并为所有后续地址调整此数量,在哈希中查找正确的符号。

关于c++ - 如何将 etrace 与动态库一起使用以按时间顺序跟踪 C++ 中的函数调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36896140/

相关文章:

c - 我如何让它解码(Perl cbc-crypt 到 C cbc_crypt 转换)

perl - 454 错误:使用 Net::SMTP::SSL 发送电子邮件时身份验证失败

c++ - 运算符==重载

c - 如何从 16 位数据计算 CRC-32?

c - 删除文件中的记录(C语言编程)

c - 使用 fgets 和使用 printf 只打印一些行

perl - 如何使用 Class::ArrayObjects?

c++ - 在 C++ 中中断函数

c++ - OpenCL - GPU 上的多线程

c# - 一种语言将 undefined 评估为等于 false 是否很常见?如果是这样,为什么这样做?