普通文本  |  1435行  |  44.72 KB

/*
  This file is part of ThreadSanitizer, a dynamic data race detector
  based on Valgrind.

  Copyright (C) 2008-2010 Google Inc
     opensource@google.com
  Copyright (C) 2007-2008 OpenWorks LLP
      info@open-works.co.uk

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 2 of the
  License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  02111-1307, USA.

  The GNU General Public License is contained in the file COPYING.
*/

// Author: Konstantin Serebryany.
// Parts of the code in this file are derived from Helgrind,
// a data race detector written by Julian Seward.
// Note that the rest of ThreadSanitizer code is not derived from Helgrind
// and is published under the BSD license.

#include "ts_valgrind.h"
#include "valgrind.h"
#include "ts_valgrind_client_requests.h"
#include "thread_sanitizer.h"
#include "ts_trace_info.h"
#include "ts_race_verifier.h"
#include "common_util.h"

#include "coregrind/pub_core_basics.h"
#include "coregrind/pub_core_machine.h"
#include "coregrind/pub_core_clreq.h"
#include "pub_tool_libcsetjmp.h"
#include "coregrind/pub_core_threadstate.h"
#include "pub_tool_libcproc.h"


//---------------------- C++ malloc support -------------- {{{1
void *operator new (size_t size) {
  return VG_(malloc)((HChar*)g_malloc_stack.Top(), size);
}
void *operator new [](size_t size) {
  return VG_(malloc)((HChar*)g_malloc_stack.Top(), size);
}
void operator delete (void *p) {
  VG_(free)(p);
}
void operator delete [](void *p) {
  VG_(free)(p);
}

extern "C" void *malloc(size_t size) {
  return VG_(malloc)((HChar*)g_malloc_stack.Top(), size);
}

extern "C" void free(void *ptr) {
  VG_(free)(ptr);
}

extern "C" void* realloc(void *ptr, size_t size) {
  return VG_(realloc)((HChar*)g_malloc_stack.Top(), ptr, size);
}


//---------------------- Utils ------------------- {{{1

extern "C" int puts(const char *s) {
  Printf("%s", s);
  return 1;
}

extern "C" void exit(int e) { VG_(exit)(e); }

#ifdef VGO_darwin
extern "C" void abort() { CHECK(0); }
#endif


// TODO: make this rtn public
extern "C" {
  Bool VG_(get_fnname_no_cxx_demangle) ( Addr a, Char* buf, Int nbuf );
}


const int kBuffSize = 1024 * 10 - 1;
// not thread-safe.
static char g_buff1[kBuffSize+1];
static char g_buff2[kBuffSize+1];

string PcToRtnName(uintptr_t pc, bool demangle) {
  if (demangle) {
    if(VG_(get_fnname)(pc, (Char*)g_buff1, kBuffSize)) {
      return g_buff1;
    }
  } else {
    if(VG_(get_fnname_no_cxx_demangle)(pc, (Char*)g_buff1, kBuffSize)) {
      return g_buff1;
    }
  }
  return "(no symbols)";
}

void PcToStrings(uintptr_t pc, bool demangle,
                string *img_name, string *rtn_name,
                string *file_name, int *line_no) {
  const int kBuffSize = 1024 * 10 - 1;
  Bool has_dirname = False;

  if (VG_(get_filename_linenum)
      (pc, (Char*)g_buff1, kBuffSize, (Char*)g_buff2, kBuffSize,
       &has_dirname, (UInt*)line_no) &&
      has_dirname) {
    *file_name = string(g_buff2) + "/" + g_buff1;
  } else {
    VG_(get_linenum)(pc, (UInt *)line_no);
    if (VG_(get_filename)(pc, (Char*)g_buff1, kBuffSize)) {
      *file_name = g_buff1;
    }
  }
  *file_name = ConvertToPlatformIndependentPath(*file_name);

  *rtn_name = PcToRtnName(pc, demangle);

  if (VG_(get_objname)(pc, (Char*)g_buff1, kBuffSize)) {
    *img_name = g_buff1;
  }
}



string Demangle(const char *str) {
  return str;
}

extern "C"
size_t strlen(const char *s) {
  return VG_(strlen)((const Char*)s);
}

static inline ThreadId GetVgTid() {
  extern ThreadId VG_(running_tid); // HACK: avoid calling get_running_tid()
  ThreadId res = VG_(running_tid);
  //DCHECK(res == VG_(get_running_tid)());
  return res;
}

static inline uintptr_t GetVgPc(ThreadId vg_tid) {
  Addr pc = VG_(threads)[vg_tid].arch.vex.VG_INSTR_PTR;
  DCHECK(pc == VG_(get_IP)(vg_tid));
  return pc;
  //return (uintptr_t)VG_(get_IP)(vg_tid);
}

static inline uintptr_t GetVgSp(ThreadId vg_tid) {
  Addr sp = VG_(threads)[vg_tid].arch.vex.VG_STACK_PTR;
  DCHECK(sp == VG_(get_SP)(vg_tid));
  return sp;
}

#ifdef VGP_arm_linux
static inline uintptr_t GetVgLr(ThreadId vg_tid) {
  return (uintptr_t)VG_(threads)[vg_tid].arch.vex.guest_R14;
}
#endif

static uintptr_t g_current_pc;

uintptr_t GetPcOfCurrentThread() {
  return g_current_pc;
}

void GetThreadStack(int tid, uintptr_t *min_addr, uintptr_t *max_addr) {
  // tid is not used because we call it from the current thread anyway.
  uintptr_t stack_max  = VG_(thread_get_stack_max)(GetVgTid());
  uintptr_t stack_size = VG_(thread_get_stack_size)(GetVgTid());
  uintptr_t stack_min  = stack_max - stack_size;
  *min_addr = stack_min;
  *max_addr = stack_max;
}

struct CallStackRecord {
  Addr pc;
  Addr sp;
#ifdef VGP_arm_linux
  // We need to store LR in order to keep the shadow stack consistent.
  Addr lr;
#endif
};

const size_t kMaxMopsPerTrace = 2048;

struct ValgrindThread {
  int32_t zero_based_uniq_tid;
  TSanThread *ts_thread;
  uint32_t literace_sampling;
  vector<CallStackRecord> call_stack;

  int ignore_accesses;
  int ignore_sync;
  int in_signal_handler;

  // thread-local event buffer (tleb).
  uintptr_t tleb[kMaxMopsPerTrace];
  TraceInfo *trace_info;

  // PC (as in trace_info->pc()) of the trace currently being verified.
  // 0 if outside of the verification sleep loop.
  // -1 in the last iteration of the loop.
  uintptr_t verifier_current_pc;

  // End time of the current verification loop.
  unsigned verifier_wakeup_time_ms;

  ValgrindThread() {
    Clear();
  }

  void Clear() {
    ts_thread = NULL;
    zero_based_uniq_tid = -1;
    literace_sampling = G_flags->literace_sampling;  // cache it.
    ignore_accesses = 0;
    ignore_sync = 0;
    in_signal_handler = 0;
    call_stack.clear();
    trace_info = NULL;
    verifier_current_pc = 0;
    verifier_wakeup_time_ms = 0;
  }
};

// If true, ignore all accesses in all threads.
extern bool global_ignore;

// Array of VG_N_THREADS
static ValgrindThread *g_valgrind_threads = 0;
static map<uintptr_t, int> *g_ptid_to_ts_tid;

// maintains a uniq thread id (first thread will have id=0)
static int32_t g_uniq_thread_id_counter = 0;

static int32_t VgTidToTsTid(ThreadId vg_tid) {
  DCHECK(vg_tid < VG_N_THREADS);
  DCHECK(vg_tid >= 1);
  DCHECK(g_valgrind_threads);
  DCHECK(g_valgrind_threads[vg_tid].zero_based_uniq_tid >= 0);
  return g_valgrind_threads[vg_tid].zero_based_uniq_tid;
}

static vector<string> *g_command_line_options = 0;
static void InitCommandLineOptions() {
  if(G_flags == NULL) {
    G_flags = new FLAGS;
  }
  if (g_command_line_options == NULL) {
    g_command_line_options = new vector<string>;
  }
}

Bool ts_process_cmd_line_option (Char* arg) {
  InitCommandLineOptions();
  g_command_line_options->push_back((char*)arg);
  return True;
}

void ts_print_usage (void) {
  InitCommandLineOptions();
  ThreadSanitizerParseFlags(g_command_line_options);

  ThreadSanitizerPrintUsage();
}

void ts_print_debug_usage(void) {
  ThreadSanitizerPrintUsage();
}

extern int VG_(clo_error_exitcode);

void ts_post_clo_init(void) {
  ScopedMallocCostCenter malloc_cc(__FUNCTION__);
  InitCommandLineOptions();
  ThreadSanitizerParseFlags(g_command_line_options);

  // we get num-callers from valgrind flags.
  G_flags->num_callers = VG_(clo_backtrace_size);
  if (!G_flags->error_exitcode)
    G_flags->error_exitcode = VG_(clo_error_exitcode);

  extern Int   VG_(clo_n_suppressions);
  extern Int   VG_(clo_gen_suppressions);
  extern Char* VG_(clo_suppressions)[];
  extern Int   VG_(clo_n_fullpath_after);
  extern Char* VG_(clo_fullpath_after)[];
  // get the suppressions from Valgrind
  for (int i = 0; i < VG_(clo_n_suppressions); i++) {
    G_flags->suppressions.push_back((char*)VG_(clo_suppressions)[i]);
  }
  // get the --fullpath-after prefixes from Valgrind and treat them as
  // --file-prefix-to-cut arguments.
  for (int i = 0; i < VG_(clo_n_fullpath_after); i++) {
    G_flags->file_prefix_to_cut.push_back((char*)VG_(clo_fullpath_after)[i]);
  }
  G_flags->generate_suppressions |= VG_(clo_gen_suppressions) >= 1;

  if (G_flags->html) {
    Report("<pre>\n"
           "<br id=race0>"
           "<a href=\"#race1\">Go to first race report</a>\n");
  }
  Report("ThreadSanitizerValgrind r%s: %s\n",
         TS_VERSION,
         G_flags->pure_happens_before ? "hybrid=no" : "hybrid=yes");
  if (DEBUG_MODE) {
    Report("INFO: Debug build\n");
  }
  if (G_flags->max_mem_in_mb) {
    Report("INFO: ThreadSanitizer memory limit: %dMB\n",
           (int)G_flags->max_mem_in_mb);
  }
  ThreadSanitizerInit();

  g_valgrind_threads = new ValgrindThread[VG_N_THREADS];
  g_ptid_to_ts_tid = new map<uintptr_t, int>;

  if (g_race_verifier_active) {
    RaceVerifierInit(G_flags->race_verifier, G_flags->race_verifier_extra);
    global_ignore = true;
  }
}

// Remember, valgrind is essentially single-threaded.
// Each time we switch to another thread, we set the global g_cur_tleb
// to the tleb of the current thread. This allows to load the tleb in one
// instruction.
static uintptr_t *g_cur_tleb;
static void OnStartClientCode(ThreadId vg_tid, ULong nDisp) {
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];
  g_cur_tleb = thr->tleb;
}

INLINE void FlushMops(ValgrindThread *thr, bool keep_trace_info = false) {
  DCHECK(!g_race_verifier_active || global_ignore);
  TraceInfo *t = thr->trace_info;
  if (!t) return;
  if (!keep_trace_info) {
    thr->trace_info = NULL;
  }

  if (global_ignore || thr->ignore_accesses ||
       (thr->literace_sampling &&
        t->LiteRaceSkipTraceRealTid(thr->zero_based_uniq_tid, thr->literace_sampling))) {
    thr->trace_info = NULL;
    return;
  }

  size_t n = t->n_mops();
  DCHECK(n > 0);
  uintptr_t *tleb = thr->tleb;
  DCHECK(thr->ts_thread);
  ThreadSanitizerHandleTrace(thr->ts_thread, t, tleb);
}

static void ShowCallStack(ValgrindThread *thr) {
  size_t n = thr->call_stack.size();
  Printf("        ");
  for (size_t i = n - 1; i > n - 10 && i >= 0; i--) {
    Printf("{pc=%p sp=%p}, ", thr->call_stack[i].pc, thr->call_stack[i].sp);
  }
  Printf("\n");
}

static INLINE void UpdateCallStack(ValgrindThread *thr, uintptr_t sp) {
  DCHECK(!g_race_verifier_active);
  if (thr->trace_info) FlushMops(thr, true /* keep_trace_info */);
  vector<CallStackRecord> &call_stack = thr->call_stack;
  while (!call_stack.empty()) {
    CallStackRecord &record = call_stack.back();
    Addr cur_top = record.sp;
    if (sp < cur_top) break;
    call_stack.pop_back();
    int32_t ts_tid = thr->zero_based_uniq_tid;
    ThreadSanitizerHandleRtnExit(ts_tid);
    if (debug_rtn) {
      Printf("T%d: [%ld]<< pc=%p sp=%p cur_sp=%p %s\n",
             ts_tid, thr->call_stack.size(), record.pc,
             record.sp, sp,
             PcToRtnNameAndFilePos(record.pc).c_str());
      ShowCallStack(thr);
    }
  }
}

VG_REGPARM(1)
static void OnTrace(TraceInfo *trace_info) {
  DCHECK(!g_race_verifier_active);
  //trace_info->counter()++;
  if (global_ignore) return;
  ThreadId vg_tid = GetVgTid();
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];

  // First, flush the old trace_info.
  if (thr->trace_info) {
    FlushMops(thr);
  }

  UpdateCallStack(thr, GetVgSp(vg_tid));

  // Start the new trace, zero the contents of tleb.
  size_t n = trace_info->n_mops();
  uintptr_t *tleb = thr->tleb;
  for (size_t i = 0; i < n; i++)
    tleb[i] = 0;
  thr->trace_info = trace_info;
  DCHECK(thr->trace_info);
  DCHECK(thr->trace_info->n_mops() <= kMaxMopsPerTrace);
}

static inline void Put(EventType type, int32_t tid, uintptr_t pc,
                       uintptr_t a, uintptr_t info) {
  if (DEBUG_MODE && G_flags->dry_run >= 1) return;
  Event event(type, tid, pc, a, info);
  ThreadSanitizerHandleOneEvent(&event);
}

static void rtn_call(Addr sp_post_call_insn, Addr pc_post_call_insn,
                     IGNORE_BELOW_RTN ignore_below) {
  DCHECK(!g_race_verifier_active);
  if (global_ignore) return;
  ThreadId vg_tid = GetVgTid();
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];
  int ts_tid = thr->zero_based_uniq_tid;
  CallStackRecord record;
  record.pc = pc_post_call_insn;
  record.sp = sp_post_call_insn + 4;  // sp before call.
  UpdateCallStack(thr, record.sp);
#ifdef VGP_arm_linux
  record.lr = GetVgLr(vg_tid);
#endif
  thr->call_stack.push_back(record);
  // If the shadow stack grows too high this usually means it is not cleaned
  // properly. Or this may be a very deep recursion.
  DCHECK(thr->call_stack.size() < 10000);
  uintptr_t call_pc = GetVgPc(vg_tid);
  if (thr->trace_info) FlushMops(thr);
  ThreadSanitizerHandleRtnCall(ts_tid, call_pc, record.pc,
                               ignore_below);

  if (debug_rtn) {
    Printf("T%d: [%ld]>> pc=%p sp=%p %s\n",
           ts_tid, thr->call_stack.size(), (void*)record.pc,
           (void*)record.sp,
           PcToRtnNameAndFilePos(record.pc).c_str());
    ShowCallStack(thr);
  }
}

VG_REGPARM(2) void evh__rtn_call_ignore_unknown ( Addr sp, Addr pc) {
  rtn_call(sp, pc, IGNORE_BELOW_RTN_UNKNOWN);
}
VG_REGPARM(2) void evh__rtn_call_ignore_yes ( Addr sp, Addr pc) {
  rtn_call(sp, pc, IGNORE_BELOW_RTN_YES);
}
VG_REGPARM(2) void evh__rtn_call_ignore_no ( Addr sp, Addr pc) {
  rtn_call(sp, pc, IGNORE_BELOW_RTN_NO);
}

#ifdef VGP_arm_linux
// Handle shadow stack frame deletion on ARM.
// Instrumented code calls this function for each non-call jump out of
// a superblock. If the |sp_post_call_insn| (the jump target address) is equal
// to a link register value of one or more frames on top of the shadow stack,
// those frames are popped out.
// TODO(glider): there may be problems with optimized recursive functions that
// don't change PC, SP and LR.
VG_REGPARM(2)
void evh__delete_frame ( Addr sp_post_call_insn,
                         Addr pc_post_call_insn) {
  DCHECK(!g_race_verifier_active);
  ThreadId vg_tid = GetVgTid();
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];
  if (thr->trace_info) FlushMops(thr);
  vector<CallStackRecord> &call_stack = thr->call_stack;
  int32_t ts_tid = VgTidToTsTid(vg_tid);
  while (!call_stack.empty()) {
    CallStackRecord &record = call_stack.back();
    if (record.lr != pc_post_call_insn) break;
    call_stack.pop_back();
    ThreadSanitizerHandleRtnExit(ts_tid);
  }
}
#endif

void ts_fini(Int exitcode) {
  ThreadSanitizerFini();
  if (g_race_verifier_active) {
    RaceVerifierFini();
  }
  if (G_flags->error_exitcode && GetNumberOfFoundErrors() > 0) {
    exit(G_flags->error_exitcode);
  }
}


void evh__pre_thread_ll_create ( ThreadId parent, ThreadId child ) {
  tl_assert(parent != child);
  ValgrindThread *thr = &g_valgrind_threads[child];
  //  Printf("thread_create: %d->%d\n", parent, child);
  if (thr->zero_based_uniq_tid != -1) {
    Printf("ThreadSanitizer WARNING: reusing TID %d w/o exiting thread\n",
           child);
  }
  thr->Clear();
  thr->zero_based_uniq_tid = g_uniq_thread_id_counter++;
  // Printf("VG: T%d: VG_THR_START: parent=%d\n", VgTidToTsTid(child), VgTidToTsTid(parent));
  Put(THR_START, VgTidToTsTid(child), 0, 0,
      parent > 0 ? VgTidToTsTid(parent) : 0);
  thr->ts_thread = ThreadSanitizerGetThreadByTid(thr->zero_based_uniq_tid);
  CHECK(thr->ts_thread);
}

void evh__pre_workq_task_start(ThreadId vg_tid, Addr workitem) {
  uintptr_t pc = GetVgPc(vg_tid);
  int32_t ts_tid = VgTidToTsTid(vg_tid);
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];
  FlushMops(thr);
  Put(WAIT, ts_tid, pc, workitem, 0);
}

void evh__pre_thread_first_insn(const ThreadId vg_tid) {
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];
  FlushMops(thr);
  Put(THR_FIRST_INSN, VgTidToTsTid(vg_tid), GetVgPc(vg_tid), 0, 0);
}


void evh__pre_thread_ll_exit ( ThreadId quit_tid ) {
//  Printf("thread_exit: %d\n", quit_tid);
//  Printf("T%d quiting thread; stack size=%ld\n",
//         VgTidToTsTid(quit_tid),
//         (int)g_valgrind_threads[quit_tid].call_stack.size());
  ValgrindThread *thr = &g_valgrind_threads[quit_tid];
  FlushMops(thr);
  Put(THR_END, VgTidToTsTid(quit_tid), 0, 0, 0);
  g_valgrind_threads[quit_tid].zero_based_uniq_tid = -1;
}

  extern "C" void VG_(show_all_errors)();

// Whether we are currently ignoring sync events for the given thread at the
// given address.
static inline Bool ignoring_sync(ThreadId vg_tid, uintptr_t addr) {
  // We ignore locking events if ignore_sync != 0 and if we are not
  // inside a signal handler.
  return (g_valgrind_threads[vg_tid].ignore_sync &&
          !g_valgrind_threads[vg_tid].in_signal_handler) ||
      ThreadSanitizerIgnoreForNacl(addr);
}

Bool ts_handle_client_request(ThreadId vg_tid, UWord* args, UWord* ret) {
  if (args[0] == VG_USERREQ__NACL_MEM_START) {
    // This will get truncated on x86-32, but we don't support it with NaCl
    // anyway.
    const uintptr_t kFourGig = (uintptr_t)0x100000000ULL;
    uintptr_t mem_start = args[1];
    uintptr_t mem_end = mem_start + kFourGig;
    ThreadSanitizerNaclUntrustedRegion(mem_start, mem_end);
    return True;
  }
  if (!VG_IS_TOOL_USERREQ('T', 'S', args[0]))
    return False;
  int32_t ts_tid = VgTidToTsTid(vg_tid);
  // Ignore almost everything in race verifier mode.
  if (g_race_verifier_active) {
    if (args[0] == TSREQ_EXPECT_RACE) {
      Put(EXPECT_RACE, ts_tid, /*descr=*/args[2],
          /*p=*/args[1], 0);
    }
    *ret = 0;
    return True;
  }
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];
  if (thr->trace_info) FlushMops(thr);
  UpdateCallStack(thr, GetVgSp(vg_tid));
  *ret = 0;
  uintptr_t pc = GetVgPc(vg_tid);
  switch (args[0]) {
    case TSREQ_SET_MY_PTHREAD_T:
      (*g_ptid_to_ts_tid)[args[1]] = ts_tid;
      break;
    case TSREQ_THR_STACK_TOP:
      Put(THR_STACK_TOP, ts_tid, pc, args[1], 0);
      break;
    case TSREQ_PTHREAD_JOIN_POST:
      Put(THR_JOIN_AFTER, ts_tid, pc, (*g_ptid_to_ts_tid)[args[1]], 0);
      break;
    case TSREQ_CLEAN_MEMORY:
      Put(MALLOC, ts_tid, pc, /*ptr=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_MAIN_IN:
      g_has_entered_main = true;
      // Report("INFO: Entred main(); argc=%d\n", (int)args[1]);
      break;
    case TSREQ_MAIN_OUT:
      g_has_exited_main = true;
      if (G_flags->exit_after_main) {
        Report("INFO: Exited main(); ret=%d\n", (int)args[1]);
        VG_(show_all_errors)();
        ThreadSanitizerFini();
        if (g_race_verifier_active) {
          RaceVerifierFini();
        }
        exit((int)args[1]);
      }
      break;
    case TSREQ_MALLOC:
      // Printf("Malloc: %p %ld\n", args[1], args[2]);
      Put(MALLOC, ts_tid, pc, /*ptr=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_FREE:
      // Printf("Free: %p\n", args[1]);
      Put(FREE, ts_tid, pc, /*ptr=*/args[1], 0);
      break;
    case TSREQ_MMAP:
      Put(MMAP, ts_tid, pc, /*ptr=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_MUNMAP:
      Put(MUNMAP, ts_tid, pc, /*ptr=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_BENIGN_RACE:
      Put(BENIGN_RACE, ts_tid, /*descr=*/args[3],
          /*p=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_EXPECT_RACE:
      Put(EXPECT_RACE, ts_tid, /*descr=*/args[2], /*p=*/args[1], 0);
      break;
    case TSREQ_FLUSH_EXPECTED_RACES:
      Put(FLUSH_EXPECTED_RACES, ts_tid, 0, 0, 0);
      break;
    case TSREQ_PCQ_CREATE:
      Put(PCQ_CREATE, ts_tid, pc, /*pcq=*/args[1], 0);
      break;
    case TSREQ_PCQ_DESTROY:
      Put(PCQ_DESTROY, ts_tid, pc, /*pcq=*/args[1], 0);
      break;
    case TSREQ_PCQ_PUT:
      Put(PCQ_PUT, ts_tid, pc, /*pcq=*/args[1], 0);
      break;
    case TSREQ_PCQ_GET:
      Put(PCQ_GET, ts_tid, pc, /*pcq=*/args[1], 0);
      break;
    case TSREQ_TRACE_MEM:
      Put(TRACE_MEM, ts_tid, pc, /*mem=*/args[1], 0);
      break;
    case TSREQ_MUTEX_IS_USED_AS_CONDVAR:
      Put(HB_LOCK, ts_tid, pc, /*lock=*/args[1], 0);
      break;
    case TSREQ_MUTEX_IS_NOT_PHB:
      Put(NON_HB_LOCK, ts_tid, pc, /*lock=*/args[1], 0);
      break;
    case TSREQ_GLOBAL_IGNORE_ON:
      Report("INFO: GLOBAL IGNORE ON\n");
      global_ignore = true;
      break;
    case TSREQ_GLOBAL_IGNORE_OFF:
      Report("INFO: GLOBAL IGNORE OFF\n");
      global_ignore = false;
      break;
    case TSREQ_IGNORE_READS_BEGIN:
      Put(IGNORE_READS_BEG, ts_tid, pc, 0, 0);
      break;
    case TSREQ_IGNORE_READS_END:
      Put(IGNORE_READS_END, ts_tid, pc, 0, 0);
      break;
    case TSREQ_IGNORE_WRITES_BEGIN:
      Put(IGNORE_WRITES_BEG, ts_tid, pc, 0, 0);
      break;
    case TSREQ_IGNORE_WRITES_END:
      Put(IGNORE_WRITES_END, ts_tid, pc, 0, 0);
      break;
    case TSREQ_SET_THREAD_NAME:
      Put(SET_THREAD_NAME, ts_tid, pc, /*name=*/args[1], 0);
      break;
    case TSREQ_SET_STACKTOP_STACKSIZE:
      Put(THR_STACK_TOP, ts_tid, pc, /*addr=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_IGNORE_ALL_ACCESSES_BEGIN:
      g_valgrind_threads[vg_tid].ignore_accesses++;
      break;
    case TSREQ_IGNORE_ALL_ACCESSES_END:
      g_valgrind_threads[vg_tid].ignore_accesses--;
      CHECK(g_valgrind_threads[vg_tid].ignore_accesses >= 0);
      break;
    case TSREQ_IGNORE_ALL_SYNC_BEGIN:
      g_valgrind_threads[vg_tid].ignore_sync++;
      break;
    case TSREQ_IGNORE_ALL_SYNC_END:
      g_valgrind_threads[vg_tid].ignore_sync--;
      CHECK(g_valgrind_threads[vg_tid].ignore_sync >= 0);
      break;
    case TSREQ_PUBLISH_MEMORY_RANGE:
      Put(PUBLISH_RANGE, ts_tid, pc, /*mem=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_UNPUBLISH_MEMORY_RANGE:
      Put(UNPUBLISH_RANGE, ts_tid, pc, /*mem=*/args[1], /*size=*/args[2]);
      break;
    case TSREQ_PRINT_MEMORY_USAGE:
    case TSREQ_PRINT_STATS:
    case TSREQ_RESET_STATS:
    case TSREQ_PTH_API_ERROR:
      break;
    case TSREQ_PTHREAD_RWLOCK_CREATE_POST:
      if (ignoring_sync(vg_tid, args[1]))
        break;
      Put(LOCK_CREATE, ts_tid, pc, /*lock=*/args[1], 0);
      break;
    case TSREQ_PTHREAD_RWLOCK_DESTROY_PRE:
      if (ignoring_sync(vg_tid, args[1]))
        break;
      Put(LOCK_DESTROY, ts_tid, pc, /*lock=*/args[1], 0);
      break;
    case TSREQ_PTHREAD_RWLOCK_LOCK_POST:
      if (ignoring_sync(vg_tid, args[1]))
        break;
      Put(args[2] ? WRITER_LOCK : READER_LOCK, ts_tid, pc, /*lock=*/args[1], 0);
      break;
    case TSREQ_PTHREAD_RWLOCK_UNLOCK_PRE:
      if (ignoring_sync(vg_tid, args[1]))
        break;
      Put(UNLOCK, ts_tid, pc, /*lock=*/args[1], 0);
      break;
    case TSREQ_PTHREAD_SPIN_LOCK_INIT_OR_UNLOCK:
      Put(UNLOCK_OR_INIT, ts_tid, pc, /*lock=*/args[1], 0);
      break;
    case TSREQ_POSIX_SEM_INIT_POST:
    case TSREQ_POSIX_SEM_DESTROY_PRE:
      break;
    case TSREQ_SIGNAL:
      if (ignoring_sync(vg_tid, args[1]))
        break;
      Put(SIGNAL, ts_tid, pc, args[1], 0);
      break;
    case TSREQ_WAIT:
      if (ignoring_sync(vg_tid, args[1]))
        break;
      Put(WAIT, ts_tid, pc, args[1], 0);
      break;
    case TSREQ_CYCLIC_BARRIER_INIT:
      Put(CYCLIC_BARRIER_INIT, ts_tid, pc, args[1], args[2]);
      break;
    case TSREQ_CYCLIC_BARRIER_WAIT_BEFORE:
      Put(CYCLIC_BARRIER_WAIT_BEFORE, ts_tid, pc, args[1], 0);
      break;
    case TSREQ_CYCLIC_BARRIER_WAIT_AFTER:
      Put(CYCLIC_BARRIER_WAIT_AFTER, ts_tid, pc, args[1], 0);
      break;
    case TSREQ_GET_MY_SEGMENT:
      break;
    case TSREQ_GET_THREAD_ID:
      *ret = ts_tid;
      break;
    case TSREQ_GET_VG_THREAD_ID:
      *ret = vg_tid;
      break;
    case TSREQ_GET_SEGMENT_ID:
      break;
    case TSREQ_THREAD_SANITIZER_QUERY:
      *ret = (UWord)ThreadSanitizerQuery((const char *)args[1]);
      break;
    case TSREQ_FLUSH_STATE:
      Put(FLUSH_STATE, ts_tid, pc, 0, 0);
      break;
    default: CHECK(0);
  }
  return True;
}

static void SignalIn(ThreadId vg_tid, Int sigNo, Bool alt_stack) {
  g_valgrind_threads[vg_tid].in_signal_handler++;
  DCHECK(g_valgrind_threads[vg_tid].in_signal_handler == 1);
//  int32_t ts_tid = VgTidToTsTid(vg_tid);
//  Printf("T%d %s\n", ts_tid, __FUNCTION__);
}

static void SignalOut(ThreadId vg_tid, Int sigNo) {
  g_valgrind_threads[vg_tid].in_signal_handler--;
  CHECK(g_valgrind_threads[vg_tid].in_signal_handler >= 0);
  DCHECK(g_valgrind_threads[vg_tid].in_signal_handler == 0);
//  int32_t ts_tid = VgTidToTsTid(vg_tid);
//  Printf("T%d %s\n", ts_tid, __FUNCTION__);
}


// ---------------------------- RaceVerifier    ---------------------------{{{1

/**
 * In race verifier mode _every_ IRSB is instrumented with a sleep loop at the
 * beginning (but, of course, in most cases it is not executed).
 * Its code logically looks like
 *  irsb_start:
 *   bool need_sleep = OnTraceVerify1();
 *   if (need_sleep) {
 *     sched_yield();
 *     goto irsb_start;
 *   }
 *   OnTraceVerify2(trace_info);
 *
 * This loop verifies mops from the _previous_ trace_info and sets up the new
 * trace info in OnTraceVerify2. Only IRSBs with "interesting" mops have
 * non-zero trace_info.
 */

/**
 * Race verification loop.
 * On the first pass (for a trace_info), if there are mops to be verified,
 * register them with RaceVerifier and calculate the wake up time.
 * On the following passes, check the wake up time against the clock.
 * The loop state is kept in ValgrindThread.
 * Returns true if need to sleep more, false if the loop must be ended.
 */
VG_REGPARM(1)
static uint32_t OnTraceVerify1() {
  DCHECK(g_race_verifier_active);
  ThreadId vg_tid = GetVgTid();

  // First, flush the old trace_info.
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];

  // thr->trace_info is the trace info for the previous superblock.
  if (!thr->trace_info)
    // Nothing to do here.
    return 0;

  if (!thr->verifier_current_pc) {
    // This is the first iteration of the sleep loop.
    // Register memory accesses.
    int sleep_time_ms = RaceVerifierGetSleepTime(thr->trace_info->pc());
    if (!sleep_time_ms) {
      thr->trace_info = NULL;
      return 0;
    }
    size_t n = thr->trace_info->n_mops();
    uintptr_t* tleb = thr->tleb;
    int need_sleep = 0;
    for (size_t i = 0; i < n; ++i) {
      uintptr_t addr = tleb[i];
      if (addr) {
        MopInfo *mop = thr->trace_info->GetMop(i);
        need_sleep += RaceVerifierStartAccess(thr->zero_based_uniq_tid, addr,
            mop->pc(), mop->is_write());
      }
    }
    // Setup the sleep timer.
    thr->verifier_current_pc = thr->trace_info->pc();
    if (need_sleep) {
      unsigned now = VG_(read_millisecond_timer)();
      thr->verifier_wakeup_time_ms = now + sleep_time_ms;
      return 1;
    } else {
      thr->verifier_current_pc = (unsigned)-1;
      return 0;
    }
  } else {
    // Continuation of the sleep loop.
    DCHECK(thr->verifier_current_pc == thr->trace_info->pc());
    unsigned now = VG_(read_millisecond_timer)();
    if (now < thr->verifier_wakeup_time_ms) {
      // sleep more
      return 1;
    } else {
      // done, go straight to OnTraceVerify2
      thr->verifier_current_pc = (unsigned)-1;
      return 0;
    }
  }
}

/**
 * Race verification loop exit.
 * Unregisters mops with the RaceVerifier.
 * Sets up the new trace_info.
 */
VG_REGPARM(1)
static void OnTraceVerify2(TraceInfo *trace_info) {
  DCHECK(g_race_verifier_active);
  ThreadId vg_tid = GetVgTid();
  ValgrindThread *thr = &g_valgrind_threads[vg_tid];

  DCHECK(!thr->trace_info || thr->verifier_current_pc == (unsigned)-1);
  thr->verifier_current_pc = 0;
  thr->verifier_wakeup_time_ms = 0;

  if (thr->trace_info) {
    // Unregister accesses from the old trace_info.
    size_t n = thr->trace_info->n_mops();
    uintptr_t* tleb = thr->tleb;
    for (size_t i = 0; i < n; ++i) {
      uintptr_t addr = tleb[i];
      if (addr) {
        MopInfo *mop = thr->trace_info->GetMop(i);
        RaceVerifierEndAccess(thr->zero_based_uniq_tid, addr,
            mop->pc(), mop->is_write());
      }
    }
  }

  // Start the new trace, zero the contents of tleb.
  thr->trace_info = trace_info;
  if (trace_info) {
    size_t n = trace_info->n_mops();
    uintptr_t *tleb = thr->tleb;
    for (size_t i = 0; i < n; i++)
      tleb[i] = 0;
    DCHECK(thr->trace_info->n_mops() <= kMaxMopsPerTrace);
  }
}

/**
 * Add a race verification preamble to the IRSB.
 */
static void ts_instrument_trace_entry_verify(IRSB *bbOut,
    VexGuestLayout* layout, TraceInfo *trace_info, uintptr_t cur_pc) {
   HChar*   hName = (HChar*)"OnTraceVerify1";
   void *callback = (void*)OnTraceVerify1;
   IRExpr **args = mkIRExprVec_0();
   IRTemp need_sleep = newIRTemp(bbOut->tyenv, Ity_I32);
   IRDirty* di = unsafeIRDirty_1_N(need_sleep, 0, hName,
       VG_(fnptr_to_fnentry)(callback), args);
   addStmtToIRSB( bbOut, IRStmt_Dirty(di));

   IRTemp need_sleep_i1 = newIRTemp(bbOut->tyenv, Ity_I1);
   IRStmt* cmp_stmt = IRStmt_WrTmp(need_sleep_i1,
       IRExpr_Binop(Iop_CmpNE32,
           IRExpr_RdTmp(need_sleep),
           IRExpr_Const(IRConst_U32(0))));
   addStmtToIRSB(bbOut, cmp_stmt);

   IRConst* exit_dst = layout->sizeof_IP == 8 ?
       IRConst_U64(cur_pc) : IRConst_U32(cur_pc);
   IRStmt* exit_stmt = IRStmt_Exit(IRExpr_RdTmp(need_sleep_i1),
       Ijk_YieldNoRedir, exit_dst);
   addStmtToIRSB(bbOut, exit_stmt);

   hName = (HChar*)"OnTraceVerify2";
   callback = (void*)OnTraceVerify2;
   args = mkIRExprVec_1(mkIRExpr_HWord((HWord)trace_info));
   di = unsafeIRDirty_0_N(1, hName, VG_(fnptr_to_fnentry)(callback), args);
   addStmtToIRSB( bbOut, IRStmt_Dirty(di));
}


// ---------------------------- Instrumentation ---------------------------{{{1

static IRTemp gen_Get_SP ( IRSB*           bbOut,
                           VexGuestLayout* layout,
                           Int             hWordTy_szB )
{
  IRExpr* sp_expr;
  IRTemp  sp_temp;
  IRType  sp_type;
  /* This in effect forces the host and guest word sizes to be the
     same. */
  tl_assert(hWordTy_szB == layout->sizeof_SP);
  sp_type = layout->sizeof_SP == 8 ? Ity_I64 : Ity_I32;
  sp_expr = IRExpr_Get( layout->offset_SP, sp_type );
  sp_temp = newIRTemp( bbOut->tyenv, sp_type );
  addStmtToIRSB( bbOut, IRStmt_WrTmp( sp_temp, sp_expr ) );
  return sp_temp;
}

static void ts_instrument_trace_entry(IRSB *bbOut, TraceInfo *trace_info) {
   CHECK(trace_info);
   HChar*   hName = (HChar*)"OnTrace";
   void *callback = (void*)OnTrace;
   IRExpr **args = mkIRExprVec_1(mkIRExpr_HWord((HWord)trace_info));
   IRDirty* di = unsafeIRDirty_0_N( 1,
                           hName,
                           VG_(fnptr_to_fnentry)(callback),
                           args);
   addStmtToIRSB( bbOut, IRStmt_Dirty(di));
}

static void ts_instrument_final_jump (
                                /*MOD*/IRSB* sbOut,
                                IRExpr* next,
                                IRJumpKind jumpkind,
                                VexGuestLayout* layout,
                                IRType gWordTy, IRType hWordTy ) {

#ifndef VGP_arm_linux
  // On non-ARM systems we instrument only function calls.
  if (jumpkind != Ijk_Call) return;
#else
  if (jumpkind != Ijk_Call) {
    // On an ARM system a non-call jump may possibly exit a function.
    IRTemp sp_post_call_insn
        = gen_Get_SP( sbOut, layout, sizeofIRType(hWordTy) );
    IRExpr **args = mkIRExprVec_2(
        IRExpr_RdTmp(sp_post_call_insn),
        next
        );
    IRDirty* di = unsafeIRDirty_0_N(
        2/*regparms*/,
        (char*)"evh__delete_frame",
        VG_(fnptr_to_fnentry)((void*) &evh__delete_frame ),
        args );
    addStmtToIRSB( sbOut, IRStmt_Dirty(di) );
    return;  // do not fall through
  }
#endif
  {
    const char *fn_name = "evh__rtn_call_ignore_unknown";
    void *fn = (void*)&evh__rtn_call_ignore_unknown;
    // Instrument the call instruction to keep the shadow stack consistent.
    IRTemp sp_post_call_insn
        = gen_Get_SP( sbOut, layout, sizeofIRType(hWordTy) );
    IRExpr **args = mkIRExprVec_2(
        IRExpr_RdTmp(sp_post_call_insn),
        next
        );
    if (next->tag == Iex_Const) {
      IRConst *con = next->Iex.Const.con;
      uintptr_t target = 0;
      if (con->tag == Ico_U32 || con->tag == Ico_U64) {
        target = con->tag == Ico_U32 ? con->Ico.U32 : con->Ico.U64;
        bool ignore = ThreadSanitizerIgnoreAccessesBelowFunction(target);
        if (ignore) {
          fn_name = "evh__rtn_call_ignore_yes";
          fn = (void*)&evh__rtn_call_ignore_yes;
        } else {
          fn_name = "evh__rtn_call_ignore_no";
          fn = (void*)&evh__rtn_call_ignore_no;
        }
      }
    }
    IRDirty* di = unsafeIRDirty_0_N(
        2/*regparms*/,
        (char*)fn_name,
        VG_(fnptr_to_fnentry)(fn),
        args );
    addStmtToIRSB( sbOut, IRStmt_Dirty(di) );
  }
}

// Generate exprs/stmts that make g_cur_tleb[idx] = x.
static void gen_store_to_tleb(IRSB *bbOut, IRTemp tleb_temp,
                              uintptr_t idx, IRExpr *x, IRType tyAddr) {
  CHECK(tleb_temp != IRTemp_INVALID);
  IRExpr *idx_expr  = mkIRExpr_HWord(idx * sizeof(uintptr_t));
  IRExpr *tleb_plus_idx_expr = IRExpr_Binop(
      sizeof(uintptr_t) == 8 ? Iop_Add64 : Iop_Add32,
      IRExpr_RdTmp(tleb_temp), idx_expr);
  IRTemp temp = newIRTemp(bbOut->tyenv, tyAddr);
  IRStmt *temp_stmt = IRStmt_WrTmp(temp, tleb_plus_idx_expr);
  IRStmt *store_stmt = IRStmt_Store(Iend_LE, IRExpr_RdTmp(temp), x);

  addStmtToIRSB(bbOut, temp_stmt);
  addStmtToIRSB(bbOut, store_stmt);
}

static void instrument_mem_access ( TraceInfo *trace_info,
                                    IRTemp tleb_temp,
                                    uintptr_t pc,
                                    size_t  *trace_idx,
                                    IRSB*   bbOut,
                                    IRStmt* st,
                                    IRExpr* addr,
                                    Int     szB,
                                    Bool    isStore,
                                    Bool    dtor_head,
                                    Int     hWordTy_szB ) {
  IRType   tyAddr   = Ity_INVALID;

  tl_assert(isIRAtom(addr));
  tl_assert(hWordTy_szB == 4 || hWordTy_szB == 8);

  tyAddr = typeOfIRExpr( bbOut->tyenv, addr );
  tl_assert(tyAddr == Ity_I32 || tyAddr == Ity_I64);

  if (szB == 28) {
    // Ignore weird-sized accesses for now.
    // See http://code.google.com/p/data-race-test/issues/detail?id=36
    return;
  }

  bool check_ident_store = false;

  if (st->tag == Ist_Store && dtor_head && 
      typeOfIRExpr(bbOut->tyenv, st->Ist.Store.data) == tyAddr) {
    check_ident_store = true;
  }

  size_t next_trace_idx = *trace_idx + 1;

  if (next_trace_idx > kMaxMopsPerTrace) {
    if (next_trace_idx == kMaxMopsPerTrace) {
      Report("INFO: too many mops in trace: %p %s\n", pc,
             PcToRtnName(pc, true).c_str());
    }
    return;
  }

  if (!trace_info) {
    // not instrumenting yet.
    *trace_idx = next_trace_idx;
    return;
  }

  IRExpr *expr_to_store = NULL;

  if (check_ident_store) {
    int is_64 = (sizeof(void*) == 8);
    // generate expression (*addr == new_value ? 0 : addr):

    // old_value = *addr
    IRExpr *addr_load_expr = IRExpr_Load(Iend_LE, tyAddr, addr);
    IRTemp star_addr = newIRTemp(bbOut->tyenv, tyAddr);
    IRStmt *star_addr_stmt = IRStmt_WrTmp(star_addr, addr_load_expr);
    addStmtToIRSB(bbOut, star_addr_stmt);
    // sub = (old_value - new_value)
    IRTemp sub = newIRTemp(bbOut->tyenv, tyAddr);
    IRExpr *sub_expr = IRExpr_Binop((IROp)(Iop_Sub32 + is_64),
                                    IRExpr_RdTmp(star_addr),
                                    st->Ist.Store.data);
    IRStmt *sub_stmt = IRStmt_WrTmp(sub, sub_expr);
    addStmtToIRSB(bbOut, sub_stmt);
    // mask = (sub==0) ? 0 : -1
    IRTemp mask = newIRTemp(bbOut->tyenv, tyAddr);
    IRExpr *mask_expr = IRExpr_Unop((IROp)(Iop_CmpwNEZ32 + is_64),
                                    IRExpr_RdTmp(sub));
    IRStmt *mask_stmt = IRStmt_WrTmp(mask, mask_expr);
    addStmtToIRSB(bbOut, mask_stmt);

    // res = mask & addr
    IRTemp and_tmp = newIRTemp(bbOut->tyenv, tyAddr);
    IRExpr *and_expr = IRExpr_Binop((IROp)(Iop_And32 + is_64),
                                    IRExpr_RdTmp(mask), addr);
    IRStmt *and_stmt = IRStmt_WrTmp(and_tmp, and_expr);
    addStmtToIRSB(bbOut, and_stmt);

    expr_to_store = IRExpr_RdTmp(and_tmp);
  } else {
    expr_to_store = addr;
  }

  // OnMop: g_cur_tleb[idx] = expr_to_store
  gen_store_to_tleb(bbOut, tleb_temp, *trace_idx, expr_to_store, tyAddr);
  // Create a mop {pc, size, is_write}
  MopInfo *mop = trace_info->GetMop(*trace_idx);
  new (mop) MopInfo(pc, szB, isStore, false);
  (*trace_idx)++;

  CHECK(*trace_idx == next_trace_idx);
}

void instrument_statement (IRStmt* st, IRSB* bbIn, IRSB* bbOut, IRType hWordTy,
                           TraceInfo *trace_info, IRTemp tleb_temp,
                           size_t *idx, uintptr_t *cur_pc, bool dtor_head) {
  switch (st->tag) {
    case Ist_NoOp:
    case Ist_AbiHint:
    case Ist_Put:
    case Ist_PutI:
    case Ist_Exit:
      /* None of these can contain any memory references. */
      break;

    case Ist_IMark:
      *cur_pc = st->Ist.IMark.addr;
      break;

    case Ist_MBE:
      //instrument_memory_bus_event( bbOut, st->Ist.MBE.event );
      switch (st->Ist.MBE.event) {
        case Imbe_Fence:
          break; /* not interesting */
        default:
          ppIRStmt(st);
          tl_assert(0);
      }
      break;

    case Ist_CAS:
      break;

    case Ist_Store:
      instrument_mem_access(trace_info, tleb_temp, *cur_pc, idx,
        bbOut, st,
        st->Ist.Store.addr,
        sizeofIRType(typeOfIRExpr(bbIn->tyenv, st->Ist.Store.data)),
        True/*isStore*/, dtor_head,
        sizeofIRType(hWordTy)
      );
      break;

    case Ist_WrTmp: {
      IRExpr* data = st->Ist.WrTmp.data;
      if (data->tag == Iex_Load) {
        instrument_mem_access(trace_info, tleb_temp, *cur_pc, idx,
            bbOut, st,
            data->Iex.Load.addr,
            sizeofIRType(data->Iex.Load.ty),
            False/*!isStore*/, dtor_head,
            sizeofIRType(hWordTy)
            );
      }
      break;
    }

    case Ist_LLSC: {
      /* Ignore load-linked's and store-conditionals. */
      break;
    }

    case Ist_Dirty: {
      Int      dataSize;
      IRDirty* d = st->Ist.Dirty.details;
      if (d->mFx != Ifx_None) {
        /* This dirty helper accesses memory.  Collect the
           details. */
        tl_assert(d->mAddr != NULL);
        tl_assert(d->mSize != 0);
        dataSize = d->mSize;
        if (d->mFx == Ifx_Read || d->mFx == Ifx_Modify) {
          instrument_mem_access(trace_info, tleb_temp, *cur_pc, idx,
            bbOut, st, d->mAddr, dataSize, False/*!isStore*/, dtor_head,
            sizeofIRType(hWordTy)
          );
        }
        if (d->mFx == Ifx_Write || d->mFx == Ifx_Modify) {
          instrument_mem_access(trace_info, tleb_temp, *cur_pc, idx,
            bbOut, st, d->mAddr, dataSize, True/*isStore*/, dtor_head,
            sizeofIRType(hWordTy)
          );
        }
      } else {
        tl_assert(d->mAddr == NULL);
        tl_assert(d->mSize == 0);
      }
      break;
    }

    default:
      ppIRStmt(st);
      tl_assert(0);
  } /* switch (st->tag) */
}

static IRSB* ts_instrument ( VgCallbackClosure* closure,
                             IRSB* bbIn,
                             VexGuestLayout* layout,
                             VexGuestExtents* vge,
                             IRType gWordTy, IRType hWordTy) {
  if (G_flags->dry_run >= 2) return bbIn;
  Int   i;
  IRSB* bbOut;
  uintptr_t pc = closure->readdr;

  char objname[kBuffSize];
  if (VG_(get_objname)(pc, (Char*)objname, kBuffSize)) {
    if (StringMatch("*/ld-2*", objname)) {
      // we want to completely ignore ld-so.
      return bbIn;
    }
  }

  bool instrument_memory = ThreadSanitizerWantToInstrumentSblock(pc);

  if (gWordTy != hWordTy) {
    /* We don't currently support this case. */
    VG_(tool_panic)((Char*)"host/guest word size mismatch");
  }

  /* Set up BB */
  bbOut           = emptyIRSB();
  bbOut->tyenv    = deepCopyIRTypeEnv(bbIn->tyenv);
  bbOut->next     = deepCopyIRExpr(bbIn->next);
  bbOut->jumpkind = bbIn->jumpkind;

  // Copy verbatim any IR preamble preceding the first IMark
  i = 0;
  while (i < bbIn->stmts_used && bbIn->stmts[i]->tag != Ist_IMark) {
    addStmtToIRSB( bbOut, bbIn->stmts[i] );
    i++;
  }
  int first = i;
  size_t n_mops = 0;
  uintptr_t cur_pc = pc;

  IRTemp tleb_temp = IRTemp_INVALID;

  bool dtor_head = false;
  char buff[1000];
  // get_fnname_w_offset returns demangled name with optional "+offset" prefix.
  // If we have "::~" and don't have "+", this SB is the first in this dtor.
  // We do all this stuff to avoid benign races on vptr:
  // http://code.google.com/p/data-race-test/wiki/PopularDataRaces#Data_race_on_vptr
  if (VG_(get_fnname_w_offset)(pc, (Char*)buff, sizeof(buff)) &&
      VG_(strstr)((Char*)buff, (Char*)"::~") != NULL) {
    char *offset_str = (char*)VG_(strchr)((Char*)buff, '+');
    if (offset_str == NULL) {
      // we are in the first BB of DTOR.
      dtor_head = true;
    } else {
      // We are not in the first BB.
      // On x86_64 (it seems like) the vfptr is updated only in the first BB.
      // On x86 with -fPIC, the vfptr may be updated in the second BB
      // (because -fPIC adds a call which splits the first BB).
      // See http://code.google.com/p/chromium/issues/detail?id=61199
#ifdef VGA_x86
      char *end;
      size_t offset = my_strtol(offset_str + 1, &end, 10);
      if (offset <= 32) {
        dtor_head = true;
      }
#endif
    }
  }


  uintptr_t instrument_pc = 0; // if != 0, instrument only the instruction at this address
  if (g_race_verifier_active) {
    uintptr_t min_pc = vge->base[0];
    uintptr_t max_pc = min_pc + vge->len[0];
    bool verify_trace = RaceVerifierGetAddresses(min_pc, max_pc, &instrument_pc);
    if (!verify_trace)
      instrument_memory = false;
  }

  // count mops
  if (instrument_memory) {
    for (i = first; i < bbIn->stmts_used; i++) {
      IRStmt* st = bbIn->stmts[i];
      tl_assert(st);
      tl_assert(isFlatIRStmt(st));
      if (st->tag == Ist_IMark)
        cur_pc = st->Ist.IMark.addr;
      if (!instrument_pc || cur_pc == instrument_pc)
        instrument_statement(st, bbIn, bbOut, hWordTy,
            NULL, tleb_temp, &n_mops, &cur_pc, dtor_head);
    } /* iterate over bbIn->stmts */
  }
  TraceInfo *trace_info = NULL;
  if (n_mops > 0) {
    trace_info = TraceInfo::NewTraceInfo(n_mops, pc);
  }
  size_t n_mops_done = 0;
  bool need_to_insert_on_trace = n_mops > 0 || g_race_verifier_active;
  // instrument mops and copy the rest of BB to the new one.
  for (i = first; i < bbIn->stmts_used; i++) {
    IRStmt* st = bbIn->stmts[i];
    tl_assert(st);
    tl_assert(isFlatIRStmt(st));
    if (st->tag != Ist_IMark && need_to_insert_on_trace) {
      if (g_race_verifier_active) {
        ts_instrument_trace_entry_verify(bbOut, layout, trace_info,
            closure->readdr);
      } else {
        ts_instrument_trace_entry(bbOut, trace_info);
      }
      need_to_insert_on_trace = false;
      // Generate temp for *g_cur_tleb.
      IRType   tyAddr = sizeof(uintptr_t) == 8 ?  Ity_I64 : Ity_I32;
      IRExpr *tleb_ptr_expr = mkIRExpr_HWord((HWord)&g_cur_tleb);
      IRExpr *tleb_expr = IRExpr_Load(Iend_LE, tyAddr, tleb_ptr_expr);
      tleb_temp = newIRTemp(bbOut->tyenv, tyAddr);
      IRStmt *stmt = IRStmt_WrTmp(tleb_temp, tleb_expr);
      addStmtToIRSB(bbOut, stmt);
    }
    if (instrument_memory) {
      if (st->tag == Ist_IMark)
        cur_pc = st->Ist.IMark.addr;
      if (!instrument_pc || cur_pc == instrument_pc)
        instrument_statement(st, bbIn, bbOut, hWordTy,
            trace_info, tleb_temp, &n_mops_done, &cur_pc, dtor_head);
    }
    addStmtToIRSB( bbOut, st );
  } /* iterate over bbIn->stmts */
  CHECK(n_mops == n_mops_done);
  if (!g_race_verifier_active)
    ts_instrument_final_jump(bbOut, bbIn->next, bbIn->jumpkind, layout, gWordTy, hWordTy);
  return bbOut;
}

extern "C"
void ts_pre_clo_init(void) {
  VG_(details_name)            ((Char*)"ThreadSanitizer");
  VG_(details_version)         ((Char*)NULL);
  VG_(details_description)     ((Char*)"a data race detector");
  VG_(details_copyright_author)(
      (Char*)"Copyright (C) 2008-2010, and GNU GPL'd, by Google Inc.");
  VG_(details_bug_reports_to)  ((Char*)"data-race-test@googlegroups.com");

  VG_(basic_tool_funcs)        (ts_post_clo_init,
                                ts_instrument,
                                ts_fini);

  VG_(needs_client_requests)     (ts_handle_client_request);

  VG_(needs_command_line_options)(ts_process_cmd_line_option,
                                  ts_print_usage,
                                  ts_print_debug_usage);
   VG_(track_pre_thread_ll_create)( evh__pre_thread_ll_create );
   VG_(track_pre_thread_ll_exit)  ( evh__pre_thread_ll_exit );

   if (!g_race_verifier_active) {
     VG_(track_workq_task_start)( evh__pre_workq_task_start );
     VG_(track_pre_thread_first_insn)( evh__pre_thread_first_insn );
   }

   VG_(clo_vex_control).iropt_unroll_thresh = 0;
   VG_(clo_vex_control).guest_chase_thresh = 0;

   VG_(track_pre_deliver_signal) (&SignalIn);
   VG_(track_post_deliver_signal)(&SignalOut);

   VG_(track_start_client_code)( OnStartClientCode );
}

VG_DETERMINE_INTERFACE_VERSION(ts_pre_clo_init)

// {{{1 end
// vim:shiftwidth=2:softtabstop=2:expandtab