QingYun/tracer

语言: C++

git: https://github.com/QingYun/tracer

一个C ++库,使您可以在运行时装饰函数。
A C++ library that enables you to decorate a function at runtime.
README.md (中文)

曳光弹

一个C ++库,使您可以在运行时装饰函数。

介绍

Tracer可以在调用给定函数之前或之后插入回调,而无需修改其代码。中文版

用法

包括tracer / tracer.h并将Boost的路径添加到include directories选项中。

示踪剂

使用TRACER_TRACE(func)作为类型名称来定义变量,可以将其称为跟踪器,func是函数指针,我们将其称为原始函数。

此TRACER_TRACE宏将扩展为一个类:

TRACER_TRACE(&Foo) foo;

// equals to 
typedef TRACER_TRACE(&Foo) FooTracer;
FooTracer foo;

跟踪器有三种公共方法:

  • 之前()

返回对tracer :: Signal对象的引用,该信号将在调用原始函数之前触发。

tracer :: Signal派生自boost.signals2,因此您可以使用其所有公共方法,例如使用connect(回调)来注册一个新的回调,每次触发此信号时都会调用该回调。

回调不应返回任何内容,因此其签名就像void(bool&,...)。第一个参数总是一个bool&,一个默认值为true的标志,表示是否应该调用原始函数,所以如果你不想调用原始函数,只需简单地给它赋值;剩余参数是对原始函数的参数的引用,顺序相同。如果原始函数是非静态函数成员,则会有一个额外的参数作为第二个参数,这是对此的引用

例子:

- `int(int)` => `void(bool&, int&)`
- `std::string (C::*)(std::string)` => `void(bool&, C*&, std::string&)`
  • 后()

返回对tracer :: Signal对象的引用,该对象将在调用原始函数后触发

回调签名与Before()的签名几乎相同,唯一的区别是第一个参数变为bool而不是bool&表示是否已调用原始函数,第二个参数将是对其返回值的引用。

  • RealFunc()

返回一个与原始函数具有相同签名的函数指针,并且对此函数指针的调用将直接定向到原始函数,而不会触发回调。

除了从boost.signals2继承的方法之外,tracer :: Signal中还有两个新方法:

  • once(cb):like connect,但回调将在第一次调用后自动断开。
  • connect_without_params(cb):喜欢connect但总是收到一个void()函数。当你对参数没兴趣时,这非常方便。

示例代码:

class C {
    std::string str_;
public:
    C(const std::string &str) :
        str_(str)
    {}
    std::string Get() {
        return str_;
    }
};

int main() {
    TRACER_TRACE(&C::Get) t;
    C a("A"), b("B");

    // conn is for managing connection state
    auto conn = t.Before().connect([&b] (bool&, C *&self) {
        // forward all the calls to b
        self = &b;
    });

    std::string result = a.Get();   // result == "B"
    conn.disconnect();
    result = a.Get();               // result == "A"
}

传递给TRACER_TRACE宏的参数是仅仅是变量还是复杂表达式并不重要,只要它计算为函数指针即可。这意味着如果要使用一组重载函数跟踪某个函数,可以将其转换为所需的签名,例如static_cast <func_point_type>(&func_name)。当您尝试跟踪在编译时仅获得其签名的函数并且在运行时之前不知道其地址(例如COM组件中的函数)时,它也非常有用。


记录仪

一些辅助类来记录跟踪函数的调用信息。

CallCountRecorder

记录调用函数的次数。

您可以使用CallCountRecorder <decltype(tracer)> recorder(tracer)或auto recorder = RecordCallCount(tracer)来创建一个。

它有两种公共方法:

  • bool HasBeenCalled():返回一个bool值,指示此函数是否至少被调用一次。
  • std :: size_t CallCount():返回确切的时间。

ArgRecorder

记录传递的所有参数。

您可以使用ArgRecorder <decltype(tracer)> recorder(tracer)或auto recorder = RecordArgs(tracer)来创建一个。

它有一个公共方法:

  • nth-param-type Arg <I>(n):返回第n次调用中的第I个参数。

RetValRecorder

记录返回值。

RetValRecorder <decltype(tracer)>记录器(示踪剂)或自动记录器= RecordRetVal(示踪剂)

  • ret-val-type RetVal(n):返回第n个调用的返回值。

CallStackRecorder

记录调用堆栈

CallStackRecorder <decltype(tracer)> recorder(tracer)或auto recorder = RecordCallStack(tracer)

  • CallStack GetCallStack(n):返回第n个调用的调用堆栈。 CallStack有两个公共方法: vector <CallStackEntry> Entries():返回所有记录,Entries()[0]是原始函数的直接调用者,Entries()[1]是调用者的调用者,依此类推。 CallStackEntry有四种公共方法: File():返回文件的名称。 Line():返回行号 FuncName():函数的名称 FuncAddr():函数的地址 bool IsCalledBy(f):返回一个bool值,表示函数f是否出现在记录的调用堆栈中。 f可以是函数名称作为字符串或函数指针

例:

struct C {
    void Foo() {}
};

void RunFoo() {
    C c;
    c.Foo();
}

int main() {
    TRACER_TRACE(&C::Foo) foo;
    auto fc = tracer::RecordCallStack(foo);

    RunFoo();

    // print the information of the direct caller
    for (auto itr : fc.GetCallStack(0).Entries())
        std::cout << itr.File() << " " << itr.Line() << " " << itr.FuncName() << std::endl;

    // check caller by name
    assert(true == fc.GetCallStack(0).IsCalledBy("RunFoo"));

    void(*f)();
    f = [] () { RunFoo(); };
    f();
    // or by function pointer
    assert(true == fc.GetCallStack(1).IsCalledBy(f));
}

MI信

宏TRACER_TRACE_WITH可以混合跟踪器和记录器的功能。它需要两个参数,一个函数指针就像你传入TRACER_TRACE的那个,以及一个记录列表(RecorderType1)(RecorderType2)。

要记录函数C :: Foo的调用次数及其调用堆栈,您可以编写:

TRACER_TRACE_WITH(C::Foo, (tracer::CallCountRecorder)(tracer::CallStackRecorder)) foo;

然后,foo将继承CallCountRecorder和CallStackRecorder的所有公共方法。这意味着您可以使用foo.Before()。connect()来插入回调;使用foo.HasBeenCalled()检查是否调用它和foo.GetCallStack()来查看调用栈。

正如您所料,您也可以将自己的记录器混合到跟踪器中,只要它具有签名为Recorder <TracerType> :: Recorder(TracerType&)的构造函数即可。

本文使用googletrans自动翻译,仅供参考, 原文来自github.com

en_README.md

Tracer

A C++ library that enables you to decorate a function at runtime.

Introduction

Tracer can insert callbacks before or after a call to a given function without the need of modifying its code. 中文版

Usage

Include tracer/tracer.h and add the path to Boost into the include directories option.

Tracers

Use TRACER_TRACE(func) as a type name to define a variable, which could be called a tracer, func is a function pointer and we call it the original function.

This TRACER_TRACE macro will expand to a class:

TRACER_TRACE(&Foo) foo;

// equals to 
typedef TRACER_TRACE(&Foo) FooTracer;
FooTracer foo;

A tracer has three public methods:

  • Before()

Returns a reference to a tracer::Signal object, this signal will be triggered just before the original function being called.

tracer::Signal derives from boost.signals2, so you can use all its public methods, like using connect(callback) to register a new callback which will be called every time when this signal is triggered.

The callback should not return anything so its signature is like void(bool&, ...). The first parameter is always a bool&, a flag with a default value of true, indicates if the original function should be called, so if you don't want the original function to be called, just simply assign false to it; remained parameters are references to the arguments to the original function, in the same order. If the original function is a non-static function member, then there will be a extra parameter as the second parameter, wich is the reference to this

Examples:

- `int(int)` => `void(bool&, int&)`
- `std::string (C::*)(std::string)` => `void(bool&, C*&, std::string&)`
  • After()

Returns a reference to a tracer::Signal object which will be triggered after the original function is called

The callback signature is almost the same as the one for Before(), the only difference is the first parameter becomes a bool instead of a bool& to indicates whether the original function has been called and the second parameter will be a reference to its return value.

  • RealFunc()

Returns a function pointer that has the same signature as the original function and invocations to this function pointer will be directly directed to the original function without triggering callbacks.

Besides methods inherited from boost.signals2, there are two new methods in tracer::Signal:

  • once(cb) : like connect but the callback will be disconnected automatically after the first invocation.
  • connect_without_params(cb) : like connect but always receives a void() function. It's very handy when you have no interests in parameters.

example code:

class C {
    std::string str_;
public:
    C(const std::string &str) :
        str_(str)
    {}
    std::string Get() {
        return str_;
    }
};

int main() {
    TRACER_TRACE(&C::Get) t;
    C a("A"), b("B");

    // conn is for managing connection state
    auto conn = t.Before().connect([&b] (bool&, C *&self) {
        // forward all the calls to b
        self = &b;
    });

    std::string result = a.Get();   // result == "B"
    conn.disconnect();
    result = a.Get();               // result == "A"
}

It doesn't matter whether the argument passed to the TRACER_TRACE macro is just a variable or a complex expression, as long as it evaluates to a function pointer. That means if you want to trace a certain function with a set of overloaded functions, you can just convert it to the signature you want, like static_cast<func_point_type>(&func_name). It also very useful when you try to trace a function you only got its signature at compile-time and you don't know its address until run-time, such as functions in a COM component.


Recorders

Some helper classes to record calling information of a traced function.

CallCountRecorder

Record how many times a function is called.

You can use CallCountRecorder<decltype(tracer)> recorder(tracer) or auto recorder = RecordCallCount(tracer) to create one.

It has two public methods:

  • bool HasBeenCalled() : Returns a bool value indicates whether this function is called at least once.
  • std::size_t CallCount() : Returns the exact times.

ArgRecorder

Record all the arguments passed.

You can use ArgRecorder<decltype(tracer)> recorder(tracer) or auto recorder = RecordArgs(tracer) to create one.

It has one public method:

  • nth-param-type Arg<I>(n) : Returns the Ith argument in the nth call.

RetValRecorder

Record the return value.

RetValRecorder<decltype(tracer)> recorder(tracer) or auto recorder = RecordRetVal(tracer)

  • ret-val-type RetVal(n) : Returns the return value of the nth call.

CallStackRecorder

Record the call stack

CallStackRecorder<decltype(tracer)> recorder(tracer) or auto recorder = RecordCallStack(tracer)

  • CallStack GetCallStack(n) : Returns the call stack of the nth call.

    CallStack has two public methods :

    • vector<CallStackEntry> Entries() : Returns all the records, Entries()[0] is the direct caller of the original function, Entries()[1] is the caller's caller and so on.

      CallStackEntry has four public methods:

      • File() : Returns the name of the file.
      • Line() : Returns the line number
      • FuncName() : The name of the function
      • FuncAddr() : The address of the function
    • bool IsCalledBy(f) : Returns a bool value indicates whether the function f appears in the recorded call stack. f could be a function name as a string or a function pointer

Example:

struct C {
    void Foo() {}
};

void RunFoo() {
    C c;
    c.Foo();
}

int main() {
    TRACER_TRACE(&C::Foo) foo;
    auto fc = tracer::RecordCallStack(foo);

    RunFoo();

    // print the information of the direct caller
    for (auto itr : fc.GetCallStack(0).Entries())
        std::cout << itr.File() << " " << itr.Line() << " " << itr.FuncName() << std::endl;

    // check caller by name
    assert(true == fc.GetCallStack(0).IsCalledBy("RunFoo"));

    void(*f)();
    f = [] () { RunFoo(); };
    f();
    // or by function pointer
    assert(true == fc.GetCallStack(1).IsCalledBy(f));
}

Mixin

The macro TRACER_TRACE_WITH could mix the functions of tracer and recorder. It expects two arguments, a function pointer just like the one you would pass into TRACER_TRACE, and a list of recorder in the form of (RecorderType1)(RecorderType2).

To record how many times the function C::Foo is called as well as its call stacks, you write:

TRACER_TRACE_WITH(C::Foo, (tracer::CallCountRecorder)(tracer::CallStackRecorder)) foo;

Then, foo will inherit all the public methods of CallCountRecorder and CallStackRecorder. That means you can use foo.Before().connect() to insert a callback; use foo.HasBeenCalled() check whether it is called and foo.GetCallStack() to see the call stacks.

As you may expect, you can also mix your own recorder into a tracer as long as it has a constructor with the signature of Recorder<TracerType>::Recorder(TracerType&)