| /* ********************************************************** |
| * Copyright (c) 2012 Google, Inc. All rights reserved. |
| * Copyright (c) 2008 VMware, 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 VMware, 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 VMWARE, 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. |
| */ |
| |
| #include "dr_api.h" |
| #include "client_tools.h" |
| |
| #define MINSERT instrlist_meta_preinsert |
| |
| #ifdef X64 |
| /* we use this for clean call base-disp refs */ |
| static reg_t buf[] = { 0xcafebabe, 0xfeedadad, 0xeeeeeeee, 0xbadcabee }; |
| #endif |
| |
| static void |
| ind_call(reg_t a1, reg_t a2) |
| { |
| dr_fprintf(STDERR, "bar "PFX" "PFX"\n", a1, a2); |
| } |
| |
| static void (*ind_call_ptr)(reg_t a1, reg_t a2) = ind_call; |
| |
| static void |
| foo(reg_t a1, reg_t a2, reg_t a3, reg_t a4, reg_t a5, reg_t a6, reg_t a7, reg_t a8) |
| { |
| dr_fprintf(STDERR, "foo "PFX" "PFX" "PFX" "PFX"\n "PFX" "PFX" "PFX" "PFX"\n", |
| a1, |
| /* printing addr of buf would be non-deterministic */ |
| IF_X64_ELSE((a2 == (reg_t) buf) ? 1 : -1, a2), |
| a3, a4, a5, a6, a7, a8); |
| } |
| |
| static void |
| bar(reg_t a1, reg_t a2) |
| { |
| /* test indirect call handling in clean call analysis */ |
| ind_call_ptr(a1, a2); |
| } |
| |
| static void |
| save_test() |
| { |
| int i; |
| void *drcontext = dr_get_current_drcontext(); |
| dr_fprintf(STDERR, "verifying values\n"); |
| if (*(((reg_t *)dr_get_tls_field(drcontext))+2) != 1) { |
| dr_fprintf(STDERR, "Write to client tls from cache failed, got %d, expected %d\n", |
| *(((reg_t *)dr_get_tls_field(drcontext))+2), 1); |
| } |
| for (i = SPILL_SLOT_1; i <= SPILL_SLOT_MAX; i++) { |
| reg_t value = dr_read_saved_reg(drcontext, i); |
| if (value != i + 1 - SPILL_SLOT_1) { |
| dr_fprintf(STDERR, "slot %d value %d doesn't match expected value %d\n", |
| i, value, i + 1 - SPILL_SLOT_1); |
| } |
| if (i % 2 == 0) { |
| /* set every other reg */ |
| value = 100 - i; |
| dr_write_saved_reg(drcontext, i, value); |
| } |
| } |
| } |
| |
| static int post_crash = 0; |
| static void *tag_of_interest; |
| |
| static void |
| restore_state_event(void *drcontext, void *tag, dr_mcontext_t *mcontext, |
| bool restore_memory, bool app_code_consistent) |
| { |
| if (tag == tag_of_interest) { |
| dr_fprintf(STDERR, "in restore_state for our clean call crash %d\n", post_crash); |
| /* flush, so we can use different instrumentation next time */ |
| dr_delay_flush_region(dr_fragment_app_pc(tag), 1, 0, NULL); |
| } |
| } |
| |
| static void |
| cleancall_aflags_save(void) |
| { |
| dr_fprintf(STDERR, "cleancall_aflags_save\n"); |
| } |
| |
| static void |
| cleancall_no_aflags_save(void) |
| { |
| dr_fprintf(STDERR, "cleancall_no_aflags_save\n"); |
| } |
| |
| static bool first_bb = true; |
| static dr_emit_flags_t |
| bb_event(void *drcontext, void* tag, instrlist_t *bb, bool for_trace, bool translating) |
| { |
| instr_t *instr, *next_instr, *next_next_instr; |
| bool modified = false; |
| |
| # define PRE(bb, i) \ |
| instrlist_preinsert(bb, instr, INSTR_XL8(i, dr_fragment_app_pc(tag))) |
| # define PREM(bb, i) \ |
| instrlist_meta_preinsert(bb, instr, INSTR_XL8(i, dr_fragment_app_pc(tag))) |
| |
| if (first_bb) { |
| instr_t *add, *cmp; |
| /* test cleancall with/without aflags save |
| * cleancall_aflags_save |
| * cmp # fake cmp app instr |
| * cleancall_no_aflags_save |
| * add # fake add app instr |
| */ |
| first_bb = false; |
| instr = instrlist_first(bb); |
| cmp = INSTR_CREATE_cmp(drcontext, opnd_create_reg(DR_REG_XAX), |
| opnd_create_reg(DR_REG_XAX)); |
| PRE(bb, cmp); |
| add = INSTR_CREATE_add(drcontext, opnd_create_reg(DR_REG_XAX), |
| OPND_CREATE_INT32(0)); |
| PRE(bb, add); |
| dr_insert_clean_call(drcontext, bb, add, |
| (void*) cleancall_no_aflags_save, false, 0); |
| dr_insert_clean_call(drcontext, bb, cmp, |
| (void*) cleancall_aflags_save, false, 0); |
| } |
| |
| /* Look for 3 nops to indicate handler is set up */ |
| for (instr = instrlist_first(bb); instr != NULL; instr = next_instr) { |
| next_instr = instr_get_next(instr); |
| |
| if (next_instr != NULL) |
| next_next_instr = instr_get_next(next_instr); |
| else |
| next_next_instr = NULL; |
| |
| if (instr_is_nop(instr) && |
| next_instr != NULL && instr_is_nop(next_instr) && |
| next_next_instr != NULL && instr_is_call_direct(next_next_instr)) { |
| |
| ASSERT(tag_of_interest == NULL || tag_of_interest == tag); |
| tag_of_interest = tag; |
| modified = true; |
| |
| /* # of crashes is tied to # of setjmps in cleancall.c */ |
| |
| if (post_crash == 0) { |
| /* Test crash in 1st clean call arg */ |
| dr_fprintf(STDERR, "inserting clean call crash code 1\n"); |
| dr_insert_clean_call(drcontext, bb, instrlist_first(bb), (void*) foo, |
| false /* don't save fp state */, 1, |
| OPND_CREATE_ABSMEM(NULL, OPSZ_4)); |
| post_crash++; |
| } else if (post_crash == 1) { |
| /* Test crash in 2nd clean call arg */ |
| dr_fprintf(STDERR, "inserting clean call crash code 2\n"); |
| dr_insert_clean_call(drcontext, bb, instrlist_first(bb), (void*) foo, |
| false /* don't save fp state */, 2, |
| OPND_CREATE_INT32(0), |
| OPND_CREATE_ABSMEM(NULL, OPSZ_4)); |
| post_crash++; |
| } else if (post_crash == 2) { |
| /* PR 307242: test xsp args */ |
| reg_id_t scratch; |
| #ifdef X64 |
| # ifdef WINDOWS |
| scratch = REG_XCX; |
| # else |
| scratch = REG_XDI; |
| # endif |
| #else |
| scratch = REG_XAX; |
| #endif |
| |
| dr_fprintf(STDERR, "inserting xsp arg testing\n"); |
| /* See notes below: we crash after, so can clobber regs */ |
| PRE(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(scratch), |
| OPND_CREATE_INT32(sizeof(reg_t)))); |
| PRE(bb, INSTR_CREATE_push_imm(drcontext, |
| OPND_CREATE_INT32((int)0xbcbcaba0))); |
| PRE(bb, INSTR_CREATE_push_imm(drcontext, |
| OPND_CREATE_INT32((int)0xbcbcaba1))); |
| dr_insert_clean_call(drcontext, bb, instr, (void*) bar, |
| false /* don't save fp state */, 2, |
| OPND_CREATE_MEM32(REG_XSP, 0), |
| /* test conflicting w/ scratch reg */ |
| opnd_create_base_disp(REG_XSP, scratch, 1, |
| 0, OPSZ_PTR)); |
| /* Even though we'll be doing a longjmp, building w/ VS2010 results |
| * in silent failure on handling the exception so we restore xsp. |
| */ |
| PRE(bb, INSTR_CREATE_lea(drcontext, opnd_create_reg(REG_XSP), |
| OPND_CREATE_MEM_lea(REG_XSP, REG_NULL, 0, |
| 2*sizeof(reg_t)))); |
| PRE(bb, INSTR_CREATE_mov_ld(drcontext, opnd_create_reg(REG_XAX), |
| OPND_CREATE_ABSMEM(NULL, OPSZ_PTR))); |
| post_crash++; |
| } else if (post_crash == 3) { |
| #ifdef X86_64 |
| /* For x64, test using calling convention regs as params. |
| * We do different things depending on order, whether a |
| * memory reference, etc. |
| * To test our values, we clobber app registers. The app |
| * has a setjmp set up, so we crash after for a deterministic |
| * result. |
| */ |
| dr_fprintf(STDERR, "inserting clean call arg testing\n"); |
| /* We do not translate the regs back */ |
| |
| /* We arrange to have our base-disps all be small offsets off buf */ |
| PRE(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(REG_RDX), |
| OPND_CREATE_INT32(sizeof(reg_t)))); |
| PRE(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(REG_RCX), |
| OPND_CREATE_INTPTR(buf))); |
| PRE(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(REG_R8), |
| OPND_CREATE_INT32(-42))); |
| PRE(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(REG_R9), |
| OPND_CREATE_INT32((int)0xdeadbeef))); |
| PRE(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(REG_RAX), |
| OPND_CREATE_INT32(2*sizeof(reg_t)))); |
| PRE(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(REG_RBP), |
| OPND_CREATE_INT32(3*sizeof(reg_t)))); |
| dr_insert_clean_call(drcontext, bb, instr, (void*) foo, |
| false /* don't save fp state */, 8, |
| /* Pick registers used by both Windows and Linux */ |
| opnd_create_reg(REG_RDX), |
| opnd_create_reg(REG_RCX), |
| opnd_create_reg(REG_R9), |
| opnd_create_reg(REG_R8), |
| OPND_CREATE_MEM32(REG_RCX, 0), |
| /* test having only index register conflict */ |
| opnd_create_base_disp(REG_RBP, REG_RCX, 1, |
| 0, OPSZ_PTR), |
| /* test OPSZ_4, and using register modified |
| * by clean call setup (rax) */ |
| opnd_create_base_disp(REG_RAX, REG_RCX, 1, |
| 0, OPSZ_4), |
| /* test having both base and index conflict */ |
| opnd_create_base_disp(REG_RDX, REG_RCX, 1, |
| 0, OPSZ_PTR)); |
| #endif |
| PRE(bb, INSTR_CREATE_mov_ld(drcontext, opnd_create_reg(REG_XAX), |
| OPND_CREATE_ABSMEM(NULL, OPSZ_PTR))); |
| post_crash++; |
| } else { |
| /* Test register saving and restoring and access to saved registers |
| * from outside the cache. */ |
| int i; |
| instr_t *fault = INSTR_CREATE_mov_ld(drcontext, opnd_create_reg(REG_XAX), |
| OPND_CREATE_ABSMEM(NULL, OPSZ_PTR)); |
| instr_t *post_fault = INSTR_CREATE_label(drcontext); |
| dr_fprintf(STDERR, "inserting saved reg access testing\n"); |
| |
| /* we want to test all the slots so juggle around to save xax and flags |
| * to client's tls. */ |
| dr_save_reg(drcontext, bb, instr, REG_XBX, SPILL_SLOT_1); |
| dr_insert_read_tls_field(drcontext, bb, instr, REG_XBX); |
| PREM(bb, INSTR_CREATE_mov_st |
| (drcontext, opnd_create_base_disp(REG_XBX, REG_NULL, 0, |
| 0 * sizeof(reg_t), OPSZ_PTR), |
| opnd_create_reg(REG_XAX))); |
| dr_save_arith_flags(drcontext, bb, instr, SPILL_SLOT_2); |
| PREM(bb, INSTR_CREATE_mov_st |
| (drcontext, opnd_create_base_disp(REG_XBX, REG_NULL, 0, |
| 1 * sizeof(reg_t), OPSZ_PTR), |
| opnd_create_reg(REG_XAX))); |
| PREM(bb, INSTR_CREATE_mov_imm(drcontext, opnd_create_reg(REG_XAX), |
| OPND_CREATE_INT32(1))); |
| /* test tls writing */ |
| PREM(bb, INSTR_CREATE_mov_st |
| (drcontext, opnd_create_base_disp(REG_XBX, REG_NULL, 0, |
| 2 * sizeof(reg_t), OPSZ_PTR), |
| opnd_create_reg(REG_XAX))); |
| dr_restore_reg(drcontext, bb, instr, REG_XBX, SPILL_SLOT_1); |
| |
| /* now test the slots */ |
| /* xax is our tls + 0, flags is our tls + sizeof(reg_t) */ |
| for (i = SPILL_SLOT_1; i <= SPILL_SLOT_MAX; i++) { |
| dr_save_reg(drcontext, bb, instr, REG_XAX, i); |
| PREM(bb, INSTR_CREATE_inc(drcontext, opnd_create_reg(REG_XAX))); |
| } |
| dr_insert_clean_call(drcontext, bb, instr, (void *)save_test, |
| true /* try saving the fp state */, 0); |
| for (i = SPILL_SLOT_1; i <= SPILL_SLOT_MAX; i++) { |
| /* test using opnd */ |
| if (i < dr_max_opnd_accessible_spill_slot()) { |
| PREM(bb, INSTR_CREATE_cmp |
| (drcontext, dr_reg_spill_slot_opnd(drcontext, i), |
| (i % 2 == 0) ? |
| OPND_CREATE_INT8(100 - i) : |
| OPND_CREATE_INT8(i + 1 - SPILL_SLOT_1))); |
| PREM(bb, INSTR_CREATE_jcc |
| (drcontext, OP_jne, opnd_create_instr(fault))); |
| } |
| /* test using restore routine */ |
| dr_restore_reg(drcontext, bb, instr, REG_XAX, i); |
| PREM(bb, INSTR_CREATE_cmp |
| (drcontext, opnd_create_reg(REG_XAX), |
| (i % 2 == 0) ? |
| OPND_CREATE_INT8(100 - i) : |
| OPND_CREATE_INT8(i + 1 - SPILL_SLOT_1))); |
| PREM(bb, INSTR_CREATE_jcc |
| (drcontext, OP_jne, opnd_create_instr(fault))); |
| } |
| PREM(bb, INSTR_CREATE_jmp_short |
| (drcontext, opnd_create_instr(post_fault))); |
| PRE(bb, fault); /* pre not prem since we want this to be an app fault */ |
| PREM(bb, post_fault); |
| |
| /* Now juggle xax and flags back from client tls */ |
| dr_save_reg(drcontext, bb, instr, REG_XBX, SPILL_SLOT_1); |
| dr_insert_read_tls_field(drcontext, bb, instr, REG_XBX); |
| PREM(bb, INSTR_CREATE_mov_ld |
| (drcontext, opnd_create_reg(REG_XAX), |
| opnd_create_base_disp(REG_XBX, REG_NULL, 0, |
| 1 * sizeof(reg_t), OPSZ_PTR))); |
| dr_restore_arith_flags(drcontext, bb, instr, SPILL_SLOT_MAX); |
| PREM(bb, INSTR_CREATE_mov_ld |
| (drcontext, opnd_create_reg(REG_XAX), |
| opnd_create_base_disp(REG_XBX, REG_NULL, 0, |
| 0 * sizeof(reg_t), OPSZ_PTR))); |
| dr_restore_reg(drcontext, bb, instr, REG_XBX, SPILL_SLOT_1); |
| |
| #if VERBOSE /* for debugging */ |
| instrlist_disassemble(drcontext, tag, bb, dr_get_stdout_file()); |
| #endif |
| post_crash++; /* note we don't actually crash so this must be the |
| * last test */ |
| } |
| } |
| } |
| |
| if (modified) /* store since not constant instrumentation */ |
| return DR_EMIT_STORE_TRANSLATIONS; |
| else |
| return DR_EMIT_DEFAULT; |
| } |
| |
| static void |
| thread_exit(void *drcontext) |
| { |
| dr_thread_free(drcontext, dr_get_tls_field(drcontext), 3 * sizeof(reg_t)); |
| } |
| |
| static void |
| thread_start(void *drcontext) |
| { |
| dr_set_tls_field(drcontext, dr_thread_alloc(drcontext, 3 * sizeof(reg_t))); |
| } |
| |
| DR_EXPORT void |
| dr_init(client_id_t id) |
| { |
| dr_register_bb_event(bb_event); |
| dr_register_thread_init_event(thread_start); |
| dr_register_thread_exit_event(thread_exit); |
| dr_register_restore_state_event(restore_state_event); |
| } |