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支持会自动执行此操作,因为共享库的链接器标志与可执行文件的链接器标志是分开的。

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