/* 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. // Author: Konstantin Serebryany. // Author: Timur Iskhodzhanov. #define __STDC_LIMIT_MACROS #include "pin.H" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <map> #include <assert.h> #include "thread_sanitizer.h" #include "ts_lock.h" #include "ts_trace_info.h" #include "ts_race_verifier.h" #include "common_util.h" #if defined(__GNUC__) # include <cxxabi.h> // __cxa_demangle # define ATOMIC_READ(a) __sync_add_and_fetch(a, 0) #elif defined(_MSC_VER) namespace WINDOWS { // This is the way of including winows.h recommended by PIN docs. #include<Windows.h> } #include <intrin.h> # define popen(x,y) (NULL) # define ATOMIC_READ(a) _InterlockedCompareExchange(a, 0, 0) # define usleep(x) WINDOWS::Sleep((x)/1000) # define UINTPTR_MAX ((uintptr_t)-1) #endif #ifdef NDEBUG # error "Please don't define NDEBUG" #endif static void DumpEvent(CONTEXT *ctx, EventType type, int32_t tid, uintptr_t pc, uintptr_t a, uintptr_t info); //------ Global PIN lock ------- {{{1 class ScopedReentrantClientLock { public: ScopedReentrantClientLock(int line) : line_(line) { // if (line && G_flags->debug_level >= 5) Printf("??Try at line %d\n", line); PIN_LockClient(); if (line && G_flags->debug_level >= 5) Printf("++Lock at line %d\n", line); } ~ScopedReentrantClientLock() { if (line_ && G_flags->debug_level >= 5) Printf("--Unlock at line %d\n", line_); PIN_UnlockClient(); } private: int line_; }; //--------------- Globals ----------------- {{{1 extern FILE *G_out; // Number of threads created by pthread_create (i.e. not counting main thread). static int n_created_threads = 0; // Number of started threads, i.e. the number of CallbackForThreadStart calls. static int n_started_threads = 0; const uint32_t kMaxThreads = PIN_MAX_THREADS; // Serializes the ThreadSanitizer callbacks if TS_SERIALIZED==1 static TSLock g_main_ts_lock; // Serializes calls to pthread_create and CreateThread. static TSLock g_thread_create_lock; // Under g_thread_create_lock. static THREADID g_tid_of_thread_which_called_create_thread = -1; #ifdef _MSC_VER // On Windows, we need to create a h-b arc between // RtlQueueWorkItem(callback, x, y) and the call to callback. // Same for RegisterWaitForSingleObject. static unordered_set<uintptr_t> *g_windows_thread_pool_calback_set; // Similarly, we need h-b arcs between the returns from callbacks and // thre related UnregisterWaitEx. Damn, what a stupid interface! static unordered_map<uintptr_t, uintptr_t> *g_windows_thread_pool_wait_object_map; #endif //--------------- StackFrame ----------------- {{{1 struct StackFrame { uintptr_t pc; uintptr_t sp; StackFrame(uintptr_t p, uintptr_t s) : pc(p), sp(s) { } }; //--------------- InstrumentedCallFrame ----- {{{1 // Machinery to implement the fast interceptors in PIN // (i.e. the ones that don't use PIN_CallApplicationFunction). // We instrument the entry of the interesting function (e.g. malloc) // and all RET instructions in this function's module (e.g. libc). // At entry, we push an InstrumentedCallFrame object onto InstrumentedCallStack. // At every RET instruction we check if the stack is not empty (fast path) // and if the top contains the current SP. If yes -- this is the function return // and we pop the stack. struct InstrumentedCallFrame { typedef void (*callback_t)(THREADID tid, InstrumentedCallFrame &frame, ADDRINT ret); callback_t callback; uintptr_t pc; uintptr_t sp; uintptr_t arg[4]; }; struct InstrumentedCallStack { public: InstrumentedCallStack() : size_(0) { } size_t size() { return size_; } void Push(InstrumentedCallFrame::callback_t callback, uintptr_t pc, uintptr_t sp, uintptr_t a0, uintptr_t a1) { CHECK(size() < TS_ARRAY_SIZE(frames_)); size_++; Top()->callback = callback; Top()->pc = pc; Top()->sp = sp; Top()->arg[0] = a0; Top()->arg[1] = a1; } void Pop() { CHECK(size() > 0); size_--; } InstrumentedCallFrame *Top() { CHECK(size() > 0); return &frames_[size_-1]; } void Print() { for (size_t i = 0; i < size(); i++) { Printf( " %p\n", frames_[i].sp); if (i > 0) CHECK(frames_[i].sp <= frames_[i-1].sp); } } private: InstrumentedCallFrame frames_[20]; size_t size_; }; //--------------- PinThread ----------------- {{{1 const size_t kThreadLocalEventBufferSize = 2048 - 2; // The number of mops should be at least 2 less than the size of TLEB // so that we have space to put SBLOCK_ENTER token and the trace_info ptr. const size_t kMaxMopsPerTrace = kThreadLocalEventBufferSize - 2; REG tls_reg; struct PinThread; struct ThreadLocalEventBuffer { PinThread *t; size_t size; uintptr_t events[kThreadLocalEventBufferSize]; }; struct PinThread { ThreadLocalEventBuffer tleb; int uniq_tid; uint32_t literace_sampling; // cache of a flag. volatile long last_child_tid; InstrumentedCallStack ic_stack; THREADID tid; THREADID parent_tid; pthread_t my_ptid; size_t thread_stack_size_if_known; size_t last_child_stack_size_if_known; vector<StackFrame> shadow_stack; TraceInfo *trace_info; int ignore_accesses; // if > 0, ignore all memory accesses. int ignore_accesses_depth; int ignore_sync; // if > 0, ignore all sync events. int spin_lock_recursion_depth; bool thread_finished; bool thread_done; bool holding_lock; int n_consumed_events; #ifdef _MSC_VER enum StartupState { STARTING, CHILD_READY, MAY_CONTINUE, }; volatile long startup_state; // used to handle the CREATE_SUSPENDED flag. #endif char padding[64]; // avoid any chance of ping-pong. }; // Array of pin threads, indexed by pin's THREADID. static PinThread *g_pin_threads; // If true, ignore all accesses in all threads. extern bool global_ignore; #ifdef _MSC_VER static unordered_set<pthread_t> *g_win_handles_which_are_threads; #endif //-------------------- ts_replace ------------------- {{{1 static void ReportAccesRange(THREADID tid, uintptr_t pc, EventType type, uintptr_t x, size_t size) { if (size && !g_pin_threads[tid].ignore_accesses) { uintptr_t end = x + size; for(uintptr_t a = x; a < end; a += 8) { size_t cur_size = min((uintptr_t)8, end - a); DumpEvent(0, type, tid, pc, a, cur_size); } } } #define REPORT_READ_RANGE(x, size) ReportAccesRange(tid, pc, READ, (uintptr_t)x, size) #define REPORT_WRITE_RANGE(x, size) ReportAccesRange(tid, pc, WRITE, (uintptr_t)x, size) #define EXTRA_REPLACE_PARAMS THREADID tid, uintptr_t pc, #define EXTRA_REPLACE_ARGS tid, pc, #include "ts_replace.h" //------------- ThreadSanitizer exports ------------ {{{1 string Demangle(const char *str) { #if defined(__GNUC__) int status; char *demangled = __cxxabiv1::__cxa_demangle(str, 0, 0, &status); if (demangled) { string res = demangled; free(demangled); return res; } #endif return str; } void PcToStrings(uintptr_t pc, bool demangle, string *img_name, string *rtn_name, string *file_name, int *line_no) { if (G_flags->symbolize) { RTN rtn; ScopedReentrantClientLock lock(__LINE__); // ClientLock must be held. PIN_GetSourceLocation(pc, NULL, line_no, file_name); *file_name = ConvertToPlatformIndependentPath(*file_name); rtn = RTN_FindByAddress(pc); string name; if (RTN_Valid(rtn)) { *rtn_name = demangle ? Demangle(RTN_Name(rtn).c_str()) : RTN_Name(rtn); *img_name = IMG_Name(SEC_Img(RTN_Sec(rtn))); } } } string PcToRtnName(uintptr_t pc, bool demangle) { string res; if (G_flags->symbolize) { { ScopedReentrantClientLock lock(__LINE__); RTN rtn = RTN_FindByAddress(pc); if (RTN_Valid(rtn)) { res = demangle ? Demangle(RTN_Name(rtn).c_str()) : RTN_Name(rtn); } } } return res; } //--------------- ThreadLocalEventBuffer ----------------- {{{1 // thread local event buffer is an array of uintptr_t. // The events are encoded like this: // { RTN_CALL, call_pc, target_pc } // { RTN_EXIT } // { SBLOCK_ENTER, trace_info_of_size_n, addr1, addr2, ... addr_n} enum TLEBSpecificEvents { TLEB_IGNORE_ALL_BEGIN = LAST_EVENT + 1, TLEB_IGNORE_ALL_END, TLEB_IGNORE_SYNC_BEGIN, TLEB_IGNORE_SYNC_END, TLEB_GLOBAL_IGNORE_ON, TLEB_GLOBAL_IGNORE_OFF, }; static bool DumpEventPlainText(EventType type, int32_t tid, uintptr_t pc, uintptr_t a, uintptr_t info) { #if DEBUG == 0 || defined(_MSC_VER) return false; #else if (G_flags->dump_events.empty()) return false; static unordered_set<uintptr_t> *pc_set; if (pc_set == NULL) { pc_set = new unordered_set<uintptr_t>; } static FILE *log_file = NULL; if (log_file == NULL) { log_file = popen(("gzip > " + G_flags->dump_events).c_str(), "w"); } if (G_flags->symbolize && pc_set->insert(pc).second) { string img_name, rtn_name, file_name; int line = 0; PcToStrings(pc, false, &img_name, &rtn_name, &file_name, &line); if (file_name.empty()) file_name = "unknown"; if (img_name.empty()) img_name = "unknown"; if (rtn_name.empty()) rtn_name = "unknown"; if (line == 0) line = 1; fprintf(log_file, "#PC %lx %s %s %s %d\n", (long)pc, img_name.c_str(), rtn_name.c_str(), file_name.c_str(), line); } fprintf(log_file, "%s %x %lx %lx %lx\n", kEventNames[type], tid, (long)pc, (long)a, (long)info); return true; #endif } static void DumpEventInternal(EventType type, int32_t uniq_tid, uintptr_t pc, uintptr_t a, uintptr_t info) { if (DumpEventPlainText(type, uniq_tid, pc, a, info)) return; // PIN wraps the tid (after 2048), but we need a uniq tid. Event event(type, uniq_tid, pc, a, info); ThreadSanitizerHandleOneEvent(&event); } void ComputeIgnoreAccesses(PinThread &t) { t.ignore_accesses = (t.ignore_accesses_depth != 0) || (global_ignore != 0); } static void HandleInnerEvent(PinThread &t, uintptr_t event) { DCHECK(event > LAST_EVENT); if (event == TLEB_IGNORE_ALL_BEGIN){ t.ignore_accesses_depth++; ComputeIgnoreAccesses(t); } else if (event == TLEB_IGNORE_ALL_END){ t.ignore_accesses_depth--; CHECK(t.ignore_accesses_depth >= 0); ComputeIgnoreAccesses(t); } else if (event == TLEB_IGNORE_SYNC_BEGIN){ t.ignore_sync++; } else if (event == TLEB_IGNORE_SYNC_END){ t.ignore_sync--; CHECK(t.ignore_sync >= 0); } else if (event == TLEB_GLOBAL_IGNORE_ON){ Report("INFO: GLOBAL IGNORE ON\n"); global_ignore = true; ComputeIgnoreAccesses(t); } else if (event == TLEB_GLOBAL_IGNORE_OFF){ Report("INFO: GLOBAL IGNORE OFF\n"); global_ignore = false; ComputeIgnoreAccesses(t); } else { Printf("Event: %ld (last: %ld)\n", event, LAST_EVENT); CHECK(0); } } static INLINE bool WantToIgnoreEvent(PinThread &t, uintptr_t event) { if (t.ignore_sync && (event == WRITER_LOCK || event == READER_LOCK || event == UNLOCK || event == SIGNAL || event == WAIT)) { // do nothing, we are ignoring locks. return true; } else if (t.ignore_accesses && (event == READ || event == WRITE)) { // do nothing, we are ignoring mops. return true; } return false; } static INLINE void TLEBFlushUnlocked(ThreadLocalEventBuffer &tleb) { if (tleb.size == 0) return; PinThread &t = *tleb.t; // global_ignore should be always on with race verifier DCHECK(!g_race_verifier_active || global_ignore); DCHECK(tleb.size <= kThreadLocalEventBufferSize); if (DEBUG_MODE && t.thread_done) { Printf("ACHTUNG!!! an event from a dead thread T%d\n", t.tid); } DCHECK(!t.thread_done); if (TS_SERIALIZED == 1 || DEBUG_MODE) { size_t max_idx = TS_ARRAY_SIZE(G_stats->tleb_flush); size_t idx = min((size_t)u32_log2(tleb.size), max_idx - 1); CHECK(idx < max_idx); G_stats->tleb_flush[idx]++; } if (TS_SERIALIZED == 1 && G_flags->offline) { fwrite(tleb.events, sizeof(uintptr_t), tleb.size, G_out); tleb.size = 0; return; } size_t i; for (i = 0; i < tleb.size; ) { uintptr_t event = tleb.events[i++]; DCHECK(!g_race_verifier_active || event == SBLOCK_ENTER || event == EXPECT_RACE || event == THR_START); if (event == RTN_EXIT) { if (DumpEventPlainText(RTN_EXIT, t.uniq_tid, 0, 0, 0)) continue; ThreadSanitizerHandleRtnExit(t.uniq_tid); } else if (event == RTN_CALL) { uintptr_t call_pc = tleb.events[i++]; uintptr_t target_pc = tleb.events[i++]; IGNORE_BELOW_RTN ignore_below = (IGNORE_BELOW_RTN)tleb.events[i++]; if (DumpEventPlainText(RTN_CALL, t.uniq_tid, call_pc, target_pc, ignore_below)) continue; ThreadSanitizerHandleRtnCall(t.uniq_tid, call_pc, target_pc, ignore_below); } else if (event == SBLOCK_ENTER){ TraceInfo *trace_info = (TraceInfo*) tleb.events[i++]; DCHECK(trace_info); bool do_this_trace = true; if (t.ignore_accesses) { do_this_trace = false; } else if (t.literace_sampling) { do_this_trace = !trace_info->LiteRaceSkipTraceRealTid( t.uniq_tid, t.literace_sampling); } size_t n = trace_info->n_mops(); if (do_this_trace) { if (DEBUG_MODE && !G_flags->dump_events.empty()) { DumpEventPlainText(SBLOCK_ENTER, t.uniq_tid, trace_info->pc(), 0, 0); for (size_t j = 0; j < n; j++) { MopInfo *mop = trace_info->GetMop(j); DCHECK(mop->size()); DCHECK(mop); uintptr_t addr = tleb.events[i + j]; if (addr) { DumpEventPlainText(mop->is_write() ? WRITE : READ, t.uniq_tid, mop->pc(), addr, mop->size()); } } } else { ThreadSanitizerHandleTrace(t.uniq_tid, trace_info, tleb.events+i); } } i += n; } else if (event == THR_START) { uintptr_t parent = -1; if (t.parent_tid != (THREADID)-1) { parent = g_pin_threads[t.parent_tid].uniq_tid; } DumpEventInternal(THR_START, t.uniq_tid, 0, 0, parent); } else if (event == THR_END) { DumpEventInternal(THR_END, t.uniq_tid, 0, 0, 0); DCHECK(t.thread_finished == true); DCHECK(t.thread_done == false); t.thread_done = true; i += 3; // consume the unneeded data. DCHECK(i == tleb.size); // should be last event in this tleb. } else if (event > LAST_EVENT) { HandleInnerEvent(t, event); } else { // all other events. CHECK(event > NOOP && event < LAST_EVENT); uintptr_t pc = tleb.events[i++]; uintptr_t a = tleb.events[i++]; uintptr_t info = tleb.events[i++]; if (!WantToIgnoreEvent(t, event)) { DumpEventInternal((EventType)event, t.uniq_tid, pc, a, info); } } } DCHECK(i == tleb.size); tleb.size = 0; if (DEBUG_MODE) { // for sanity checking. memset(tleb.events, 0xf0, sizeof(tleb.events)); } } static INLINE void TLEBFlushLocked(PinThread &t) { #if TS_SERIALIZED==1 if (G_flags->dry_run) { t.tleb.size = 0; return; } CHECK(t.tleb.size <= kThreadLocalEventBufferSize); G_stats->lock_sites[0]++; ScopedLock lock(&g_main_ts_lock); TLEBFlushUnlocked(t.tleb); #else TLEBFlushUnlocked(t.tleb); #endif } static void TLEBAddRtnCall(PinThread &t, uintptr_t call_pc, uintptr_t target_pc, IGNORE_BELOW_RTN ignore_below) { if (TS_SERIALIZED == 0) { TLEBFlushLocked(t); ThreadSanitizerHandleRtnCall(t.uniq_tid, call_pc, target_pc, ignore_below); return; } DCHECK(t.tleb.size <= kThreadLocalEventBufferSize); if (t.tleb.size + 4 > kThreadLocalEventBufferSize) { TLEBFlushLocked(t); DCHECK(t.tleb.size == 0); } t.tleb.events[t.tleb.size++] = RTN_CALL; t.tleb.events[t.tleb.size++] = call_pc; t.tleb.events[t.tleb.size++] = target_pc; t.tleb.events[t.tleb.size++] = ignore_below; DCHECK(t.tleb.size <= kThreadLocalEventBufferSize); } static void TLEBAddRtnExit(PinThread &t) { if (TS_SERIALIZED == 0) { TLEBFlushLocked(t); ThreadSanitizerHandleRtnExit(t.uniq_tid); return; } if (t.tleb.size + 1 > kThreadLocalEventBufferSize) { TLEBFlushLocked(t); } t.tleb.events[t.tleb.size++] = RTN_EXIT; DCHECK(t.tleb.size <= kThreadLocalEventBufferSize); } static INLINE uintptr_t *TLEBAddTrace(PinThread &t) { size_t n = t.trace_info->n_mops(); DCHECK(n > 0); if (TS_SERIALIZED == 0) { TLEBFlushLocked(t); } else if (t.tleb.size + 2 + n > kThreadLocalEventBufferSize) { TLEBFlushLocked(t); } if (TS_SERIALIZED == 1) { t.tleb.events[t.tleb.size++] = SBLOCK_ENTER; t.tleb.events[t.tleb.size++] = (uintptr_t)t.trace_info; } else { DCHECK(t.tleb.size == 0); t.tleb.events[0] = SBLOCK_ENTER; t.tleb.events[1] = (uintptr_t)t.trace_info; t.tleb.size += 2; } uintptr_t *mop_addresses = &t.tleb.events[t.tleb.size]; // not every address will be written to. so they will stay 0. for (size_t i = 0; i < n; i++) { mop_addresses[i] = 0; } t.tleb.size += n; DCHECK(t.tleb.size <= kThreadLocalEventBufferSize); return mop_addresses; } static void TLEBStartThread(PinThread &t) { CHECK(t.tleb.size == 0); t.tleb.events[t.tleb.size++] = THR_START; } static void TLEBSimpleEvent(PinThread &t, uintptr_t event) { if (g_race_verifier_active) return; if (TS_SERIALIZED == 0) { TLEBFlushLocked(t); if (event < LAST_EVENT) { Event e((EventType)event, t.uniq_tid, 0, 0, 0); ThreadSanitizerHandleOneEvent(&e); } else { HandleInnerEvent(t, event); } return; } if (t.tleb.size + 1 > kThreadLocalEventBufferSize) { TLEBFlushLocked(t); } t.tleb.events[t.tleb.size++] = event; DCHECK(t.tleb.size <= kThreadLocalEventBufferSize); } static void TLEBAddGenericEventAndFlush(PinThread &t, EventType type, uintptr_t pc, uintptr_t a, uintptr_t info) { if (TS_SERIALIZED == 0) { if (WantToIgnoreEvent(t, type)) return; TLEBFlushLocked(t); Event e(type, t.uniq_tid, pc, a, info); ThreadSanitizerHandleOneEvent(&e); return; } if (t.tleb.size + 4 > kThreadLocalEventBufferSize) { TLEBFlushLocked(t); } DCHECK(type > NOOP && type < LAST_EVENT); t.tleb.events[t.tleb.size++] = type; t.tleb.events[t.tleb.size++] = pc; t.tleb.events[t.tleb.size++] = a; t.tleb.events[t.tleb.size++] = info; TLEBFlushLocked(t); DCHECK(t.tleb.size <= kThreadLocalEventBufferSize); } static void UpdateCallStack(PinThread &t, ADDRINT sp); // Must be called from its thread (except for THR_END case)! static void DumpEventWithSp(uintptr_t sp, EventType type, int32_t tid, uintptr_t pc, uintptr_t a, uintptr_t info) { if (!g_race_verifier_active || type == EXPECT_RACE) { PinThread &t = g_pin_threads[tid]; if (sp) { UpdateCallStack(t, sp); } TLEBAddGenericEventAndFlush(t, type, pc, a, info); } } static void DumpEvent(CONTEXT *ctx, EventType type, int32_t tid, uintptr_t pc, uintptr_t a, uintptr_t info) { DumpEventWithSp(ctx ? PIN_GetContextReg(ctx, REG_STACK_PTR) : 0, type, tid, pc, a, info); } //--------- Wraping and relacing --------------- {{{1 static set<string> g_wrapped_functions; static void InformAboutFunctionWrap(RTN rtn, string name) { g_wrapped_functions.insert(name); if (!debug_wrap) return; Printf("Function wrapped: %s (%s %s)\n", name.c_str(), RTN_Name(rtn).c_str(), IMG_Name(SEC_Img(RTN_Sec(rtn))).c_str()); } static bool RtnMatchesName(const string &rtn_name, const string &name) { CHECK(name.size() > 0); size_t pos = rtn_name.find(name); if (pos == string::npos) { return false; } if (pos == 0 && name.size() == rtn_name.size()) { // Printf("Full match: %s %s\n", rtn_name.c_str(), name.c_str()); return true; } // match MyFuncName@123 if (pos == 0 && name.size() < rtn_name.size() && rtn_name[name.size()] == '@') { // Printf("Versioned match: %s %s\n", rtn_name.c_str(), name.c_str()); return true; } // match _MyFuncName@123 if (pos == 1 && rtn_name[0] == '_' && name.size() < rtn_name.size() && rtn_name[name.size() + 1] == '@') { // Printf("Versioned match: %s %s\n", rtn_name.c_str(), name.c_str()); return true; } return false; } #define FAST_WRAP_PARAM0 THREADID tid, ADDRINT pc, ADDRINT sp #define FAST_WRAP_PARAM1 FAST_WRAP_PARAM0, ADDRINT arg0 #define FAST_WRAP_PARAM2 FAST_WRAP_PARAM1, ADDRINT arg1 #define FAST_WRAP_PARAM3 FAST_WRAP_PARAM2, ADDRINT arg2 #define FAST_WRAP_PARAM_AFTER \ THREADID tid, InstrumentedCallFrame &frame, ADDRINT ret #define DEBUG_FAST_INTERCEPTORS 0 //#define DEBUG_FAST_INTERCEPTORS (tid == 1) #define PUSH_AFTER_CALLBACK1(callback, a0) \ g_pin_threads[tid].ic_stack.Push(callback, pc, sp, a0, 0); \ if (DEBUG_FAST_INTERCEPTORS) \ Printf("T%d %s pc=%p sp=%p *sp=(%p) arg0=%p stack_size=%ld\n",\ tid, __FUNCTION__, pc, sp,\ ((void**)sp)[0],\ arg0,\ g_pin_threads[tid].ic_stack.size()\ );\ #define WRAP_NAME(name) Wrap_##name #define WRAP4(name) WrapFunc4(img, rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD1(name) WrapStdCallFunc1(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD2(name) WrapStdCallFunc2(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD3(name) WrapStdCallFunc3(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD4(name) WrapStdCallFunc4(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD5(name) WrapStdCallFunc5(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD6(name) WrapStdCallFunc6(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD7(name) WrapStdCallFunc7(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD8(name) WrapStdCallFunc8(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD10(name) WrapStdCallFunc10(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAPSTD11(name) WrapStdCallFunc11(rtn, #name, (AFUNPTR)Wrap_##name) #define WRAP_PARAM4 THREADID tid, ADDRINT pc, CONTEXT *ctx, \ AFUNPTR f,\ uintptr_t arg0, uintptr_t arg1, \ uintptr_t arg2, uintptr_t arg3 #define WRAP_PARAM6 WRAP_PARAM4, uintptr_t arg4, uintptr_t arg5 #define WRAP_PARAM8 WRAP_PARAM6, uintptr_t arg6, uintptr_t arg7 #define WRAP_PARAM10 WRAP_PARAM8, uintptr_t arg8, uintptr_t arg9 #define WRAP_PARAM12 WRAP_PARAM10, uintptr_t arg10, uintptr_t arg11 static uintptr_t CallFun4(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) { uintptr_t ret = 0xdeadbee1; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_DEFAULT, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG(uintptr_t), arg2, PIN_PARG(uintptr_t), arg3, PIN_PARG_END()); return ret; } static uintptr_t CallFun6(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5) { uintptr_t ret = 0xdeadbee1; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_DEFAULT, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG(uintptr_t), arg2, PIN_PARG(uintptr_t), arg3, PIN_PARG(uintptr_t), arg4, PIN_PARG(uintptr_t), arg5, PIN_PARG_END()); return ret; } #define CALL_ME_INSIDE_WRAPPER_4() CallFun4(ctx, tid, f, arg0, arg1, arg2, arg3) #define CALL_ME_INSIDE_WRAPPER_6() CallFun6(ctx, tid, f, arg0, arg1, arg2, arg3, arg4, arg5) // Completely replace (i.e. not wrap) a function with 3 (or less) parameters. // The original function will not be called. void ReplaceFunc3(IMG img, RTN rtn, const char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_DEFAULT, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_END); PROTO_Free(proto); } } // Wrap a function with up to 4 parameters. void WrapFunc4(IMG img, RTN rtn, const char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_DEFAULT, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_END); PROTO_Free(proto); } } // Wrap a function with up to 6 parameters. void WrapFunc6(IMG img, RTN rtn, const char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_DEFAULT, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_FUNCARG_ENTRYPOINT_VALUE, 5, IARG_END); PROTO_Free(proto); } } //--------- Instrumentation callbacks --------------- {{{1 //---------- Debug -----------------------------------{{{2 #define DEB_PR (0) static void ShowPcAndSp(const char *where, THREADID tid, ADDRINT pc, ADDRINT sp) { Printf("%s T%d sp=%ld pc=%p %s\n", where, tid, sp, pc, PcToRtnName(pc, true).c_str()); } static void PrintShadowStack(PinThread &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; uintptr_t sp = t.shadow_stack[i].sp; Printf(" sp=%ld pc=%lx %s\n", sp, pc, PcToRtnName(pc, true).c_str()); } } static void DebugOnlyShowPcAndSp(const char *where, THREADID tid, ADDRINT pc, ADDRINT sp) { if (DEB_PR) { ShowPcAndSp(where, tid, pc, sp); } } static uintptr_t WRAP_NAME(ThreadSanitizerQuery)(WRAP_PARAM4) { const char *query = (const char*)arg0; return (uintptr_t)ThreadSanitizerQuery(query); } //--------- Ignores -------------------------------- {{{2 static void IgnoreMopsBegin(THREADID tid) { // if (tid != 0) Printf("T%d IgnoreMops++\n", tid); TLEBSimpleEvent(g_pin_threads[tid], TLEB_IGNORE_ALL_BEGIN); } static void IgnoreMopsEnd(THREADID tid) { // if (tid != 0) Printf("T%d IgnoreMops--\n", tid); TLEBSimpleEvent(g_pin_threads[tid], TLEB_IGNORE_ALL_END); } static void IgnoreSyncAndMopsBegin(THREADID tid) { // if (tid != 0) Printf("T%d IgnoreSync++\n", tid); IgnoreMopsBegin(tid); TLEBSimpleEvent(g_pin_threads[tid], TLEB_IGNORE_SYNC_BEGIN); } static void IgnoreSyncAndMopsEnd(THREADID tid) { // if (tid != 0) Printf("T%d IgnoreSync--\n", tid); IgnoreMopsEnd(tid); TLEBSimpleEvent(g_pin_threads[tid], TLEB_IGNORE_SYNC_END); } //--------- __cxa_guard_* -------------------------- {{{2 // From gcc/cp/decl.c: // -------------------------------------------------------------- // Emit code to perform this initialization but once. This code // looks like: // // static <type> guard; // if (!guard.first_byte) { // if (__cxa_guard_acquire (&guard)) { // bool flag = false; // try { // // Do initialization. // flag = true; __cxa_guard_release (&guard); // // Register variable for destruction at end of program. // } catch { // if (!flag) __cxa_guard_abort (&guard); // } // } // -------------------------------------------------------------- // So, when __cxa_guard_acquire returns true, we start ignoring all accesses // and in __cxa_guard_release we stop ignoring them. // We also need to ignore all accesses inside these two functions. static void Before_cxa_guard_acquire(THREADID tid, ADDRINT pc, ADDRINT guard) { IgnoreMopsBegin(tid); } static void After_cxa_guard_acquire(THREADID tid, ADDRINT pc, ADDRINT ret) { if (ret) { // Continue ignoring, it will end in __cxa_guard_release. } else { // Stop ignoring, there will be no matching call to __cxa_guard_release. IgnoreMopsEnd(tid); } } static void After_cxa_guard_release(THREADID tid, ADDRINT pc) { IgnoreMopsEnd(tid); } static uintptr_t WRAP_NAME(pthread_once)(WRAP_PARAM4) { uintptr_t ret; IgnoreMopsBegin(tid); ret = CALL_ME_INSIDE_WRAPPER_4(); IgnoreMopsEnd(tid); return ret; } void TmpCallback1(THREADID tid, ADDRINT pc) { Printf("%s T%d %lx\n", __FUNCTION__, tid, pc); } void TmpCallback2(THREADID tid, ADDRINT pc) { Printf("%s T%d %lx\n", __FUNCTION__, tid, pc); } //--------- Threads --------------------------------- {{{2 static void HandleThreadCreateBefore(THREADID tid, ADDRINT pc) { DumpEvent(0, THR_CREATE_BEFORE, tid, pc, 0, 0); g_thread_create_lock.Lock(); IgnoreMopsBegin(tid); CHECK(g_tid_of_thread_which_called_create_thread == (THREADID)-1); g_tid_of_thread_which_called_create_thread = tid; n_created_threads++; } static void HandleThreadCreateAbort(THREADID tid) { CHECK(g_tid_of_thread_which_called_create_thread == tid); g_tid_of_thread_which_called_create_thread = (THREADID)-1; n_created_threads--; IgnoreMopsEnd(tid); g_thread_create_lock.Unlock(); } static THREADID HandleThreadCreateAfter(THREADID tid, pthread_t child_ptid, bool suspend_child) { // Spin, waiting for last_child_tid to appear (i.e. wait for the thread to // actually start) so that we know the child's tid. No locks. while (!ATOMIC_READ(&g_pin_threads[tid].last_child_tid)) { YIELD(); } CHECK(g_tid_of_thread_which_called_create_thread == tid); g_tid_of_thread_which_called_create_thread = -1; THREADID last_child_tid = g_pin_threads[tid].last_child_tid; CHECK(last_child_tid); PinThread &child_t = g_pin_threads[last_child_tid]; child_t.my_ptid = child_ptid; #ifdef _MSC_VER if (suspend_child) { while (ATOMIC_READ(&child_t.startup_state) != PinThread::CHILD_READY) { YIELD(); } // Strictly speaking, PIN forbids calling system functions like this. // This may violate application library isolation but // a) YIELD == WINDOWS::Sleep, so we violate it anyways // b) SuspendThread probably calls NtSuspendThread right away WINDOWS::DWORD old_count = WINDOWS::SuspendThread((WINDOWS::HANDLE)child_ptid); // TODO handle? CHECK(old_count == 0); } child_t.startup_state = PinThread::MAY_CONTINUE; #else CHECK(!suspend_child); // Not implemented - do we need to? #endif int uniq_tid_of_child = child_t.uniq_tid; g_pin_threads[tid].last_child_tid = 0; IgnoreMopsEnd(tid); g_thread_create_lock.Unlock(); DumpEvent(0, THR_CREATE_AFTER, tid, 0, 0, uniq_tid_of_child); return last_child_tid; } static uintptr_t WRAP_NAME(pthread_create)(WRAP_PARAM4) { HandleThreadCreateBefore(tid, pc); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret != 0) { HandleThreadCreateAbort(tid); return ret; } pthread_t child_ptid = *(pthread_t*)arg0; HandleThreadCreateAfter(tid, child_ptid, false); return ret; } void CallbackForThreadStart(THREADID tid, CONTEXT *ctxt, INT32 flags, void *v) { // We can not rely on PIN_GetParentTid() since it is broken on Windows. if (g_pin_threads == NULL) { g_pin_threads = new PinThread[kMaxThreads]; } bool has_parent = true; if (tid == 0) { // Main thread or we have attached to a running process. has_parent = false; } else { CHECK(tid > 0); } CHECK(tid < kMaxThreads); PinThread &t = g_pin_threads[tid]; memset(&t, 0, sizeof(PinThread)); t.uniq_tid = n_started_threads++; t.literace_sampling = G_flags->literace_sampling; t.tid = tid; t.tleb.t = &t; #if defined(_MSC_VER) t.startup_state = PinThread::STARTING; #endif ComputeIgnoreAccesses(t); PIN_SetContextReg(ctxt, tls_reg, (ADDRINT)&t.tleb.events[2]); t.parent_tid = -1; if (has_parent) { t.parent_tid = g_tid_of_thread_which_called_create_thread; #if !defined(_MSC_VER) // On Windows, threads may appear out of thin air. CHECK(t.parent_tid != (THREADID)-1); #endif // _MSC_VER } if (debug_thread) { Printf("T%d ThreadStart parent=%d child=%d\n", tid, t.parent_tid, tid); } if (has_parent && t.parent_tid != (THREADID)-1) { g_pin_threads[t.parent_tid].last_child_tid = tid; t.thread_stack_size_if_known = g_pin_threads[t.parent_tid].last_child_stack_size_if_known; } else { #if defined(_MSC_VER) t.startup_state = PinThread::MAY_CONTINUE; #endif } // This is a lock-free (thread local) operation. TLEBStartThread(t); /* TODO(timurrrr): investigate and un-comment #ifdef _MSC_VER // Ignore all mops & sync before the real thread code. // See the corresponding IgnoreSyncAndMopsEnd in Before_BaseThreadInitThunk. IgnoreSyncAndMopsBegin(tid); #endif */ } static void Before_start_thread(THREADID tid, ADDRINT pc, ADDRINT sp) { PinThread &t = g_pin_threads[tid]; if (debug_thread) { Printf("T%d Before_start_thread: sp=%p my_ptid=%p diff=%p\n", tid, sp, t.my_ptid, t.my_ptid - sp); } // This is a rather scary hack, but I see no easy way to avoid it. // On linux NPTL, the pthread_t structure is the same block of memory // as the stack (and the tls?). Somewhere inside the pthread_t // object lives the address of stackblock followed by its size // (see nptl/descr.h). // At the current point we may not know the value of pthread_t (my_ptid), // but we do know the current sp, which is a bit less than my_ptid. // // address value // ------------------------------------------------ // 0xffffffffffffffff: // // stackblock + stackblock_size: // my_ptid: // // stackblock_size // stackblock // // current_sp: // // // stackblock: // // 0x0000000000000000: // ------------------------------------------------- // // So, we itrate from sp to the higher addresses (but just in case, not more // than a few pages) trying to find a pair of values which looks like // stackblock and stackblock_size. Oh well. // Note that in valgrind we are able to get this info from // pthread_getattr_np (linux) or pthread_get_stackaddr_np (mac), // but in PIN we can't call those (can we?). uintptr_t prev = 0; for (uintptr_t sp1 = sp; sp1 - sp < 0x2000; sp1 += sizeof(uintptr_t)) { uintptr_t val = *(uintptr_t*)sp1; if (val == 0) continue; if (prev && (prev & 0xfff) == 0 && // stack is page aligned prev < sp && // min stack is < sp prev + val > sp && // max stack is > sp val >= (1 << 15) && // stack size is >= 32k val <= 128 * (1 << 20) // stack size is hardly > 128M ) { if (debug_thread) { Printf("T%d found stack: %p size=%p\n", tid, prev, val); } DumpEvent(0, THR_STACK_TOP, tid, pc, prev + val, val); return; } prev = val; } // The hack above does not always works. (TODO(kcc)). Do something. Printf("WARNING: ThreadSanitizerPin is guessing stack size for T%d\n", tid); DumpEvent(0, THR_STACK_TOP, tid, pc, sp, t.thread_stack_size_if_known); } #ifdef _MSC_VER static uintptr_t WRAP_NAME(CreateThread)(WRAP_PARAM6) { PinThread &t = g_pin_threads[tid]; t.last_child_stack_size_if_known = arg1 ? arg1 : 1024 * 1024; HandleThreadCreateBefore(tid, pc); // We can't start the thread suspended because we want to get its // PIN thread ID before leaving CreateThread. // So, we reset the CREATE_SUSPENDED flag and SuspendThread before any client // code is executed in the HandleThreadCreateAfter if needed. bool should_be_suspended = arg4 & CREATE_SUSPENDED; arg4 &= ~CREATE_SUSPENDED; uintptr_t ret = CALL_ME_INSIDE_WRAPPER_6(); if (ret == NULL) { HandleThreadCreateAbort(tid); return ret; } pthread_t child_ptid = ret; THREADID child_tid = HandleThreadCreateAfter(tid, child_ptid, should_be_suspended); { ScopedReentrantClientLock lock(__LINE__); if (g_win_handles_which_are_threads == NULL) { g_win_handles_which_are_threads = new unordered_set<pthread_t>; } g_win_handles_which_are_threads->insert(child_ptid); } return ret; } static void Before_BaseThreadInitThunk(THREADID tid, ADDRINT pc, ADDRINT sp) { PinThread &t = g_pin_threads[tid]; size_t stack_size = t.thread_stack_size_if_known; // Printf("T%d %s %p %p\n", tid, __FUNCTION__, sp, stack_size); /* TODO(timurrrr): investigate and uncomment if (tid != 0) { // Ignore all mops & sync before the real thread code. // See the corresponding IgnoreSyncAndMopsBegin in CallbackForThreadStart. IgnoreSyncAndMopsEnd(tid); TLEBFlushLocked(t); CHECK(t.ignore_sync == 0); CHECK(t.ignore_accesses == 0); } */ DumpEvent(0, THR_STACK_TOP, tid, pc, sp, stack_size); #ifdef _MSC_VER if (t.startup_state != PinThread::MAY_CONTINUE) { CHECK(t.startup_state == PinThread::STARTING); t.startup_state = PinThread::CHILD_READY; while (ATOMIC_READ(&t.startup_state) != PinThread::MAY_CONTINUE) { YIELD(); } // Corresponds to SIGNAL from ResumeThread if the thread was suspended on // start. DumpEvent(0, WAIT, tid, pc, t.my_ptid, 0); } #endif } static void Before_RtlExitUserThread(THREADID tid, ADDRINT pc) { PinThread &t = g_pin_threads[tid]; if (t.tid != 0) { // Once we started exiting the thread, ignore the locking events. // This way we will avoid h-b arcs between unrelated threads. // We also start ignoring all mops, otherwise we will get tons of race // reports from the windows guts. IgnoreSyncAndMopsBegin(tid); } } #endif // _MSC_VER void CallbackForThreadFini(THREADID tid, const CONTEXT *ctxt, INT32 code, void *v) { PinThread &t = g_pin_threads[tid]; t.thread_finished = true; // We can not DumpEvent here, // due to possible deadlock with PIN's internal lock. if (debug_thread) { Printf("T%d Thread finished (ptid=%d)\n", tid, t.my_ptid); } } static bool HandleThreadJoinAfter(THREADID tid, pthread_t joined_ptid) { THREADID joined_tid = kMaxThreads; int max_uniq_tid_found = -1; // TODO(timurrrr): walking through g_pin_threads may be slow. // Do we need to/Can we optimize it? for (THREADID j = 1; j < kMaxThreads; j++) { if (g_pin_threads[j].thread_finished == false) continue; if (g_pin_threads[j].my_ptid == joined_ptid) { // We search for the thread with the maximum uniq_tid to work around // thread HANDLE reuse issues. if (max_uniq_tid_found < g_pin_threads[j].uniq_tid) { max_uniq_tid_found = g_pin_threads[j].uniq_tid; joined_tid = j; } } } if (joined_tid == kMaxThreads) { // This may happen in the following case: // - A non-joinable thread is created and a handle is assigned to it. // - Since the thread is non-joinable, the handle is then reused // for some other purpose, e.g. for a WaitableEvent. // - We did not yet register the thread fini event. // - We observe WaitForSingleObjectEx(ptid) and think that this is thread // join event, while it is not. if (debug_thread) Printf("T%d JoinAfter returns false! ptid=%d\n", tid, joined_ptid); return false; } CHECK(joined_tid < kMaxThreads); CHECK(joined_tid > 0); g_pin_threads[joined_tid].my_ptid = 0; int joined_uniq_tid = g_pin_threads[joined_tid].uniq_tid; if (debug_thread) { Printf("T%d JoinAfter parent=%d child=%d (uniq=%d)\n", tid, tid, joined_tid, joined_uniq_tid); } // Here we send an event for a different thread (joined_tid), which is already // dead. DumpEvent(0, THR_END, joined_tid, 0, 0, 0); DumpEvent(0, THR_JOIN_AFTER, tid, 0, joined_uniq_tid, 0); return true; } static uintptr_t WRAP_NAME(pthread_join)(WRAP_PARAM4) { if (G_flags->debug_level >= 2) Printf("T%d in pthread_join %p\n", tid, arg0); pthread_t joined_ptid = (pthread_t)arg0; uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); HandleThreadJoinAfter(tid, joined_ptid); if (G_flags->debug_level >= 2) Printf("T%d out pthread_join %p\n", tid, arg0); return ret; } static size_t WRAP_NAME(fwrite)(WRAP_PARAM4) { void* p = (void*)arg0; size_t size = (size_t)arg1 * (size_t)arg2; REPORT_READ_RANGE(p, size); IgnoreMopsBegin(tid); size_t ret = CALL_ME_INSIDE_WRAPPER_4(); IgnoreMopsEnd(tid); return ret; } #ifdef _MSC_VER uintptr_t CallStdCallFun1(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0) { uintptr_t ret = 0xdeadbee1; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_STDCALL, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG_END()); return ret; } uintptr_t CallStdCallFun2(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1) { uintptr_t ret = 0xdeadbee2; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_STDCALL, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG_END()); return ret; } uintptr_t CallStdCallFun3(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2) { uintptr_t ret = 0xdeadbee3; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_STDCALL, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG(uintptr_t), arg2, PIN_PARG_END()); return ret; } uintptr_t CallStdCallFun4(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) { uintptr_t ret = 0xdeadbee4; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_STDCALL, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG(uintptr_t), arg2, PIN_PARG(uintptr_t), arg3, PIN_PARG_END()); return ret; } uintptr_t CallStdCallFun5(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4) { uintptr_t ret = 0xdeadbee5; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_STDCALL, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG(uintptr_t), arg2, PIN_PARG(uintptr_t), arg3, PIN_PARG(uintptr_t), arg4, PIN_PARG_END()); return ret; } uintptr_t CallStdCallFun6(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5) { uintptr_t ret = 0xdeadbee6; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_STDCALL, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG(uintptr_t), arg2, PIN_PARG(uintptr_t), arg3, PIN_PARG(uintptr_t), arg4, PIN_PARG(uintptr_t), arg5, PIN_PARG_END()); return ret; } uintptr_t CallStdCallFun7(CONTEXT *ctx, THREADID tid, AFUNPTR f, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5, uintptr_t arg6) { uintptr_t ret = 0xdeadbee7; PIN_CallApplicationFunction(ctx, tid, CALLINGSTD_STDCALL, (AFUNPTR)(f), PIN_PARG(uintptr_t), &ret, PIN_PARG(uintptr_t), arg0, PIN_PARG(uintptr_t), arg1, PIN_PARG(uintptr_t), arg2, PIN_PARG(uintptr_t), arg3, PIN_PARG(uintptr_t), arg4, PIN_PARG(uintptr_t), arg5, PIN_PARG(uintptr_t), arg6, PIN_PARG_END()); return ret; } uintptr_t WRAP_NAME(ResumeThread)(WRAP_PARAM4) { // Printf("T%d %s arg0=%p\n", tid, __FUNCTION__, arg0); DumpEvent(ctx, SIGNAL, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); return ret; } uintptr_t WRAP_NAME(RtlInitializeCriticalSection)(WRAP_PARAM4) { // Printf("T%d pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); DumpEvent(ctx, LOCK_CREATE, tid, pc, arg0, 0); IgnoreSyncAndMopsBegin(tid); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); IgnoreSyncAndMopsEnd(tid); return ret; } uintptr_t WRAP_NAME(RtlInitializeCriticalSectionAndSpinCount)(WRAP_PARAM4) { // Printf("T%d pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); DumpEvent(ctx, LOCK_CREATE, tid, pc, arg0, 0); IgnoreSyncAndMopsBegin(tid); uintptr_t ret = CallStdCallFun2(ctx, tid, f, arg0, arg1); IgnoreSyncAndMopsEnd(tid); return ret; } uintptr_t WRAP_NAME(RtlInitializeCriticalSectionEx)(WRAP_PARAM4) { // Printf("T%d pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); DumpEvent(ctx, LOCK_CREATE, tid, pc, arg0, 0); IgnoreSyncAndMopsBegin(tid); uintptr_t ret = CallStdCallFun3(ctx, tid, f, arg0, arg1, arg2); IgnoreSyncAndMopsEnd(tid); return ret; } uintptr_t WRAP_NAME(RtlDeleteCriticalSection)(WRAP_PARAM4) { // Printf("T%d pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); DumpEvent(ctx, LOCK_DESTROY, tid, pc, arg0, 0); IgnoreSyncAndMopsBegin(tid); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); IgnoreSyncAndMopsEnd(tid); return ret; } uintptr_t WRAP_NAME(RtlEnterCriticalSection)(WRAP_PARAM4) { // Printf("T%d pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); return ret; } uintptr_t WRAP_NAME(RtlTryEnterCriticalSection)(WRAP_PARAM4) { // Printf("T%d pc=%p %s: %p\n", tid, pc, __FUNCTION__+5, arg0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); if (ret) { DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); } return ret; } uintptr_t WRAP_NAME(RtlLeaveCriticalSection)(WRAP_PARAM4) { // Printf("T%d pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); DumpEvent(ctx, UNLOCK, tid, pc, arg0, 0); return CallStdCallFun1(ctx, tid, f, arg0); } uintptr_t WRAP_NAME(DuplicateHandle)(WRAP_PARAM8) { Printf("WARNING: DuplicateHandle called for handle 0x%X.\n", arg1); Printf("Future events on this handle may be processed incorrectly.\n"); return CallStdCallFun7(ctx, tid, f, arg0, arg1, arg2, arg3, arg4, arg5, arg6); } uintptr_t WRAP_NAME(SetEvent)(WRAP_PARAM4) { //Printf("T%d before pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); DumpEvent(ctx, SIGNAL, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); //Printf("T%d after pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0); return ret; } uintptr_t InternalWrapCreateSemaphore(WRAP_PARAM4) { if (arg3 != NULL) { Printf("WARNING: CreateSemaphore called with lpName='%s'.\n", arg3); Printf("Future events on this semaphore may be processed incorrectly " "if it is reused.\n"); } return CallStdCallFun4(ctx, tid, f, arg0, arg1, arg2, arg3); } uintptr_t WRAP_NAME(CreateSemaphoreA)(WRAP_PARAM4) { return InternalWrapCreateSemaphore(tid, pc, ctx, f, arg0, arg1, arg2, arg3); } uintptr_t WRAP_NAME(CreateSemaphoreW)(WRAP_PARAM4) { return InternalWrapCreateSemaphore(tid, pc, ctx, f, arg0, arg1, arg2, arg3); } uintptr_t WRAP_NAME(ReleaseSemaphore)(WRAP_PARAM4) { DumpEvent(ctx, SIGNAL, tid, pc, arg0, 0); return CallStdCallFun3(ctx, tid, f, arg0, arg1, arg2); } uintptr_t WRAP_NAME(RtlInterlockedPushEntrySList)(WRAP_PARAM4) { DumpEvent(ctx, SIGNAL, tid, pc, arg1, 0); uintptr_t ret = CallStdCallFun2(ctx, tid, f, arg0, arg1); // Printf("T%d %s list=%p item=%p\n", tid, __FUNCTION__, arg0, arg1); return ret; } uintptr_t WRAP_NAME(RtlInterlockedPopEntrySList)(WRAP_PARAM4) { uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); // Printf("T%d %s list=%p item=%p\n", tid, __FUNCTION__, arg0, ret); if (ret) { DumpEvent(ctx, WAIT, tid, pc, ret, 0); } return ret; } uintptr_t WRAP_NAME(RtlAcquireSRWLockExclusive)(WRAP_PARAM4) { uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); return ret; } uintptr_t WRAP_NAME(RtlAcquireSRWLockShared)(WRAP_PARAM4) { uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); DumpEvent(ctx, READER_LOCK, tid, pc, arg0, 0); return ret; } uintptr_t WRAP_NAME(RtlTryAcquireSRWLockExclusive)(WRAP_PARAM4) { // Printf("T%d %s %p\n", tid, __FUNCTION__, arg0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); if (ret & 0xFF) { // Looks like this syscall return value is just 1 byte. DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); } return ret; } uintptr_t WRAP_NAME(RtlTryAcquireSRWLockShared)(WRAP_PARAM4) { // Printf("T%d %s %p\n", tid, __FUNCTION__, arg0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); if (ret & 0xFF) { // Looks like this syscall return value is just 1 byte. DumpEvent(ctx, READER_LOCK, tid, pc, arg0, 0); } return ret; } uintptr_t WRAP_NAME(RtlReleaseSRWLockExclusive)(WRAP_PARAM4) { // Printf("T%d %s %p\n", tid, __FUNCTION__, arg0); DumpEvent(ctx, UNLOCK, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); return ret; } uintptr_t WRAP_NAME(RtlReleaseSRWLockShared)(WRAP_PARAM4) { // Printf("T%d %s %p\n", tid, __FUNCTION__, arg0); DumpEvent(ctx, UNLOCK, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); return ret; } uintptr_t WRAP_NAME(RtlInitializeSRWLock)(WRAP_PARAM4) { // Printf("T%d %s %p\n", tid, __FUNCTION__, arg0); DumpEvent(ctx, LOCK_CREATE, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); return ret; } uintptr_t WRAP_NAME(RtlWakeConditionVariable)(WRAP_PARAM4) { // Printf("T%d %s arg0=%p\n", tid, __FUNCTION__, arg0); DumpEvent(ctx, SIGNAL, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); return ret; } uintptr_t WRAP_NAME(RtlWakeAllConditionVariable)(WRAP_PARAM4) { // Printf("T%d %s arg0=%p\n", tid, __FUNCTION__, arg0); DumpEvent(ctx, SIGNAL, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun1(ctx, tid, f, arg0); return ret; } uintptr_t WRAP_NAME(RtlSleepConditionVariableSRW)(WRAP_PARAM4) { // No need to unlock/lock - looks like RtlSleepConditionVariableSRW performs // Rtl{Acquire,Release}SRW... calls itself! uintptr_t ret = CallStdCallFun4(ctx, tid, f, arg0, arg1, arg2, arg3); if ((ret & 0xFF) == 0) DumpEvent(ctx, WAIT, tid, pc, arg0, 0); // Printf("T%d %s arg0=%p arg1=%p; ret=%d\n", tid, __FUNCTION__, arg0, arg1, ret); return ret; } uintptr_t WRAP_NAME(RtlSleepConditionVariableCS)(WRAP_PARAM4) { // TODO(timurrrr): do we need unlock/lock? uintptr_t ret = CallStdCallFun3(ctx, tid, f, arg0, arg1, arg2); if ((ret & 0xFF) == 0) DumpEvent(ctx, WAIT, tid, pc, arg0, 0); // Printf("T%d %s arg0=%p arg1=%p; ret=%d\n", tid, __FUNCTION__, arg0, arg1, ret); return ret; } uintptr_t WRAP_NAME(RtlQueueWorkItem)(WRAP_PARAM4) { // Printf("T%d %s arg0=%p arg1=%p; arg2=%d\n", tid, __FUNCTION__, arg0, arg1, arg2); g_windows_thread_pool_calback_set->insert(arg0); DumpEvent(ctx, SIGNAL, tid, pc, arg0, 0); uintptr_t ret = CallStdCallFun3(ctx, tid, f, arg0, arg1, arg2); return ret; } uintptr_t WRAP_NAME(RegisterWaitForSingleObject)(WRAP_PARAM6) { // Printf("T%d %s arg0=%p arg2=%p\n", tid, __FUNCTION__, arg0, arg2); g_windows_thread_pool_calback_set->insert(arg2); DumpEvent(ctx, SIGNAL, tid, pc, arg2, 0); uintptr_t ret = CallStdCallFun6(ctx, tid, f, arg0, arg1, arg2, arg3, arg4, arg5); if (ret) { uintptr_t wait_object = *(uintptr_t*)arg0; (*g_windows_thread_pool_wait_object_map)[wait_object] = arg2; // Printf("T%d %s *arg0=%p\n", tid, __FUNCTION__, wait_object); } return ret; } uintptr_t WRAP_NAME(UnregisterWaitEx)(WRAP_PARAM4) { CHECK(g_windows_thread_pool_wait_object_map); uintptr_t obj = (*g_windows_thread_pool_wait_object_map)[arg0]; // Printf("T%d %s arg0=%p obj=%p\n", tid, __FUNCTION__, arg0, obj); uintptr_t ret = CallStdCallFun2(ctx, tid, f, arg0, arg1); if (ret) { DumpEvent(ctx, WAIT, tid, pc, obj, 0); } return ret; } uintptr_t WRAP_NAME(VirtualAlloc)(WRAP_PARAM4) { // Printf("T%d VirtualAlloc: %p %p %p %p\n", tid, arg0, arg1, arg2, arg3); uintptr_t ret = CallStdCallFun4(ctx, tid, f, arg0, arg1, arg2, arg3); return ret; } uintptr_t WRAP_NAME(GlobalAlloc)(WRAP_PARAM4) { uintptr_t ret = CallStdCallFun2(ctx, tid, f, arg0, arg1); // Printf("T%d %s(%p %p)=%p\n", tid, __FUNCTION__, arg0, arg1, ret); if (ret != 0) { DumpEvent(ctx, MALLOC, tid, pc, ret, arg1); } return ret; } uintptr_t WRAP_NAME(ZwAllocateVirtualMemory)(WRAP_PARAM6) { // Printf("T%d >>%s(%p %p %p %p %p %p)\n", tid, __FUNCTION__, arg0, arg1, arg2, arg3, arg4, arg5); uintptr_t ret = CallStdCallFun6(ctx, tid, f, arg0, arg1, arg2, arg3, arg4, arg5); // Printf("T%d <<%s(%p %p) = %p\n", tid, __FUNCTION__, *(void**)arg1, *(void**)arg3, ret); if (ret == 0) { DumpEvent(ctx, MALLOC, tid, pc, *(uintptr_t*)arg1, *(uintptr_t*)arg3); } return ret; } uintptr_t WRAP_NAME(AllocateHeap)(WRAP_PARAM4) { uintptr_t ret = CallStdCallFun3(ctx, tid, f, arg0, arg1, arg2); // Printf("T%d RtlAllocateHeap(%p %p %p)=%p\n", tid, arg0, arg1, arg2, ret); if (ret != 0) { DumpEvent(ctx, MALLOC, tid, pc, ret, arg3); } return ret; } uintptr_t WRAP_NAME(HeapCreate)(WRAP_PARAM4) { uintptr_t ret = CallStdCallFun3(ctx, tid, f, arg0, arg1, arg2); Printf("T%d %s(%p %p %p)=%p\n", tid, __FUNCTION__, arg0, arg1, arg2, ret); return ret; } // We don't use the definition of WAIT_OBJECT_0 from winbase.h because // it can't be compiled here for some reason. #define WAIT_OBJECT_0_ 0 uintptr_t WRAP_NAME(WaitForSingleObjectEx)(WRAP_PARAM4) { if (G_flags->verbosity >= 1) { ShowPcAndSp(__FUNCTION__, tid, pc, 0); Printf("arg0=%lx arg1=%lx\n", arg0, arg1); } //Printf("T%d before pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0, arg1); uintptr_t ret = CallStdCallFun3(ctx, tid, f, arg0, arg1, arg2); //Printf("T%d after pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0, arg1); if (ret == WAIT_OBJECT_0_) { bool is_thread_handle = false; { ScopedReentrantClientLock lock(__LINE__); if (g_win_handles_which_are_threads) { is_thread_handle = g_win_handles_which_are_threads->count(arg0) > 0; g_win_handles_which_are_threads->erase(arg0); } } if (is_thread_handle) HandleThreadJoinAfter(tid, arg0); DumpEvent(ctx, WAIT, tid, pc, arg0, 0); } return ret; } uintptr_t WRAP_NAME(WaitForMultipleObjectsEx)(WRAP_PARAM6) { if (G_flags->verbosity >= 1) { ShowPcAndSp(__FUNCTION__, tid, pc, 0); Printf("arg0=%lx arg1=%lx arg2=%lx arg3=%lx\n", arg0, arg1, arg2, arg3); } //Printf("T%d before pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0, arg1); uintptr_t ret = CallStdCallFun5(ctx, tid, f, arg0, arg1, arg2, arg3, arg4); //Printf("T%d after pc=%p %s: %p\n", tid, pc, __FUNCTION__+8, arg0, arg1); if (ret >= WAIT_OBJECT_0_ && ret < WAIT_OBJECT_0_ + arg0) { // TODO(timurrrr): add support for WAIT_ABANDONED_0 int start_id, count; if (arg2 /* wait_for_all */ == 1) { start_id = 0; count = arg0; } else { start_id = ret - WAIT_OBJECT_0_; count = 1; } for (int i = start_id; i < start_id + count; i++) { uintptr_t handle = ((uintptr_t*)arg1)[i]; bool is_thread_handle = false; { ScopedReentrantClientLock lock(__LINE__); if (g_win_handles_which_are_threads) { is_thread_handle = g_win_handles_which_are_threads->count(handle) > 0; g_win_handles_which_are_threads->erase(handle); } } if (is_thread_handle) HandleThreadJoinAfter(tid, handle); DumpEvent(ctx, WAIT, tid, pc, handle, 0); } } return ret; } #endif // _MSC_VER //--------- memory allocation ---------------------- {{{2 uintptr_t WRAP_NAME(mmap)(WRAP_PARAM6) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_6(); if (ret != (ADDRINT)-1L) { DumpEvent(ctx, MMAP, tid, pc, ret, arg1); } return ret; } uintptr_t WRAP_NAME(munmap)(WRAP_PARAM4) { PinThread &t = g_pin_threads[tid]; TLEBFlushLocked(t); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret != (uintptr_t)-1L) { DumpEvent(ctx, MUNMAP, tid, pc, arg0, arg1); } return ret; } void After_malloc(FAST_WRAP_PARAM_AFTER) { size_t size = frame.arg[0]; if (DEBUG_FAST_INTERCEPTORS) Printf("T%d %s %ld %p\n", tid, __FUNCTION__, size, ret); IgnoreSyncAndMopsEnd(tid); DumpEventWithSp(frame.sp, MALLOC, tid, frame.pc, ret, size); } void Before_malloc(FAST_WRAP_PARAM1) { IgnoreSyncAndMopsBegin(tid); PUSH_AFTER_CALLBACK1(After_malloc, arg0); } void After_free(FAST_WRAP_PARAM_AFTER) { if (DEBUG_FAST_INTERCEPTORS) Printf("T%d %s %p\n", tid, __FUNCTION__, frame.arg[0]); IgnoreSyncAndMopsEnd(tid); } void Before_free(FAST_WRAP_PARAM1) { PinThread &t = g_pin_threads[tid]; TLEBFlushLocked(t); DumpEvent(0, FREE, tid, pc, arg0, 0); IgnoreSyncAndMopsBegin(tid); PUSH_AFTER_CALLBACK1(After_free, arg0); } void Before_calloc(FAST_WRAP_PARAM2) { IgnoreSyncAndMopsBegin(tid); PUSH_AFTER_CALLBACK1(After_malloc, arg0 * arg1); } void Before_realloc(FAST_WRAP_PARAM2) { PinThread &t = g_pin_threads[tid]; TLEBFlushLocked(t); IgnoreSyncAndMopsBegin(tid); // TODO: handle FREE? We don't do it in Valgrind right now. PUSH_AFTER_CALLBACK1(After_malloc, arg1); } // Fast path for INS_InsertIfCall. ADDRINT Before_RET_IF(THREADID tid, ADDRINT pc, ADDRINT sp, ADDRINT ret) { PinThread &t = g_pin_threads[tid]; return t.ic_stack.size(); } void Before_RET_THEN(THREADID tid, ADDRINT pc, ADDRINT sp, ADDRINT ret) { PinThread &t = g_pin_threads[tid]; if (t.ic_stack.size() == 0) return; DCHECK(t.ic_stack.size()); InstrumentedCallFrame *frame = t.ic_stack.Top(); if (DEBUG_FAST_INTERCEPTORS) { Printf("T%d RET pc=%p sp=%p *sp=%p frame.sp=%p stack_size %ld\n", tid, pc, sp, *(uintptr_t*)sp, frame->sp, t.ic_stack.size()); t.ic_stack.Print(); } while (frame->sp <= sp) { if (DEBUG_FAST_INTERCEPTORS) Printf("pop\n"); frame->callback(tid, *frame, ret); t.ic_stack.Pop(); if (t.ic_stack.size()) { frame = t.ic_stack.Top(); } else { break; } } } // These are no longer used in favor of "fast" wrappers (e.g. Before_malloc) // TODO(timurrrr): Check on the buildbot and remove. uintptr_t WRAP_NAME(malloc)(WRAP_PARAM4) { CHECK(0); } uintptr_t WRAP_NAME(realloc)(WRAP_PARAM4) { CHECK(0); } uintptr_t WRAP_NAME(calloc)(WRAP_PARAM4) { CHECK(0); } uintptr_t WRAP_NAME(free)(WRAP_PARAM4) { CHECK(0); } //-------- Routines and stack ---------------------- {{{2 static INLINE void UpdateCallStack(PinThread &t, ADDRINT sp) { while (t.shadow_stack.size() > 0 && sp >= t.shadow_stack.back().sp) { TLEBAddRtnExit(t); size_t size = t.shadow_stack.size(); CHECK(size < 1000000); // stay sane. uintptr_t popped_pc = t.shadow_stack.back().pc; #ifdef _MSC_VER // h-b edge from here to UnregisterWaitEx. CHECK(g_windows_thread_pool_calback_set); if (g_windows_thread_pool_calback_set->count(popped_pc)) { DumpEvent(0, SIGNAL, t.tid, 0, popped_pc, 0); // Printf("T%d ret %p\n", t.tid, popped_pc); } #endif if (debug_rtn) { ShowPcAndSp("RET : ", t.tid, popped_pc, sp); } t.shadow_stack.pop_back(); CHECK(size - 1 == t.shadow_stack.size()); if (DEB_PR) { Printf("POP SHADOW STACK\n"); PrintShadowStack(t); } } } void InsertBeforeEvent_SysCall(THREADID tid, ADDRINT sp) { PinThread &t = g_pin_threads[tid]; UpdateCallStack(t, sp); TLEBFlushLocked(t); } void InsertBeforeEvent_Call(THREADID tid, ADDRINT pc, ADDRINT target, ADDRINT sp, IGNORE_BELOW_RTN ignore_below) { PinThread &t = g_pin_threads[tid]; DebugOnlyShowPcAndSp(__FUNCTION__, t.tid, pc, sp); UpdateCallStack(t, sp); TLEBAddRtnCall(t, pc, target, ignore_below); t.shadow_stack.push_back(StackFrame(target, sp)); if (DEB_PR) { PrintShadowStack(t); } if (DEBUG_MODE && debug_rtn) { ShowPcAndSp("CALL: ", t.tid, target, sp); } #ifdef _MSC_VER // h-b edge from RtlQueueWorkItem to here. CHECK(g_windows_thread_pool_calback_set); if (g_windows_thread_pool_calback_set->count(target)) { DumpEvent(0, WAIT, tid, pc, target, 0); } #endif } static void OnTraceSerial(THREADID tid, ADDRINT sp, TraceInfo *trace_info, uintptr_t **tls_reg_p) { PinThread &t = g_pin_threads[tid]; DCHECK(trace_info); DCHECK(trace_info->n_mops() > 0); DebugOnlyShowPcAndSp(__FUNCTION__, t.tid, trace_info->pc(), sp); UpdateCallStack(t, sp); t.trace_info = trace_info; trace_info->counter()++; *tls_reg_p = TLEBAddTrace(t); } static void OnTraceParallel(uintptr_t *tls_reg, ADDRINT sp, TraceInfo *trace_info) { // Get the thread handler directly from tls_reg. PinThread &t = *(PinThread*)(tls_reg - 4); t.trace_info = trace_info; if (t.ignore_accesses) return; DCHECK(trace_info); DCHECK(trace_info->n_mops() > 0); DebugOnlyShowPcAndSp(__FUNCTION__, t.tid, trace_info->pc(), sp); UpdateCallStack(t, sp); if (DEBUG_MODE && G_flags->show_stats) // this stat may be racey; avoid ping-pong. trace_info->counter()++; TLEBAddTrace(t); } /* Verify all mop accesses in the last trace of the given thread by registering them with RaceVerifier and sleeping a bit. */ static void OnTraceVerifyInternal(PinThread &t, uintptr_t **tls_reg_p) { DCHECK(g_race_verifier_active); if (t.trace_info) { int need_sleep = 0; for (unsigned i = 0; i < t.trace_info->n_mops(); ++i) { uintptr_t addr = (*tls_reg_p)[i]; if (addr) { MopInfo *mop = t.trace_info->GetMop(i); need_sleep += RaceVerifierStartAccess(t.uniq_tid, addr, mop->pc(), mop->is_write()); } } if (!need_sleep) return; usleep(G_flags->race_verifier_sleep_ms * 1000); for (unsigned i = 0; i < t.trace_info->n_mops(); ++i) { uintptr_t addr = (*tls_reg_p)[i]; if (addr) { MopInfo *mop = t.trace_info->GetMop(i); RaceVerifierEndAccess(t.uniq_tid, addr, mop->pc(), mop->is_write()); } } } } static void OnTraceNoMopsVerify(THREADID tid, ADDRINT sp, uintptr_t **tls_reg_p) { PinThread &t = g_pin_threads[tid]; DCHECK(g_race_verifier_active); OnTraceVerifyInternal(t, tls_reg_p); t.trace_info = NULL; } static void OnTraceVerify(THREADID tid, ADDRINT sp, TraceInfo *trace_info, uintptr_t **tls_reg_p) { DCHECK(g_race_verifier_active); PinThread &t = g_pin_threads[tid]; OnTraceVerifyInternal(t, tls_reg_p); DCHECK(trace_info->n_mops() > 0); t.trace_info = trace_info; trace_info->counter()++; *tls_reg_p = TLEBAddTrace(t); } //---------- Memory accesses -------------------------- {{{2 // 'addr' is the section of t.tleb.events which is set in OnTrace. // 'idx' is the number of this mop in its trace. // 'a' is the actuall address. // 'tid' is thread ID, used only in debug mode. // // In opt mode this is just one instruction! Something like this: // mov %rcx,(%rdi,%rdx,8) static void OnMop(uintptr_t *addr, THREADID tid, ADDRINT idx, ADDRINT a) { if (DEBUG_MODE) { PinThread &t= g_pin_threads[tid]; CHECK(idx < kMaxMopsPerTrace); CHECK(idx < t.trace_info->n_mops()); uintptr_t *ptr = addr + idx; CHECK(ptr >= t.tleb.events); CHECK(ptr < t.tleb.events + kThreadLocalEventBufferSize); if (a == G_flags->trace_addr) { Printf("T%d %s %lx\n", t.tid, __FUNCTION__, a); } } addr[idx] = a; } static void On_PredicatedMop(BOOL is_running, uintptr_t *addr, THREADID tid, ADDRINT idx, ADDRINT a) { if (is_running) { OnMop(addr, tid, idx, a); } } static void OnMopCheckIdentStoreBefore(uintptr_t *addr, THREADID tid, ADDRINT idx, ADDRINT a) { // Write the value of *a to tleb. addr[idx] = *(uintptr_t*)a; } static void OnMopCheckIdentStoreAfter(uintptr_t *addr, THREADID tid, ADDRINT idx, ADDRINT a) { // Check if the previous value of *a is equal to the new one. // If not, we have a regular memory access. If yes, we have an ident operation, // which we want to ignore. uintptr_t previous_value_of_a = addr[idx]; uintptr_t new_value_of_a = *(uintptr_t*)a; // 111...111 if the values are different, 0 otherwise. uintptr_t ne_mask = -(uintptr_t)(new_value_of_a != previous_value_of_a); addr[idx] = ne_mask & a; } //---------- I/O; exit------------------------------- {{{2 static const uintptr_t kIOMagic = 0x1234c678; static void Before_SignallingIOCall(THREADID tid, ADDRINT pc) { DumpEvent(0, SIGNAL, tid, pc, kIOMagic, 0); } static void After_WaitingIOCall(THREADID tid, ADDRINT pc) { DumpEvent(0, WAIT, tid, pc, kIOMagic, 0); } static const uintptr_t kAtexitMagic = 0x9876f432; static void On_atexit(THREADID tid, ADDRINT pc) { DumpEvent(0, SIGNAL, tid, pc, kAtexitMagic, 0); } static void On_exit(THREADID tid, ADDRINT pc) { DumpEvent(0, WAIT, tid, pc, kAtexitMagic, 0); } //---------- Synchronization -------------------------- {{{2 // locks static void Before_pthread_unlock(THREADID tid, ADDRINT pc, ADDRINT mu) { DumpEvent(0, UNLOCK, tid, pc, mu, 0); } static void After_pthread_mutex_lock(FAST_WRAP_PARAM_AFTER) { DumpEventWithSp(frame.sp, WRITER_LOCK, tid, frame.pc, frame.arg[0], 0); } static void Before_pthread_mutex_lock(FAST_WRAP_PARAM1) { PUSH_AFTER_CALLBACK1(After_pthread_mutex_lock, arg0); } // In some versions of libpthread, pthread_spin_lock is effectively // a recursive function. It jumps to its first insn: // beb0: f0 ff 0f lock decl (%rdi) // beb3: 75 0b jne bec0 <pthread_spin_lock+0x10> // beb5: 31 c0 xor %eax,%eax // beb7: c3 retq // beb8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) // bebf: 00 // bec0: f3 90 pause // bec2: 83 3f 00 cmpl $0x0,(%rdi) // bec5: 7f e9 >>>>>>>>>>>>> jg beb0 <pthread_spin_lock> // bec7: eb f7 jmp bec0 <pthread_spin_lock+0x10> // // So, we need to act only when we return from the last (depth=0) invocation. static uintptr_t WRAP_NAME(pthread_spin_lock)(WRAP_PARAM4) { PinThread &t= g_pin_threads[tid]; t.spin_lock_recursion_depth++; uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); t.spin_lock_recursion_depth--; if (t.spin_lock_recursion_depth == 0) { DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); } return ret; } static uintptr_t WRAP_NAME(pthread_rwlock_wrlock)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); return ret; } static uintptr_t WRAP_NAME(pthread_rwlock_rdlock)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); DumpEvent(ctx, READER_LOCK, tid, pc, arg0, 0); return ret; } static uintptr_t WRAP_NAME(pthread_mutex_trylock)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret == 0) DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); return ret; } static uintptr_t WRAP_NAME(pthread_spin_trylock)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret == 0) DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); return ret; } static uintptr_t WRAP_NAME(pthread_spin_init)(WRAP_PARAM4) { DumpEvent(ctx, UNLOCK_OR_INIT, tid, pc, arg0, 0); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); return ret; } static uintptr_t WRAP_NAME(pthread_spin_destroy)(WRAP_PARAM4) { DumpEvent(ctx, LOCK_DESTROY, tid, pc, arg0, 0); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); return ret; } static uintptr_t WRAP_NAME(pthread_spin_unlock)(WRAP_PARAM4) { DumpEvent(ctx, UNLOCK_OR_INIT, tid, pc, arg0, 0); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); return ret; } static uintptr_t WRAP_NAME(pthread_rwlock_trywrlock)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret == 0) DumpEvent(ctx, WRITER_LOCK, tid, pc, arg0, 0); return ret; } static uintptr_t WRAP_NAME(pthread_rwlock_tryrdlock)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret == 0) DumpEvent(ctx, READER_LOCK, tid, pc, arg0, 0); return ret; } static void Before_pthread_mutex_init(THREADID tid, ADDRINT pc, ADDRINT mu) { DumpEvent(0, LOCK_CREATE, tid, pc, mu, 0); } static void Before_pthread_rwlock_init(THREADID tid, ADDRINT pc, ADDRINT mu) { DumpEvent(0, LOCK_CREATE, tid, pc, mu, 0); } static void Before_pthread_mutex_destroy(THREADID tid, ADDRINT pc, ADDRINT mu) { DumpEvent(0, LOCK_DESTROY, tid, pc, mu, 0); } static void Before_pthread_rwlock_destroy(THREADID tid, ADDRINT pc, ADDRINT mu) { DumpEvent(0, LOCK_DESTROY, tid, pc, mu, 0); } // barrier static uintptr_t WRAP_NAME(pthread_barrier_init)(WRAP_PARAM4) { DumpEvent(ctx, CYCLIC_BARRIER_INIT, tid, pc, arg0, arg2); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); return ret; } static uintptr_t WRAP_NAME(pthread_barrier_wait)(WRAP_PARAM4) { DumpEvent(ctx, CYCLIC_BARRIER_WAIT_BEFORE, tid, pc, arg0, 0); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); DumpEvent(ctx, CYCLIC_BARRIER_WAIT_AFTER, tid, pc, arg0, 0); return ret; } // condvar static void Before_pthread_cond_signal(THREADID tid, ADDRINT pc, ADDRINT cv) { DumpEvent(0, SIGNAL, tid, pc, cv, 0); } static uintptr_t WRAP_NAME(pthread_cond_wait)(WRAP_PARAM4) { DumpEvent(ctx, UNLOCK, tid, pc, arg1, 0); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); DumpEvent(ctx, WAIT, tid, pc, arg0, 0); DumpEvent(ctx, WRITER_LOCK, tid, pc, arg1, 0); return ret; } static uintptr_t WRAP_NAME(pthread_cond_timedwait)(WRAP_PARAM4) { DumpEvent(ctx, UNLOCK, tid, pc, arg1, 0); uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret == 0) { DumpEvent(ctx, WAIT, tid, pc, arg0, 0); } DumpEvent(ctx, WRITER_LOCK, tid, pc, arg1, 0); return ret; } // epoll static const uintptr_t kSocketMagic = 0xDEADFBAD; static void Before_epoll_ctl(THREADID tid, ADDRINT pc) { DumpEvent(0, SIGNAL, tid, pc, kSocketMagic, 0); } static void After_epoll_wait(THREADID tid, ADDRINT pc) { DumpEvent(0, WAIT, tid, pc, kSocketMagic, 0); } // sem static void After_sem_open(THREADID tid, ADDRINT pc, ADDRINT ret) { // TODO(kcc): need to handle it more precise? DumpEvent(0, SIGNAL, tid, pc, ret, 0); } static void Before_sem_post(THREADID tid, ADDRINT pc, ADDRINT sem) { DumpEvent(0, SIGNAL, tid, pc, sem, 0); } static uintptr_t WRAP_NAME(sem_wait)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); DumpEvent(ctx, WAIT, tid, pc, arg0, 0); return ret; } static uintptr_t WRAP_NAME(sem_trywait)(WRAP_PARAM4) { uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (ret == 0) { DumpEvent(ctx, WAIT, tid, pc, arg0, 0); } return ret; } // etc #if defined(__GNUC__) uintptr_t WRAP_NAME(lockf)(WRAP_PARAM4) { if (arg1 == F_ULOCK) { DumpEvent(0, SIGNAL, tid, pc, kSocketMagic, 0); } uintptr_t ret = CALL_ME_INSIDE_WRAPPER_4(); if (arg1 == F_LOCK && ret == 0) { DumpEvent(0, WAIT, tid, pc, kSocketMagic, 0); } return ret; } #endif //--------- Annotations -------------------------- {{{2 static void On_AnnotateBenignRace(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a, ADDRINT descr) { DumpEvent(0, BENIGN_RACE, tid, descr, a, 1); } static void On_AnnotateBenignRaceSized(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a, ADDRINT size, ADDRINT descr) { DumpEvent(0, BENIGN_RACE, tid, descr, a, size); } static void On_AnnotateExpectRace(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a, ADDRINT descr) { DumpEvent(0, EXPECT_RACE, tid, descr, a, 0); } static void On_AnnotateFlushExpectedRaces(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line) { DumpEvent(0, FLUSH_EXPECTED_RACES, 0, 0, 0, 0); } static void On_AnnotateTraceMemory(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a) { DumpEvent(0, TRACE_MEM, tid, pc, a, 0); } static void On_AnnotateNewMemory(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a, ADDRINT size) { DumpEvent(0, MALLOC, tid, pc, a, size); } static void On_AnnotateNoOp(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a) { Printf("%s T%d: %s:%d %p\n", __FUNCTION__, tid, (char*)file, (int)line, a); //DumpEvent(0, STACK_TRACE, tid, pc, 0, 0); // PrintShadowStack(tid); } static void On_AnnotateFlushState(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line) { DumpEvent(0, FLUSH_STATE, tid, pc, 0, 0); } static void On_AnnotateCondVarSignal(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT obj) { DumpEvent(0, SIGNAL, tid, pc, obj, 0); } static void On_AnnotateCondVarWait(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT obj) { DumpEvent(0, WAIT, tid, pc, obj, 0); } static void On_AnnotateHappensBefore(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT obj) { DumpEvent(0, SIGNAL, tid, pc, obj, 0); } static void On_AnnotateHappensAfter(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT obj) { DumpEvent(0, WAIT, tid, pc, obj, 0); } static void On_AnnotateEnableRaceDetection(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT enable) { if (!g_race_verifier_active) TLEBSimpleEvent(g_pin_threads[tid], enable ? TLEB_GLOBAL_IGNORE_OFF : TLEB_GLOBAL_IGNORE_ON); } static void On_AnnotateIgnoreReadsBegin(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line) { DumpEvent(0, IGNORE_READS_BEG, tid, pc, 0, 0); } static void On_AnnotateIgnoreReadsEnd(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line) { DumpEvent(0, IGNORE_READS_END, tid, pc, 0, 0); } static void On_AnnotateIgnoreWritesBegin(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line) { DumpEvent(0, IGNORE_WRITES_BEG, tid, pc, 0, 0); } static void On_AnnotateIgnoreWritesEnd(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line) { DumpEvent(0, IGNORE_WRITES_END, tid, pc, 0, 0); } static void On_AnnotateThreadName(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT name) { DumpEvent(0, SET_THREAD_NAME, tid, pc, name, 0); } static void On_AnnotatePublishMemoryRange(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a, ADDRINT size) { DumpEvent(0, PUBLISH_RANGE, tid, pc, a, size); } static void On_AnnotateUnpublishMemoryRange(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT a, ADDRINT size) { // Printf("T%d %s %lx %lx\n", tid, __FUNCTION__, a, size); DumpEvent(0, UNPUBLISH_RANGE, tid, pc, a, size); } static void On_AnnotateMutexIsUsedAsCondVar(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT mu) { DumpEvent(0, HB_LOCK, tid, pc, mu, 0); } static void On_AnnotateMutexIsNotPhb(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT mu) { DumpEvent(0, NON_HB_LOCK, tid, pc, mu, 0); } static void On_AnnotatePCQCreate(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT pcq) { DumpEvent(0, PCQ_CREATE, tid, pc, pcq, 0); } static void On_AnnotatePCQDestroy(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT pcq) { DumpEvent(0, PCQ_DESTROY, tid, pc, pcq, 0); } static void On_AnnotatePCQPut(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT pcq) { DumpEvent(0, PCQ_PUT, tid, pc, pcq, 0); } static void On_AnnotatePCQGet(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT pcq) { DumpEvent(0, PCQ_GET, tid, pc, pcq, 0); } static void On_AnnotateRWLockCreate(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT lock) { DumpEvent(0, LOCK_CREATE, tid, pc, lock, 0); } static void On_AnnotateRWLockDestroy(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT lock) { DumpEvent(0, LOCK_DESTROY, tid, pc, lock, 0); } static void On_AnnotateRWLockAcquired(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT lock, ADDRINT is_w) { DumpEvent(0, is_w ? WRITER_LOCK : READER_LOCK, tid, pc, lock, 0); } static void On_AnnotateRWLockReleased(THREADID tid, ADDRINT pc, ADDRINT file, ADDRINT line, ADDRINT lock, ADDRINT is_w) { DumpEvent(0, UNLOCK, tid, pc, lock, 0); } int WRAP_NAME(RunningOnValgrind)(WRAP_PARAM4) { return 1; } //--------- Instrumentation ----------------------- {{{1 static bool IgnoreImage(IMG img) { string name = IMG_Name(img); if (name.find("/ld-") != string::npos) return true; return false; } static bool IgnoreRtn(RTN rtn) { CHECK(rtn != RTN_Invalid()); ADDRINT rtn_address = RTN_Address(rtn); if (ThreadSanitizerWantToInstrumentSblock(rtn_address) == false) return true; return false; } static bool InstrumentCall(INS ins) { // Call. if (INS_IsProcedureCall(ins) && !INS_IsSyscall(ins)) { IGNORE_BELOW_RTN ignore_below = IGNORE_BELOW_RTN_UNKNOWN; if (INS_IsDirectBranchOrCall(ins)) { ADDRINT target = INS_DirectBranchOrCallTargetAddress(ins); bool ignore = ThreadSanitizerIgnoreAccessesBelowFunction(target); ignore_below = ignore ? IGNORE_BELOW_RTN_YES : IGNORE_BELOW_RTN_NO; } INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)InsertBeforeEvent_Call, IARG_THREAD_ID, IARG_INST_PTR, IARG_BRANCH_TARGET_ADDR, IARG_REG_VALUE, REG_STACK_PTR, IARG_ADDRINT, ignore_below, IARG_END); return true; } if (INS_IsSyscall(ins)) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)InsertBeforeEvent_SysCall, IARG_THREAD_ID, IARG_REG_VALUE, REG_STACK_PTR, IARG_END); } return false; } // return the number of inserted instrumentations. static void InstrumentMopsInBBl(BBL bbl, RTN rtn, TraceInfo *trace_info, uintptr_t instrument_pc, size_t *mop_idx) { // compute 'dtor_head', see // http://code.google.com/p/data-race-test/wiki/PopularDataRaces#Data_race_on_vptr // On x86_64 only the first BB of DTOR is treated as dtor_head. // On x86, we have to treat more BBs as dtor_head due to -fPIC. // See http://code.google.com/p/chromium/issues/detail?id=61199 bool dtor_head = false; #ifdef TARGET_IA32 size_t max_offset_for_dtor_head = 32; #else size_t max_offset_for_dtor_head = 0; #endif if (BBL_Address(bbl) - RTN_Address(rtn) <= max_offset_for_dtor_head) { string demangled_rtn_name = Demangle(RTN_Name(rtn).c_str()); if (demangled_rtn_name.find("::~") != string::npos) dtor_head = true; } INS tail = BBL_InsTail(bbl); // All memory reads/writes for( INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins) ) { if (ins != tail) { CHECK(!INS_IsRet(ins)); CHECK(!INS_IsProcedureCall(ins)); } // bool is_stack = INS_IsStackRead(ins) || INS_IsStackWrite(ins); if (INS_IsAtomicUpdate(ins)) continue; int n_mops = INS_MemoryOperandCount(ins); if (n_mops == 0) continue; string opcode_str = OPCODE_StringShort(INS_Opcode(ins)); if (trace_info && debug_ins) { Printf(" INS: opcode=%s n_mops=%d dis=\"%s\"\n", opcode_str.c_str(), n_mops, INS_Disassemble(ins).c_str()); } bool ins_ignore_writes = false; bool ins_ignore_reads = false; // CALL writes to stack and (if the call is indirect) reads the target // address. We don't want to handle the stack write. if (INS_IsCall(ins)) { CHECK(n_mops == 1 || n_mops == 2); ins_ignore_writes = true; } // PUSH: we ignore the write to stack but we don't ignore the read (if any). if (opcode_str == "PUSH") { CHECK(n_mops == 1 || n_mops == 2); ins_ignore_writes = true; } // POP: we are reading from stack, Ignore it. if (opcode_str == "POP") { CHECK(n_mops == 1 || n_mops == 2); ins_ignore_reads = true; continue; } // RET/LEAVE -- ignore it, it just reads the return address and stack. if (INS_IsRet(ins) || opcode_str == "LEAVE") { CHECK(n_mops == 1); continue; } bool is_predicated = INS_IsPredicated(ins); for (int i = 0; i < n_mops; i++) { if (*mop_idx >= kMaxMopsPerTrace) { Report("INFO: too many mops in trace: %d %s\n", INS_Address(ins), PcToRtnName(INS_Address(ins), true).c_str()); return; } size_t size = INS_MemoryOperandSize(ins, i); CHECK(size); bool is_write = INS_MemoryOperandIsWritten(ins, i); if (ins_ignore_writes && is_write) continue; if (ins_ignore_reads && !is_write) continue; if (instrument_pc && instrument_pc != INS_Address(ins)) continue; bool check_ident_store = false; if (dtor_head && is_write && INS_IsMov(ins) && size == sizeof(void*)) { // This is a special case for '*addr = value', where we want to ignore the // access if *addr == value before the store. CHECK(!is_predicated); check_ident_store = true; } if (trace_info) { if (debug_ins) { Printf(" size=%ld is_w=%d\n", size, (int)is_write); } IPOINT point = IPOINT_BEFORE; AFUNPTR on_mop_callback = (AFUNPTR)OnMop; if (check_ident_store) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)OnMopCheckIdentStoreBefore, IARG_REG_VALUE, tls_reg, IARG_THREAD_ID, IARG_ADDRINT, *mop_idx, IARG_MEMORYOP_EA, i, IARG_END); // This is just a MOV, so we can insert the instrumentation code // after the insn. point = IPOINT_AFTER; on_mop_callback = (AFUNPTR)OnMopCheckIdentStoreAfter; } MopInfo *mop = trace_info->GetMop(*mop_idx); new (mop) MopInfo(INS_Address(ins), size, is_write, false); if (is_predicated) { INS_InsertPredicatedCall(ins, point, (AFUNPTR)On_PredicatedMop, IARG_EXECUTING, IARG_REG_VALUE, tls_reg, IARG_THREAD_ID, IARG_ADDRINT, *mop_idx, IARG_MEMORYOP_EA, i, IARG_END); } else { INS_InsertCall(ins, point, on_mop_callback, IARG_REG_VALUE, tls_reg, IARG_THREAD_ID, IARG_ADDRINT, *mop_idx, IARG_MEMORYOP_EA, i, IARG_END); } } (*mop_idx)++; } } } void CallbackForTRACE(TRACE trace, void *v) { CHECK(n_started_threads > 0); RTN rtn = TRACE_Rtn(trace); bool ignore_memory = false; string img_name = "<>"; string rtn_name = "<>"; if (RTN_Valid(rtn)) { SEC sec = RTN_Sec(rtn); IMG img = SEC_Img(sec); rtn_name = RTN_Name(rtn); img_name = IMG_Name(img); if (IgnoreImage(img)) { // Printf("Ignoring memory accesses in %s\n", IMG_Name(img).c_str()); ignore_memory = true; } else if (IgnoreRtn(rtn)) { ignore_memory = true; } } uintptr_t instrument_pc = 0; if (g_race_verifier_active) { // Check if this trace looks like part of a possible race report. uintptr_t min_pc = UINTPTR_MAX; uintptr_t max_pc = 0; for(BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { min_pc = MIN(min_pc, INS_Address(BBL_InsHead(bbl))); max_pc = MAX(max_pc, INS_Address(BBL_InsTail(bbl))); } bool verify_trace = RaceVerifierGetAddresses(min_pc, max_pc, &instrument_pc); if (!verify_trace) ignore_memory = true; } size_t n_mops = 0; // count the mops. for(BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { if (!ignore_memory) { InstrumentMopsInBBl(bbl, rtn, NULL, instrument_pc, &n_mops); } INS tail = BBL_InsTail(bbl); if (INS_IsRet(tail)) { #if 0 INS_InsertIfCall(tail, IPOINT_BEFORE, (AFUNPTR)Before_RET_IF, IARG_THREAD_ID, IARG_END); INS_InsertThenCall( #else INS_InsertCall( #endif tail, IPOINT_BEFORE, (AFUNPTR)Before_RET_THEN, IARG_THREAD_ID, IARG_INST_PTR, IARG_REG_VALUE, REG_STACK_PTR, IARG_FUNCRET_EXITPOINT_VALUE, IARG_END); } } // Handle the head of the trace INS head = BBL_InsHead(TRACE_BblHead(trace)); CHECK(n_mops <= kMaxMopsPerTrace); TraceInfo *trace_info = NULL; if (n_mops) { trace_info = TraceInfo::NewTraceInfo(n_mops, INS_Address(head)); if (TS_SERIALIZED == 0) { // TODO(kcc): implement race verifier here. INS_InsertCall(head, IPOINT_BEFORE, (AFUNPTR)OnTraceParallel, IARG_REG_VALUE, tls_reg, IARG_REG_VALUE, REG_STACK_PTR, IARG_PTR, trace_info, IARG_END); } else { AFUNPTR handler = (AFUNPTR)(g_race_verifier_active ? OnTraceVerify : OnTraceSerial); INS_InsertCall(head, IPOINT_BEFORE, handler, IARG_THREAD_ID, IARG_REG_VALUE, REG_STACK_PTR, IARG_PTR, trace_info, IARG_REG_REFERENCE, tls_reg, IARG_END); } } else { if (g_race_verifier_active) { INS_InsertCall(head, IPOINT_BEFORE, (AFUNPTR)OnTraceNoMopsVerify, IARG_THREAD_ID, IARG_REG_VALUE, REG_STACK_PTR, IARG_REG_REFERENCE, tls_reg, IARG_END); } } // instrument the mops. We want to do it after we instrumented the head // to maintain the right order of instrumentation callbacks (head first, then // mops). size_t i = 0; if (n_mops) { if (debug_ins) { Printf("TRACE %p (%p); n_mops=%ld %s\n", trace_info, TRACE_Address(trace), trace_info->n_mops(), PcToRtnName(trace_info->pc(), false).c_str()); } for(BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { InstrumentMopsInBBl(bbl, rtn, trace_info, instrument_pc, &i); } } // instrument the calls, do it after all other instrumentation. if (!g_race_verifier_active) { for(BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { InstrumentCall(BBL_InsTail(bbl)); } } CHECK(n_mops == i); } #define INSERT_FN_HELPER(point, name, rtn, to_insert, ...) \ RTN_Open(rtn); \ if (G_flags->verbosity >= 2) Printf("RTN: Inserting %-50s (%s) %s (%s) img: %s\n", \ #to_insert, #point, RTN_Name(rtn).c_str(), name, IMG_Name(img).c_str());\ RTN_InsertCall(rtn, point, (AFUNPTR)to_insert, IARG_THREAD_ID, \ IARG_INST_PTR, __VA_ARGS__, IARG_END);\ RTN_Close(rtn); \ #define INSERT_FN(point, name, to_insert, ...) \ while (RtnMatchesName(rtn_name, name)) {\ INSERT_FN_HELPER(point, name, rtn, to_insert, __VA_ARGS__); \ break;\ }\ #define INSERT_BEFORE_FN(name, to_insert, ...) \ INSERT_FN(IPOINT_BEFORE, name, to_insert, __VA_ARGS__) #define INSERT_BEFORE_1_SP(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_REG_VALUE, REG_STACK_PTR, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0) #define INSERT_BEFORE_2_SP(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_REG_VALUE, REG_STACK_PTR, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 1) #define INSERT_BEFORE_0(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, IARG_END); #define INSERT_BEFORE_1(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0) #define INSERT_BEFORE_2(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 1) #define INSERT_BEFORE_3(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 1, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 2) #define INSERT_BEFORE_4(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 1, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 2, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 3) #define INSERT_BEFORE_5(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 1, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 2, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 3, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 4) #define INSERT_BEFORE_6(name, to_insert) \ INSERT_BEFORE_FN(name, to_insert, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 0, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 1, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 2, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 3, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 4, \ IARG_FUNCARG_ENTRYPOINT_VALUE, 5) #define INSERT_AFTER_FN(name, to_insert, ...) \ INSERT_FN(IPOINT_AFTER, name, to_insert, __VA_ARGS__) #define INSERT_AFTER_0(name, to_insert) \ INSERT_AFTER_FN(name, to_insert, IARG_END) #define INSERT_AFTER_1(name, to_insert) \ INSERT_AFTER_FN(name, to_insert, IARG_FUNCRET_EXITPOINT_VALUE) #ifdef _MSC_VER void WrapStdCallFunc1(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc2(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc3(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc4(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc5(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc6(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_FUNCARG_ENTRYPOINT_VALUE, 5, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc7(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_FUNCARG_ENTRYPOINT_VALUE, 5, IARG_FUNCARG_ENTRYPOINT_VALUE, 6, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc8(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_FUNCARG_ENTRYPOINT_VALUE, 5, IARG_FUNCARG_ENTRYPOINT_VALUE, 6, IARG_FUNCARG_ENTRYPOINT_VALUE, 7, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc10(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_FUNCARG_ENTRYPOINT_VALUE, 5, IARG_FUNCARG_ENTRYPOINT_VALUE, 6, IARG_FUNCARG_ENTRYPOINT_VALUE, 7, IARG_FUNCARG_ENTRYPOINT_VALUE, 8, IARG_FUNCARG_ENTRYPOINT_VALUE, 9, IARG_END); PROTO_Free(proto); } } void WrapStdCallFunc11(RTN rtn, char *name, AFUNPTR replacement_func) { if (RTN_Valid(rtn) && RtnMatchesName(RTN_Name(rtn), name)) { InformAboutFunctionWrap(rtn, name); PROTO proto = PROTO_Allocate(PIN_PARG(uintptr_t), CALLINGSTD_STDCALL, "proto", PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG(uintptr_t), PIN_PARG_END()); RTN_ReplaceSignature(rtn, AFUNPTR(replacement_func), IARG_PROTOTYPE, proto, IARG_THREAD_ID, IARG_INST_PTR, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 1, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_FUNCARG_ENTRYPOINT_VALUE, 3, IARG_FUNCARG_ENTRYPOINT_VALUE, 4, IARG_FUNCARG_ENTRYPOINT_VALUE, 5, IARG_FUNCARG_ENTRYPOINT_VALUE, 6, IARG_FUNCARG_ENTRYPOINT_VALUE, 7, IARG_FUNCARG_ENTRYPOINT_VALUE, 8, IARG_FUNCARG_ENTRYPOINT_VALUE, 9, IARG_FUNCARG_ENTRYPOINT_VALUE, 10, IARG_END); PROTO_Free(proto); } } #endif static void MaybeInstrumentOneRoutine(IMG img, RTN rtn) { if (IgnoreImage(img)) { return; } string rtn_name = RTN_Name(rtn); string img_name = IMG_Name(img); if (debug_wrap) { Printf("%s: %s %s pc=%p\n", __FUNCTION__, rtn_name.c_str(), img_name.c_str(), RTN_Address(rtn)); } // malloc/free/etc const char *malloc_names[] = { "malloc", #if defined(__GNUC__) "_Znwm", "_Znam", "_Znwj", "_Znaj", "_ZnwmRKSt9nothrow_t", "_ZnamRKSt9nothrow_t", "_ZnwjRKSt9nothrow_t", "_ZnajRKSt9nothrow_t", #endif #if defined(_MSC_VER) "operator new", "operator new[]", #endif // _MSC_VER }; const char *free_names[] = { "free", #if defined(__GNUC__) "_ZdaPv", "_ZdlPv", "_ZdlPvRKSt9nothrow_t", "_ZdaPvRKSt9nothrow_t", #endif // __GNUC__ #if defined(_MSC_VER) "operator delete", "operator delete[]", #endif // _MSC_VER }; for (size_t i = 0; i < TS_ARRAY_SIZE(malloc_names); i++) { const char *name = malloc_names[i]; INSERT_BEFORE_1_SP(name, Before_malloc); } for (size_t i = 0; i < TS_ARRAY_SIZE(free_names); i++) { const char *name = free_names[i]; INSERT_BEFORE_1_SP(name, Before_free); } INSERT_BEFORE_2_SP("calloc", Before_calloc); INSERT_BEFORE_2_SP("realloc", Before_realloc); #if defined(__GNUC__) WrapFunc6(img, rtn, "mmap", (AFUNPTR)WRAP_NAME(mmap)); WrapFunc4(img, rtn, "munmap", (AFUNPTR)WRAP_NAME(munmap)); WrapFunc4(img, rtn, "lockf", (AFUNPTR)WRAP_NAME(lockf)); // pthread create/join WrapFunc4(img, rtn, "pthread_create", (AFUNPTR)WRAP_NAME(pthread_create)); WrapFunc4(img, rtn, "pthread_join", (AFUNPTR)WRAP_NAME(pthread_join)); WrapFunc4(img, rtn, "fwrite", (AFUNPTR)WRAP_NAME(fwrite)); INSERT_FN(IPOINT_BEFORE, "start_thread", Before_start_thread, IARG_REG_VALUE, REG_STACK_PTR, IARG_END); // pthread_cond_* INSERT_BEFORE_1("pthread_cond_signal", Before_pthread_cond_signal); WRAP4(pthread_cond_wait); WRAP4(pthread_cond_timedwait); // pthread_mutex_* INSERT_BEFORE_1("pthread_mutex_init", Before_pthread_mutex_init); INSERT_BEFORE_1("pthread_mutex_destroy", Before_pthread_mutex_destroy); INSERT_BEFORE_1("pthread_mutex_unlock", Before_pthread_unlock); INSERT_BEFORE_1_SP("pthread_mutex_lock", Before_pthread_mutex_lock); WRAP4(pthread_mutex_trylock); WRAP4(pthread_spin_lock); WRAP4(pthread_spin_trylock); WRAP4(pthread_spin_init); WRAP4(pthread_spin_destroy); WRAP4(pthread_spin_unlock); WRAP4(pthread_rwlock_wrlock); WRAP4(pthread_rwlock_rdlock); WRAP4(pthread_rwlock_trywrlock); WRAP4(pthread_rwlock_tryrdlock); // pthread_rwlock_* INSERT_BEFORE_1("pthread_rwlock_init", Before_pthread_rwlock_init); INSERT_BEFORE_1("pthread_rwlock_destroy", Before_pthread_rwlock_destroy); INSERT_BEFORE_1("pthread_rwlock_unlock", Before_pthread_unlock); // pthread_barrier_* WrapFunc4(img, rtn, "pthread_barrier_init", (AFUNPTR)WRAP_NAME(pthread_barrier_init)); WrapFunc4(img, rtn, "pthread_barrier_wait", (AFUNPTR)WRAP_NAME(pthread_barrier_wait)); // pthread_once WrapFunc4(img, rtn, "pthread_once", (AFUNPTR)WRAP_NAME(pthread_once)); // sem_* INSERT_AFTER_1("sem_open", After_sem_open); INSERT_BEFORE_1("sem_post", Before_sem_post); WRAP4(sem_wait); WRAP4(sem_trywait); INSERT_BEFORE_0("epoll_ctl", Before_epoll_ctl); INSERT_AFTER_0("epoll_wait", After_epoll_wait); #endif // __GNUC__ #ifdef _MSC_VER WrapStdCallFunc6(rtn, "CreateThread", (AFUNPTR)WRAP_NAME(CreateThread)); WRAPSTD1(ResumeThread); INSERT_FN(IPOINT_BEFORE, "BaseThreadInitThunk", Before_BaseThreadInitThunk, IARG_REG_VALUE, REG_STACK_PTR, IARG_END); INSERT_BEFORE_0("RtlExitUserThread", Before_RtlExitUserThread); INSERT_BEFORE_0("ExitThread", Before_RtlExitUserThread); WRAPSTD1(RtlInitializeCriticalSection); WRAPSTD2(RtlInitializeCriticalSectionAndSpinCount); WRAPSTD3(RtlInitializeCriticalSectionEx); WRAPSTD1(RtlDeleteCriticalSection); WRAPSTD1(RtlEnterCriticalSection); WRAPSTD1(RtlTryEnterCriticalSection); WRAPSTD1(RtlLeaveCriticalSection); WRAPSTD7(DuplicateHandle); WRAPSTD1(SetEvent); WRAPSTD4(CreateSemaphoreA); WRAPSTD4(CreateSemaphoreW); WRAPSTD3(ReleaseSemaphore); WRAPSTD1(RtlInterlockedPopEntrySList); WRAPSTD2(RtlInterlockedPushEntrySList); #if 1 WRAPSTD1(RtlAcquireSRWLockExclusive); WRAPSTD1(RtlAcquireSRWLockShared); WRAPSTD1(RtlTryAcquireSRWLockExclusive); WRAPSTD1(RtlTryAcquireSRWLockShared); WRAPSTD1(RtlReleaseSRWLockExclusive); WRAPSTD1(RtlReleaseSRWLockShared); WRAPSTD1(RtlInitializeSRWLock); // For some reason, RtlInitializeSRWLock is aliased to RtlInitializeSRWLock.. WrapStdCallFunc1(rtn, "RtlRunOnceInitialize", (AFUNPTR)Wrap_RtlInitializeSRWLock); /* We haven't seen these syscalls used in the wild yet. WRAPSTD2(RtlUpdateClonedSRWLock); WRAPSTD1(RtlAcquireReleaseSRWLockExclusive); WRAPSTD1(RtlUpdateClonedCriticalSection); */ WRAPSTD1(RtlWakeConditionVariable); WRAPSTD1(RtlWakeAllConditionVariable); WRAPSTD4(RtlSleepConditionVariableSRW); WRAPSTD3(RtlSleepConditionVariableCS); #endif // if 1 WRAPSTD3(RtlQueueWorkItem); WRAPSTD6(RegisterWaitForSingleObject); WRAPSTD2(UnregisterWaitEx); WRAPSTD3(WaitForSingleObjectEx); WRAPSTD5(WaitForMultipleObjectsEx); WrapStdCallFunc4(rtn, "VirtualAlloc", (AFUNPTR)(WRAP_NAME(VirtualAlloc))); WrapStdCallFunc6(rtn, "ZwAllocateVirtualMemory", (AFUNPTR)(WRAP_NAME(ZwAllocateVirtualMemory))); WrapStdCallFunc2(rtn, "GlobalAlloc", (AFUNPTR)WRAP_NAME(GlobalAlloc)); // WrapStdCallFunc3(rtn, "RtlAllocateHeap", (AFUNPTR) WRAP_NAME(AllocateHeap)); // WrapStdCallFunc3(rtn, "HeapCreate", (AFUNPTR) WRAP_NAME(HeapCreate)); #endif // _MSC_VER // Annotations. INSERT_BEFORE_4("AnnotateBenignRace", On_AnnotateBenignRace); INSERT_BEFORE_5("AnnotateBenignRaceSized", On_AnnotateBenignRaceSized); INSERT_BEFORE_5("WTFAnnotateBenignRaceSized", On_AnnotateBenignRaceSized); INSERT_BEFORE_4("AnnotateExpectRace", On_AnnotateExpectRace); INSERT_BEFORE_2("AnnotateFlushExpectedRaces", On_AnnotateFlushExpectedRaces); INSERT_BEFORE_3("AnnotateTraceMemory", On_AnnotateTraceMemory); INSERT_BEFORE_4("AnnotateNewMemory", On_AnnotateNewMemory); INSERT_BEFORE_3("AnnotateNoOp", On_AnnotateNoOp); INSERT_BEFORE_2("AnnotateFlushState", On_AnnotateFlushState); INSERT_BEFORE_3("AnnotateCondVarWait", On_AnnotateCondVarWait); INSERT_BEFORE_3("AnnotateCondVarSignal", On_AnnotateCondVarSignal); INSERT_BEFORE_3("AnnotateCondVarSignalAll", On_AnnotateCondVarSignal); INSERT_BEFORE_3("AnnotateHappensBefore", On_AnnotateHappensBefore); INSERT_BEFORE_3("WTFAnnotateHappensBefore", On_AnnotateHappensBefore); INSERT_BEFORE_3("AnnotateHappensAfter", On_AnnotateHappensAfter); INSERT_BEFORE_3("WTFAnnotateHappensAfter", On_AnnotateHappensAfter); INSERT_BEFORE_3("AnnotateEnableRaceDetection", On_AnnotateEnableRaceDetection); INSERT_BEFORE_0("AnnotateIgnoreReadsBegin", On_AnnotateIgnoreReadsBegin); INSERT_BEFORE_0("AnnotateIgnoreReadsEnd", On_AnnotateIgnoreReadsEnd); INSERT_BEFORE_0("AnnotateIgnoreWritesBegin", On_AnnotateIgnoreWritesBegin); INSERT_BEFORE_0("AnnotateIgnoreWritesEnd", On_AnnotateIgnoreWritesEnd); INSERT_BEFORE_3("AnnotateThreadName", On_AnnotateThreadName); INSERT_BEFORE_4("AnnotatePublishMemoryRange", On_AnnotatePublishMemoryRange); INSERT_BEFORE_4("AnnotateUnpublishMemoryRange", On_AnnotateUnpublishMemoryRange); INSERT_BEFORE_3("AnnotateMutexIsUsedAsCondVar", On_AnnotateMutexIsUsedAsCondVar); INSERT_BEFORE_3("AnnotateMutexIsNotPHB", On_AnnotateMutexIsNotPhb); INSERT_BEFORE_3("AnnotatePCQCreate", On_AnnotatePCQCreate); INSERT_BEFORE_3("AnnotatePCQDestroy", On_AnnotatePCQDestroy); INSERT_BEFORE_3("AnnotatePCQPut", On_AnnotatePCQPut); INSERT_BEFORE_3("AnnotatePCQGet", On_AnnotatePCQGet); INSERT_BEFORE_3("AnnotateRWLockCreate", On_AnnotateRWLockCreate); INSERT_BEFORE_3("AnnotateRWLockDestroy", On_AnnotateRWLockDestroy); INSERT_BEFORE_4("AnnotateRWLockAcquired", On_AnnotateRWLockAcquired); INSERT_BEFORE_4("AnnotateRWLockReleased", On_AnnotateRWLockReleased); // ThreadSanitizerQuery WrapFunc4(img, rtn, "ThreadSanitizerQuery", (AFUNPTR)WRAP_NAME(ThreadSanitizerQuery)); WrapFunc4(img, rtn, "RunningOnValgrind", (AFUNPTR)WRAP_NAME(RunningOnValgrind)); // I/O INSERT_BEFORE_0("write", Before_SignallingIOCall); INSERT_BEFORE_0("unlink", Before_SignallingIOCall); INSERT_BEFORE_0("rmdir", Before_SignallingIOCall); // INSERT_BEFORE_0("send", Before_SignallingIOCall); INSERT_AFTER_0("__read_nocancel", After_WaitingIOCall); INSERT_AFTER_0("fopen", After_WaitingIOCall); INSERT_AFTER_0("__fopen_internal", After_WaitingIOCall); INSERT_AFTER_0("open", After_WaitingIOCall); INSERT_AFTER_0("opendir", After_WaitingIOCall); // INSERT_AFTER_0("recv", After_WaitingIOCall); // strlen and friends. // These wrappers will generate memory access events. // So, if we don't want to get those events (e.g. memcpy inside // ld.so or ntdll.dll) we don't wrap them and the regular // ignore machinery will make sure we don't get the events. if (ThreadSanitizerWantToInstrumentSblock(RTN_Address(rtn))) { ReplaceFunc3(img, rtn, "memchr", (AFUNPTR)Replace_memchr); ReplaceFunc3(img, rtn, "strchr", (AFUNPTR)Replace_strchr); ReplaceFunc3(img, rtn, "index", (AFUNPTR)Replace_strchr); ReplaceFunc3(img, rtn, "strchrnul", (AFUNPTR)Replace_strchrnul); 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, "strncmp", (AFUNPTR)Replace_strncmp); ReplaceFunc3(img, rtn, "memcpy", (AFUNPTR)Replace_memcpy); ReplaceFunc3(img, rtn, "memcmp", (AFUNPTR)Replace_memcmp); ReplaceFunc3(img, rtn, "memmove", (AFUNPTR)Replace_memmove); ReplaceFunc3(img, rtn, "strcpy", (AFUNPTR)Replace_strcpy); ReplaceFunc3(img, rtn, "strncpy", (AFUNPTR)Replace_strncpy); ReplaceFunc3(img, rtn, "strcat", (AFUNPTR)Replace_strcat); ReplaceFunc3(img, rtn, "stpcpy", (AFUNPTR)Replace_stpcpy); } // __cxa_guard_acquire / __cxa_guard_release INSERT_BEFORE_1("__cxa_guard_acquire", Before_cxa_guard_acquire); INSERT_AFTER_1("__cxa_guard_acquire", After_cxa_guard_acquire); INSERT_AFTER_0("__cxa_guard_release", After_cxa_guard_release); INSERT_BEFORE_0("atexit", On_atexit); INSERT_BEFORE_0("exit", On_exit); } // Pin calls this function every time a new img is loaded. static void CallbackForIMG(IMG img, void *v) { if (debug_wrap) { Printf("Started CallbackForIMG %s\n", IMG_Name(img).c_str()); } string img_name = IMG_Name(img); for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec)) { for (RTN rtn = SEC_RtnHead(sec); RTN_Valid(rtn); rtn = RTN_Next(rtn)) { MaybeInstrumentOneRoutine(img, rtn); } } // In DEBUG_MODE check that we have the debug symbols in the Windows guts. // We should work w/o them too. // TODO(timurrrr): I doubt the problem is the missing symbols. // I have a strong gut feeling that this syscall was added // in Vista but only used since Windows 7. We had its wrapper wrong // (found on W7) but the Vista build was fine for months. // Also, we wrap RtlReleaseSRWLock*, so our TSan assertions would have been // broken if RtlTryAcquireSRWLock* wasn't wrapped - and we haven't see this. if (DEBUG_MODE && img_name.find("ntdll.dll") != string::npos) { if (g_wrapped_functions.count("RtlTryAcquireSRWLockExclusive") == 0) { Printf("WARNING: Debug symbols for ntdll.dll not found.\n"); } } } // Returns: // TRUE // If user is interested to inject Pin (and tool) into child/exec-ed process // FALSE // If user is not interested to inject Pin (and tool) into child/exec-ed process static BOOL CallbackForExec(CHILD_PROCESS childProcess, VOID *val) { int argc = 0; const CHAR *const * argv = NULL; CHILD_PROCESS_GetCommandLine(childProcess, &argc, &argv); CHECK(argc > 0); CHECK(argv); bool follow = G_flags->trace_children; if (DEBUG_MODE) { Printf("CallbackForExec: follow=%d: ", follow); for (int i = 0; i < argc; i++) { Printf("%s ", argv[i]); } } Printf("\n"); return follow; } //--------- Fini ---------- {{{1 static void CallbackForFini(INT32 code, void *v) { DumpEvent(0, THR_END, 0, 0, 0, 0); ThreadSanitizerFini(); if (g_race_verifier_active) { RaceVerifierFini(); } if (G_flags->show_stats) { TraceInfo::PrintTraceProfile(); } if (G_flags->error_exitcode && GetNumberOfFoundErrors() > 0) { exit(G_flags->error_exitcode); } } //--------- Call Coverage ----------------- {{{1 // A simplistic call coverage tool. // Outputs all pairs <call_site,call_target>. typedef set<pair<uintptr_t, uintptr_t> > CallCoverageSet; static CallCoverageSet *call_coverage_set; static map<uintptr_t, string> *function_names_map; static uintptr_t symbolized_functions_cache[1023]; static pair<uintptr_t, uintptr_t> registered_pairs_cache[1023]; static void symbolize_pc(uintptr_t pc) { // Check a simple cache if we already symbolized this pc (racey). size_t idx = pc % TS_ARRAY_SIZE(symbolized_functions_cache); if (symbolized_functions_cache[idx] == pc) return; ScopedReentrantClientLock lock(__LINE__); CHECK(function_names_map); if (function_names_map->count(pc) == 0) { (*function_names_map)[pc] = PcToRtnName(pc, false); } symbolized_functions_cache[idx] = pc; } static void CallCoverageRegisterCall(uintptr_t from, uintptr_t to) { symbolize_pc(from); symbolize_pc(to); // Check if we already registered this pair (racey). size_t idx = (from ^ to) % TS_ARRAY_SIZE(registered_pairs_cache); if (registered_pairs_cache[idx] == make_pair(from,to)) return; ScopedReentrantClientLock lock(__LINE__); call_coverage_set->insert(make_pair(from, to)); registered_pairs_cache[idx] = make_pair(from,to); } static void CallCoverageCallbackForTRACE(TRACE trace, void *v) { RTN rtn = TRACE_Rtn(trace); if (RTN_Valid(rtn)) { SEC sec = RTN_Sec(rtn); IMG img = SEC_Img(sec); string img_name = IMG_Name(img); // Don't instrument system libraries. if (img_name.find("/usr/") == 0) return; } if (call_coverage_set == NULL) { call_coverage_set = new CallCoverageSet; function_names_map = new map<uintptr_t, string>; } for(BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { INS ins = BBL_InsTail(bbl); if (!INS_IsProcedureCall(ins) || INS_IsSyscall(ins)) continue; if (INS_IsDirectBranchOrCall(ins)) { // If <from, to> is know at instrumentation time, don't instrument. ADDRINT to = INS_DirectBranchOrCallTargetAddress(ins); ADDRINT from = INS_Address(ins); CallCoverageRegisterCall(from, to); } else { // target is dynamic. Need to instrument. INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)CallCoverageRegisterCall, IARG_INST_PTR, IARG_BRANCH_TARGET_ADDR, IARG_END); } } } // Output all <from,to> pairs. static void CallCoverageCallbackForFini(INT32 code, void *v) { CHECK(call_coverage_set); CHECK(function_names_map); for (CallCoverageSet::iterator it = call_coverage_set->begin(); it != call_coverage_set->end(); ++it) { string from_name = (*function_names_map)[it->first]; string to_name = (*function_names_map)[it->second]; if (to_name == ".plt" || to_name == "") continue; Printf("CallCoverage: %s => %s\n", from_name.c_str(), to_name.c_str()); } } //--------- Main -------------------------- {{{1 int main(INT32 argc, CHAR **argv) { PIN_Init(argc, argv); PIN_InitSymbols(); G_out = stderr; // Init ThreadSanitizer. int first_param = 1; // skip until '-t something.so'. for (; first_param < argc && argv[first_param] != string("-t"); first_param++) { } first_param += 2; vector<string> args; for (; first_param < argc; first_param++) { string param = argv[first_param]; if (param == "--") break; if (param == "-short_name") continue; if (param == "-slow_asserts") continue; if (param == "1") continue; args.push_back(param); } G_flags = new FLAGS; ThreadSanitizerParseFlags(&args); if (G_flags->dry_run >= 2) { PIN_StartProgram(); return 0; } FILE *socket_output = OpenSocketForWriting(G_flags->log_file); if (socket_output) { G_out = socket_output; } else if (!G_flags->log_file.empty()) { // Replace %p with tool PID string fname = G_flags->log_file; char pid_str[100] = ""; sprintf(pid_str, "%u", getpid()); while (fname.find("%p") != fname.npos) fname.replace(fname.find("%p"), 2, pid_str); G_out = fopen(fname.c_str(), "w"); CHECK(G_out); } ThreadSanitizerInit(); if (G_flags->call_coverage) { PIN_AddFiniFunction(CallCoverageCallbackForFini, 0); TRACE_AddInstrumentFunction(CallCoverageCallbackForTRACE, 0); PIN_StartProgram(); return 0; } tls_reg = PIN_ClaimToolRegister(); CHECK(REG_valid(tls_reg)); #if _MSC_VER g_windows_thread_pool_calback_set = new unordered_set<uintptr_t>; g_windows_thread_pool_wait_object_map = new unordered_map<uintptr_t, uintptr_t>; #endif // Set up PIN callbacks. PIN_AddThreadStartFunction(CallbackForThreadStart, 0); PIN_AddThreadFiniFunction(CallbackForThreadFini, 0); PIN_AddFiniFunction(CallbackForFini, 0); IMG_AddInstrumentFunction(CallbackForIMG, 0); TRACE_AddInstrumentFunction(CallbackForTRACE, 0); PIN_AddFollowChildProcessFunction(CallbackForExec, NULL); Report("ThreadSanitizerPin r%s pin %d: %s\n", TS_VERSION, PIN_BUILD_NUMBER, G_flags->pure_happens_before ? "hybrid=no" : "hybrid=yes"); if (DEBUG_MODE) { Report("INFO: Debug build\n"); } if (g_race_verifier_active) { RaceVerifierInit(G_flags->race_verifier, G_flags->race_verifier_extra); global_ignore = true; } // Fire! PIN_StartProgram(); return 0; } //--------- Questions about PIN -------------------------- {{{1 /* Questions about PIN: - Names (e.g. pthread_create@... __pthread_mutex_unlock) - How to get name of a global var by it's address? - How to get stack pointer at thread creation? - How to get a stack trace (other than intercepting calls, entries, exits) - assert with full stack trace? */ // end. {{{1 // vim:shiftwidth=2:softtabstop=2:expandtab