Hooking call instruction

158 views
Skip to first unread message

carlo cannarsa

unread,
Jul 4, 2022, 11:08:42 AM7/4/22
to DynamoRIO Users
Hi, I'm a university student and I'm new to DBI. This will kinda look like a newbie question.
I have this simple piece of code here:
for (int i = 0: i < 10: i++) {
    Sleep(1000);
}

I just want to make the tool understand if it encounters a loop where there is a Sleep.
My idea is to get the address of the instruction where is called the sleep and check if it is called more than one time. For now, I've written an event_instruction that finds a call to a Sleep inside the main code of the client:
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)
{
    app_pc pc = instr_get_app_pc(instr);
    dr_mcontext_t mc;
    mc.size = sizeof(mc);
    mc.flags = DR_MC_ALL;
    dr_get_mcontext(drcontext, &mc);

    app_pc API_address = (app_pc)0x74e16490; // sleep addr

    if (instr_is_call(instr)) {
        int num_srcs = instr_num_srcs(instr);
        int* iat_addr;
        int true_addr;
        if (num_srcs > 0) {
            opnd_t src = instr_get_src(instr, 0);
            if (opnd_is_memory_reference(src)) {
                iat_addr = (int*)opnd_compute_address(src, &mc);
                true_addr = *iat_addr;
                if (true_addr == (int)API_address) {
                    dr_fprintf(STDERR, "\nFOUND SLEEP: 0x%x -> 0x%x\n", iat_addr, true_addr);
                    disassemble(drcontext, pc, STDERR);
                }
            }
        }
    }

    return DR_EMIT_DEFAULT;
}
For some reason I get only one print, and I don't understand why. The tool should print "FOUND SLEEP .. " exactly 10 times.

Hope someone can help me, thanks in advance! 


carlo cannarsa

unread,
Jul 4, 2022, 11:27:42 AM7/4/22
to DynamoRIO Users
Adding one more thing:

If I have this piece of code rather than the one with the loop:
Sleep(1000);
Sleep(1000);
Sleep(1000);

The hook from the tool will work and will print exactly 3 times the address found in the code. So I don't understand why there is a problem with loops.

sharma...@google.com

unread,
Jul 8, 2022, 10:25:01 AM7/8/22
to DynamoRIO Users
Hi,
The first case has multiple dynamic instances of the same Sleep call, whereas in the second case they are actually different calls. Note that the instrumentation event callback (event_app_instruction) is NOT invoked at every execution of the basic block. It is invoked only when DynamoRIO doesn't find an instrumented copy of that basic block in our code cache, which typically means only when the BB executes the first time (unless there's a code cache flush).

Instead of counting/printing in event_app_instruction, you need to use dr_insert_clean_call (or one of its variants) to insert a call to a function that counts/prints.

I hope this helps.

Abhinav

carlo cannarsa

unread,
Jul 8, 2022, 10:32:42 AM7/8/22
to DynamoRIO Users
Hi, thank you for solving my doubts! Ok, so it is inevitable to have a little bit overhead in my case, since clean calls will bring a cut in performance for the context change. For now, I solved the problem with a clean call that does an dr_unlink_region(pc, 1) to flush the cache, and in the event_app_instruction I insert the NOPs with the inline assembly macros that are re-executed since the region is rebuilt. Is it possible to do all the code modifications in a clean call, instead? I need to replace those Sleeps with NOPs but I can't pass an instr_t in a clean_call (tried but had crashes since I was passing a nullptr, maybe because the instr was already deallocated). I'm asking this since I think that flushing the cache in a clean call will bring an additional overhead, and I would like to optimize my solution.

Carlo

sharma...@google.com

unread,
Jul 8, 2022, 11:19:19 AM7/8/22
to DynamoRIO Users
Hi Carlo,
I'll try answering but it'd help if you can provide some clarity about your goal.

For just counting/printing when Sleep is called, you can insert a clean call to a routine (that does the counting/printing) prior to each such call instruction (see the inscount sample -- https://github.com/DynamoRIO/dynamorio/blob/9dbaf90d41e9da6ea240641f97877a2566378836/api/samples/inscount.cpp#L226) . This is similar to what you do in the event_app_instruction code you shared above, changing the drprintf call to dr_insert_clean_call. If you're concerned about the overhead of clean calls, you can add inline counter updates instead (see the countcalls sample -- https://github.com/DynamoRIO/dynamorio/blob/9dbaf90d41e9da6ea240641f97877a2566378836/api/samples/countcalls.c#L174). Both of these methods do not require flushing the code cache at all.

> Is it possible to do all the code modifications in a clean call, instead?

No, clean calls cannot modify instrumentation.

> I can't pass an instr_t in a clean_call (tried but had crashes since I was passing a nullptr, maybe because the instr was already deallocated)

Correct. The instrlist_t and instr_t you see in event_app_instruction live only until the fragment is encoded and stored in the code cache.

> I need to replace those Sleeps with NOPs

So, you need the dynamic call count for Sleep AND also replace them with NOPs? You can modify app code in the app2app event of drmgr. (Note that there are separate callbacks intended for app2app transformation, analysis and instrumentation insertion, drmgr_register_bb_instrumentation_ex_event).

>  I think that flushing the cache in a clean call will bring an additional overhead, and I would like to optimize my solution.

As you note, dr_unlink_flush_region indeed increases overhead since it forces rebuilding of the code cache fragments. The only time you'd need dr_unlink_flush_region is when you want to change fragment code. Let me know if your use case indeed requires that.

Abhinav

carlo cannarsa

unread,
Jul 8, 2022, 12:54:06 PM7/8/22
to sharma...@google.com, DynamoRIO Users
So, you need the dynamic call count for Sleep AND also replace them with NOPs? You can modify app code in the app2app event of drmgr. (Note that there are separate callbacks intended for app2app transformation, analysis and instrumentation insertion, drmgr_register_bb_instrumentation_ex_event).
Yes, thank you. I switched to drmg event_bb_app2app indeed. 
 
As you note, dr_unlink_flush_region indeed increases overhead since it forces rebuilding of the code cache fragments. The only time you'd need dr_unlink_flush_region is when you want to change fragment code. Let me know if your use case indeed requires that.
I think that it needs it, because I want to replace the Sleeps in a cycle, so I need to rebuild the fragment every time I hit one of those. Basically, I need to fire the event_bb_app2app each time I encouter a Sleep, . Am I correct? Is there a better solution?  

Carlo

Mail priva di virus. www.avast.com

--
You received this message because you are subscribed to a topic in the Google Groups "DynamoRIO Users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/dynamorio-users/0RPFu9QU0f4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to dynamorio-use...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dynamorio-users/ab9e94fd-bc35-4f5b-b152-6f547b2793e3n%40googlegroups.com.
Message has been deleted

sharma...@google.com

unread,
Jul 8, 2022, 1:38:47 PM7/8/22
to DynamoRIO Users
Hi,

>  I want to replace the Sleeps in a cycle, so I need to rebuild the fragment every time I hit one of those. Basically, I need to fire the event_bb_app2app each time I encouter a Sleep

Before the app executes a basic block, the BB is first instrumented by DynamoRIO (meaning that it goes through all the drmgr phases like app2app, analysis, insertion etc) and the instrumented BB is put in the code cache. All future executions then use this instrumented BB.

So, if you replace the Sleep call with nop in the app2app phase, it ensures that no Sleep calls will ever execute.

Abhinav

carlo cannarsa

unread,
Jul 8, 2022, 2:12:10 PM7/8/22
to DynamoRIO Users
I tried with drmgr app2app event but I still can't manage to get all Sleeps modified. It only modifies the first one. This is the code:

static dr_emit_flags_t
event_bb_app2app(void* drcontext, void* tag, instrlist_t* bb, bool for_trace,
    bool translating) {
    instr_t* instr = NULL, * next, * next_app, * prev;
    app_pc API_address = (app_pc)0x753f6490; // Sleep API address
   for (instr = instrlist_first(bb); instr != NULL; instr = next) {
        next = instr_get_next(instr);
        prev = instr_get_prev(instr);
        byte* pc = instr_get_app_pc(instr);
        byte* opcode = (byte*)instr_get_opcode(instr);

         if (instr_is_call(instr)) {
             int num_srcs = instr_num_srcs(instr);
             int* iat_addr;
             int true_addr;
             if (num_srcs > 0) {
                opnd_t src = instr_get_src(instr, 0);
                if (opnd_is_memory_reference(src)) {
                   iat_addr = (int *)opnd_compute_address(src, &mc);
                    true_addr = *iat_addr;
                    if (true_addr == (int)API_address) { // There is a call to a Sleep
                        insert_call_sleep_instrumentation(drcontext, bb, instr, *iat_addr, true_addr, pc); // here goes the instrumentation
                    }
                }
            }
         }
    }
    return DR_EMIT_DEFAULT;
}

void insert_call_sleep_instrumentation(void* drcontext, instrlist_t* bb, instr_t* instr, int iat_addr, int true_addr, app_pc pc) {
    dr_fprintf(STDERR, "\nFOUND SLEEP: 0x%x -> 0x%x @ 0x%x\n", iat_addr, true_addr, pc);
   
    instr_t* new_instr = INSTR_CREATE_nop(drcontext); // NOP instruction
    if (instr_get_prefix_flag(instr, PREFIX_LOCK))
        instr_set_prefix_flag(new_instr, PREFIX_LOCK);
    instr_set_translation(new_instr, instr_get_app_pc(instr));
    instrlist_replace(bb, instr, new_instr);
    instr_destroy(drcontext, instr);
}

I registered this event with
drmgr_register_bb_app2app_event((drmgr_xform_cb_t)event_bb_app2app, NULL);

sharma...@google.com

unread,
Jul 8, 2022, 3:01:15 PM7/8/22
to DynamoRIO Users
> It only modifies the first one

Do you mean your code finds only one Sleep call in the BB, even though there are multiple?

Try printing the instrlist_t with instrlist_disassemble, at the beginning and end of event_bb_app2app. Is the output expected? If not, I'd suggest investigating your logic of replacing the instrs in the list; is it messing up your loop iteration?

Abhinav

carlo cannarsa

unread,
Jul 8, 2022, 3:01:52 PM7/8/22
to DynamoRIO Users
I'm testing with different cases and you're right, it works!
There is another problem, though. And maybe I know why it didn't work before. I am also wrapping the Sleep function with drwrap. I'll explain better what I want to do:
I want to 
1) Hook every Sleep in the program with drwrap
2) If I detect that there is a cycle of Sleep in app2app phase, I want to replace all them with NOPs

I thought that for the second case there will be no problem with drwrap because if I replace them, no hook will be invoked. But what happens is that when I encounter a Sleep the drwrap handler kicks in and doesn't return in the event_app2app code, so I lose control in it (and this is proved by the fact that there is no print, so the code inside event_app2app is not executed). Maybe there is some problem of priorities, it's like drwrap has the priority over app2app. Is it possible to set priority on the app2app code and then on drwrap?
Carlo

Reply all
Reply to author
Forward
0 new messages