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

背景:

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

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


问题:

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

我专门查看了etrace 文档 ,它说:

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

如果查看repo, exampleexample2文件夹中的文件没有区别。 在example2只有一个额外的.h文件。

另一方面,如果你看一下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 `*/ 

题:

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

我需要#include任何文件吗?

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

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

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


更新1:

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

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

 Hexadecimal number > 0xffffffff non-portable" 

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

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


更新2:

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

 Use of uninitialized value within %SYMBOLTABLE in list assignment at etrace2.pl line 99,  line 1. \-- ??? | \-- ??? \-- ??? | \-- ??? | | \-- ??? \-- ??? | \-- ??? \-- ??? | \-- ??? \-- ??? | \-- ??? \-- ??? | \-- ??? \-- ??? | \-- ??? 

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

 gcc -g -finstrument-functions -shared -fPIC ptrace.c -o etrace.so -I  

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

 #ifndef __ETRACE_H_ #define __ETRACE_H_ #include  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作为#define ,使用etrace.h作为#include

修复Perl脚本

hex数> 0xffffffff非便携式

这是来自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所做的是从您指定的标题中获取函数名称。 例如,在example2的情况下,这将是以下内容:

 #include "crumble.h" #define PTRACE_REFERENCE_FUNCTION Crumble_buy 

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

 CFLAGS = -g -finstrument-functions -I. 

注意添加包含-I. 。 标题中的符号地址(在我们的例子中是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 = ; } else { # This is static $baseAddress = 0; } 

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