【DynamoRIO 入門教程】二: cbrtrace.c

首先注意,本例子 開頭
#include “utils.h”
utils 不是extension ,只是封裝了一個創建 日誌文件 的函數,源碼在 utils.c 裏,可以在 samples 文件夾裏找到。
所以,我們需要在 CMakeLists.txt 裏這樣寫:

add_library( cbrtrace  SHARED cbrtrace.c  utils.c)
然後要多加一個 drx extension:
use_DynamoRIO_extension(bbcount drx)
這個 extension  是在  utils.c 裏面調用的。

首先來看 dr_client_main 函數,該函數是最初被調用的函數,我們一般在裏面註冊各種事件的回調函數:

DR_EXPORT
void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
    drmgr_init();

    client_id = id;
    tls_idx = drmgr_register_tls_field();

    dr_register_exit_event(event_exit);
    if (!drmgr_register_thread_init_event(event_thread_init) ||
        !drmgr_register_thread_exit_event(event_thread_exit) ||
        !drmgr_register_bb_instrumentation_event(NULL, event_app_instruction, NULL))
        DR_ASSERT(false);
}

drmgr_init() 用來初始化插件 drmgr。

drmgr_register_tls_field() 爲每個線程保留 線程本地存儲(tls)插槽。返回槽的索引,該索引可以傳遞給drmgr_get_tls_field()和drmgr_set_tls_field()。如果沒有可用的插槽,則返回-1。所謂 tls 就是每個線程專屬的一個存儲器。通過 drmgr_set_tls_field() 來設定tls裏的值,用 drmgr_get_tls_field() 用來獲取tls 裏面的值。

dr_register_exit_event 爲 進程退出事件註冊 回調函數(event_exit)。

drmgr_register_thread_init_event 爲線程初始化事件 註冊回調函數(event_thread_init)。

drmgr_register_thread_exit_event 爲線程退出事件 註冊回調函數 (event_thread_exit)。

drmgr_register_bb_instrumentation_event 爲 basic block 創建事件 註冊回調函數 event_app_instruction。

DR_ASSERT 則是一個斷言,用來確保上面的初始化都正確完成。

提醒一下,以dr_ 開頭的函數都是 DynamoRIO 原始的API ,而以drmgr_ 開頭的函數則是 drmgr extension 所使用的API。二者有什麼關係,部分 drmgr_ 函數是對 dr_ 函數的增強。比如 drmgr_register_bb_instrumentation_event 是對 dr_register_bb_event 函數的增強,強在哪裏?原函數 只對應 basic block 創建時刻,而 drmgr 版本則將該時刻細分爲4個步驟,我們可以對其中 的兩個步驟設置回調函數。具體的可以查閱文檔。

接下來看那些註冊的回調函數:

首先是 event_thread_init :

static void
event_thread_init(void *drcontext)
{
    file_t log;
    log =
        log_file_open(client_id, drcontext, NULL /* using client lib path */, "cbrtrace",
#ifndef WINDOWS
                      DR_FILE_CLOSE_ON_FORK |
#endif
                          DR_FILE_ALLOW_LARGE);
    DR_ASSERT(log != INVALID_FILE);
    drmgr_set_tls_field(drcontext, tls_idx, (void *)(ptr_uint_t)log);
}

file_t 的類型用於打開文件的句柄。
log_file_open 就是utils.c 文件夾裏封裝的創建日誌文件的函數。
drmgr_set_tls_field 就是前面說的,把當前線程創建的日誌文件句柄存放到了當前線程的tls裏。這樣,就可以在其他回調函數打開這個文件進行讀寫操作了。

event_thread_exit:

static void
event_thread_exit(void *drcontext)
{
    log_file_close((file_t)(ptr_uint_t)drmgr_get_tls_field(drcontext, tls_idx));
}

這個更簡單,直接關閉掉了日誌文件,這個函數也是封裝在了 utils.c 文件裏,實際上是直接調用了 dr_close_file() 函數。

event_exit() 進程退出回調函數:

static void
event_exit(void)
{
 
    if (!drmgr_unregister_bb_insertion_event(event_app_instruction) ||
        !drmgr_unregister_tls_field(tls_idx))
        DR_ASSERT(false);
    drmgr_exit();
}

可以看到,這裏只是 unregister 了2個回調函數,注意 drmgr_exit() 要和 drmgr_init() 成對出現。
另外,我們使用drmgr_register_bb_instrumentation_event 進行的註冊,但是卻用drmgr_unregister_bb_insertion_event 進行解除註冊。因爲我們註冊的時候只用了第二個參數,也就是在 4個階段中的 insert 階段進行註冊,這種情況下如果需要,則必須使用後者進行解除註冊。
drmgr_unregister_tls_field 釋放之前預定的 tls 插槽索引,如果該索引沒有被預定,則返回false 。

下面是關鍵的 event_app_instruction :

static void
at_cbr(app_pc inst_addr, app_pc targ_addr, app_pc fall_addr, int taken, void *bb_addr)
{
    void *drcontext = dr_get_current_drcontext();
    file_t log = (file_t)(ptr_uint_t)drmgr_get_tls_field(drcontext, tls_idx);
    dr_fprintf(log, "" PFX " [" PFX ", " PFX ", " PFX "] => " PFX "\n", bb_addr,
               inst_addr, fall_addr, targ_addr, taken == 0 ? fall_addr : targ_addr);
}

static dr_emit_flags_t
event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr,
                      bool for_trace, bool translating, void *user_data)
{
    if (instr_is_cbr(instr)) {
        dr_insert_cbr_instrumentation_ex(drcontext, bb, instr, (void *)at_cbr,
                                         OPND_CREATE_INTPTR(dr_fragment_app_pc(tag)));
    }
    return DR_EMIT_DEFAULT;
}

先看 event_app_instruction 函數,
instr_is_cbr 返回 True, 如果instr 是一個條件分支指令: OP_jcc, OP_jcc_short, OP_loop*, or OP_jecxz on x86; OP_cbnz, OP_cbz, or when a predicate is present any of OP_b, OP_b_short, OP_bx, OP_bxj, OP_bl, OP_blx, OP_blx_ind on ARM.
dr_insert_cbr_instrumentation_ex 假設instr是一個條件分支指令,並在 instr 之前插入一個 clean call ,調用 at_cbr ,並向at_cbr 傳遞 4個默認參數,分別是下面的:

  1. 分支指令的地址,應該就是 instr 的地址
  2. 分支的目標地址
  3. 分支的落實地址
  4. 如果採用該分支則爲1,不採用則爲0
  5. 用戶自定義操作數

那麼at_cbr 就會在 instr 之前被調用。
dr_get_current_drcontext 返回當前線程的 DR context (線程上下文)
drmgr_get_tls_field 獲取TLS 裏存放的文件句柄
dr_fprintf 格式化字符串,向日志文件裏寫入我們的記錄,主要是幾個地址。

本例子的關鍵就是 dr_insert_cbr_instrumentation_ex 用來獲取 條件分支指令的地址信息。

接下來就是編譯並使用了:
我把 cbrtrace.txt 放在了 DynamoRIO 的 samples 文件夾裏。
命令行進入 samples 文件夾

mkdir build
cd build 
cmake -DDynamoRIO_DIR=C:\Users\Documents\\DynamoRIO-Windows-7.0.17873-0\cmake  ..
cmake --build . --config Release

這樣就編譯完成,注意,首先電腦裏一定要安裝有編譯器和鏈接器,如果不知道,裝個vs 就行。
第一個cmake 命令用來生成配置項目信息
第二個cmake 命令用來進行編譯和鏈接。

然後DynamoRIO-Windows-7.0.17873-0\samples\build\bin\Release 這個文件夾裏就會有 cbrtrace.dll 文件了,這就是客戶端文件。

然後你可以拷貝這個文件到 DynamoRIO-Windows-7.0.17873-0\bin32 文件夾裏,然後命令行執行:

drrun.exe  -c cbrtrace.dll  --   hello.exe 

hello.exe 是我的目標應用程序。
當然你也可以把 drrun.exe 和 cbrtrace.dll 單獨放一個文件夾裏,但是注意 drinjectlib.dll 和 drconfiglib.dll 也要一併放過去(原本和drrun.exe 在同一個文件夾裏)。

完整的 cbrtrace.c 在這裏:


#include "dr_api.h"
#include "drmgr.h"
#include "utils.h"

static client_id_t client_id;

static int tls_idx;

/* Clean call for the cbr */
static void
at_cbr(app_pc inst_addr, app_pc targ_addr, app_pc fall_addr, int taken, void *bb_addr)
{
    void *drcontext = dr_get_current_drcontext();
    file_t log = (file_t)(ptr_uint_t)drmgr_get_tls_field(drcontext, tls_idx);
    dr_fprintf(log, "" PFX " [" PFX ", " PFX ", " PFX "] => " PFX "\n", bb_addr,
               inst_addr, fall_addr, targ_addr, taken == 0 ? fall_addr : targ_addr);
}

static dr_emit_flags_t
event_app_instruction(void *drcontext, void *tag, instrlist_t *bb, instr_t *instr,
                      bool for_trace, bool translating, void *user_data)
{
    if (instr_is_cbr(instr)) {
        dr_insert_cbr_instrumentation_ex(drcontext, bb, instr, (void *)at_cbr,
                                         OPND_CREATE_INTPTR(dr_fragment_app_pc(tag)));
    }
    return DR_EMIT_DEFAULT;
}

static void
event_thread_init(void *drcontext)
{
    file_t log;
    log =
        log_file_open(client_id, drcontext, NULL /* using client lib path */, "cbrtrace",
#ifndef WINDOWS
                      DR_FILE_CLOSE_ON_FORK |
#endif
                          DR_FILE_ALLOW_LARGE);
    DR_ASSERT(log != INVALID_FILE);
    drmgr_set_tls_field(drcontext, tls_idx, (void *)(ptr_uint_t)log);
}

static void
event_thread_exit(void *drcontext)
{
    log_file_close((file_t)(ptr_uint_t)drmgr_get_tls_field(drcontext, tls_idx));
}

static void
event_exit(void)
{
#ifdef SHOW_RESULTS
    if (dr_is_notify_on())
        dr_fprintf(STDERR, "Client 'cbrtrace' exiting\n");
#endif
    if (!drmgr_unregister_bb_insertion_event(event_app_instruction) ||
        !drmgr_unregister_tls_field(tls_idx))
        DR_ASSERT(false);
    drmgr_exit();
}

DR_EXPORT
void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
    //dr_set_client_name("DynamoRIO Sample Client 'cbrtrace'",
    //                   "http://dynamorio.org/issues");
    //dr_log(NULL, DR_LOG_ALL, 1, "Client 'cbrtrace' initializing");

    drmgr_init();

    client_id = id;
    tls_idx = drmgr_register_tls_field();

    dr_register_exit_event(event_exit);
    if (!drmgr_register_thread_init_event(event_thread_init) ||
        !drmgr_register_thread_exit_event(event_thread_exit) ||
        !drmgr_register_bb_instrumentation_event(NULL, event_app_instruction, NULL))
        DR_ASSERT(false);

#ifdef SHOW_RESULTS
    if (dr_is_notify_on()) {
#    ifdef WINDOWS
        dr_enable_console_printing();
#    endif /* WINDOWS */
        dr_fprintf(STDERR, "Client 'cbrtrace' is running\n");
    }
#endif /* SHOW_RESULTS */
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章