/* 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