Хронологический след вызовов функций в С++ с использованием etrace

Фон:

У меня есть один большой инструмент моделирования, и мне нужно понять его логическое поведение. Чтобы сделать это, большую часть помощи я получу, если у меня есть хронологический порядок вызовов функций, для минимального рабочего примера.

Я нашел много инструментов в Интернете, например CygProfiler и etrace. Я стал настолько несчастным в поиске решения, которое я начал придерживаться самого сумасшедшего решения, использующего "шаг в" с отладчиком. Это хороший вариант, если у вас небольшая программа, но не полный инструмент моделирования.


Проблема:

Одна из проблем, с которыми я сталкиваюсь, заключается в том, что вышеупомянутые решения изначально предназначены для C, и они сгенерируют статический файл (*.o) при компиляции. С другой стороны, инструмент моделирования генерирует общую библиотеку (.so). У меня мало знаний о материалах более низкого уровня, поэтому я, кажется, терплю неудачу, когда я пытаюсь связать их.

Я посмотрел на etrace документацию, и он говорит:

Чтобы узнать, как изменить ptrace.c для работы с динамической библиотекой, посмотрите каталог example2. Источники также создают автономные исполняемый файл, но макрос PTRACE_REFERENCE_FUNCTION определяется так же, как и это будет для динамической библиотеки.

Если вы посмотрите на репо, нет никакой разницы между файлами в папках example и example2. Только в файле 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 any дополнительный файл. Просто свяжите свой код с ptrace.c и используйте -finstrument-functions в качестве опции компиляции для gcc. Это должно сделать это.

Как связать код С++, который создается с помощью make файлов с помощью ptrace.c

Заключительное примечание:. Я был бы признателен, если бы кто-то несла с моим невежеством и обеспечил пошаговое решение моего вопроса.


Обновление 1:

Мне удалось добавить библиотеки, связанные с etrace, в инструмент моделирования, и он отлично работает.

Однако (вероятно, потому, что скрипты слишком стары или не предназначены для использования с С++), я получаю следующую ошибку при использовании perl script, предоставленный по умолчанию etrace

Hexadecimal number > 0xffffffff non-portable"

Вероятно, это немного изменяет характер этого вопроса, превращая его больше в проблему, связанную с perl.

Если эта проблема решена, я надеюсь, что etrace будет работать со сложным проектом, и я расскажу подробности


Обновление 2:

Я принял предложения от @Harry, и я считаю, что это будет работать в большинстве проектов. Однако в моем случае я получаю следующее из perl script:

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

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

Из-за автогенерированных make файлов я использовал LD_PRELOAD для загрузки общей библиотеки для etrace.so, которую получил следующим образом:

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

Я создал dummy 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 для #define и etrace.h для #include.

Ответы

Ответ 1

Фиксация Perl Script

Шестнадцатеричное число > 0xffffffff не переносимое "

Это предупреждение от hex, поскольку оно обнаруживает, возможно, не переносимое значение (что-то > 32 бита).

В самом верху script добавьте следующее:

use bigint qw/hex oct/;

Когда этот инструмент был написан, я подозреваю, что люди были на 32-битных машинах. Вы можете скомпилировать программу, используя 32-разрядную версию с флагом -m32, но если вы измените perl script, как указано выше, вам это не понадобится.

Обратите внимание, что если вы находитесь на Mac, вы не можете использовать mknod способ, которым он использовался в script для создания канала; вам нужно использовать 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

Затем вы должны отредактировать make файл, чтобы убедиться, что файл заголовка можно найти:

CFLAGS = -g -finstrument-functions -I.

Обратите внимание на добавление include -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'
        };

Обратите внимание на верхнее подчеркивание, потому что это на Mac, используя clang. Во время выполнения эти адреса неверны, но их относительные смещения. Если вы можете решить, что такое смещение, вы можете настроить адреса, которые вы получаете во время выполнения, чтобы найти фактический символ. Код, который делает следующее:

 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. Код C в ptrace использует этот MACRO и, если он определен, выводит адрес этой функции как первое. Затем он вычисляет смещение и для всех последующих адресов настраивает их на эту величину, просматривая правильный символ в хеше.