| /* -*- mode: C; c-basic-offset: 3; -*- */ |
| /* |
| This file is part of drd, a thread error detector. |
| |
| Copyright (C) 2006-2010 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 |
| #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) |
| { |
| if (DRD_(is_any_traced)(addr, addr + size)) |
| { |
| char* vc; |
| |
| vc = DRD_(vc_aprint)(DRD_(thread_get_vc)(DRD_(thread_get_running_tid)())); |
| VG_(message)(Vg_UserMsg, |
| "%s 0x%lx size %ld (thread %d / vc %s)\n", |
| 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); |
| VG_(get_and_pp_StackTrace)(VG_(get_running_tid)(), |
| VG_(clo_backtrace_size)); |
| 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); |
| } |
| |
| static VG_REGPARM(2) void drd_trace_mem_store(const Addr addr,const SizeT size) |
| { |
| return DRD_(trace_mem_access)(addr, size, eStore); |
| } |
| |
| static void drd_report_race(const Addr addr, const SizeT size, |
| const BmAccessTypeT access_type) |
| { |
| DataRaceErrInfo drei; |
| |
| drei.tid = DRD_(thread_get_running_tid)(); |
| drei.addr = addr; |
| drei.size = size; |
| drei.access_type = access_type; |
| VG_(maybe_record_error)(VG_(get_running_tid)(), |
| DataRaceErr, |
| VG_(get_IP)(VG_(get_running_tid)()), |
| "Conflicting accesses", |
| &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 void instrument_load(IRSB* const bb, |
| IRExpr* const addr_expr, |
| const HWord size) |
| { |
| IRExpr* size_expr; |
| IRExpr** argv; |
| IRDirty* di; |
| |
| if (UNLIKELY(DRD_(any_address_is_traced)())) |
| { |
| addStmtToIRSB(bb, |
| IRStmt_Dirty( |
| unsafeIRDirty_0_N(/*regparms*/2, |
| "drd_trace_load", |
| VG_(fnptr_to_fnentry) |
| (drd_trace_mem_load), |
| mkIRExprVec_2(addr_expr, |
| mkIRExpr_HWord(size))))); |
| } |
| |
| 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* const addr_expr, |
| const HWord size) |
| { |
| IRExpr* size_expr; |
| IRExpr** argv; |
| IRDirty* di; |
| |
| if (UNLIKELY(DRD_(any_address_is_traced)())) |
| { |
| addStmtToIRSB(bb, |
| IRStmt_Dirty( |
| unsafeIRDirty_0_N(/*regparms*/2, |
| "drd_trace_store", |
| VG_(fnptr_to_fnentry) |
| (drd_trace_mem_store), |
| mkIRExprVec_2(addr_expr, |
| mkIRExpr_HWord(size))))); |
| } |
| |
| 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; |
| |
| 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, |
| sizeofIRType(typeOfIRExpr(bb->tyenv, |
| st->Ist.Store.data))); |
| } |
| addStmtToIRSB(bb, st); |
| break; |
| |
| case Ist_WrTmp: |
| if (instrument) |
| { |
| const IRExpr* const data = st->Ist.WrTmp.data; |
| if (data->tag == Iex_Load) |
| { |
| 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 */ |
| instrument_load(bb, cas->addr, dataSize); |
| } |
| addStmtToIRSB(bb, st); |
| break; |
| |
| case Ist_LLSC: { |
| /* Ignore store-conditionals, 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) { |
| instrument_load(bb, |
| st->Ist.LLSC.addr, |
| sizeofIRType(dataTy)); |
| } |
| } |
| else |
| { |
| /* SC */ |
| /*ignore */ |
| } |
| 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; |
| } |
| |