我是从 main 函数开始分析的
tinyinst-converage.cpp
中的 面函数
int main(int argc, char **argv)
{
instrumentation = new LiteCov();
instrumentation->Init(argc, argv);
int target_opt_ind = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--") == 0) {
target_opt_ind = i + 1;
break;
}
}
int target_argc = (target_opt_ind) ? argc - target_opt_ind : 0;
char **target_argv = (target_opt_ind) ? argv + target_opt_ind : NULL;
unsigned int pid = GetIntOption("-pid", argc, argv, 0);
persist = GetBinaryOption("-persist", argc, argv, false);
num_iterations = GetIntOption("-iterations", argc, argv, 1);
char *outfile = GetOption("-coverage_file", argc, argv);
if (!target_argc && !pid) {
printf("Usage:\\n");
printf("%s <options> -- <target command line>\\n", argv[0]);
printf("Or:\\n");
printf("%s <options> -pid <pid to attach to>\\n", argv[0]);
return 0;
}
Coverage coverage, newcoverage;
for (int i = 0; i < num_iterations; i++) {
RunTarget(target_argc, target_argv, pid, 0xFFFFFFFF);
Coverage newcoverage;
instrumentation->GetCoverage(newcoverage, true);
for (auto iter = newcoverage.begin(); iter != newcoverage.end(); iter++) {
printf("Found %zd new offsets in %s\\n", iter->offsets.size(), iter->module_name.c_str());
}
instrumentation->IgnoreCoverage(newcoverage);
MergeCoverage(coverage, newcoverage);
}
if (outfile) WriteCoverage(coverage, outfile);
instrumentation->Kill();
return 0;
}
首先创建一个 ListCov
对象
然后进行初始化 instrumentation->Init(argc, argv)
2.1 这里先初始化 父类 TinyInst::Init
void LiteCov::Init(int argc, char **argv) {
TinyInst::Init(argc, argv);
coverage_type = COVTYPE_BB;
char *option = GetOption("-covtype", argc, argv);
if (option) {
if (strcmp(option, "bb") == 0)
coverage_type = COVTYPE_BB;
else if (strcmp(option, "edge") == 0)
coverage_type = COVTYPE_EDGE;
else
FATAL("Unknown coverage type");
}
compare_coverage = GetBinaryOption("-cmp_coverage", argc, argv, false);
for (auto iter = instrumented_modules.begin();
iter != instrumented_modules.end(); iter++) {
ModuleInfo *module = *iter;
module->client_data = new ModuleCovData();
}
}
2.1.1 其中父类 TinyInst 的初始化 主要就是我们的根据我们的命令行参数进行一个对应的 参数初始化
// initializes instrumentation from command line options
void TinyInst::Init(int argc, char **argv) {
// init the debugger first
Debugger::Init(argc, argv);
#ifdef ARM64
#else
assembler_ = new X86Assembler(*this);
#endif
assembler_->Init();
instrumentation_disabled = false;
instrument_modules_on_load = GetBinaryOption("-instrument_modules_on_load", argc, argv, false);
patch_return_addresses = GetBinaryOption("-patch_return_addresses", argc, argv, false);
instrument_cross_module_calls = GetBinaryOption("-instrument_cross_module_calls", argc, argv, true);
persist_instrumentation_data = GetBinaryOption("-persist_instrumentation_data", argc, argv, true);
trace_basic_blocks = GetBinaryOption("-trace_basic_blocks", argc, argv, false);
trace_module_entries = GetBinaryOption("-trace_module_entries", argc, argv, false);
sp_offset = GetIntOption("-stack_offset", argc, argv, 0);
list <char *> module_names;
GetOptionAll("-instrument_module", argc, argv, &module_names);
for (auto iter = module_names.begin(); iter != module_names.end(); iter++) {
ModuleInfo *new_module = new ModuleInfo();
new_module->module_name = *iter;
instrumented_modules.push_back(new_module);
}
char *option;
indirect_instrumentation_mode = II_AUTO;
option = GetOption("-indirect_instrumentation", argc, argv);
if (option) {
if (strcmp(option, "none") == 0)
indirect_instrumentation_mode = II_NONE;
else if (strcmp(option, "local") == 0)
indirect_instrumentation_mode = II_LOCAL;
else if (strcmp(option, "global") == 0)
indirect_instrumentation_mode = II_GLOBAL;
else if (strcmp(option, "auto") == 0)
indirect_instrumentation_mode = II_AUTO;
else
FATAL("Unknown indirect instrumentation mode");
}
generate_unwind = GetBinaryOption("-generate_unwind", argc, argv, false);
if (!generate_unwind) {
unwind_generator = new UnwindGenerator(*this);
} else {
#ifdef __APPLE__
unwind_generator = new UnwindGeneratorMacOS(*this);
#else
WARN("Unwind generator not implemented for the current platform");
unwind_generator = new UnwindGenerator(*this);
#endif
}
}
其中初始化 了 Debugger类
根据逻辑 这个类是最先被初始化的
然后其中还初始化了 每个模块,且把每个 模块加入到了 instrumented_modules
中。
2.1接着回到 LiteCov
的初始化。
这里初始化 覆盖的 类型 coverage_type
默认是基本快覆盖率。
coverage_type = COVTYPE_BB;
char *option = GetOption("-covtype", argc, argv);
if (option) {
if (strcmp(option, "bb") == 0)
coverage_type = COVTYPE_BB;
else if (strcmp(option, "edge") == 0)
coverage_type = COVTYPE_EDGE;
else
FATAL("Unknown coverage type");
}
然后每个 模块的 client_data
初始化为 ModuleCovData类
记录覆盖率相关的内容
然后获取对应的 参数 格式的定义和 其他参数的初始化
int target_opt_ind = 0;
for (int i = 1; i < argc; i++) {54
if (strcmp(argv[i], "--") == 0) {
target_opt_ind = i + 1;
break;
}
}
int target_argc = (target_opt_ind) ? argc - target_opt_ind : 0;
char **target_argv = (target_opt_ind) ? argv + target_opt_ind : NULL;
unsigned int pid = GetIntOption("-pid", argc, argv, 0);
persist = GetBinaryOption("-persist", argc, argv, false);
num_iterations = GetIntOption("-iterations", argc, argv, 1);
char *outfile = GetOption("-coverage_file", argc, argv);
if (!target_argc && !pid) {
printf("Usage:\\n");
printf("%s <options> -- <target command line>\\n", argv[0]);
printf("Or:\\n");
printf("%s <options> -pid <pid to attach to>\\n", argv[0]);
return 0;
}
循环运算
for (int i = 0; i < num_iterations; i++) {
RunTarget(target_argc, target_argv, pid, 0xFFFFFFFF);
Coverage newcoverage;
instrumentation->GetCoverage(newcoverage, true);
for (auto iter = newcoverage.begin(); iter != newcoverage.end(); iter++) {
printf("Found %zd new offsets in %s\\n", iter->offsets.size(), iter->module_name.c_str());
}
instrumentation->IgnoreCoverage(newcoverage);
MergeCoverage(coverage, newcoverage);
}
4.1 RunTarget(target_argc, target_argv, pid, 0xFFFFFFFF);
函数定义在 tinyinst-coverage.cpp
中
在目标进程上运行single迭代,不管它是whole进程还是目标方法,也不管目标是否持久(应该知道在几乎所有情况下该怎么做)
void RunTarget(int argc, char **argv, unsigned int pid, uint32_t timeout) {
DebuggerStatus status;
if (instrumentation->IsTargetFunctionDefined()) {
if (cur_iteration == num_iterations) {
instrumentation->Kill();
cur_iteration = 0;
}
}
// else clear only when the target function is reached
if (!instrumentation->IsTargetFunctionDefined()) {
instrumentation->ClearCoverage();
}
if (instrumentation->IsTargetAlive() && persist) {
status = instrumentation->Continue(timeout);
} else {
instrumentation->Kill();
cur_iteration = 0;
if (argc) {
status = instrumentation->Run(argc, argv, timeout);
} else {
status = instrumentation->Attach(pid, timeout);
}
}
// if target function is defined,
// we should wait until it is hit
if (instrumentation->IsTargetFunctionDefined()) {
if ((status != DEBUGGER_TARGET_START) && argc) {
// try again with a clean process
WARN("Target function not reached, retrying with a clean process\\n");
instrumentation->Kill();
cur_iteration = 0;
status = instrumentation->Run(argc, argv, timeout);
}
if (status != DEBUGGER_TARGET_START) {
switch (status) {
case DEBUGGER_CRASHED:
FATAL("Process crashed before reaching the target method\\n");
break;
case DEBUGGER_HANGED:
FATAL("Process hanged before reaching the target method\\n");
break;
case DEBUGGER_PROCESS_EXIT:
FATAL("Process exited before reaching the target method\\n");
break;
default:
FATAL("An unknown problem occured before reaching the target method\\n");
break;
}
}
instrumentation->ClearCoverage();
status = instrumentation->Continue(timeout);
}
switch (status) {
case DEBUGGER_CRASHED:
printf("Process crashed\\n");
instrumentation->Kill();
break;
case DEBUGGER_HANGED:
printf("Process hanged\\n");
instrumentation->Kill();
break;
case DEBUGGER_PROCESS_EXIT:
if (instrumentation->IsTargetFunctionDefined()) {
printf("Process exit during target function\\n");
} else {
printf("Process finished normally\\n");
}
break;
case DEBUGGER_TARGET_END:
if (instrumentation->IsTargetFunctionDefined()) {
printf("Target function returned normally\\n");
cur_iteration++;
} else {
FATAL("Unexpected status received from the debugger\\n");
}
break;
default:
FATAL("Unexpected status received from the debugger\\n");
break;
}
}
4.1.1其中 IsTargetFunctionDefined
返回 target_function_defined
这个变量,这个变量的初始化在 debugger.cpp
Debugger::Init
中 默认为 false,然后根据我们的
target_module[0] || target_offset || target_method[0]
是否定义了 目标函数
从而赋值接 true 检查我们是否在持久化模式下运行
// check if we are running in persistence mode
if (target_module[0] || target_offset || target_method[0]) {
target_function_defined = true;
if ((target_module[0] == 0) || ((target_offset == 0) && (target_method[0] == 0))) {
FATAL("target_module and either target_offset or target_method must be specified together\\n");
}
}
4.1.2 然后是一个 根据我们的 一个 num_iterations
迭代数 判断是否会被 kill 掉。
4.1.3 如果没有到达目标 会 ClearCoverage
4.1.4 然后是一个 if 判断 首先先 Run
启动进程 如果下次循环,程序在存活就 Continue
if (instrumentation->IsTargetAlive() && persist) {
status = instrumentation->Continue(timeout);
} else {
instrumentation->Kill();
cur_iteration = 0;
if (argc) {
status = instrumentation->Run(argc, argv, timeout);
} else {
status = instrumentation->Attach(pid, timeout);
}
}
Run 函数
和 continues 函数
// starts the process
and waits for the next event
DebuggerStatus Debugger::Run(char *cmd, uint32_t timeout) {
attach_mode = false;
StartProcess(cmd);
return Continue(timeout);
}
DebuggerStatus Debugger::Run(int argc, char **argv, uint32_t timeout) {
char* cmd = NULL;
cmd = ArgvToCmd(argc, argv);
DebuggerStatus ret_dbg_status = Run(cmd, timeout);
free(cmd);
return ret_dbg_status;
}
// continues after Run() or previous Continue()
// return with a non-terminal status
DebuggerStatus Debugger::Continue(uint32_t timeout) {
if (!child_handle && (dbg_last_status != DEBUGGER_ATTACHED))
return DEBUGGER_PROCESS_EXIT;
if (loop_mode && (dbg_last_status == DEBUGGER_TARGET_END)) {
// saves us a breakpoint
dbg_last_status = DEBUGGER_TARGET_START;
return dbg_last_status;
}
dbg_last_status = DebugLoop(timeout);
if (dbg_last_status == DEBUGGER_PROCESS_EXIT) {
CloseHandle(child_handle);
CloseHandle(child_thread_handle);
child_handle = NULL;
child_thread_handle = NULL;
}
return dbg_last_status;
}
在 Run
函数里面 的 StartProcess(cmd);
这个函数初始化 dbg_continue_needed
参数为 false 这个参数在 (后面 Debugger::Continue()
中是一个 重要的标志位)然后删除断点 ,分配内存,创建进程 。
其中 Debugger::Continue()
的 DebugLoop()
很重要。
传入的参数为 timeout 的时间。
这里会根据不同的时间 进行不同的 处理。
// standard debugger loop that listens to events in the target process
DebuggerStatus Debugger::DebugLoop(uint32_t timeout, bool killing)
{
DebuggerStatus ret;
bool alive = true;
if (dbg_continue_needed) {
ContinueDebugEvent(dbg_debug_event.dwProcessId,
dbg_debug_event.dwThreadId,
dbg_continue_status);
}
LPDEBUG_EVENT DebugEv = &dbg_debug_event;
while (alive)
{
have_thread_context = false;
uint64_t begin_time = GetCurTime();
BOOL wait_ret = WaitForDebugEvent(DebugEv, 100);
uint64_t end_time = GetCurTime();
uint64_t time_elapsed = end_time - begin_time;
timeout = ((uint64_t)timeout >= time_elapsed) ? timeout - (uint32_t)time_elapsed : 0;
// printf("timeout: %u\\n", timeout);
// printf("time: %lld\\n", get_cur_time_us());
if (wait_ret) {
dbg_continue_needed = true;
} else {
dbg_continue_needed = false;
}
if (timeout == 0) return DEBUGGER_HANGED;
if (!wait_ret) {
//printf("WaitForDebugEvent returned 0\\n");
continue;
}
dbg_continue_status = DBG_CONTINUE;
thread_id = DebugEv->dwThreadId;
// printf("eventCode: %x\\n", DebugEv->dwDebugEventCode);
switch (DebugEv->dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
if (!killing) {
ret = HandleExceptionInternal(&DebugEv->u.Exception.ExceptionRecord);
if (ret == DEBUGGER_CRASHED) OnCrashed(&last_exception);
if (ret != DEBUGGER_CONTINUE) return ret;
} else {
dbg_continue_status = DBG_EXCEPTION_NOT_HANDLED;
}
break;
case CREATE_THREAD_DEBUG_EVENT:
break;
case CREATE_PROCESS_DEBUG_EVENT: {
if (trace_debug_events) printf("Debugger: Process created or attached\\n");
OnProcessCreated();
CloseHandle(DebugEv->u.CreateProcessInfo.hFile);
break;
}
case EXIT_THREAD_DEBUG_EVENT:
break;
case EXIT_PROCESS_DEBUG_EVENT:
if (trace_debug_events) printf("Debugger: Process exit\\n");
OnProcessExit();
alive = false;
break;
case LOAD_DLL_DEBUG_EVENT: {
if(!killing) HandleDllLoadInternal(&DebugEv->u.LoadDll);
CloseHandle(DebugEv->u.LoadDll.hFile);
break;
}
case UNLOAD_DLL_DEBUG_EVENT:
if (trace_debug_events)
printf("Debugger: Unloaded module from %p\\n", DebugEv->u.UnloadDll.lpBaseOfDll);
OnModuleUnloaded(DebugEv->u.UnloadDll.lpBaseOfDll);
break;
default:
break;
}
ContinueDebugEvent(DebugEv->dwProcessId,
DebugEv->dwThreadId,
dbg_continue_status);
}
return DEBUGGER_PROCESS_EXIT;
}
其中进行不同的 操作 是通过 DebugEv
的 dwDebugEventCode
属性来实现的。
异常调试事件 EXCEPTION_DEBUG_EVENT
HandleExceptionInternal
函数
对于断点的情况调用 Debugger:: HandleDebuggerBreakpoint函数
,返回DEBUGGER_TARGET_START 或者 DEBUGGER_CONTINU
对于 EXCEPTION_ACCESS_VIOLATION 的情况如果指定了 target_module 和target_method/target_offset 并且 ExceptionAddress 是PERSIST_END_EXCEPTION 说明这是目标方法返回产生的异常,调用 Debugger::HandleTargetEnded函数
,返回 DEBUGGER_TARGET_END;
对于其它情况返回 DEBUGGER_CRASHED
创建进程调试事件 CREATE_PROCESS_DEBUG_EVENT
当进程被创建 或 附加的时候会调用 Debugger::OnProcessCreated
函数
在附加到现有进程的情况下,将生成主模块的dll加载事件处理主模块负载
然后会根据 fliename 找到对应的 文件进行调用 Debugger::OnModuleLoaded
不是附加的情况,这里 在模块的入口添加一个断点。
退出进程调试事件 EXIT_PROCESS_DEBUG_EVENT
调用 Debugger::OnProcessExit()
这里会对申请的 数据进行一个清除,其中也包括对 共享的内存的清理。
还要处理 僵尸进程
加载DLL调试事件 LOAD_DLL_DEBUG_EVENT
加载dll时调用 如果 child_entrypoint_reached
是 true 这里会在后面调用 OnModuleLoaded
函数 在这个函数中, GetTargetAddress
函数获取 对应模块的 地址,然后添加 断点。
卸载DLL调试事件 UNLOAD_DLL_DEBUG_EVENT
取消loader 的时候使用
GetCoverage
接着调用这个函数
检查该模块是否已经在覆盖范围列表中(如果客户端使用非空的初始覆盖范围调用)
如果没有就加入到 coverage
列表中。
IgnoreCoverage
函数调用
设置要忽略的(新)覆盖率
通过名字获得 对应的 模块。然后通过偏移量来确定 需要忽略的地方。 data->ignore_coverage.insert(iter->offsets.begin(), iter->offsets.end());
然后是 MergeCoverage
函数
对 coverage
进行一个管理
最后写入 文件 WriteCoverage
上面是我的对 tinyinst-converage.cpp
中 main 函数运行过程的分析。
在安全客上有对所有函数的分析。
https://www.anquanke.com/post/id/234925#h2-4
其中 一共 4个 关键的 cpp 文件。
debugger.cpp
实现调试器功能
在目标方法地址处设置一个断点,程序运行到目标方法时触发这个断点产生异常被调试器捕获,此时保存参数和返回地址,修改返回地址使得目标方法返回时产生一个异常又被调试器捕获,此时恢复参数和返回地址,恢复之前设置的断点,以此循环。
tinyinst.cpp
这里涉及到的就是插桩具体的一些实现
设计到的 ModuleInfo 初始化
ModuleInfo::ModuleInfo() {
module_name[0] = 0;
module_header = NULL;
min_address = 0;
max_address = 0;
loaded = false;
instrumented = false;
instrumented_code_local = NULL;
instrumented_code_remote = NULL;
instrumented_code_remote_previous = NULL;
instrumented_code_size = 0;
unwind_data = NULL;
}
TinyInst中支持两种转换间接调用/跳转的方法:
1.本地列表
(TinyInst::InstrumentLocalIndirect)
2.全局hash表(默认)
(TinyInst::InitGlobalJumptable、TinyInst::InstrumentGlobalIndirect)
主要插桩代码 是在 x86_assembler.cpp
中