使用etrace在C++中按时间顺序跟踪函数调用

cro*_*oss 15 c c++ macros perl profiling

背景:

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

我在网上找到了很多工具,比如CygProfileretrace.我找到了一个解决方案,我开始遵循使用调试器"步入"的最疯狂的解决方案,我变得非常痛苦.如果您有一个小程序但不是一个完整的模拟工具,这是一个很好的选择.


问题:

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

我特意看了一下etrace 文档,它说:

要了解如何修改ptrace.c以使用动态库,请查看example2目录.这里的源代码也创建了一个独立的可执行文件,但PTRACE_REFERENCE_FUNCTION宏的定义与动态库一样.

如果你看看repo,文件exampleexample2文件夹之间没有区别.只有一个额外的.h文件example2.

另一方面,如果你看src/ptrace.c那里它说:

在动态库上使用ptrace时,必须将PTRACE_REFERENCE_FUNCTION宏设置为库中函数的名称.此功能的加载时,地址将线路输出第一跟踪文件,并且允许其他出入境指针的翻译符号名.您可以将宏PTRACE_INCLUDE设置为该函数可访问此源文件所需的任何#include指令.

稍微低于评论代码:

/* 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
`*/
Run Code Online (Sandbox Code Playgroud)

题:

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

我需要#include任何文件吗?

要跟踪独立程序,不需要#include任何其他文件.只需将代码链接到ptrace.c,并使用-finstrument-functions选项作为gcc的编译选项.这应该做到这一点.

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

最后的注意事项:如果有人承担我的无知并为我的问题提供逐步解决方案,我将不胜感激.


更新1:

我设法将与etrace相关的库添加到模拟工具中,并且执行正常.

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

Hexadecimal number > 0xffffffff non-portable"
Run Code Online (Sandbox Code Playgroud)

可能这会改变这个问题的性质,在这一点上更多地转向与perl相关的问题.

如果这个问题得到解决,我希望etrace能够处理一个复杂的项目,我会提供详细信息


更新2:

我接受了@Harry的建议,我相信这对大多数项目都有效.但是在我的情况下,我从perl脚本中获得以下内容:

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

\-- ???
|   \-- ???
\-- ???
|   \-- ???
|   |   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???
\-- ???
|   \-- ???
Run Code Online (Sandbox Code Playgroud)

由于autegenerated makefile我使用LD_PRELOAD加载etrace.so的共享库,我得到如下:

gcc -g -finstrument-functions -shared -fPIC ptrace.c -o etrace.so -I <path-to-etrace>
Run Code Online (Sandbox Code Playgroud)

我在工具中创建了虚拟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
Run Code Online (Sandbox Code Playgroud)

并且使用Crumble_buy#defineetrace.h#include.

Har*_*rry 4

修复 Perl 脚本

十六进制数 > 0xffffffff 不可移植”

这是一个警告,hex因为它检测到可能不可移植的值(大于 32 位的值)。

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

use bigint qw/hex oct/;
Run Code Online (Sandbox Code Playgroud)

当这个工具被编写时,我怀疑人们使用的是 32 位机器。您可以使用带有 flag 的 32 位编译程序-m32,但如果您按照上面提到的方式更改 perl 脚本,则不需要这样做。

请注意,如果您使用的是 Mac,则无法使用mknod脚本中使用的方式来创建管道;你需要使用mkfifo不带参数的方法。

在 Linux 上,添加bigint上述修复即可工作。然后,您需要从同一目录运行这两个命令,我使用以下命令执行此操作example2

../src/etrace.pl crumble
# Switch to a different terminal
./crumble
Run Code Online (Sandbox Code Playgroud)

我在 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
Run Code Online (Sandbox Code Playgroud)

关于动态库...

当加载动态库时,目标文件中的地址并不是运行时将使用的地址。etrace 的作用是从您指定的标头中获取函数名称。例如,在 的情况下example2,将如下所示:

#include "crumble.h"
#define PTRACE_REFERENCE_FUNCTION Crumble_buy
Run Code Online (Sandbox Code Playgroud)

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

CFLAGS = -g -finstrument-functions -I.
Run Code Online (Sandbox Code Playgroud)

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

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

0000000100000960 T _Crumble_bake
00000001000005b0 T _Crumble_buy
0000000100000640 T _Crumble_buy_stuff
00000001000009f0 T _Crumble_cook
Run Code Online (Sandbox Code Playgroud)

左边的地址是相对的,也就是说,在运行时,这些地址实际上会发生变化。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'
        };
Run Code Online (Sandbox Code Playgroud)

请注意前导下划线,因为这是在使用 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;
  }
Run Code Online (Sandbox Code Playgroud)

这就是这条线#define PTRACE_REFERENCE_FUNCTION Crumble_buy的用途。ptrace 中的代码C正在使用该宏,如果定义了,则首先输出该函数的地址。然后,它计算偏移量,并针对所有后续地址,按此量调整它们,在散列中查找正确的符号。

  • 它使用动态库,无法解析符号或地址偏移错误。你不断改变问题,即它是一个移动目标,如果我回答你的下一个问题,接下来的几天将会有另一个问题。您需要了解 etrace 的工作原理,并使用 crumble 程序使用静态和动态方法使其工作。目前,听起来您正在尝试将一些您不理解的内容添加到您不理解的程序中,以便理解它。 (2认同)