DynamoRIO API介紹與工作機制

原文地址:http://dynamorio.org/docs/API_BT.html

DynamoRIO 提供的API 允許工具在 目標程序執行時觀察和修改應用程序的實際代碼流。

Instruction Representation(指令的表示)

DynamoRIO 使用 instr_t 結構表示單個 IA-32 指令,使用instrlist_t 表示一串指令,比如一個基本塊(basic block) 裏的一串有序指令。在 dr_ir_instrlist.h 和 dr_ir_instr.h 頭文件裏, 列出了一系列的函數用來操作這兩個結構體,主要包括以下幾類:

  • 創建一個新的指令
  • 遍歷指令的操作數
  • 遍歷 一個 instrlist_t 結構體
  • 在 instrlist_t 裏插入或者去除一個 instr_t

客戶端通常以 基本塊(basic block) 或者 trace 的方式與 instrlist_t 進行交互。
basic block 是一系列的指令,並且以一個控制轉移操作結束。trace 則是高頻率執行的一系列 basic block, 是由DynamoRIO 在程序執行時動態形成的。無論時 basic block 還是 trace ,都呈現出了控制流的線性視圖。換句話說,指令序列只有一個入口,出口則有一個(basic block) 或多個(trace)。正是這種指令表示機制極大的簡化了代碼分析問題,並提高了 DynamoRIo 的效率。

指令表示法包括所有操作數,無論是隱式還是顯式的,以及每條指令的條件代碼效果(所謂代碼效果,我覺得應該是對標誌寄存器等造成的影響)。這有助於分析寄存器和條件代碼的活躍性。

Events (事件機制)

客戶端與DynamoRIO交互的核心是通過事件掛鉤發生的:客戶端爲每一個感興趣的事件註冊回調函數。DynamoRIo 則在適當的時機調用客戶端的事件回調函數,使得客戶端可以在應用程序執行期間訪問關鍵操作。
下面是 所有的事件和對應的註冊函數:

  • Basic block and trace creation or deletion (dr_register_bb_event(), dr_register_trace_event(), dr_register_delete_event())
  • Process initialization and exit (dr_client_main(), dr_register_exit_event())
  • Thread initialization and exit (dr_register_thread_init_event(), dr_register_thread_exit_event())
  • Fork child initialization (Linux-only); meant to be used for re-initialization of data structures and creation of new log files (dr_register_fork_init_event())
  • Application library load and unload (dr_register_module_load_event(), dr_register_module_unload_event())
  • Application fault or exception (signal on Linux) (dr_register##_exception_event(), dr_register_signal_event())
  • System call interception: pre-system call, post-system call, and system call filtering by number (dr_register_pre_syscall_event(), dr_register_post_syscall_event(), dr_register_filter_syscall_event())
  • Signal interception (Linux-only) (dr_register_signal_event())
  • Nudge received - see Communication (dr_register_nudge_event())

一般情況下, 客戶端會在dr_client_main() 函數裏面進行回調函數的註冊,然後DynamoRIO就會在適當的時機調用回調函數。
注意,一個客戶端允許對同一個事件註冊多個回調函數,同時DynamoRIO 也允許有多個客戶端,並且多個客戶端爲同一個事件註冊回調函數。對與這種情況,DynamoRIO 會以註冊順序的逆向來調用回調函數。此方案優先考慮先前註冊的回調,因爲它可以覆蓋或者修改後註冊的回調函數的操作。還有,當存在多個客戶端時,DynamoRIo 根據客戶端的priority (優先級)調用每個客戶端的dr_client_main()。

爲單個事件註冊多個回調的系統應該知道: 客戶端修改在後續回調中是可見的。 DynamoRIO不會嘗試減輕回調函數之間的干擾。客戶端有責任確保其回調函數與其他客戶端的回調函數之間的兼容性

客戶端還可以使用相應的取消註冊函數 (unregister) 取消註冊回調(請參閱dr_events.h)。雖然不太常見,但一個回調例程可以取消註冊另一個例程。在這種情況下,DynamoRIO仍會調用在事件之前註冊的例程。取消註冊在下一個事件之前生效

Transformation Versus Execution Time(轉換與執行時間)

當程序代碼將要執行,將其從原位置轉移到 code cache(DynamoRIO 的代碼緩存空間)時,basic block 和 trace 的 事件會被提出。當code cache 裏的代碼執行時,不會提出 事件(event)。在典型的運行中,一個特定的 basic block 只會提出一次 event,也就是第一次運行,從 原位置轉換到code cache 時;當它進入 code cache 之後,哪怕這段代碼之後會多次運行,但都是直接從code cache 裏面調用,而不會再提起 event。

basic block 的create event 提出時,就是程序代碼將要被拷貝到 code cache ,這個時間點叫做 transformation time (轉換事件)。在這個時間點,客戶端可以插入指令去監視程序代碼,或者直接修改程序的代碼。經過檢測或修改的代碼在代碼高速緩存內重複執行的時間就是執行時間。 所以我們要區分開 轉換時間與執行時間。

Basic Block Creation

通過 dr_register_bb_event 註冊 basic block 創建事件的回調函數,客戶端能夠在執行之前檢查和轉換任何代碼。

dr_emit_flags_t new_block(void *drcontext, void *tag, instrlist_t *bb,
                          bool for_trace, bool translating);
  • drcontext 是一個指向輸入程序機器上下文的指針。客戶端不應該去檢查或修改這個內容。它作爲一個不透明指針(即void *)提供,以傳遞給需要訪問此內部數據的API例程。
  • tag 是基本快片段大的唯一標識符。
  • bb 是一個指令的鏈表,組成了basic block。客戶端可以檢查,操作或完全替換列表中的指令。
  • for_trace 指示此回調是針對新基本塊(false)還是將基本塊添加到正在創建的trace(true)。客戶端有機會對獨立基本塊進行相同的修改,或者對跟蹤中的代碼使用不同的修改。
  • translating 此回調是用於創建基本塊(false)還是用於地址轉換(true)。

Application Versus Meta Instructions(應用與元指令)

客戶端對指令流的改變分爲兩類:應被視爲應用程序行爲一部分的更改或添加,和在觀察性質上不代表應用程序的添加。後者就是元指令。
可以使用以下的API來標記元指令

instr_set_meta()
instrlist_meta_preinsert()
instrlist_meta_postinsert()
instrlist_meta_append()

在客戶端完成後,DynamoRIO會對基本塊執行一些處理,主要是修改分支以確保DynamoRIO在執行後保留控制權。重要的是客戶端將任何不希望作爲應用程序指令視爲元指令的控制流指令標記。這樣做會通知DynamoRIO這些指令應該本機執行而不是被捕獲並重定向到新的基本塊片段。

通過元指令,客戶端可以添加自己的內部控制流或調用本機例程。元調用的目標不會被DynamoRIO帶入代碼緩存。但是,此類本機調用需要小心保持透明(clean call)

元指令通常是觀察性的,在這種情況下它們不應該是錯誤的並且應該具有NULL轉換字段。可以使用故意故障或通過訪問應用程序內存地址而出錯的元指令,但僅當客戶端處理所有此類故障時纔可能。

如果使用instr_get_next()和instrlist_first(),則元指令對客戶端代碼可見。要僅遍歷應用程序(非元)指令,客戶端可以使用以下API函數:

instr_get_next_app()
instrlist_first_app()

我們建議客戶遵循一個規範的模型,該模型將應用程序的代碼分析過程與插入指令過程分開。 Multi-Instrumentation Manager擴展通過分離應用程序轉換,應用程序分析和檢測來實現這一點。然而,即使使用這種分離,在應用程序轉換期間添加的標籤指令和在某些情況下的其他元指令(例如,來自drwrap_replace_native()),這些應該在分析期間被跳過。在應用程序分析期間建議使用instrlist_first_app()和instr_get_next_app():它會自動跳過非應用程序(元)指令,保證是標籤或對寄存器狀態無影響的指令或對應用程序代碼分析關鍵部分沒有影響的指令。

雖然DynamoRIO嘗試支持任意代碼轉換,但其內部操作要求我們強加以下限制:

  • 如果有多個應用程序分支,則只有最後一個可以是有條件的。
  • 應用程序條件分支必須是塊中的最後一條指令。
  • 基本塊中只能有一個間接分支(調用,跳轉或返回),它必須是塊中的最終應用程序分支。
  • 無法更改以系統調用結束的塊的出口控制流。

除了被處理的之外(如果是控制流程),應用程序指令或非元指令也被認爲是在DynamoRIO必須移動線程的極少數情況下重新定位的安全點。因此,客戶應確保在提供的翻譯字段地址重新啓動應用程序指令是安全的。

Trace Creation

DynamoRIO主要通過dr_register_trace_event()註冊的trace creation event 回調函數 提供對trace 的訪問。請務必注意,客戶端不需要使用trace-creation event 來確保完整的檢測。相反,使用基本塊事件來完成所有代碼修改就足夠了。 DynamoRIO選擇放置在trace 中的任何基本塊都將包含所有客戶端修改(除非客戶端在其for_trace參數爲true時在基本塊掛鉤中的行爲不同)。trace-creation event 爲客戶端提供了單獨檢測熱代碼的機制。

trace-creation event 掛鉤的參數幾乎與基本塊掛鉤的參數相同。

dr_emit_flags_t new_trace(void *drcontext, void *tag, instrlist_t *trace,
                          bool translating);
  • tag 是跟蹤片段的唯一標識符。
  • bb 是一個指向一串指令的指針,這些指令組成了trace 。客戶端可以檢查,操作或完全替換列表中的指令。
  • translating指示此回調是用於創建跟蹤(false)還是用於地址轉換(true)。

每次創建trace 時,DynamoRIO都會在 trace 發送到代碼緩存之前調用客戶端提供的event 掛鉤。此外,當每個組成 trace 的 basic block 添加到trace 時,DynamoRIO會調用基本塊創建掛鉤,並將for_trace參數設置爲true。爲了在trace 內部保留基本塊檢測,客戶端只需要相對於for_trace參數執行相同的操作(即無論for_trace 如何,執行相同的代碼);如果其目標是在所有代碼上放置檢測,它可以忽略 trace 事件

組成trace 的 基本塊將在插入代碼高速緩存之前被拼接在一起:條件分支將被重新對齊,以便它們的直通目標保留在trace 上,並且內聯間接分支將與 on-trace 目標進行比較。

如果basic block 回調函數基於for_trace參數有不同的行爲,則trace 中將存在不同的檢測,而不是獨立的基本塊。如果基本塊對應於trace 開始時的應用程序代碼(即,它是 trace 頭),則trace 將遮蔽基本塊並且將優先執trace 。如果調用dr_delete_fragment(),它也將首先刪除trace 並保留基本塊。但是,刷新例程(dr_flush_region(),dr_delay_flush_region(),dr_unlink_flush_region())將刪除trace 和基本塊

State Restoration

如果客戶端僅添加不引用應用程序內存的instrumentation(元指令),並且不重新排序或刪除應用程序指令,則無需註冊此事件(此事件指的就是 State Restoration事件)。但是,如果客戶端正在修改應用程序代碼或添加可能出錯的指令,則客戶端必須能夠恢復原始上下文。每當需要將代碼緩存上下文轉換爲原始應用程序上下文時,DynamoRIO 可以調用 dr_register_restore_state_event() 或者dr_register_restore_state_ex_event() 註冊的state restoration 事件的回調函數。

void restore_state(void *drcontext, void *tag, dr_mcontext_t *mcontext,
                   bool restore_memory, bool app_code_consistent)
void restore_state_ex(void *drcontext, bool restore_memory,
                      dr_restore_state_info_t *info)

Basic Block and Trace Deletion(basic block 和trace 的刪除事件)

DynamoRIO可以通過dr_register_delete_event()提供片段刪除通知。此事件回調的簽名是:

void fragment_deleted(void *drcontext, void *tag);

每次從代碼緩存中刪除片段時,DynamoRIO都會調用此事件掛鉤。

Special System Calls(特殊的系統調用)

對於64位Windows內核(“Windows-on-Windows-64”或“WOW64”)上的32位應用程序,DynamoRIO 視那些 來自32位系統庫 並且被轉換爲WOW64編組代碼的間接調用爲系統調用,即使在某些版本的Windows上執行了一些32位指令。對於監視調用和返回的工具 還需要 檢查被視爲系統調用的指令。

Decoding and Encoding

正如基本塊創建和 trace 創建中所討論的,客戶端的代碼檢查和操作的主要接口是通過基本的塊和trace 的掛鉤函數。但是,DynamoRIO還會導出一組豐富的函數和數據結構,以直接對指令進行解碼和編碼。以下小節概述了此功能:

Decoding

DynamoRIO提供了幾種用於解碼和分解IA-32指令的例程。用於解碼的最常用方法是decode()例程,它使用關於指令的所有信息(例如,操作碼和操作數信息)填充instr_t數據結構。

解碼指令時,客戶端必須顯式管理instr_t數據結構。例如,以下代碼顯示瞭如何使用instr_init(),instr_reset()和instr_free()例程來解碼一系列任意指令:

instr_t instr;
instr_init(&instr);
do {
  instr_reset(dcontext, &instr);
  pc = decode(dcontext, pc, &instr);
  /* check for invalid instr */
  if (pc == NULL)
    break;
  if (instr_writes_memory(&instr)) {
    /* do some processing */
  }
} while (pc < stop_pc);
instr_free(dcontext, &instr);

DynamoRIO支持解碼多種指令集模式。有關完整詳細信息,請參閱 Instruction Set Modes 。

Instruction Generation(指令生成)

客戶可以通過兩種不同的方式從頭開始構建指令:

  1. 使用自動填充隱式操作數的INSTR_CREATE_opcode宏:
      instr_t *instr = INSTR_CREATE_dec(dcontext, opnd_create_reg(REG_EDX));
  1. 指定操作碼和所有操作數(包括隱式操作數):
instr_t *instr = instr_create(dcontext);
instr_set_opcode(instr, OP_dec);
instr_set_num_opnds(dcontext, instr, 1, 1);
instr_set_dst(instr, 0, opnd_create_reg(REG_EDX));
instr_set_src(instr, 0, opnd_create_reg(REG_EDX));

Encoding(編碼)

DynamoRIO的 編碼例程(encoding routines) 接受指令或指令列表,並將它們編碼爲相應的IA-32位模式:

instr_encode(), instrlist_encode() 

當encode 一個 以另一指令爲目標的 控制轉移指令時,執行兩個編碼過程:一個用於找到目標指令的偏移量,另一個用於將控制轉移鏈接到適當的目標偏移量。 DynamoRIO能夠編碼多種指令集模式。有關詳細信息,請參閱Instruction Set Modes。

Disassembly(反彙編)

DynamoRIO 提供幾種方法去打印指令到文件或緩存裏。這些方法包括: disassemble(), opnd_disassemble(), instr_disassemble(), instrlist_disassemble(), disassemble_with_info(), disassemble_from_copy(), and disassemble_to_buffer().

可以通過
-syntax_intel(控制生成 Intel風格的反彙編),
-syntax_att(控制生成 AT&T樣式反彙編)和
-syntax_arm(控制生成 ARM樣式反彙編)運行時選項
或disassemble_set_syntax()函數來控制反彙編的樣式。
默認的反彙編樣式是DynamoRIO的自定義樣式,它列出了所有操作數(包括隱式和顯式)。首先列出源,然後是“ - >”,然後是目的地。這提供了比任何其他格式更多的信息。

64-bit Versus 32-bit Instructions(64位和32位指令對比)

默認情況下,64位版本的DynamoRIO使用64位解碼和編碼,而32位版本使用32位。 64位版本還能夠解碼和編碼32位指令。

對於64位版本的DynamoRIO,指令創建宏都使用64位大小的寄存器。生成32位代碼時推薦的模型是使用宏來創建指令列表,並在編碼之前在每條指令上調用instr_set_isa_mode(DR_ISA_IA32)和instr_shrink_to_32_bits()。當然,任何不同於寄存器選擇的指令都必須是特殊的。

Thumb Mode Addresses

對於32位ARM,作爲事件回調,作爲乾淨調用目標或作爲dr_redirect_execution()目標傳遞的目標地址,如果需要在Thumb模式(DR_ISA_ARM_THUMB)中執行,則應將其最低有效位設置爲1。通過dr_get_proc_address()獲得的地址或源代碼級別的函數指針應自動具有此屬性。 dr_app_pc_as_jump_target()也可用於從對齊值構造正確的地址

解碼時,如果目標地址的最低有效位設置爲1,則無論當前線程的模式如何,解碼器都會在解碼期間切換到Thumb模式。

Utilities

除了指令解碼和編碼之外,這些API還包括幾個更高級的例程以便於代碼檢測。這些包括以下內容:

  • 用於向客戶端定義的函數插入乾淨調用的例程。
  • 儀表控制流程指令的例程。
  • 將寄存器溢出到DynamoRIO的線程專用溢出插槽的例程。
  • 用於快速保存和恢復算術標誌,浮點狀態和MMX / SSE寄存器的例程。

以下是插入一個調用 at_mbr 的clean call:

if (instr_is_mbr(instr)) {
  app_pc address = instr_get_app_pc(instr);
  uint opcode = instr_get_opcode(instr);
  instr_t *nxt = instr_get_next(instr);
  dr_insert_clean_call(drcontext, ilist, nxt, (void *) at_mbr,
                       false/*don't need to save fp state*/,
                       2 /* 2 parameters */,
                       /* opcode is 1st parameter */
                       OPND_CREATE_INT32(opcode),
                       /* address is 2nd parameter */
                       OPND_CREATE_INTPTR(address));
}

通過這種機制,客戶端可以用C語言或其他高級語言編寫分析代碼,並輕鬆地在指令流中插入對這些例程的調用。但請注意,保存和恢復機器狀態是一項昂貴的操作。應該內聯關鍵性能操作以實現最高效率。

DynamoRIO切換到clean call 的堆棧相對較小:默認情況下僅爲20KB。客戶端可以使用-stack_size運行時選項增加堆棧的大小。客戶端還應該避免在clean call 的堆棧上保持持久狀態,因爲在每次clean call開始時它都會被清除乾淨。

可以使用dr_get_mcontext()訪問保存的中斷應用程序狀態,並使用dr_set_mcontext()進行修改。

出於性能原因,默認情況下,clean 不會保存或恢復浮點,MMX或SSE狀態。如果clean call 函數正在使用浮點或多媒體操作,它應該請求clean call 機制通過適當的參數調用 dr_insert_clean_call() 將浮點狀態保存)。另請參見Floating Point State, MMX, 和 SSE Transparency.

如果需要對呼叫序列進行更詳細的控制,可以將其分解爲其組成部分:

  • dr_prepare_for_call()
  • Optionally, dr_insert_save_fpstate()
  • dr_insert_call()
  • Optionally, dr_insert_restore_fpstate()
  • dr_cleanup_after_call()

DynamoRIO分析每個clean call 的被調用者目標,並嘗試減少上下文切換大小,如果被調用者足夠簡單,則自動內聯它。當被調用者完全優化時,此分析和潛在內聯最有效。因此,我們建議在客戶端中使用高優化級別,即使在調試版本中運行DynamoRIO本身只是爲了檢查是否內聯被調用者。有關如何調整這些優化的主動性以及影響內聯的特定條件列表的信息,請參閱-opt_cleancall。

State Preservation

爲了便於代碼轉換,DynamoRIO提供其寄存器溢出插槽和其他狀態保留功能。它導出API例程,用於在線程本地溢出槽中 保存和恢復 寄存器,

dr_save_reg(), dr_restore_reg(), and dr_reg_spill_slot_opnd() 

存儲在這些溢出槽中的值在下一個應用程序(即非元)指令之前保持有效,因此可以使用以下方式從clean call 訪問:

dr_read_saved_reg(), dr_write_saved_reg() 

對於長期持久性,DynamoRIO還提供了一個通用的專用線程本地存儲字段(TLS) 供客戶端使用,從而可以輕鬆編寫支持線程的客戶端。從C代碼,使用:

dr_get_tls_field(), dr_set_tls_field() 

要從代碼緩存中訪問此線程本地字段,請使用以下例程生成必要的代碼:

dr_insert_read_tls_field(), dr_insert_write_tls_field() 

由於幾乎所有代碼轉換都需要保存和恢復eflags寄存器,並且因爲很難有效地執行,所以我們導出使用我們有效的算術標誌保存方法的例程:

dr_save_arith_flags(), dr_restore_arith_flags() 

正如剛纔在Clean Calls中所討論的那樣,我們還導出了便利例程,用於從代碼緩存中進行乾淨(即透明)本機調用,以及浮點和多媒體狀態保存。

Branch Instrumentation

DynamoRIO爲檢測調用指令,直接(或無條件)分支,間接(或多向)分支和條件分支提供明確支持。這些便利例程插入 對客戶端提供的方法的 clean call ,將每個控件傳輸的指令pc和目標pc作爲參數傳遞,以及爲條件分支採用或不採用信息:

dr_insert_call_instrumentation()
dr_insert_ubr_instrumentation()
dr_insert_mbr_instrumentation()
dr_insert_cbr_instrumentation()

Dynamic Instrumentation

DynamoRIO允許客戶端通過提供例程來刷新與應用程序代碼區域對應的所有緩存片段,從而動態調整其檢測:

dr_flush_region()
dr_unlink_flush_region()
dr_delay_flush_region()

爲了直接修改特定片段的 instrumentation (而不是替換對應於特定應用程序代碼的所有片段副本上的 instrumentation ),DynamoRIO還支持使用新的instrlist_t直接替換現有片段:

dr_replace_fragment()

但是,僅在使用-thread_private運行時選項 運行時才支持此例程,並且它僅替換當前線程的片段。即使在待替換片段內部(例如,在片段內部的clean call中),客戶端也可以調用該例程。在這種情況下,舊片段執行完成,新代碼在下次執行之前插入。

Custom Traces

DynamoRIO將頻繁執行的基本塊序列組合成trace 。它使用基於trace head 的簡單分析方案,跟蹤頭是後向分支或從現有trace 退出的目標。爲每個trace head 保留執行計數器。一旦trace head超過閾值,執行的下一個基本塊序列將成爲新的trace 。

DynamoRIO允許客戶端通過標記自己的trace head(除了DynamoRIO的正常trace head)和決定何時結束trace 來構建自定義trace 。如果客戶端註冊了以下事件,DynamoRIO將在使用新的基本塊(帶標記next_tag)擴展trace (帶標記trace_tag)之前調用其掛鉤:

int query_end_trace(void *drcontext, void *trace_tag, void *next_tag);

客戶端 的鉤子返回以下值之一:

  • CUSTOM_TRACE_DR_DECIDES = use standard termination criteria
  • CUSTOM_TRACE_END_NOW = end trace now
  • CUSTOM_TRACE_CONTINUE = do not end trace

如果使用標準終止條件,DynamoRIO會在它到達trace head 或其他tracce(或某些不能組成 trace 的角落基本塊)時結束跟蹤。 客戶端還可以將任何基本塊標記爲trace head。

dr_mark_trace_head() 

Register Stolen by DynamoRIO(DynamoRIO 竊取寄存器)

在某些體系結構上,例如ARM和AArch64,DynamoRIO竊取了一個寄存器,用於保存DynamoRIO自己的TLS(線程局部存儲)的基址。DynamoRIO通過在每個應用程序指令使用該寄存器之前和之後保存和恢復被盜寄存器的值來保證應用程序執行的正確性。DynamoRIO還保證被盜寄存器的值存儲在應用程序機器上下文(dr_mcontext_t)中,供客戶在事件回調或clean call 時使用。但是,DynamoRIO會將被盜寄存器暴露給客戶端,並將負擔放在客戶端上以確保其儀器的正確性。

客戶端可以使用reg_is_stolen()或dr_get_stolen_reg()來識別被盜寄存器。要在插入的代碼中使用被盜寄存器的應用程序值,客戶端必須首先使用dr_insert_get_stolen_reg_value()來插入代碼以將值傳入另一個寄存器。否則,可能會得到並使用TLS基值這個值。同樣,客戶端應使用dr_insert_set_stolen_reg_value()來設置被盜寄存器的應用程序值。這裏所說寄存器的應用程序值就是原本該寄存器的值。

State Translation

爲了支持透明的故障處理,DynamoRIO必須將代碼緩存中的故障轉換爲相應應用程序地址的故障。當應用程序或DynamoRIO本身檢查掛起的線程以進行內部同步時,DynamoRIO還必須能夠進行轉換。

如果客戶只是添加觀察工具(即應用程序與元指令)(不可能會出錯)並且不修改,重新排序或刪除應用程序指令,則可以忽略這些細節。在這種情況下,客戶端的基本塊和trace 回調除了具有確定性和冪等性之外還應返回DR_EMIT_DEFAULT(即,DynamoRIO應該能夠重複調用回調並接收相同的結果指令列表,而不會對客戶端進行淨狀態更改) 。

如果客戶端正在執行修改,那麼爲了使DynamoRIO正確轉換代碼緩存地址,客戶端必須在基本塊和trace 創建回調中使用instr_set_translation()(可通過INSTR_XL8()鏈接)來爲每個添加的 可能故障的元指令 ,每個修改的指令,以及每個添加的應用程序指令 設置相應的應用程序地址。轉換值應該是 作爲錯誤地址 呈現給應用程序的 應用程序地址,或者應該是 在掛起後重新啓動的應用程序地址。目前,轉換地址必須在基本塊或跟蹤的 現有源地址 範圍內。

使用翻譯地址有兩種方法:

  1. 從基本塊創建回調中返回DR_EMIT_STORE_TRANSLATIONS。然後,DR將存儲轉換地址並使用存儲的故障信息。當 translatings 參數爲true時,不會調用 tag 的基本塊回調。請注意,除非還爲for_trace調用返回DR_EMIT_STORE_TRANSLATIONS(或者在trace 回調中返回DR_EMIT_STORE_TRANSLATIONS),否則需要重新創建 組成 trace 的每個基本快,同時將for_trace和translating設置爲true。存儲翻譯可能使用很重要的額外內存:在某些情況下高達20%,因爲它阻止DR使用其簡單的數據結構 並迫使它 回退到複雜 。這就是DR默認不存儲所有翻譯的原因。
  2. 從基本塊或trace 創建回調中返回DR_EMIT_DEFAULT。然後,DynamoRIO將在故障轉換期間再次調用回調,並將translating設置爲true。 創建回調中執行的所有對指令列表的修改 必須在 轉換回調上重複一次(轉換回調就是指translating 爲 true)。只有當基本塊修改具有確定性和冪等性時,此選項纔可用,但它可以節省內存。當然,由塊創建觸發的全局狀態更改應該包含在檢查中,以便將其轉換爲false。即使在這種情況下,即使translating 爲 false ,也應該調用instr_set_translation()爲適當的指令,因爲DynamoRIO可能會因創建自己的原因而決定在創建時存儲翻譯。

此外,如果客戶端的修改改變了除程序計數器之外的機器狀態的任何部分,則客戶端應使用dr_register_restore_state_event()或dr_register_restore_state_ex_event()(請參閱狀態恢復)將寄存器恢復爲其原始應用程序值。

Conditionally Executed Instructions(條件執行指令)

DynamoRIO將條件執行或“預測”指令建模爲具有額外預測屬性的常規指令。使用instr_is_predicated()來確定是否指令是有條件地執行的。如果是這樣,請使用instr_get_predicate()來確定條件的類型。在執行時,instr_predicate_triggered()可用於查詢指令是否執行。

條件執行的程度各不相同。在某些情況下,當未執行指令時,它不會讀取任何源操作數,也不會寫入任何目標操作數。在其他情況下,它所依賴的條件涉及源操作數的值(例如,OP_bsf或OP_maskmovq)。但是,所有 條件執行的指令共享相同的屬性: 目標操作數被有條件的寫入。但是這不適用於eflags ,它是由一些條件指令無條件寫入的。

爲了幫助分析應用程序代碼的活躍性和其他屬性,查詢是否寫入寄存器或flags 的所有API例程 都採用dr_opnd_query_flags_t類型的參數來控制 如何處理有條件訪問的操作數:是包含它們還是跳過它們。

對原始指令信息進行操作的API例程(例如instr_num_dsts())包括所有可能的操作數。當使用不接受dr_opnd_query_flags_t的API例程時,客戶端應顯式查詢instr_is_predicated()。

IT Blocks

在ARM AArch32上,Thumb模式包括稱爲IT塊的條件指令組。 OP_it標頭指令指示塊中有多少指令以及每條指令的條件方向。因此,在塊內插入檢測而不更新標題會導致不可編碼的指令列表。爲了解決這個問題,我們提供了兩個API例程:dr_remove_it_instrs()和dr_insert_it_instrs()。第一個簡單地刪除標題。由於來自塊的各個指令在IR中標記了它們的條件,因此工具不需要標題來分析指令。第二個重新啓動標題,創建一個合法的指令列表。在instru2instru事件之後,重新創建正確的IT塊標題是drmgr中的最後階段。這意味着存在原始OP_it頭指令供客戶端在分析階段觀察。

Persisting Code

將代碼解碼,檢測和發送到代碼緩存需要時間。短時間運行的應用程序或在少量代碼重用時執行大量代碼的應用程序在DynamoRIO下運行時會產生明顯的開銷。一種解決方案是將代碼緩存寫入文件,以便通過簡單地加載文件在後續運行中快速重用。 DynamoRIO爲持久保存其檢測代碼的工具提供支持。

首先,必須設置-persist運行時選項和可選的-persist_dir,以便保留任何緩存。僅支持基本塊持久性:不支持 trace 。在客戶端存在的情況下,默認情況下不會保留基本塊。僅當基本塊事件回調的返回值包含DR_EMIT_PERSISTABLE標誌時,纔有資格獲得持久性的塊。即使這樣,持久性仍有進一步的限制,因爲只有簡單的塊是可持久的。

持久緩存以擴展名.dpc結束,用於DynamoRIO持久緩存,並存儲在-persist_dir運行時選項指定的目錄中,如果未指定,則存儲在每用戶子目錄內的日誌目錄中。

客戶端可能需要將數據存儲在持久文件中,以便確定在再次加載時是否可重複使用,或者提供生成的代碼或其他輔助數據和 或持久代碼所需的代碼。爲此目的提供了一組事件。這些事件允許客戶端在持久化文件中存儲三種類型的數據,超出每個基本塊內的檢測代碼:只讀數據,執行代碼(基本塊之外)和可寫數據。數據類型是分開的,因爲文件佈局在不同的保護區域中。可以使用dr_register_persist_ro()添加只讀數據,使用dr_register_persist_rx()添加可執行代碼,使用dr_register_persist_rw()添加可寫數據。此外,可以使用dr_register_persist_patch()修補要保留的基本塊。

每當代碼即將持久化時,DynamoRIO將調用該模塊的所有已註冊事件。用戶數據參數可用於跨事件回調共享信息。

當客戶端庫基礎或持久代碼地址發生變化時,會提醒客戶確保其工具與位置無關或正確修補以正常運行。例如,如果插入的檢測包括調用或跳轉到客戶端庫,則如果客戶端還將其基址存儲在只讀部分中,並且復活回調將其與當前基址進行檢查,則這些檢測可以保持不變。如果不匹配,則必須拒絕持久文件。更復雜的方法需要間接,代碼中的位置獨立性或修補。

DynamoRIO本身確保僅在應用程序模塊未更改時,如果正在使用的客戶端集與創建文件時存在的客戶端集相同且TLS偏移量相同,則僅重用持久文件。應用程序模塊檢查當前包括Windows上的基本地址,這會阻止通過ASLR爲在不同地址加載的庫重用持久文件。 (將來我們計劃提供應用程序重定位支持,但它今天不存在。)。客戶端檢查基於絕對路徑。如果客戶端需要根據其運行時選項進行驗證,或者根據自己的更改檢測進行版本檢查,則必須在事件回調中自行完成。 TLS檢查確保TLS暫存槽相同。 DynamoRIO還確保影響持久性代碼的任何運行時選項(例如是否啓用了跟蹤)都是相同的。

Running a Subset of an Application(運行應用程序的子集)

在DynamoRIO控制下運行整個應用程序的替代方法是使用應用程序接口指定要運行的應用程序的一部分。該接口包含以下例程:

dr_app_setup()
dr_app_start()
dr_app_stop()
dr_app_cleanup()
dr_app_take_over()

構建使用DynamoRIO應用程序接口的可執行文件時,請按照構建客戶端的步驟 去包含頭文件 並 與DynamoRIO庫鏈接 ,但省略不請求標準庫或啓動文件的鏈接器標誌。 DynamoRIO的CMake支持會自動執行此操作,因爲共享庫的鏈接器標誌與可執行文件的鏈接器標誌是分開的。

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