| /* ********************************************************** |
| * Copyright (c) 2011-2012 Google, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of Google, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Tests the drwrap extension */ |
| |
| #include "dr_api.h" |
| #include "drwrap.h" |
| #include "drmgr.h" |
| #include <string.h> /* memset */ |
| |
| #define CHECK(x, msg) do { \ |
| if (!(x)) { \ |
| dr_fprintf(STDERR, "%s\n", msg); \ |
| dr_abort(); \ |
| } \ |
| } while (0); |
| |
| #define DRWRAP_NATIVE_PARAM 0xdeadbeef |
| |
| static void event_exit(void); |
| static void wrap_pre(void *wrapcxt, OUT void **user_data); |
| static void wrap_post(void *wrapcxt, void *user_data); |
| static void wrap_unwindtest_pre(void *wrapcxt, OUT void **user_data); |
| static void wrap_unwindtest_post(void *wrapcxt, void *user_data); |
| static void wrap_unwindtest_seh_pre(void *wrapcxt, OUT void **user_data); |
| static void wrap_unwindtest_seh_post(void *wrapcxt, void *user_data); |
| static int replacewith(int *x); |
| static int replacewith2(int *x); |
| static int replace_callsite(int *x); |
| |
| static uint load_count; |
| |
| static int tls_idx; |
| |
| static app_pc addr_replace; |
| static app_pc addr_replace2; |
| static app_pc addr_replace_callsite; |
| |
| static app_pc addr_level0; |
| static app_pc addr_level1; |
| static app_pc addr_level2; |
| static app_pc addr_tailcall; |
| static app_pc addr_skipme; |
| static app_pc addr_preonly; |
| static app_pc addr_postonly; |
| static app_pc addr_runlots; |
| |
| static app_pc addr_long0; |
| static app_pc addr_long1; |
| static app_pc addr_long2; |
| static app_pc addr_long3; |
| static app_pc addr_longdone; |
| |
| static void |
| wrap_addr(OUT app_pc *addr, const char *name, const module_data_t *mod, |
| bool pre, bool post) |
| { |
| bool ok; |
| *addr = (app_pc) dr_get_proc_address(mod->handle, name); |
| CHECK(*addr != NULL, "cannot find lib export"); |
| ok = drwrap_wrap(*addr, pre ? wrap_pre : NULL, post ? wrap_post : NULL); |
| CHECK(ok, "wrap failed"); |
| CHECK(drwrap_is_wrapped(*addr, pre ? wrap_pre : NULL, post ? wrap_post : NULL), |
| "drwrap_is_wrapped query failed"); |
| } |
| |
| static void |
| unwrap_addr(app_pc addr, const char *name, const module_data_t *mod, |
| bool pre, bool post) |
| { |
| bool ok; |
| ok = drwrap_unwrap(addr, pre ? wrap_pre : NULL, post ? wrap_post : NULL); |
| CHECK(ok, "unwrap failed"); |
| CHECK(!drwrap_is_wrapped(addr, pre ? wrap_pre : NULL, post ? wrap_post : NULL), |
| "drwrap_is_wrapped query failed"); |
| } |
| |
| static void |
| wrap_unwindtest_addr(OUT app_pc *addr, const char *name, const module_data_t *mod) |
| { |
| bool ok; |
| *addr = (app_pc) dr_get_proc_address(mod->handle, name); |
| CHECK(*addr != NULL, "cannot find lib export"); |
| ok = drwrap_wrap(*addr, wrap_unwindtest_pre, wrap_unwindtest_post); |
| CHECK(ok, "wrap unwindtest failed"); |
| CHECK(drwrap_is_wrapped(*addr, wrap_unwindtest_pre, wrap_unwindtest_post), |
| "drwrap_is_wrapped query failed"); |
| } |
| |
| static void |
| unwrap_unwindtest_addr(app_pc addr, const char *name, const module_data_t *mod) |
| { |
| bool ok; |
| ok = drwrap_unwrap(addr, wrap_unwindtest_pre, wrap_unwindtest_post); |
| CHECK(ok, "unwrap failed"); |
| CHECK(!drwrap_is_wrapped(addr, wrap_unwindtest_pre, wrap_unwindtest_post), |
| "drwrap_is_wrapped query failed"); |
| } |
| |
| static void |
| module_load_event(void *drcontext, const module_data_t *mod, bool loaded) |
| { |
| if (strstr(dr_module_preferred_name(mod), |
| "client.drwrap-test.appdll.") != NULL) { |
| bool ok; |
| instr_t inst; |
| app_pc init_pc, pc, next_pc; |
| |
| load_count++; |
| if (load_count == 2) { |
| /* test no-frills */ |
| drwrap_set_global_flags(DRWRAP_NO_FRILLS); |
| } |
| |
| addr_replace = (app_pc) dr_get_proc_address(mod->handle, "replaceme"); |
| CHECK(addr_replace != NULL, "cannot find lib export"); |
| ok = drwrap_replace(addr_replace, (app_pc) replacewith, false); |
| CHECK(ok, "replace failed"); |
| |
| addr_replace2 = (app_pc) dr_get_proc_address(mod->handle, "replaceme2"); |
| CHECK(addr_replace2 != NULL, "cannot find lib export"); |
| ok = drwrap_replace_native(addr_replace2, (app_pc) replacewith2, true/*at entry*/, |
| 0, (void *)(ptr_int_t)DRWRAP_NATIVE_PARAM, false); |
| CHECK(ok, "replace_native failed"); |
| |
| init_pc = (app_pc) dr_get_proc_address(mod->handle, "replace_callsite"); |
| CHECK(init_pc != NULL, "cannot find lib export"); |
| /* Find callsite: we assume we'll linearly hit a ret. We take final call |
| * to skip any PIC call. |
| */ |
| instr_init(drcontext, &inst); |
| pc = init_pc; |
| do { |
| instr_reset(drcontext, &inst); |
| next_pc = decode(drcontext, pc, &inst); |
| if (!instr_valid(&inst)) |
| break; |
| /* if initial jmp, follow it to handle ILT-indirection */ |
| if (pc == init_pc && instr_is_ubr(&inst)) |
| next_pc = opnd_get_pc(instr_get_target(&inst)); |
| else if (instr_is_call(&inst)) |
| addr_replace_callsite = pc; |
| pc = next_pc; |
| } while (instr_valid(&inst) && instr_get_opcode(&inst) != OP_ret); |
| CHECK(addr_replace_callsite != NULL, "cannot find lib export"); |
| ok = drwrap_replace_native(addr_replace_callsite, (app_pc) replace_callsite, |
| false/*!at entry*/, 0, |
| (void *)(ptr_int_t)DRWRAP_NATIVE_PARAM, false); |
| CHECK(ok, "replace_native failed"); |
| instr_free(drcontext, &inst); |
| |
| wrap_addr(&addr_level0, "level0", mod, true, true); |
| wrap_addr(&addr_level1, "level1", mod, true, true); |
| wrap_addr(&addr_level2, "level2", mod, true, true); |
| wrap_addr(&addr_tailcall, "makes_tailcall", mod, true, true); |
| wrap_addr(&addr_skipme, "skipme", mod, true, true); |
| wrap_addr(&addr_preonly, "preonly", mod, true, false); |
| wrap_addr(&addr_postonly, "postonly", mod, false, true); |
| wrap_addr(&addr_runlots, "runlots", mod, false, true); |
| |
| /* test longjmp */ |
| wrap_unwindtest_addr(&addr_long0, "long0", mod); |
| wrap_unwindtest_addr(&addr_long1, "long1", mod); |
| wrap_unwindtest_addr(&addr_long2, "long2", mod); |
| wrap_unwindtest_addr(&addr_long3, "long3", mod); |
| wrap_unwindtest_addr(&addr_longdone, "longdone", mod); |
| drmgr_set_tls_field(drcontext, tls_idx, (void *)(ptr_uint_t)0); |
| #ifdef WINDOWS |
| /* test SEH */ |
| /* we can't do this test for no-frills b/c only one wrap per addr */ |
| if (load_count == 1) { |
| ok = drwrap_wrap_ex(addr_long0, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post, |
| NULL, DRWRAP_UNWIND_ON_EXCEPTION); |
| CHECK(ok, "wrap failed"); |
| ok = drwrap_wrap_ex(addr_long1, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post, |
| NULL, DRWRAP_UNWIND_ON_EXCEPTION); |
| CHECK(ok, "wrap failed"); |
| ok = drwrap_wrap_ex(addr_long2, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post, |
| NULL, DRWRAP_UNWIND_ON_EXCEPTION); |
| CHECK(ok, "wrap failed"); |
| ok = drwrap_wrap_ex(addr_long3, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post, |
| NULL, DRWRAP_UNWIND_ON_EXCEPTION); |
| CHECK(ok, "wrap failed"); |
| ok = drwrap_wrap_ex(addr_longdone, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post, |
| NULL, DRWRAP_UNWIND_ON_EXCEPTION); |
| CHECK(ok, "wrap failed"); |
| } |
| #endif |
| } |
| } |
| |
| static void |
| module_unload_event(void *drcontext, const module_data_t *mod) |
| { |
| if (strstr(dr_module_preferred_name(mod), |
| "client.drwrap-test.appdll.") != NULL) { |
| bool ok; |
| ok = drwrap_replace(addr_replace, NULL, true); |
| CHECK(ok, "un-replace failed"); |
| ok = drwrap_replace_native(addr_replace2, NULL, true, 0, NULL, true); |
| CHECK(ok, "un-replace_native failed"); |
| ok = drwrap_replace_native(addr_replace_callsite, NULL, false, 0, NULL, true); |
| CHECK(ok, "un-replace_native failed"); |
| |
| unwrap_addr(addr_level0, "level0", mod, true, true); |
| unwrap_addr(addr_level1, "level1", mod, true, true); |
| unwrap_addr(addr_level2, "level2", mod, true, true); |
| unwrap_addr(addr_tailcall, "makes_tailcall", mod, true, true); |
| unwrap_addr(addr_preonly, "preonly", mod, true, false); |
| /* skipme, postonly, and runlots were already unwrapped */ |
| |
| /* test longjmp */ |
| unwrap_unwindtest_addr(addr_long0, "long0", mod); |
| unwrap_unwindtest_addr(addr_long1, "long1", mod); |
| unwrap_unwindtest_addr(addr_long2, "long2", mod); |
| unwrap_unwindtest_addr(addr_long3, "long3", mod); |
| unwrap_unwindtest_addr(addr_longdone, "longdone", mod); |
| drmgr_set_tls_field(drcontext, tls_idx, (void *)(ptr_uint_t)0); |
| #ifdef WINDOWS |
| /* test SEH */ |
| if (load_count == 1) { |
| ok = drwrap_unwrap(addr_long0, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post); |
| CHECK(ok, "unwrap failed"); |
| ok = drwrap_unwrap(addr_long1, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post); |
| CHECK(ok, "unwrap failed"); |
| ok = drwrap_unwrap(addr_long2, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post); |
| CHECK(ok, "unwrap failed"); |
| ok = drwrap_unwrap(addr_long3, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post); |
| CHECK(ok, "unwrap failed"); |
| ok = drwrap_unwrap(addr_longdone, wrap_unwindtest_seh_pre, |
| wrap_unwindtest_seh_post); |
| } |
| CHECK(ok, "unwrap failed"); |
| #endif |
| } |
| } |
| |
| DR_EXPORT void |
| dr_init(client_id_t id) |
| { |
| drwrap_init(); |
| dr_register_exit_event(event_exit); |
| drmgr_register_module_load_event(module_load_event); |
| drmgr_register_module_unload_event(module_unload_event); |
| tls_idx = drmgr_register_tls_field(); |
| CHECK(tls_idx > -1, "unable to reserve TLS field"); |
| } |
| |
| static void |
| event_exit(void) |
| { |
| drmgr_unregister_tls_field(tls_idx); |
| drwrap_exit(); |
| dr_fprintf(STDERR, "all done\n"); |
| } |
| |
| static int |
| replacewith(int *x) |
| { |
| *x = 6; |
| return 0; |
| } |
| |
| static int |
| replacewith2(int *x) |
| { |
| ptr_int_t param = dr_read_saved_reg(dr_get_current_drcontext(), |
| DRWRAP_REPLACE_NATIVE_DATA_SLOT); |
| CHECK(param == DRWRAP_NATIVE_PARAM, "native param wrong"); |
| *x = 999; |
| /* We must call this prior to returning, to avoid going native. |
| * This also serves as a test of dr_redirect_native_target() |
| * as drwrap's continuation relies on that. |
| * Because drwrap performs a bunch of flushes, it tests |
| * the unlink/relink of the client ibl xfer gencode. |
| * |
| * XXX: could also verify that retaddr is app's, and that |
| * traces continue on the other side. The latter, certainly, |
| * is very difficult to write a simple test for. |
| */ |
| drwrap_replace_native_fini(dr_get_current_drcontext()); |
| return 1; |
| } |
| |
| static int |
| replace_callsite(int *x) |
| { |
| ptr_int_t param = dr_read_saved_reg(dr_get_current_drcontext(), |
| DRWRAP_REPLACE_NATIVE_DATA_SLOT); |
| CHECK(param == DRWRAP_NATIVE_PARAM, "native param wrong"); |
| *x = 777; |
| drwrap_replace_native_fini(dr_get_current_drcontext()); |
| return 2; |
| } |
| |
| static void |
| wrap_pre(void *wrapcxt, OUT void **user_data) |
| { |
| bool ok; |
| CHECK(wrapcxt != NULL && user_data != NULL, "invalid arg"); |
| if (drwrap_get_func(wrapcxt) == addr_level0) { |
| dr_fprintf(STDERR, " <pre-level0>\n"); |
| CHECK(drwrap_get_arg(wrapcxt, 0) == (void *) 37, "get_arg wrong"); |
| ok = drwrap_set_arg(wrapcxt, 0, (void *) 42); |
| CHECK(ok, "set_arg error"); |
| *user_data = (void *) 99; |
| } else if (drwrap_get_func(wrapcxt) == addr_level1) { |
| dr_fprintf(STDERR, " <pre-level1>\n"); |
| ok = drwrap_set_arg(wrapcxt, 1, (void *) 1111); |
| CHECK(ok, "set_arg error"); |
| } else if (drwrap_get_func(wrapcxt) == addr_tailcall) { |
| dr_fprintf(STDERR, " <pre-makes_tailcall>\n"); |
| } else if (drwrap_get_func(wrapcxt) == addr_level2) { |
| dr_fprintf(STDERR, " <pre-level2>\n"); |
| } else if (drwrap_get_func(wrapcxt) == addr_skipme) { |
| dr_fprintf(STDERR, " <pre-skipme>\n"); |
| drwrap_skip_call(wrapcxt, (void *) 7, 0); |
| } else if (drwrap_get_func(wrapcxt) == addr_preonly) { |
| dr_fprintf(STDERR, " <pre-preonly>\n"); |
| } else |
| CHECK(false, "invalid wrap"); |
| } |
| |
| static void |
| wrap_post(void *wrapcxt, void *user_data) |
| { |
| bool ok; |
| CHECK(wrapcxt != NULL, "invalid arg"); |
| if (drwrap_get_func(wrapcxt) == addr_level0) { |
| dr_fprintf(STDERR, " <post-level0>\n"); |
| /* not preserved for no-frills */ |
| CHECK(load_count == 2 || user_data == (void *)99, "user_data not preserved"); |
| CHECK(drwrap_get_retval(wrapcxt) == (void *) 42, "get_retval error"); |
| } else if (drwrap_get_func(wrapcxt) == addr_level1) { |
| dr_fprintf(STDERR, " <post-level1>\n"); |
| ok = drwrap_set_retval(wrapcxt, (void *) -4); |
| CHECK(ok, "set_retval error"); |
| } else if (drwrap_get_func(wrapcxt) == addr_tailcall) { |
| dr_fprintf(STDERR, " <post-makes_tailcall>\n"); |
| } else if (drwrap_get_func(wrapcxt) == addr_level2) { |
| dr_fprintf(STDERR, " <post-level2>\n"); |
| } else if (drwrap_get_func(wrapcxt) == addr_skipme) { |
| CHECK(false, "should have skipped!"); |
| } else if (drwrap_get_func(wrapcxt) == addr_postonly) { |
| dr_fprintf(STDERR, " <post-postonly>\n"); |
| drwrap_unwrap(addr_skipme, wrap_pre, wrap_post); |
| CHECK(!drwrap_is_wrapped(addr_skipme, wrap_pre, wrap_post), |
| "drwrap_is_wrapped query failed"); |
| drwrap_unwrap(addr_postonly, NULL, wrap_post); |
| CHECK(!drwrap_is_wrapped(addr_postonly, NULL, wrap_post), |
| "drwrap_is_wrapped query failed"); |
| drwrap_unwrap(addr_runlots, NULL, wrap_post); |
| CHECK(!drwrap_is_wrapped(addr_runlots, NULL, wrap_post), |
| "drwrap_is_wrapped query failed"); |
| } else if (drwrap_get_func(wrapcxt) == addr_runlots) { |
| dr_fprintf(STDERR, " <post-runlots>\n"); |
| } else |
| CHECK(false, "invalid wrap"); |
| } |
| |
| static void |
| wrap_unwindtest_pre(void *wrapcxt, OUT void **user_data) |
| { |
| if (drwrap_get_func(wrapcxt) != addr_longdone) { |
| void *drcontext = dr_get_current_drcontext(); |
| ptr_uint_t val = (ptr_uint_t) drmgr_get_tls_field(drcontext, tls_idx); |
| dr_fprintf(STDERR, " <pre-long%d>\n", val); |
| /* increment per level of regular calls on way up */ |
| val++; |
| drmgr_set_tls_field(drcontext, tls_idx, (void *)val); |
| } |
| } |
| |
| static void |
| wrap_unwindtest_post(void *wrapcxt, void *user_data) |
| { |
| void *drcontext = dr_get_current_drcontext(); |
| ptr_uint_t val = (ptr_uint_t) drmgr_get_tls_field(drcontext, tls_idx); |
| if (drwrap_get_func(wrapcxt) == addr_longdone) { |
| /* ensure our post-calls were all called and we got back to 0 */ |
| CHECK(val == 0, "post-calls were bypassed"); |
| } else { |
| /* decrement on way down */ |
| val--; |
| dr_fprintf(STDERR, " <post-long%d%s>\n", val, |
| wrapcxt == NULL ? " abnormal" : ""); |
| drmgr_set_tls_field(drcontext, tls_idx, (void *)val); |
| } |
| } |
| |
| static void |
| wrap_unwindtest_seh_pre(void *wrapcxt, OUT void **user_data) |
| { |
| wrap_unwindtest_pre(wrapcxt, user_data); |
| } |
| |
| static void |
| wrap_unwindtest_seh_post(void *wrapcxt, void *user_data) |
| { |
| wrap_unwindtest_post(wrapcxt, user_data); |
| } |