You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
694 lines
17 KiB
694 lines
17 KiB
/*
|
|
* Copyright 2002-2019 Intel Corporation.
|
|
*
|
|
* This software is provided to you as Sample Source Code as defined in the accompanying
|
|
* End User License Agreement for the Intel(R) Software Development Products ("Agreement")
|
|
* section 1.L.
|
|
*
|
|
* This software and the related documents are provided as is, with no express or implied
|
|
* warranties, other than those that are expressly stated in the License.
|
|
*/
|
|
|
|
/*
|
|
* Collect an address trace
|
|
*
|
|
* During execution of the program, record values in a MLOG and use the
|
|
* MLOG values to reconstuct the trace. The MLOG can contain the actual
|
|
* addresses or register values that can be used to compute the
|
|
* address. This tool is thread safe. Each thread writes to its own MLOG
|
|
* and each MLOG is dumped to a separate file.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* We do TRACE based instrumentation. At the top of the TRACE, we allocate
|
|
* the maximum amount of space that might be needed for this trace in the
|
|
* log. At each memory instruction, we just record the address at a
|
|
* pre-determined slot in the log. If there are early exits from a trace,
|
|
* then their slots will be empty. We initialize all the empty slots to an
|
|
* invalid address so we can tell later that this instruction did not
|
|
* reference memory.
|
|
*
|
|
* We check if the log is full at the top of the trace. If it is full, we
|
|
* empty the log and reset the log pointer.
|
|
*
|
|
*/
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <unistd.h>
|
|
#include "pin.H"
|
|
using std::ofstream;
|
|
using std::vector;
|
|
using std::hex;
|
|
using std::string;
|
|
using std::endl;
|
|
|
|
#define MEMTRACE_DEBUG 0
|
|
|
|
TLS_KEY mlog_key;
|
|
REG scratch_reg0;
|
|
REG scratch_reg1;
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* Knobs for tool
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Name of the output file
|
|
*/
|
|
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool", "o", "memtrace.out", "output file");
|
|
|
|
/*
|
|
* Emit the address trace to the output file
|
|
*/
|
|
KNOB<BOOL> KnobEmitTrace(KNOB_MODE_WRITEONCE, "pintool", "emit", "0", "emit a trace in the output file");
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* Forward type definitions
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
class TRACE_HEADER;
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* Static info about the memory reference
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
class REF
|
|
{
|
|
public:
|
|
REF() {};
|
|
|
|
REF(ADDRINT ip, BOOL read)
|
|
{
|
|
_ip = ip;
|
|
_read = read;
|
|
}
|
|
|
|
ADDRINT IP() const { return _ip; }
|
|
|
|
private:
|
|
// ip of instruction making reference
|
|
ADDRINT _ip;
|
|
|
|
// is it a memory read
|
|
BOOL _read;
|
|
|
|
};
|
|
|
|
/*
|
|
* Instrumentation call
|
|
*
|
|
* We need to insert the instrumentation calls in the order that they
|
|
* should be called, so we record all the calls first and insert them in
|
|
* the proper order later.
|
|
*
|
|
*/
|
|
class CALL
|
|
{
|
|
public:
|
|
CALL(INS ins, AFUNPTR afunptr, UINT32 offset, IARG_TYPE itype)
|
|
{
|
|
_ins = ins;
|
|
_afunptr = afunptr;
|
|
_offset = offset;
|
|
_itype = itype;
|
|
}
|
|
|
|
void Insert(INT32 frameSize)
|
|
{
|
|
INS_InsertCall(_ins, IPOINT_BEFORE, _afunptr, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, scratch_reg0, IARG_ADDRINT, ADDRINT(_offset - frameSize), _itype, IARG_END);
|
|
}
|
|
|
|
private:
|
|
INS _ins;
|
|
AFUNPTR _afunptr;
|
|
INT32 _offset;
|
|
IARG_TYPE _itype;
|
|
};
|
|
|
|
|
|
/*
|
|
* These are the types of commands used to interpret the entries in the
|
|
* MLOG to reconstruct the trace
|
|
*/
|
|
typedef enum
|
|
{
|
|
TCMD_INVALID,
|
|
|
|
// trace entry is effective address
|
|
TCMD_IMMEDIATE,
|
|
|
|
// trace entry is register value
|
|
TCMD_REG_VALUE,
|
|
|
|
// no trace entry, address is offset from register value
|
|
TCMD_REG_OFFSET,
|
|
|
|
// no trace entry, address of offset from previous trace entry
|
|
TCMD_TRACE_OFFSET,
|
|
|
|
// if trace entry is 0, basic block is not executed, abort
|
|
TCMD_BBLOCK
|
|
|
|
} TCMD_TYPE;
|
|
|
|
/*
|
|
*
|
|
* Temporarily store trace header information until all information is
|
|
* known, then a more compact one is generated from this
|
|
*
|
|
*/
|
|
class SCRATCH_TRACE_HEADER
|
|
{
|
|
friend class TRACE_HEADER;
|
|
|
|
public:
|
|
SCRATCH_TRACE_HEADER()
|
|
{
|
|
// The first element of the log is the trace header so leave space for it
|
|
_frameOffset = sizeof(TRACE_HEADER*);
|
|
};
|
|
|
|
UINT32 NumCmds() const { return _cmds.size(); }
|
|
|
|
/*
|
|
* Record a call to store an address in the log
|
|
*/
|
|
void RecordLogImmediate(INS ins, IARG_TYPE itype);
|
|
|
|
/*
|
|
* Insert all the recorded calls into the trace
|
|
*/
|
|
void InsertCalls();
|
|
|
|
private:
|
|
INT32 _frameOffset;
|
|
vector<TCMD_TYPE> _cmds;
|
|
vector<REF> _refs;
|
|
vector<CALL> _calls;
|
|
};
|
|
|
|
/*
|
|
*
|
|
* Permanent trace header storage
|
|
*
|
|
*/
|
|
class TRACE_HEADER
|
|
{
|
|
public:
|
|
TRACE_HEADER(SCRATCH_TRACE_HEADER * header);
|
|
void Expand(char * traceBuffer, char * log);
|
|
int LogBytes() const { return _logBytes; }
|
|
int NumCmds() const { return _numCmds; }
|
|
TCMD_TYPE CmdType(int i) const { assert(i < NumCmds()); return _cmds[i]; }
|
|
REF const * Ref(int i) const { assert(i < NumCmds()); return _refs + i; }
|
|
|
|
private:
|
|
int _logBytes;
|
|
int _numCmds;
|
|
TCMD_TYPE * _cmds;
|
|
REF * _refs;
|
|
};
|
|
|
|
/*
|
|
*
|
|
* MLOG
|
|
*
|
|
* Instrumentation records information into a MLOG. This information is used
|
|
* with the TRACE_HEADER to construct the address trace.
|
|
*
|
|
*
|
|
*/
|
|
class MLOG
|
|
{
|
|
private:
|
|
enum
|
|
{
|
|
BUFFER_SIZE = 1000000,
|
|
EMPTY_SLOT = 0
|
|
};
|
|
|
|
public:
|
|
MLOG(THREADID tid);
|
|
~MLOG();
|
|
|
|
/*
|
|
* Record an address immediate in log
|
|
*/
|
|
static void PIN_FAST_ANALYSIS_CALL RecordImmediate(CHAR * logCursor, ADDRINT offset, ADDRINT value)
|
|
{
|
|
#if MEMTRACE_DEBUG > 10
|
|
fprintf(stderr,"LogImmediate %p logCursor %p offset %p\n", logCursor+offset, logCursor, offset);
|
|
#endif
|
|
|
|
*reinterpret_cast<ADDRINT*>(logCursor+offset) = value;
|
|
}
|
|
|
|
static ADDRINT PIN_FAST_ANALYSIS_CALL TraceAllocIf(char * logCursor, char * logEnd, ADDRINT size);
|
|
static char * PIN_FAST_ANALYSIS_CALL TraceAllocThen(char * logCursor, ADDRINT size, THREADID tid);
|
|
static char * PIN_FAST_ANALYSIS_CALL RecordTraceBegin(char * logCursor, TRACE_HEADER * theader, ADDRINT size);
|
|
|
|
/*
|
|
* Add a trace header to the log and return next logCursor
|
|
*/
|
|
static char * PIN_FAST_ANALYSIS_CALL BeginTrace(char * logCursor, char * logEnd, TRACE_HEADER * theader, UINT32 size, THREADID tid);
|
|
|
|
/*
|
|
* Expand the log into an address trace
|
|
*/
|
|
void Expand();
|
|
|
|
/*
|
|
* Pointer to beginning of log data
|
|
*/
|
|
char * Begin() { return _data; }
|
|
|
|
/*
|
|
* Pointer to end of log data
|
|
*/
|
|
char * End() { return _data + BUFFER_SIZE; }
|
|
|
|
private:
|
|
ofstream ofile;
|
|
|
|
/*
|
|
* Reset log cursor to the beginning of the buffer
|
|
*/
|
|
void ResetLogCursor(char ** logCursor);
|
|
|
|
/*
|
|
* Return true if position in log is empty
|
|
*/
|
|
static BOOL EmptySlot(char * logCursor)
|
|
{
|
|
return *reinterpret_cast<ADDRINT *>(logCursor) == EMPTY_SLOT;
|
|
}
|
|
|
|
/*
|
|
* Mark this position in log as empty
|
|
*/
|
|
static void MarkEmptySlot(char * logCursor)
|
|
{
|
|
*reinterpret_cast<ADDRINT *>(logCursor) = EMPTY_SLOT;
|
|
}
|
|
|
|
static ADDRINT Immediate(char * logCursor)
|
|
{
|
|
return *reinterpret_cast<ADDRINT*>(logCursor);
|
|
}
|
|
|
|
static TRACE_HEADER const * TraceHeader(char * logCursor)
|
|
{
|
|
return *reinterpret_cast<TRACE_HEADER const **>(logCursor);
|
|
}
|
|
|
|
static void RecordTraceHeader(char * logCursor, TRACE_HEADER * traceHeader)
|
|
{
|
|
*reinterpret_cast<TRACE_HEADER const **>(logCursor) = traceHeader;
|
|
}
|
|
|
|
/*
|
|
* Mark all the slots in the log as empty
|
|
*/
|
|
void Reset();
|
|
|
|
int NumLogBytes(TCMD_TYPE ttype);
|
|
|
|
void ExpandTrace(TRACE_HEADER const * traceHeader, char * logCursor);
|
|
|
|
char * _data;
|
|
};
|
|
|
|
MLOG::MLOG(THREADID tid)
|
|
{
|
|
if (KnobEmitTrace)
|
|
{ // no need for output file if nothing is being emitted
|
|
const string filename = KnobOutputFile.Value() + "." + decstr(getpid()) + "." + decstr(tid);
|
|
// Open the memtrace file
|
|
ofile.open(filename.c_str());
|
|
ofile << hex;
|
|
}
|
|
|
|
_data = new char[BUFFER_SIZE];
|
|
Reset();
|
|
}
|
|
|
|
MLOG::~MLOG()
|
|
{
|
|
// Flush remaining data from buffer
|
|
Expand();
|
|
|
|
if (KnobEmitTrace)
|
|
{
|
|
ofile.close();
|
|
}
|
|
|
|
delete [] _data;
|
|
}
|
|
|
|
/*
|
|
* Mark all the slots in the log as empty
|
|
*/
|
|
void MLOG::Reset()
|
|
{
|
|
for (int i = 0; i < BUFFER_SIZE; i += sizeof(ADDRINT))
|
|
{
|
|
MarkEmptySlot(_data + i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Interpret all the commands and MLOG entries to generate the addresses
|
|
* for a single TRACE
|
|
*/
|
|
void MLOG::ExpandTrace(TRACE_HEADER const * traceHeader, char * logCursor)
|
|
{
|
|
int ref = 0;
|
|
for (int cmd = 0; cmd < traceHeader->NumCmds(); cmd++)
|
|
{
|
|
switch(traceHeader->CmdType(cmd))
|
|
{
|
|
case TCMD_IMMEDIATE:
|
|
if (KnobEmitTrace && (!EmptySlot(logCursor)))
|
|
{
|
|
ofile << traceHeader->Ref(ref)->IP() << " " << Immediate(logCursor) << endl;
|
|
}
|
|
ref++;
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
// Advance the log cursor
|
|
logCursor += NumLogBytes(traceHeader->CmdType(cmd));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The log has filled, generate the address trace
|
|
*/
|
|
void MLOG::Expand()
|
|
{
|
|
#if MEMTRACE_DEBUG > 10
|
|
fprintf(stderr,"WriteLog\n");
|
|
#endif
|
|
|
|
if (!KnobEmitTrace)
|
|
return;
|
|
|
|
TRACE_HEADER const * theader = 0;
|
|
|
|
for (char * logCursor = Begin(); !EmptySlot(logCursor); logCursor += theader->LogBytes())
|
|
{
|
|
// Read the trace header
|
|
theader = TraceHeader(logCursor);
|
|
|
|
// Use the log to construct the memory trace
|
|
ExpandTrace(theader, logCursor + sizeof(TRACE_HEADER*));
|
|
}
|
|
|
|
Reset();
|
|
}
|
|
|
|
/*
|
|
* Return 0 if the MLOG has room for this TRACE
|
|
*
|
|
* @param[in] logCursor Pointer to next entry in MLOG to be used
|
|
* @param[in] logEnd Pointer to end of MLOG buffer
|
|
* @param[in] size Number of bytes required by this TRACE
|
|
*/
|
|
ADDRINT MLOG::TraceAllocIf(char * logCursor, char * logEnd, ADDRINT size)
|
|
{
|
|
return (logCursor + size >= logEnd);
|
|
}
|
|
|
|
|
|
/*
|
|
* The MLOG does not have room for the next TRACE, generate all the addresses and reset
|
|
*/
|
|
char * MLOG::TraceAllocThen(char * logCursor, ADDRINT size, THREADID tid)
|
|
{
|
|
MLOG * mlog = static_cast<MLOG*>(PIN_GetThreadData(mlog_key, tid));
|
|
|
|
// If adding this trace will exceed the buffer size then flush the log
|
|
assert(logCursor + size >= mlog->End());
|
|
mlog->Expand();
|
|
mlog->ResetLogCursor(&logCursor);
|
|
|
|
return logCursor;
|
|
}
|
|
|
|
/*
|
|
* Record the TRACE_HEADER and advance MLOG cursor
|
|
*/
|
|
char * MLOG::RecordTraceBegin(char * logCursor, TRACE_HEADER * theader, ADDRINT size)
|
|
{
|
|
// Record the trace header in the log
|
|
RecordTraceHeader(logCursor, theader);
|
|
|
|
// Advance the log pointer
|
|
return logCursor + size;
|
|
}
|
|
|
|
/*
|
|
* Processing for the beginning of a Pin TRACE
|
|
*
|
|
* Check if the MLOG has room for this trace. If not, empty the
|
|
* MLOG. Record the trace header and advance the pointer to where the next
|
|
* trace header should go.
|
|
*
|
|
*/
|
|
char * MLOG::BeginTrace(char * logCursor, char * logEnd, TRACE_HEADER * theader, UINT32 size, THREADID tid)
|
|
{
|
|
#if MEMTRACE_DEBUG > 10
|
|
fprintf(stderr,"LogTraceBegin %p\n", logCursor);
|
|
#endif
|
|
|
|
if (TraceAllocIf(logCursor, logEnd, size))
|
|
{
|
|
logCursor = TraceAllocThen(logCursor, size, tid);
|
|
}
|
|
|
|
logCursor = RecordTraceBegin(logCursor, theader, size);
|
|
|
|
return logCursor;
|
|
}
|
|
|
|
/*
|
|
* Number of bytes in the MLOG that this command consumes
|
|
*/
|
|
int MLOG::NumLogBytes(TCMD_TYPE ttype)
|
|
{
|
|
switch(ttype)
|
|
{
|
|
case TCMD_IMMEDIATE:
|
|
return sizeof(ADDRINT);
|
|
|
|
default:
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reset the cursor to the beginning of the MLOG
|
|
*/
|
|
void MLOG::ResetLogCursor(char ** logCursor)
|
|
{
|
|
*logCursor = Begin();
|
|
}
|
|
|
|
TRACE_HEADER::TRACE_HEADER(SCRATCH_TRACE_HEADER * scratch)
|
|
{
|
|
_logBytes = scratch->_frameOffset;
|
|
|
|
_numCmds = scratch->_cmds.size();
|
|
|
|
_cmds = new TCMD_TYPE[scratch->_cmds.size()];
|
|
for (UINT32 i = 0; i < scratch->_cmds.size(); i++)
|
|
{
|
|
_cmds[i] = scratch->_cmds[i];
|
|
}
|
|
|
|
_refs = new REF[scratch->_refs.size()];
|
|
for (UINT32 i = 0; i < scratch->_refs.size(); i++)
|
|
{
|
|
_refs[i] = scratch->_refs[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We determined all the required instrumentation, insert the calls
|
|
*/
|
|
void SCRATCH_TRACE_HEADER::InsertCalls()
|
|
{
|
|
for (vector<CALL>::iterator c = _calls.begin(); c != _calls.end(); c++)
|
|
{
|
|
c->Insert(_frameOffset);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Record that we need to insert instrumentation to record an immediate
|
|
*/
|
|
void SCRATCH_TRACE_HEADER::RecordLogImmediate(INS ins, IARG_TYPE itype)
|
|
{
|
|
_cmds.push_back(TCMD_IMMEDIATE);
|
|
_refs.push_back(REF(INS_Address(ins), itype != IARG_MEMORYWRITE_EA));
|
|
_calls.push_back(CALL(ins, AFUNPTR(MLOG::RecordImmediate), _frameOffset, itype));
|
|
_frameOffset += sizeof(ADDRINT);
|
|
|
|
#if MEMTRACE_DEBUG > 5
|
|
fprintf(stderr,"InsertLogImmediate ip %p\n",INS_Address(ins));
|
|
#endif
|
|
}
|
|
|
|
|
|
void InstrumentBBL(BBL bbl, SCRATCH_TRACE_HEADER * theader)
|
|
{
|
|
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins))
|
|
{
|
|
// Log every memory references of the instruction
|
|
if (INS_IsMemoryRead(ins) && INS_IsStandardMemop(ins))
|
|
{
|
|
theader->RecordLogImmediate(ins, IARG_MEMORYREAD_EA);
|
|
}
|
|
if (INS_IsMemoryWrite(ins) && INS_IsStandardMemop(ins))
|
|
{
|
|
theader->RecordLogImmediate(ins, IARG_MEMORYWRITE_EA);
|
|
}
|
|
if (INS_HasMemoryRead2(ins) && INS_IsStandardMemop(ins))
|
|
{
|
|
theader->RecordLogImmediate(ins, IARG_MEMORYREAD2_EA);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InstrumentTrace(TRACE trace, void *)
|
|
{
|
|
// We have to determine how many bytes are needed in the MLOG to record
|
|
// information for this trace, so make a pass to determine what type of
|
|
// instrumentation is needed, but do not insert anything yet.
|
|
//
|
|
SCRATCH_TRACE_HEADER scratchHeader;
|
|
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
|
|
{
|
|
InstrumentBBL(bbl, &scratchHeader);
|
|
}
|
|
|
|
// No addresses in this trace
|
|
if (scratchHeader.NumCmds() == 0)
|
|
return;
|
|
|
|
// Pack everything into a TRACE_HEADER
|
|
TRACE_HEADER * theader = new TRACE_HEADER(&scratchHeader);
|
|
|
|
// Now that we know how many MLOG bytes are needed, we can insert instrumentation
|
|
// Insert call to allocate trace entries and write trace header to log
|
|
#define INSERT_IF
|
|
#if defined(INSERT_IF)
|
|
// Instead of using G1 to hold the end, we could use known alignment to detect buffer end
|
|
TRACE_InsertIfCall(trace, IPOINT_BEFORE, AFUNPTR(MLOG::TraceAllocIf),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, scratch_reg0,
|
|
IARG_REG_VALUE, scratch_reg1,
|
|
IARG_UINT32, theader->LogBytes(),
|
|
IARG_END);
|
|
TRACE_InsertThenCall(trace, IPOINT_BEFORE, AFUNPTR(MLOG::TraceAllocThen),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, scratch_reg0,
|
|
IARG_UINT32, theader->LogBytes(),
|
|
IARG_RETURN_REGS, scratch_reg0,
|
|
IARG_THREAD_ID,
|
|
IARG_END);
|
|
TRACE_InsertCall(trace, IPOINT_BEFORE, AFUNPTR(MLOG::RecordTraceBegin),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, scratch_reg0,
|
|
IARG_PTR, theader,
|
|
IARG_UINT32, theader->LogBytes(),
|
|
IARG_RETURN_REGS, scratch_reg0,
|
|
IARG_END);
|
|
#else
|
|
TRACE_InsertCall(trace, IPOINT_BEFORE, AFUNPTR(MLOG::BeginTrace),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, scratch_reg0,
|
|
IARG_REG_VALUE, scratch_reg1,
|
|
IARG_PTR, theader,
|
|
IARG_UINT32, theader->LogBytes(),
|
|
IARG_RETURN_REGS, scratch_reg0,
|
|
IARG_THREAD_ID,
|
|
IARG_END);
|
|
#endif
|
|
|
|
// Insert calls for the individual loads and stores
|
|
scratchHeader.InsertCalls();
|
|
}
|
|
|
|
VOID ThreadStart(THREADID tid, CONTEXT *ctxt, INT32 flags, VOID *v)
|
|
{
|
|
// There is a new MLOG for every thread
|
|
MLOG * mlog = new MLOG(tid);
|
|
|
|
// A thread will need to look up its MLOG, so save pointer in TLS
|
|
PIN_SetThreadData(mlog_key, mlog, tid);
|
|
|
|
// Initialize cursor to point at beginning of buffer
|
|
PIN_SetContextReg(ctxt, scratch_reg0, reinterpret_cast<ADDRINT>(mlog->Begin()));
|
|
|
|
PIN_SetContextReg(ctxt, scratch_reg1, reinterpret_cast<ADDRINT>(mlog->End()));
|
|
}
|
|
|
|
VOID ThreadFini(THREADID tid, const CONTEXT *ctxt, INT32 code, VOID *v)
|
|
{
|
|
MLOG * mlog = static_cast<MLOG*>(PIN_GetThreadData(mlog_key, tid));
|
|
|
|
delete mlog;
|
|
|
|
PIN_SetThreadData(mlog_key, 0, tid);
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
PIN_Init(argc, argv);
|
|
|
|
mlog_key = PIN_CreateThreadDataKey(0);
|
|
scratch_reg0 = PIN_ClaimToolRegister();
|
|
scratch_reg1 = PIN_ClaimToolRegister();
|
|
|
|
if (! (REG_valid(scratch_reg0) && REG_valid(scratch_reg1)) )
|
|
{
|
|
std::cerr << "Cannot allocate a scratch register.\n";
|
|
std::cerr << std::flush;
|
|
return 1;
|
|
}
|
|
|
|
TRACE_AddInstrumentFunction(InstrumentTrace, 0);
|
|
PIN_AddThreadStartFunction(ThreadStart, 0);
|
|
PIN_AddThreadFiniFunction(ThreadFini, 0);
|
|
|
|
PIN_StartProgram();
|
|
|
|
return 0;
|
|
}
|