About instrumentation clients

This topic describes the basic structure of an instrumentation client, including the main events which occur during execution and what is typically done in each event. 

To correctly modify the libopcodes_emulated.so client, you must understand its existing implementation, opcodes_emulated.cpp (download opcodes_emulated.cpp here).  The diagram below shows the key functions in opcodes_emulated.cpp and how they relate to each other.

The key functions in opcodes_emulated.cpp and how they relate to each other

The easiest way to understand the client is to think of it as event-driven. Each function is called as a result of events which occur as the application is running:

  1. DynamoRIO loads and runs the client, calling dr_client_main(), before beginning to execute the application.
  2. In dr_client_main(), the client registers a function which will be called just before the client stops running, event_exit().
    Registering such a function for an event is usually referred to as a 'callback function'.
  3. In dr_client_main(), the client registers a callback function as each block of code in the application is prepared before being executed.
  4. In event_basic_block(), the client registers a callback function which will be executed for each emulated instruction which appears in the application's code, record_emulated_inst().
    This record_emulated_inst() function is the instrumentation which is the purpose of the client.
  5. In event_basic_block(), the client registers a callback function which will be executed for each native instruction which appears in the application's code, opcount().
    This opcount() function is the instrumentation which is the purpose of the client.
  6. The application stops running and DynamoRIO calls event_exit().

This is a simplified explanation to describe how a client operates. For more detailed information, read the opcodes_emulated.cpp file (download opcodes_emulated.cpp here) and refer to details of key functions in the DynamoRIO functions reference manual, especially:

  • dr_insert_clean_call() which implements the instrumentation you want.
  • drmgr_register_bb_app2app_event() which defines where the instrumentation should be inserted.

Code Transformation and Code Execution

If you are new to the DynamoRIO dynamic binary instrumentation tool platform (DBI) in general and DynamoRIO in particular, ensure you understand the method by which instrumentation is added to application code.

Remember that instrumentation occurs in two phases, transformation and execution:

  • Transformation - Instrumentation code is inserted into the application code.
  • Execution - The application code runs, including the instrumentation code which was inserted during transformation.

DynamoRIO performs transformation and execution transparently, provided that you conform to the rules of its API.

In the example above, event_basic_block() is the transformation phase. Calls to opcount() or record_emulated_inst() will be inserted for each instruction but will not be called at transformation time. If or when a particular block of code is run at execution time, those functions will be called, to increment and store the instruction and count.

This is a subtle distinction for new users. The best way to think of the difference is to recognize that dr_insert_clean_call() will be called once when a block of application code is transformed but the function it registered may be called many times when the block is executed.

Related information