/* This file is part of drd, a thread error detector. Copyright (C) 2006-2012 Bart Van Assche <bvanassche@acm.org>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. The GNU General Public License is contained in the file COPYING. */ #include "drd_bitmap.h" #include "drd_thread_bitmap.h" #include "drd_vc.h" /* DRD_(vc_snprint)() */ /* Include several source files here in order to allow the compiler to */ /* do more inlining. */ #include "drd_bitmap.c" #include "drd_load_store.h" #include "drd_segment.c" #include "drd_thread.c" #include "drd_vc.c" #include "libvex_guest_offsets.h" /* STACK_POINTER_OFFSET: VEX register offset for the stack pointer register. */ #if defined(VGA_x86) #define STACK_POINTER_OFFSET OFFSET_x86_ESP #elif defined(VGA_amd64) #define STACK_POINTER_OFFSET OFFSET_amd64_RSP #elif defined(VGA_ppc32) #define STACK_POINTER_OFFSET OFFSET_ppc32_GPR1 #elif defined(VGA_ppc64) #define STACK_POINTER_OFFSET OFFSET_ppc64_GPR1 #elif defined(VGA_arm) #define STACK_POINTER_OFFSET OFFSET_arm_R13 #elif defined(VGA_s390x) #define STACK_POINTER_OFFSET OFFSET_s390x_r15 #elif defined(VGA_mips32) #define STACK_POINTER_OFFSET OFFSET_mips32_r29 #else #error Unknown architecture. #endif /* Local variables. */ static Bool s_check_stack_accesses = False; static Bool s_first_race_only = False; /* Function definitions. */ Bool DRD_(get_check_stack_accesses)() { return s_check_stack_accesses; } void DRD_(set_check_stack_accesses)(const Bool c) { tl_assert(c == False || c == True); s_check_stack_accesses = c; } Bool DRD_(get_first_race_only)() { return s_first_race_only; } void DRD_(set_first_race_only)(const Bool fro) { tl_assert(fro == False || fro == True); s_first_race_only = fro; } void DRD_(trace_mem_access)(const Addr addr, const SizeT size, const BmAccessTypeT access_type, const HWord stored_value_hi, const HWord stored_value_lo) { if (DRD_(is_any_traced)(addr, addr + size)) { char* vc; vc = DRD_(vc_aprint)(DRD_(thread_get_vc)(DRD_(thread_get_running_tid)())); if (access_type == eStore && size <= sizeof(HWord)) { DRD_(trace_msg_w_bt)("store 0x%lx size %ld val %ld/0x%lx (thread %d /" " vc %s)", addr, size, stored_value_lo, stored_value_lo, DRD_(thread_get_running_tid)(), vc); } else if (access_type == eStore && size > sizeof(HWord)) { ULong sv; tl_assert(sizeof(HWord) == 4); sv = ((ULong)stored_value_hi << 32) | stored_value_lo; DRD_(trace_msg_w_bt)("store 0x%lx size %ld val %lld/0x%llx (thread %d" " / vc %s)", addr, size, sv, sv, DRD_(thread_get_running_tid)(), vc); } else { DRD_(trace_msg_w_bt)("%s 0x%lx size %ld (thread %d / vc %s)", access_type == eLoad ? "load " : access_type == eStore ? "store" : access_type == eStart ? "start" : access_type == eEnd ? "end " : "????", addr, size, DRD_(thread_get_running_tid)(), vc); } VG_(free)(vc); tl_assert(DRD_(DrdThreadIdToVgThreadId)(DRD_(thread_get_running_tid)()) == VG_(get_running_tid)()); } } static VG_REGPARM(2) void drd_trace_mem_load(const Addr addr, const SizeT size) { return DRD_(trace_mem_access)(addr, size, eLoad, 0, 0); } static VG_REGPARM(3) void drd_trace_mem_store(const Addr addr,const SizeT size, const HWord stored_value_hi, const HWord stored_value_lo) { return DRD_(trace_mem_access)(addr, size, eStore, stored_value_hi, stored_value_lo); } static void drd_report_race(const Addr addr, const SizeT size, const BmAccessTypeT access_type) { ThreadId vg_tid; vg_tid = VG_(get_running_tid)(); if (!DRD_(get_check_stack_accesses)() && DRD_(thread_address_on_any_stack)(addr)) { #if 0 GenericErrInfo GEI = { .tid = DRD_(thread_get_running_tid)(), .addr = addr, }; VG_(maybe_record_error)(vg_tid, GenericErr, VG_(get_IP)(vg_tid), "--check-stack-var=no skips checking stack" " variables shared over threads", &GEI); #endif } else { DataRaceErrInfo drei = { .tid = DRD_(thread_get_running_tid)(), .addr = addr, .size = size, .access_type = access_type, }; VG_(maybe_record_error)(vg_tid, DataRaceErr, VG_(get_IP)(vg_tid), "Conflicting access", &drei); if (s_first_race_only) DRD_(start_suppression)(addr, addr + size, "first race only"); } } VG_REGPARM(2) void DRD_(trace_load)(Addr addr, SizeT size) { #ifdef ENABLE_DRD_CONSISTENCY_CHECKS /* The assert below has been commented out because of performance reasons.*/ tl_assert(DRD_(thread_get_running_tid)() == DRD_(VgThreadIdToDrdThreadId)(VG_(get_running_tid()))); #endif if (DRD_(running_thread_is_recording_loads)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_load_triggers_conflict(addr, addr + size) && ! DRD_(is_suppressed)(addr, addr + size)) { drd_report_race(addr, size, eLoad); } } static VG_REGPARM(1) void drd_trace_load_1(Addr addr) { if (DRD_(running_thread_is_recording_loads)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_load_1_triggers_conflict(addr) && ! DRD_(is_suppressed)(addr, addr + 1)) { drd_report_race(addr, 1, eLoad); } } static VG_REGPARM(1) void drd_trace_load_2(Addr addr) { if (DRD_(running_thread_is_recording_loads)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_load_2_triggers_conflict(addr) && ! DRD_(is_suppressed)(addr, addr + 2)) { drd_report_race(addr, 2, eLoad); } } static VG_REGPARM(1) void drd_trace_load_4(Addr addr) { if (DRD_(running_thread_is_recording_loads)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_load_4_triggers_conflict(addr) && ! DRD_(is_suppressed)(addr, addr + 4)) { drd_report_race(addr, 4, eLoad); } } static VG_REGPARM(1) void drd_trace_load_8(Addr addr) { if (DRD_(running_thread_is_recording_loads)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_load_8_triggers_conflict(addr) && ! DRD_(is_suppressed)(addr, addr + 8)) { drd_report_race(addr, 8, eLoad); } } VG_REGPARM(2) void DRD_(trace_store)(Addr addr, SizeT size) { #ifdef ENABLE_DRD_CONSISTENCY_CHECKS /* The assert below has been commented out because of performance reasons.*/ tl_assert(DRD_(thread_get_running_tid)() == DRD_(VgThreadIdToDrdThreadId)(VG_(get_running_tid()))); #endif if (DRD_(running_thread_is_recording_stores)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_store_triggers_conflict(addr, addr + size) && ! DRD_(is_suppressed)(addr, addr + size)) { drd_report_race(addr, size, eStore); } } static VG_REGPARM(1) void drd_trace_store_1(Addr addr) { if (DRD_(running_thread_is_recording_stores)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_store_1_triggers_conflict(addr) && ! DRD_(is_suppressed)(addr, addr + 1)) { drd_report_race(addr, 1, eStore); } } static VG_REGPARM(1) void drd_trace_store_2(Addr addr) { if (DRD_(running_thread_is_recording_stores)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_store_2_triggers_conflict(addr) && ! DRD_(is_suppressed)(addr, addr + 2)) { drd_report_race(addr, 2, eStore); } } static VG_REGPARM(1) void drd_trace_store_4(Addr addr) { if (DRD_(running_thread_is_recording_stores)() && (s_check_stack_accesses || !DRD_(thread_address_on_stack)(addr)) && bm_access_store_4_triggers_conflict(addr) && !DRD_(is_suppressed)(addr, addr + 4)) { drd_report_race(addr, 4, eStore); } } static VG_REGPARM(1) void drd_trace_store_8(Addr addr) { if (DRD_(running_thread_is_recording_stores)() && (s_check_stack_accesses || ! DRD_(thread_address_on_stack)(addr)) && bm_access_store_8_triggers_conflict(addr) && ! DRD_(is_suppressed)(addr, addr + 8)) { drd_report_race(addr, 8, eStore); } } /** * Return true if and only if addr_expr matches the pattern (SP) or * <offset>(SP). */ static Bool is_stack_access(IRSB* const bb, IRExpr* const addr_expr) { Bool result = False; if (addr_expr->tag == Iex_RdTmp) { int i; for (i = 0; i < bb->stmts_size; i++) { if (bb->stmts[i] && bb->stmts[i]->tag == Ist_WrTmp && bb->stmts[i]->Ist.WrTmp.tmp == addr_expr->Iex.RdTmp.tmp) { IRExpr* e = bb->stmts[i]->Ist.WrTmp.data; if (e->tag == Iex_Get && e->Iex.Get.offset == STACK_POINTER_OFFSET) { result = True; } //ppIRExpr(e); //VG_(printf)(" (%s)\n", result ? "True" : "False"); break; } } } return result; } static const IROp u_widen_irop[5][9] = { [Ity_I1 - Ity_I1] = { [4] = Iop_1Uto32, [8] = Iop_1Uto64 }, [Ity_I8 - Ity_I1] = { [4] = Iop_8Uto32, [8] = Iop_8Uto64 }, [Ity_I16 - Ity_I1] = { [4] = Iop_16Uto32, [8] = Iop_16Uto64 }, [Ity_I32 - Ity_I1] = { [8] = Iop_32Uto64 }, }; /** * Instrument the client code to trace a memory load (--trace-addr). */ static IRExpr* instr_trace_mem_load(IRSB* const bb, IRExpr* addr_expr, const HWord size) { IRTemp tmp; tmp = newIRTemp(bb->tyenv, typeOfIRExpr(bb->tyenv, addr_expr)); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, addr_expr)); addr_expr = IRExpr_RdTmp(tmp); addStmtToIRSB(bb, IRStmt_Dirty( unsafeIRDirty_0_N(/*regparms*/2, "drd_trace_mem_load", VG_(fnptr_to_fnentry) (drd_trace_mem_load), mkIRExprVec_2(addr_expr, mkIRExpr_HWord(size))))); return addr_expr; } /** * Instrument the client code to trace a memory store (--trace-addr). */ static void instr_trace_mem_store(IRSB* const bb, IRExpr* const addr_expr, IRExpr* data_expr_hi, IRExpr* data_expr_lo) { IRType ty_data_expr; HWord size; tl_assert(sizeof(HWord) == 4 || sizeof(HWord) == 8); tl_assert(!data_expr_hi || typeOfIRExpr(bb->tyenv, data_expr_hi) == Ity_I32); ty_data_expr = typeOfIRExpr(bb->tyenv, data_expr_lo); size = sizeofIRType(ty_data_expr); #if 0 // Test code if (ty_data_expr == Ity_I32) { IRTemp tmp = newIRTemp(bb->tyenv, Ity_F32); data_expr_lo = IRExpr_Unop(Iop_ReinterpI32asF32, data_expr_lo); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, data_expr_lo)); data_expr_lo = IRExpr_RdTmp(tmp); ty_data_expr = Ity_F32; } else if (ty_data_expr == Ity_I64) { IRTemp tmp = newIRTemp(bb->tyenv, Ity_F64); data_expr_lo = IRExpr_Unop(Iop_ReinterpI64asF64, data_expr_lo); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, data_expr_lo)); data_expr_lo = IRExpr_RdTmp(tmp); ty_data_expr = Ity_F64; } #endif if (ty_data_expr == Ity_F32) { IRTemp tmp = newIRTemp(bb->tyenv, Ity_I32); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, IRExpr_Unop(Iop_ReinterpF32asI32, data_expr_lo))); data_expr_lo = IRExpr_RdTmp(tmp); ty_data_expr = Ity_I32; } else if (ty_data_expr == Ity_F64) { IRTemp tmp = newIRTemp(bb->tyenv, Ity_I64); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, IRExpr_Unop(Iop_ReinterpF64asI64, data_expr_lo))); data_expr_lo = IRExpr_RdTmp(tmp); ty_data_expr = Ity_I64; } if (size == sizeof(HWord) && (ty_data_expr == Ity_I32 || ty_data_expr == Ity_I64)) { /* No conversion necessary */ } else { IROp widen_op; if (Ity_I1 <= ty_data_expr && ty_data_expr < Ity_I1 + sizeof(u_widen_irop)/sizeof(u_widen_irop[0])) { widen_op = u_widen_irop[ty_data_expr - Ity_I1][sizeof(HWord)]; if (!widen_op) widen_op = Iop_INVALID; } else { widen_op = Iop_INVALID; } if (widen_op != Iop_INVALID) { IRTemp tmp; /* Widen the integer expression to a HWord */ tmp = newIRTemp(bb->tyenv, sizeof(HWord) == 4 ? Ity_I32 : Ity_I64); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, IRExpr_Unop(widen_op, data_expr_lo))); data_expr_lo = IRExpr_RdTmp(tmp); } else if (size > sizeof(HWord) && !data_expr_hi && ty_data_expr == Ity_I64) { IRTemp tmp; tl_assert(sizeof(HWord) == 4); tl_assert(size == 8); tmp = newIRTemp(bb->tyenv, Ity_I32); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, IRExpr_Unop(Iop_64HIto32, data_expr_lo))); data_expr_hi = IRExpr_RdTmp(tmp); tmp = newIRTemp(bb->tyenv, Ity_I32); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, IRExpr_Unop(Iop_64to32, data_expr_lo))); data_expr_lo = IRExpr_RdTmp(tmp); } else { data_expr_lo = mkIRExpr_HWord(0); } } addStmtToIRSB(bb, IRStmt_Dirty( unsafeIRDirty_0_N(/*regparms*/3, "drd_trace_mem_store", VG_(fnptr_to_fnentry)(drd_trace_mem_store), mkIRExprVec_4(addr_expr, mkIRExpr_HWord(size), data_expr_hi ? data_expr_hi : mkIRExpr_HWord(0), data_expr_lo)))); } static void instrument_load(IRSB* const bb, IRExpr* const addr_expr, const HWord size) { IRExpr* size_expr; IRExpr** argv; IRDirty* di; if (!s_check_stack_accesses && is_stack_access(bb, addr_expr)) return; switch (size) { case 1: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_load_1", VG_(fnptr_to_fnentry)(drd_trace_load_1), argv); break; case 2: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_load_2", VG_(fnptr_to_fnentry)(drd_trace_load_2), argv); break; case 4: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_load_4", VG_(fnptr_to_fnentry)(drd_trace_load_4), argv); break; case 8: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_load_8", VG_(fnptr_to_fnentry)(drd_trace_load_8), argv); break; default: size_expr = mkIRExpr_HWord(size); argv = mkIRExprVec_2(addr_expr, size_expr); di = unsafeIRDirty_0_N(/*regparms*/2, "drd_trace_load", VG_(fnptr_to_fnentry)(DRD_(trace_load)), argv); break; } addStmtToIRSB(bb, IRStmt_Dirty(di)); } static void instrument_store(IRSB* const bb, IRExpr* addr_expr, IRExpr* const data_expr) { IRExpr* size_expr; IRExpr** argv; IRDirty* di; HWord size; size = sizeofIRType(typeOfIRExpr(bb->tyenv, data_expr)); if (UNLIKELY(DRD_(any_address_is_traced)())) { IRTemp tmp = newIRTemp(bb->tyenv, typeOfIRExpr(bb->tyenv, addr_expr)); addStmtToIRSB(bb, IRStmt_WrTmp(tmp, addr_expr)); addr_expr = IRExpr_RdTmp(tmp); instr_trace_mem_store(bb, addr_expr, NULL, data_expr); } if (!s_check_stack_accesses && is_stack_access(bb, addr_expr)) return; switch (size) { case 1: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_store_1", VG_(fnptr_to_fnentry)(drd_trace_store_1), argv); break; case 2: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_store_2", VG_(fnptr_to_fnentry)(drd_trace_store_2), argv); break; case 4: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_store_4", VG_(fnptr_to_fnentry)(drd_trace_store_4), argv); break; case 8: argv = mkIRExprVec_1(addr_expr); di = unsafeIRDirty_0_N(/*regparms*/1, "drd_trace_store_8", VG_(fnptr_to_fnentry)(drd_trace_store_8), argv); break; default: size_expr = mkIRExpr_HWord(size); argv = mkIRExprVec_2(addr_expr, size_expr); di = unsafeIRDirty_0_N(/*regparms*/2, "drd_trace_store", VG_(fnptr_to_fnentry)(DRD_(trace_store)), argv); break; } addStmtToIRSB(bb, IRStmt_Dirty(di)); } IRSB* DRD_(instrument)(VgCallbackClosure* const closure, IRSB* const bb_in, VexGuestLayout* const layout, VexGuestExtents* const vge, IRType const gWordTy, IRType const hWordTy) { IRDirty* di; Int i; IRSB* bb; IRExpr** argv; Bool instrument = True; /* Set up BB */ bb = emptyIRSB(); bb->tyenv = deepCopyIRTypeEnv(bb_in->tyenv); bb->next = deepCopyIRExpr(bb_in->next); bb->jumpkind = bb_in->jumpkind; bb->offsIP = bb_in->offsIP; for (i = 0; i < bb_in->stmts_used; i++) { IRStmt* const st = bb_in->stmts[i]; tl_assert(st); tl_assert(isFlatIRStmt(st)); switch (st->tag) { /* Note: the code for not instrumenting the code in .plt */ /* sections is only necessary on CentOS 3.0 x86 (kernel 2.4.21 */ /* + glibc 2.3.2 + NPTL 0.60 + binutils 2.14.90.0.4). */ /* This is because on this platform dynamic library symbols are */ /* relocated in another way than by later binutils versions. The */ /* linker e.g. does not generate .got.plt sections on CentOS 3.0. */ case Ist_IMark: instrument = VG_(DebugInfo_sect_kind)(NULL, 0, st->Ist.IMark.addr) != Vg_SectPLT; addStmtToIRSB(bb, st); break; case Ist_MBE: switch (st->Ist.MBE.event) { case Imbe_Fence: break; /* not interesting */ default: tl_assert(0); } addStmtToIRSB(bb, st); break; case Ist_Store: if (instrument) instrument_store(bb, st->Ist.Store.addr, st->Ist.Store.data); addStmtToIRSB(bb, st); break; case Ist_WrTmp: if (instrument) { const IRExpr* const data = st->Ist.WrTmp.data; IRExpr* addr_expr = data->Iex.Load.addr; if (data->tag == Iex_Load) { if (UNLIKELY(DRD_(any_address_is_traced)())) { addr_expr = instr_trace_mem_load(bb, addr_expr, sizeofIRType(data->Iex.Load.ty)); } instrument_load(bb, data->Iex.Load.addr, sizeofIRType(data->Iex.Load.ty)); } } addStmtToIRSB(bb, st); break; case Ist_Dirty: if (instrument) { IRDirty* d = st->Ist.Dirty.details; IREffect const mFx = d->mFx; switch (mFx) { case Ifx_None: break; case Ifx_Read: case Ifx_Write: case Ifx_Modify: tl_assert(d->mAddr); tl_assert(d->mSize > 0); argv = mkIRExprVec_2(d->mAddr, mkIRExpr_HWord(d->mSize)); if (mFx == Ifx_Read || mFx == Ifx_Modify) { di = unsafeIRDirty_0_N( /*regparms*/2, "drd_trace_load", VG_(fnptr_to_fnentry)(DRD_(trace_load)), argv); addStmtToIRSB(bb, IRStmt_Dirty(di)); } if (mFx == Ifx_Write || mFx == Ifx_Modify) { di = unsafeIRDirty_0_N( /*regparms*/2, "drd_trace_store", VG_(fnptr_to_fnentry)(DRD_(trace_store)), argv); addStmtToIRSB(bb, IRStmt_Dirty(di)); } break; default: tl_assert(0); } } addStmtToIRSB(bb, st); break; case Ist_CAS: if (instrument) { /* * Treat compare-and-swap as a read. By handling atomic * instructions as read instructions no data races are reported * between conflicting atomic operations nor between atomic * operations and non-atomic reads. Conflicts between atomic * operations and non-atomic write operations are still reported * however. */ Int dataSize; IRCAS* cas = st->Ist.CAS.details; tl_assert(cas->addr != NULL); tl_assert(cas->dataLo != NULL); dataSize = sizeofIRType(typeOfIRExpr(bb->tyenv, cas->dataLo)); if (cas->dataHi != NULL) dataSize *= 2; /* since it's a doubleword-CAS */ if (UNLIKELY(DRD_(any_address_is_traced)())) instr_trace_mem_store(bb, cas->addr, cas->dataHi, cas->dataLo); instrument_load(bb, cas->addr, dataSize); } addStmtToIRSB(bb, st); break; case Ist_LLSC: { /* * Ignore store-conditionals (except for tracing), and handle * load-linked's exactly like normal loads. */ IRType dataTy; if (st->Ist.LLSC.storedata == NULL) { /* LL */ dataTy = typeOfIRTemp(bb_in->tyenv, st->Ist.LLSC.result); if (instrument) { IRExpr* addr_expr = st->Ist.LLSC.addr; if (UNLIKELY(DRD_(any_address_is_traced)())) addr_expr = instr_trace_mem_load(bb, addr_expr, sizeofIRType(dataTy)); instrument_load(bb, addr_expr, sizeofIRType(dataTy)); } } else { /* SC */ instr_trace_mem_store(bb, st->Ist.LLSC.addr, NULL, st->Ist.LLSC.storedata); } addStmtToIRSB(bb, st); break; } case Ist_NoOp: case Ist_AbiHint: case Ist_Put: case Ist_PutI: case Ist_Exit: /* None of these can contain any memory references. */ addStmtToIRSB(bb, st); break; default: ppIRStmt(st); tl_assert(0); } } return bb; }