A simple hook library for testing and debuging
Tracer可以让你在不修改代码的前提下, 在指定函数调用前后以回调的形式插入代码.
首先, 包含头文件tracer/tracer.h, 然后配置一下Boost路径.
###Tracers
使用TRACER_TRACE(func)来定义一个变量, 这个变量我们称之为一个tracer, func应该是一个求值为函数指针的表达式, 我们称之为原始函数.
这个宏展开后是一个类:
TRACER_TRACE(&Foo) foo;
// 等价于
typedef TRACER_TRACE(&Foo) FooTracer;
FooTracer foo;
tracer有三个公开的方法:
-
Before()
返回一个
tracer::Signal对象的引用, 它会在调用原始函数前被触发.tracer::Signal派生自boost.signals2, 可以使用其所有公开接口, 比如使用connect(callback)注册一个回调, 这个回调会在每次信号被触发时被调用.回调应该是一个没有返回值的可调用对象, 其第一个参数应该是
bool&类型的, 表示是否想要调用原始函数, 将其赋值为false将不会调用原始函数; 如果原始函数是一个类的非静态成员函数, 则第二个参数应该是类指针的引用, 传入时它的值即为this, 在回调中可以修改它, 将其指向其他对象, 这样可以将调用转移到其他对象上; 剩下的参数依次是原始函数参数的引用, 它们都可以在回调中被修改.比如原始函数的签名为
int(int), 则回调的类型应该是void(bool&, int&) -
After()
返回一个
tracer::Signal对象的引用, 它会在调用原始函数后被触发.回调类型类似于
Before()的回调, 但是第一个参数应该是bool的, 表示是否已经调用了原始函数, 接下来是返回值的引用, 剩下的和Before()一样 -
RealFunc()
返回一个和原始函数签名一样的函数指针, 调用这个指针可以避免触发信号, 直接调用原始函数.
除了boost.signals2原有的接口, tracer::Signal还提供了两个新的方法:
once(cb): 类似于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");
// 注册一个在C::Get调用前被调用回调, conn是回调的链接管理器
auto conn = t.Before().connect([&b] (bool&, C *&self) {
// 将所有对C::Get的调用都无条件转移到对象b身上
self = &b;
});
std::string result = a.Get(); // result == "B"
conn.disconnect();
result = a.Get(); // result == "A"
}
传递给宏的可以是任意求值为函数指针的表达式, 因此如果需要追踪有多个重载版本的函数的某一重载版本,
只需传递static_cast<func_point_type>(&func_name)即可, 这个技巧还可以用来处理那些在编译期只知道类型, 地址在运行期才能获取的函数
###Recorders
recorder是用来记录tracer调用情况的类.
####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)> recorder(tracer) 或者 auto recorder = RecordRetVal(tracer)创建,
它有一个公开方法:
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对象有4个公开方法:File(): 返回函数所在的文件名称Line(): 返回函数在文件中的行号FuncName(): 返回函数名FuncAddr(): 返回函数地址
-
bool IsCalledBy(f):f可以是字符串形式的函数名或者是函数指针, 如果在调用栈中找到匹配项则返回true, 否则返回false.
-
示例:
struct C {
void Foo() {}
};
void RunFoo() {
C c;
c.Foo();
}
int main() {
TRACER_TRACE(&C::Foo) foo;
auto fc = tracer::RecordCallStack(foo);
RunFoo();
// 输出第一次调用中各个调用者的信息
for (auto itr : fc.GetCallStack(0).Entries())
std::cout << itr.File() << " " << itr.Line() << " " << itr.FuncName() << std::endl;
// 检查第一次调用时调用栈中是否有名为"RunFoo"的调用者
assert(true == fc.GetCallStack(0).IsCalledBy("RunFoo"));
void(*f)();
f = [] () { RunFoo(); };
f();
// 通过比较函数地址检查第二次调用时是否有调用者f
assert(true == fc.GetCallStack(1).IsCalledBy(f));
}
###Mixin Tracer
使用TRACER_TRACE_WITH可以把 tracer 和 recorder 的功能混合到一起.
这个宏接受两个参数, 第一个参数是函数名, 第二个宏是要混合的 recorder 列表, 是(recorder1)(recorder2)形式的.
比如要记录C::Foo的调用次数和调用栈, 可以这样写
TRACER_TRACE_WITH(C::Foo, (tracer::CallCountRecorder)(tracer::CallStackRecorder)) foo;
foo继承了这两个 recorder 的接口, 所以你可以用foo.Before().connect()插入调用前的回调, 也可以用foo.HasBeenCalled()判断C::Foo是否被调用过, 还可以用foo.GetCallStack()来获取调用栈.
除了内置的 recorder 以外, 只要是用Recorder<T>::Recorder(T&)形式构造的自定义 recorder 也可以用TRACER_TRACE_WITH混合.