| /* Copyright (c) 2008-2010, 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. |
| * * 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 THE COPYRIGHT |
| * OWNER 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. |
| */ |
| |
| // This file is part of ThreadSanitizer, a dynamic data race detector. |
| |
| // Some parts of the code in this file are taken from the examples |
| // in DynamoRIO distribution, which have the following copyright. |
| /* ********************************************************** |
| * Copyright (c) 2003-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. |
| */ |
| |
| // Author: Konstantin Serebryany. |
| // Author: Timur Iskhodzhanov. |
| // |
| // ******* WARNING ******** |
| // This code is experimental. Do not expect anything here to work. |
| // ***** END WARNING ****** |
| |
| #include "dr_api.h" |
| |
| #include "ts_util.h" |
| |
| #define EXTRA_REPLACE_PARAMS |
| #define REPORT_READ_RANGE(a,b) |
| #define REPORT_WRITE_RANGE(a,b) |
| #include "ts_replace.h" |
| |
| #define Printf dr_printf |
| |
| static void *g_lock; |
| static int g_n_created_threads; |
| |
| typedef unordered_map<intptr_t, string> SymbolsTable; |
| static SymbolsTable *sym_tab; |
| |
| string *g_main_module_path; |
| |
| //--------------- StackFrame ----------------- {{{1 |
| struct StackFrame { |
| uintptr_t pc; |
| uintptr_t sp; |
| StackFrame(uintptr_t p, uintptr_t s) : pc(p), sp(s) { } |
| }; |
| |
| |
| //--------------- DrThread ----------------- {{{1 |
| struct DrThread { |
| int tid; // A unique 0-based thread id. |
| vector<StackFrame> shadow_stack; |
| }; |
| |
| static DrThread &GetCurrentThread(void *drcontext) { |
| return *(DrThread*)dr_get_tls_field(drcontext); |
| } |
| |
| //--------------- ShadowStack ----------------- {{{1 |
| #define DEB_PR (0 && t.tid == 1) |
| |
| static void PrintShadowStack(DrThread &t) { |
| Printf("T%d Shadow stack (%d)\n", t.tid, (int)t.shadow_stack.size()); |
| for (int i = t.shadow_stack.size() - 1; i >= 0; i--) { |
| uintptr_t pc = t.shadow_stack[i].pc; |
| Printf("%s[%p]\n", g_main_module_path->c_str(), pc); |
| } |
| for (int i = t.shadow_stack.size() - 1; i >= 0; i--) { |
| uintptr_t pc = t.shadow_stack[i].pc; |
| uintptr_t sp = t.shadow_stack[i].sp; |
| Printf(" sp=%p pc=%p\n", sp, pc); |
| } |
| } |
| |
| static void UpdateShadowStack(DrThread &t, uintptr_t sp) { |
| while (t.shadow_stack.size() > 0 && sp >= t.shadow_stack.back().sp) { |
| t.shadow_stack.pop_back(); |
| if (DEB_PR) { |
| dr_mutex_lock(g_lock); |
| Printf("T%d PopShadowStack\n", t.tid); |
| PrintShadowStack(t); |
| dr_mutex_unlock(g_lock); |
| } |
| } |
| } |
| |
| static void PushShadowStack(DrThread &t, uintptr_t pc, uintptr_t target_pc, uintptr_t sp) { |
| if (t.shadow_stack.size() > 0) { |
| t.shadow_stack.back().pc = pc; |
| } |
| t.shadow_stack.push_back(StackFrame(target_pc, sp)); |
| if (DEB_PR) { |
| dr_mutex_lock(g_lock); |
| Printf("T%d PushShadowStack %p %p %d\n", t.tid, pc, target_pc, sp); |
| PrintShadowStack(t); |
| dr_mutex_unlock(g_lock); |
| } |
| } |
| |
| //--------------- callbacks ----------------- {{{1 |
| static void OnEvent_ThreadInit(void *drcontext) { |
| DrThread *t_ptr = new DrThread; |
| DrThread &t = *t_ptr; |
| |
| dr_mutex_lock(g_lock); |
| t.tid = g_n_created_threads++; |
| dr_mutex_unlock(g_lock); |
| |
| dr_set_tls_field(drcontext, t_ptr); |
| |
| dr_printf("T%d %s\n", t.tid, (char*)__FUNCTION__+8); |
| } |
| |
| static void OnEvent_ThreadExit(void *drcontext) { |
| DrThread &t = GetCurrentThread(drcontext); |
| dr_printf("T%d %s\n", t.tid, (char*)__FUNCTION__+8); |
| } |
| |
| void OnEvent_ModuleLoaded(void *drcontext, const module_data_t *info, |
| bool loaded) { |
| // if this assertion fails, your DynamoRIO is too old. You need rev261 with some patches... |
| CHECK(info->full_path); |
| |
| dr_printf("%s: %s (%s)\n", __FUNCTION__, |
| dr_module_preferred_name(info), info->full_path); |
| if (g_main_module_path == NULL) { |
| g_main_module_path = new string(info->full_path); |
| } |
| } |
| |
| static void OnEvent_Exit(void) { |
| dr_printf("ThreadSanitizerDynamoRio: done\n"); |
| dr_mutex_destroy(g_lock); |
| } |
| |
| static void On_Mop(uintptr_t pc, size_t size, void *a, bool is_w) { |
| void *drcontext = dr_get_current_drcontext(); |
| DrThread &t = GetCurrentThread(drcontext); |
| if (t.tid == 777) { |
| dr_fprintf(STDERR, "T%d pc=%p a=%p size=%ld %s\n", t.tid, pc, a, size, is_w ? "WRITE" : "READ"); |
| } |
| } |
| |
| static void On_Read(uintptr_t pc, size_t size, void *a) { |
| On_Mop(pc, size, a, false); |
| } |
| |
| static void On_Write(uintptr_t pc, size_t size, void *a) { |
| On_Mop(pc, size, a, true); |
| } |
| |
| static void On_AnyCall(uintptr_t pc, uintptr_t target_pc, uintptr_t sp, bool is_direct) { |
| void *drcontext = dr_get_current_drcontext(); |
| DrThread &t = GetCurrentThread(drcontext); |
| // dr_fprintf(STDOUT, "T%d CALL %p => %p; sp=%p\n", t.tid, pc, target_pc, sp); |
| PushShadowStack(t, pc, target_pc, sp); |
| } |
| |
| static void On_DirectCall(uintptr_t pc, uintptr_t target_pc, uintptr_t sp) { |
| On_AnyCall(pc, target_pc, sp, true); |
| } |
| |
| static void On_IndirectCall(uintptr_t pc, uintptr_t target_pc, uintptr_t sp) { |
| On_AnyCall(pc, target_pc, sp, false); |
| } |
| |
| static void On_TraceEnter(uintptr_t pc, uintptr_t sp) { |
| void *drcontext = dr_get_current_drcontext(); |
| DrThread &t = GetCurrentThread(drcontext); |
| // dr_fprintf(STDOUT, "T%d TRACE:\n%p\n%p\n", t.tid, pc, sp); |
| UpdateShadowStack(t, sp); |
| } |
| |
| //--------------- instrumentation ----------------- {{{1 |
| opnd_t opnd_create_base_disp_from_dst(opnd_t dst) { |
| return opnd_create_base_disp(opnd_get_base(dst), |
| opnd_get_index(dst), |
| opnd_get_scale(dst), |
| opnd_get_disp(dst), |
| OPSZ_lea); |
| } |
| |
| static void InstrumentOneMop(void* drcontext, instrlist_t *bb, |
| instr_t *instr, opnd_t opnd, bool is_w) { |
| // opnd_disassemble(drcontext, opnd, 1); |
| // dr_printf(" -- (%s opnd)\n", is_w ? "write" : "read"); |
| void *callback = (void*)(is_w ? On_Write : On_Read); |
| int size = opnd_size_in_bytes(opnd_get_size(opnd)); |
| |
| instr_t *tmp_instr = NULL; |
| reg_id_t reg = REG_XAX; |
| |
| /* save %xax */ |
| dr_save_reg(drcontext, bb, instr, reg, SPILL_SLOT_2); |
| |
| if (opnd_is_base_disp(opnd)) { |
| /* lea opnd => %xax */ |
| opnd_set_size(&opnd, OPSZ_lea); |
| tmp_instr = INSTR_CREATE_lea(drcontext, |
| opnd_create_reg(reg), |
| opnd); |
| } else if( |
| #ifdef X86_64 |
| opnd_is_rel_addr(opnd) || |
| #endif |
| opnd_is_abs_addr(opnd)) { |
| tmp_instr = INSTR_CREATE_mov_imm(drcontext, |
| opnd_create_reg(reg), |
| OPND_CREATE_INTPTR(opnd_get_addr(opnd))); |
| } |
| if (tmp_instr) { |
| // CHECK(tmp_instr); |
| instrlist_meta_preinsert(bb, instr, tmp_instr); |
| |
| /* clean call */ |
| dr_insert_clean_call(drcontext, bb, instr, callback, false, |
| 3, |
| OPND_CREATE_INTPTR(instr_get_app_pc(instr)), |
| OPND_CREATE_INT32(size), |
| opnd_create_reg(reg)); |
| /* restore %xax */ |
| dr_restore_reg(drcontext, bb, instr, REG_XAX, SPILL_SLOT_2); |
| } else { |
| dr_printf("%s ????????????????????\n", __FUNCTION__); |
| } |
| } |
| |
| static void InstrumentMopInstruction(void *drcontext, |
| instrlist_t *bb, instr_t *instr) { |
| // reads: |
| for (int a = 0; a < instr_num_srcs(instr); a++) { |
| opnd_t curop = instr_get_src(instr, a); |
| if (opnd_is_memory_reference(curop)) { |
| InstrumentOneMop(drcontext, bb, instr, curop, false); |
| } |
| } |
| // writes: |
| for (int a = 0; a < instr_num_dsts(instr); a++) { |
| opnd_t curop = instr_get_dst(instr, a); |
| if (opnd_is_memory_reference(curop)) { |
| InstrumentOneMop(drcontext, bb, instr, curop, true); |
| } |
| } |
| //dr_printf("reads: %d writes: %d\n", n_reads, n_writes); |
| } |
| |
| static void InstrumentInstruction(void *drcontext, instrlist_t *bb, |
| instr_t *instr) { |
| // instr_disassemble(drcontext, instr, 1); |
| // dr_printf(" -- \n"); |
| if (instr_is_call_direct(instr)) { |
| dr_insert_call_instrumentation(drcontext, bb, instr, |
| (app_pc)On_DirectCall); |
| } else if (instr_is_call_indirect(instr)) { |
| dr_insert_mbr_instrumentation(drcontext, bb, instr, |
| (app_pc)On_IndirectCall, SPILL_SLOT_1); |
| |
| } else if (instr_reads_memory(instr) || instr_writes_memory(instr)) { |
| InstrumentMopInstruction(drcontext, bb, instr); |
| } |
| } |
| |
| static dr_emit_flags_t OnEvent_Trace(void *drcontext, void *tag, |
| instrlist_t *trace, bool translating) { |
| instr_t *first_instr = NULL; |
| for (instr_t *instr = instrlist_first(trace); instr != NULL; |
| instr = instr_get_next(instr)) { |
| if (instr_get_app_pc(instr)) { |
| first_instr = instr; |
| break; |
| } |
| } |
| if (first_instr) { |
| // instr_disassemble(drcontext, first_instr, 1); |
| // dr_printf(" -- in_trace %p\n", instr_get_app_pc(first_instr)); |
| dr_insert_clean_call(drcontext, trace, first_instr, |
| (void*)On_TraceEnter, false, |
| 2, |
| OPND_CREATE_INTPTR(instr_get_app_pc(first_instr)), |
| opnd_create_reg(REG_XSP) |
| ); |
| } |
| return DR_EMIT_DEFAULT; |
| } |
| |
| int replace_foo(int i, int j, int k) { |
| dr_printf(" dy 'foo_replace'(%i, %i, %i)\n", i, j, k); |
| return 1; |
| } |
| |
| typedef unordered_map<intptr_t, void*> FunctionsReplaceMap; |
| static FunctionsReplaceMap *fun_replace_map; |
| |
| namespace wrap { |
| |
| int (*orig_foo)(int,int,int) = NULL; |
| int in_wrapper = 0; // TODO: Make it thread-local |
| |
| static int wrapped_foo(int i, int j, int k) { |
| in_wrapper = 1; |
| |
| dr_printf(" dy 'foo_wrap'(%i, %i, %i)\n", i, j, k); |
| dr_printf("orig_foo = %p\n", orig_foo); |
| int ret = 13; |
| if (orig_foo != NULL) |
| ret = orig_foo(i, j, k) + 4200; |
| else |
| dr_printf("ERROR! orig_foo is not set!\n");/**/ |
| |
| in_wrapper = 0; |
| return ret; |
| } |
| |
| int is_in_wrapper(int arg) { |
| // TODO: this may not work well with recursive functions |
| return in_wrapper; |
| } |
| } |
| |
| void print_bb(void* drcontext, instrlist_t *bb, const char * desc) { |
| dr_printf("==================\n"); |
| dr_printf("%s:\n", desc); |
| for (instr_t *i = instrlist_first(bb); i != NULL; i = instr_get_next(i)) { |
| instr_disassemble(drcontext, i, 1); |
| dr_printf("\n"); |
| } |
| dr_printf("==================\n"); |
| } |
| |
| static dr_emit_flags_t OnEvent_BB(void* drcontext, void *tag, instrlist_t *bb, |
| bool for_trace, bool translating) { |
| instr_t *first_instr = instrlist_first(bb); |
| app_pc pc = instr_get_app_pc(first_instr); |
| string symbol_name = "UNKNOWN"; |
| if (sym_tab->find((intptr_t)pc) != sym_tab->end()) { |
| symbol_name = (*sym_tab)[(intptr_t)pc]; |
| //dr_printf("Symbol = %s\n", symbol_name.c_str()); |
| } |
| |
| if (fun_replace_map->count((intptr_t)pc) > 0) { |
| // Replace client function with the function supplied by the tool. |
| // The logic is inspired by drmemory/replace.c |
| app_pc target_fun = (app_pc)(*fun_replace_map)[(intptr_t)pc]; |
| const module_data_t *info = dr_lookup_module(pc); |
| dr_printf("REDIR: %s (from %s) redirected to %p\n", |
| symbol_name.c_str(), info->full_path, target_fun); |
| |
| instrlist_clear(drcontext, bb); |
| instrlist_append(bb, INSTR_XL8(INSTR_CREATE_jmp(drcontext, opnd_create_pc(target_fun)), pc)); |
| } else { |
| if (StringMatch("*foo_to_wrap*", symbol_name)) { |
| const module_data_t *info = dr_lookup_module(pc); |
| dr_printf(" 'foo_to_wrap' entry point: bb %p, %s / %s\n", pc, dr_module_preferred_name(info), info->full_path); |
| wrap::orig_foo = (int (*)(int,int,int))(void*)pc; |
| |
| //print_bb(drcontext, bb, "BEFORE"); |
| // TODO: Use something more optimized than clean_call |
| dr_insert_clean_call(drcontext, bb, first_instr, (void*)wrap::is_in_wrapper, |
| false, 1, OPND_CREATE_INTPTR(pc)); |
| instr_t *opr_instr = INSTR_CREATE_test(drcontext, opnd_create_reg(REG_XAX), |
| opnd_create_reg(REG_XAX)); |
| instr_t *jne_instr = INSTR_CREATE_jcc(drcontext, OP_jz, |
| opnd_create_pc((app_pc)wrap::wrapped_foo)); |
| instrlist_meta_preinsert(bb, first_instr, opr_instr); |
| instrlist_meta_preinsert(bb, first_instr, jne_instr); |
| |
| //print_bb(drcontext, bb, "AFTER"); |
| } |
| |
| instr_t *instr, *next_instr; |
| for (instr = instrlist_first(bb); instr != NULL; instr = next_instr) { |
| next_instr = instr_get_next(instr); |
| if (instr_get_app_pc(instr)) // don't instrument non-app code |
| InstrumentInstruction(drcontext, bb, instr); |
| } |
| |
| |
| OnEvent_Trace(drcontext, tag, bb, translating); |
| } |
| |
| return DR_EMIT_DEFAULT; |
| } |
| |
| void ReadSymbolsTableFromFile(const char *filename) { |
| file_t f = dr_open_file(filename, DR_FILE_READ); |
| CHECK(f != INVALID_FILE); |
| |
| const int BUFF_SIZE = 1 << 16; // should be enough for testing |
| char buff[BUFF_SIZE]; |
| dr_read_file(f, buff, BUFF_SIZE); |
| char *cur_line = buff; |
| while (*cur_line) { |
| char *next_line = strstr(cur_line, "\n"); |
| if (next_line != NULL) |
| *next_line = 0; |
| char fun_name[1024]; |
| char dummy; |
| void* pc; |
| sscanf(cur_line, "%p %c %s", &pc, &dummy, fun_name); |
| //dr_printf("%s => %p\n", fun_name, pc); |
| (*sym_tab)[(intptr_t)pc] = fun_name; |
| |
| if (next_line == NULL) break; |
| cur_line = next_line + 1; |
| } |
| |
| } |
| |
| void ReplaceFunc3(void *img, void *rtn, string filter, void *fun_ptr) { |
| for (SymbolsTable::iterator i = sym_tab->begin(); i != sym_tab->end(); i++) { |
| if (StringMatch(filter, i->second)) |
| (*fun_replace_map)[(intptr_t)i->first] = fun_ptr; |
| } |
| } |
| |
| //--------------- dr_init ----------------- {{{1 |
| DR_EXPORT void dr_init(client_id_t id) { |
| sym_tab = new SymbolsTable; |
| |
| // HACK doesn't work if multiple options are passed. |
| const char *opstr = dr_get_options(id); |
| dr_printf("Options: %s\n", opstr); |
| const char *fname = strstr(opstr, "--symbols="); |
| if (fname) { |
| ReadSymbolsTableFromFile(fname + 10); |
| } |
| |
| // Register events. |
| dr_register_exit_event(OnEvent_Exit); |
| dr_register_bb_event(OnEvent_BB); |
| dr_register_trace_event(OnEvent_Trace); |
| dr_register_thread_init_event(OnEvent_ThreadInit); |
| dr_register_thread_exit_event(OnEvent_ThreadExit); |
| dr_register_module_load_event(OnEvent_ModuleLoaded); |
| g_lock = dr_mutex_create(); |
| |
| fun_replace_map = new FunctionsReplaceMap(); |
| void *img = NULL, *rtn = NULL; |
| #define AFUNPTR void* |
| ReplaceFunc3(img, rtn, "memchr", (AFUNPTR)Replace_memchr); |
| ReplaceFunc3(img, rtn, "strchr", (AFUNPTR)Replace_strchr); |
| ReplaceFunc3(img, rtn, "index", (AFUNPTR)Replace_strchr); |
| ReplaceFunc3(img, rtn, "strrchr", (AFUNPTR)Replace_strrchr); |
| ReplaceFunc3(img, rtn, "rindex", (AFUNPTR)Replace_strrchr); |
| ReplaceFunc3(img, rtn, "strlen", (AFUNPTR)Replace_strlen); |
| ReplaceFunc3(img, rtn, "strcmp", (AFUNPTR)Replace_strcmp); |
| ReplaceFunc3(img, rtn, "memcpy", (AFUNPTR)Replace_memcpy); |
| ReplaceFunc3(img, rtn, "strcpy", (AFUNPTR)Replace_strcpy); |
| ReplaceFunc3(img, rtn, "*foo_to_replace*", (AFUNPTR)replace_foo); |
| } |
| // end. {{{1 |
| // vim:shiftwidth=2:softtabstop=2:expandtab |