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.
485 lines
16 KiB
485 lines
16 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.
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* This tool collects an address trace of instructions that access memory
|
|
* by filling a per-thread buffer. Each memory access of an instruction is recorded
|
|
* as a MEMREF in the buffer (see MEMREF below).
|
|
*
|
|
|
|
* 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
|
|
* buffer. At each memory instruction, we just record the MEMREF at a
|
|
* pre-determined offset in the buffer. If there are early exits from a trace,
|
|
* then MEMREF records will not be placed in the buffer for INSs that follow the
|
|
* early exit - We recognize these by initializing the buffer to 0 - so that the
|
|
* entries in the buffer which were not filled in will have invalid values for both
|
|
* pc and ea of any MEMREF
|
|
*
|
|
* We check if the buffer is full at the top of the trace. If it is full, we
|
|
* process the buffer and reset the buffer pointer.
|
|
*
|
|
* Each application thread has it's own buffer - so multiple application threads do NOT
|
|
* block each other on buffer accesses
|
|
*
|
|
* This tool is similar to membuffer_simple, but does NOT use the Pin Buffering API, it
|
|
* manages the buffers on it's own
|
|
*
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <map>
|
|
#include <set>
|
|
#include "pin.H"
|
|
using std::vector;
|
|
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
* Knobs for tool
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Emit the address trace to the output file
|
|
*/
|
|
KNOB<BOOL> KnobProcessBuffer(KNOB_MODE_WRITEONCE, "pintool", "process_buffs", "1", "process the filled buffers");
|
|
|
|
// 256*4096=1048576 - same size buffer in memtrace_simple, membuffer_simple, membuffer_multi
|
|
KNOB<UINT32> KnobNumBytesInBuffer(KNOB_MODE_WRITEONCE, "pintool", "num_bytes_in_buffer", "1048576", "number of bytes in buffer");
|
|
|
|
|
|
/* Struct of memory reference written to the buffer
|
|
*/
|
|
struct MEMREF
|
|
{
|
|
ADDRINT pc; // pc (ip) of the instruction doing the memory reference
|
|
ADDRINT ea; // the address of the memory being referenced
|
|
};
|
|
|
|
|
|
// the Pin TLS slot that an application-thread will use to hold the APP_THREAD_BUFFER_HANDLER
|
|
// object that it owns
|
|
TLS_KEY appThreadRepresentitiveKey;
|
|
|
|
UINT32 totalBuffersFilled = 0;
|
|
UINT64 totalElementsProcessed = 0;
|
|
|
|
/* Pin registers that this tool allocates (per-thread) to manage the writing
|
|
* to the per-thread buffer
|
|
*/
|
|
REG endOfTraceInBufferReg;
|
|
REG endOfBufferReg;
|
|
/*
|
|
*
|
|
* APP_THREAD_REPRESENTITVE
|
|
*
|
|
* Each application thread, creates an object of this class and saves it in it's Pin TLS
|
|
* slot (appThreadRepresentitiveKey).
|
|
* The class provides the management of the per thread buffer, including the
|
|
* analysis routines to be used
|
|
*/
|
|
class APP_THREAD_REPRESENTITVE
|
|
{
|
|
|
|
public:
|
|
APP_THREAD_REPRESENTITVE(THREADID tid);
|
|
~APP_THREAD_REPRESENTITVE();
|
|
|
|
/*
|
|
* ProcessBuffer
|
|
*/
|
|
void ProcessBuffer(char *end);
|
|
|
|
/*
|
|
* Pointer to beginning of the buffer
|
|
*/
|
|
char * Begin() { return _buffer; }
|
|
|
|
/*
|
|
* Pointer to end of the buffer
|
|
*/
|
|
char * End() { return _buffer + KnobNumBytesInBuffer.Value(); }
|
|
|
|
UINT32 NumBuffersFilled() {return _numBuffersFilled;}
|
|
|
|
UINT32 NumElementsProcessed() {return _numElementsProcessed;}
|
|
|
|
/*
|
|
* Analysis routine to record a MEMREF (pc, ea) in the buffer
|
|
*/
|
|
static void PIN_FAST_ANALYSIS_CALL RecordMEMREFInBuffer(CHAR * endOfTraceInBuffer, ADDRINT offsetFromEndOfTrace, ADDRINT pc, ADDRINT ea)
|
|
{
|
|
*reinterpret_cast<ADDRINT*>(endOfTraceInBuffer+offsetFromEndOfTrace) = pc;
|
|
*reinterpret_cast<ADDRINT*>(endOfTraceInBuffer+offsetFromEndOfTrace+sizeof(ADDRINT)) = ea;
|
|
}
|
|
|
|
/*
|
|
* Analysis routine called at beginning of each trace - it is the IF part of the IF THEN analysis routine pair
|
|
* Return 0 if there is room in the buffer for the current trace
|
|
*
|
|
* @param[in] endOfPreviousTraceInBuffer Pointer to next entry in the buffer
|
|
* @param[in] bufferEnd Pointer to end of the buffer
|
|
* @param[in] totalSizeOccupiedByTraceInBuffer Number of bytes required by this TRACE
|
|
*/
|
|
static ADDRINT PIN_FAST_ANALYSIS_CALL CheckIfNoSpaceForTraceInBuffer(char * endOfPreviousTraceInBuffer, char * bufferEnd, ADDRINT totalSizeOccupiedByTraceInBuffer)
|
|
{
|
|
return (endOfPreviousTraceInBuffer + totalSizeOccupiedByTraceInBuffer >= bufferEnd);
|
|
}
|
|
|
|
/*
|
|
* Analysis routine called when the above IF part returns 1 - this the THEN part
|
|
* The buffer does not have room for this trace, process the current buffer entries
|
|
* and restart to fill the buffer from it's beginning
|
|
*/
|
|
static char * PIN_FAST_ANALYSIS_CALL BufferFull(char *endOfTraceInBuffer, ADDRINT tid)
|
|
{
|
|
|
|
APP_THREAD_REPRESENTITVE * appThreadRepresentitive
|
|
= static_cast<APP_THREAD_REPRESENTITVE*>(PIN_GetThreadData(appThreadRepresentitiveKey, tid));
|
|
appThreadRepresentitive->ProcessBuffer(endOfTraceInBuffer);
|
|
endOfTraceInBuffer = appThreadRepresentitive->Begin();
|
|
|
|
|
|
return endOfTraceInBuffer;
|
|
}
|
|
|
|
/*
|
|
* Analysis routine called at beginning of each trace (after the IF-THEN)-
|
|
* moves the endOfPreviousTraceInBuffer further down in the buffer to allocate space for all
|
|
* the possible MEMREF elements that may be written by this trace
|
|
*/
|
|
static char * PIN_FAST_ANALYSIS_CALL AllocateSpaceForTraceInBuffer(char * endOfPreviousTraceInBuffer,
|
|
ADDRINT totalSizeOccupiedByTraceInBuffer)
|
|
{
|
|
return (endOfPreviousTraceInBuffer + totalSizeOccupiedByTraceInBuffer);
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
/*
|
|
* Reset curMEMREFElement to the beginning of the buffer
|
|
*/
|
|
void ResetCurMEMREFElement(char ** curMEMREFElement);
|
|
|
|
|
|
|
|
|
|
char * _buffer; // this is the actual buffer
|
|
UINT32 _numBuffersFilled;
|
|
UINT32 _numElementsProcessed;
|
|
THREADID _myTid;
|
|
};
|
|
|
|
/*
|
|
* ANALYSIS_CALL_INFO
|
|
*
|
|
* Analysis calls that must be inserted at an INS in the trace are recorded in an
|
|
* ANALYSIS_CALL_INFO object
|
|
*
|
|
*/
|
|
class ANALYSIS_CALL_INFO
|
|
{
|
|
public:
|
|
ANALYSIS_CALL_INFO(INS ins, UINT32 offsetFromTraceStartInBuffer, UINT32 memop) :
|
|
_ins(ins),
|
|
_offsetFromTraceStartInBuffer(offsetFromTraceStartInBuffer),
|
|
_memop (memop)
|
|
{
|
|
}
|
|
|
|
void InsertAnalysisCall(INT32 sizeofTraceInBuffer)
|
|
{
|
|
/* the place in the buffer where the MEMREF of this _ins should be recorded is
|
|
computed by: (the value in endOfTraceInBufferReg)
|
|
-sizeofTraceInBuffer + _offsetFromTraceStartInBuffer(of this _ins)
|
|
*/
|
|
INS_InsertCall(_ins, IPOINT_BEFORE, AFUNPTR(APP_THREAD_REPRESENTITVE::RecordMEMREFInBuffer),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, endOfTraceInBufferReg,
|
|
IARG_ADDRINT, ADDRINT(_offsetFromTraceStartInBuffer - sizeofTraceInBuffer),
|
|
IARG_INST_PTR,
|
|
IARG_MEMORYOP_EA, _memop,
|
|
IARG_END);
|
|
}
|
|
|
|
private:
|
|
INS _ins;
|
|
INT32 _offsetFromTraceStartInBuffer;
|
|
UINT32 _memop;
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
* TRACE_ANALYSIS_CALLS_NEEDED
|
|
*
|
|
* Information about what analysis calls must be inserted at INSs in the trace are recorded here
|
|
* This info is held in a vector of ANALYSIS_CALL_INFO objects
|
|
*
|
|
*/
|
|
class TRACE_ANALYSIS_CALLS_NEEDED
|
|
{
|
|
|
|
public:
|
|
TRACE_ANALYSIS_CALLS_NEEDED() : _currentOffsetFromTraceStartInBuffer(0), _numAnalysisCallsNeeded(0)
|
|
{}
|
|
|
|
UINT32 NumAnalysisCallsNeeded() const { return _numAnalysisCallsNeeded; }
|
|
|
|
UINT32 TotalSizeOccupiedByTraceInBuffer() const { return _currentOffsetFromTraceStartInBuffer; }
|
|
|
|
|
|
/*
|
|
* Record a call to store an address in the log
|
|
*/
|
|
void RecordAnalysisCallNeeded(INS ins, UINT32 memop);
|
|
|
|
/*
|
|
* InsertAnalysisCall all the recorded necessary analysis calls into the trace
|
|
*/
|
|
void InsertAnalysisCalls();
|
|
|
|
|
|
|
|
private:
|
|
|
|
INT32 _currentOffsetFromTraceStartInBuffer;
|
|
INT32 _numAnalysisCallsNeeded;
|
|
vector<ANALYSIS_CALL_INFO> _analysisCalls;
|
|
};
|
|
|
|
|
|
APP_THREAD_REPRESENTITVE::APP_THREAD_REPRESENTITVE(THREADID myTid)
|
|
{
|
|
_buffer = new char[KnobNumBytesInBuffer.Value()];
|
|
_numBuffersFilled = 0;
|
|
_numElementsProcessed = 0;
|
|
_myTid = myTid;
|
|
}
|
|
|
|
APP_THREAD_REPRESENTITVE::~APP_THREAD_REPRESENTITVE()
|
|
{
|
|
delete [] _buffer;
|
|
}
|
|
|
|
|
|
/*
|
|
* Process the buffer
|
|
*/
|
|
void APP_THREAD_REPRESENTITVE::ProcessBuffer(char *end)
|
|
{
|
|
_numBuffersFilled++;
|
|
if (!KnobProcessBuffer)
|
|
{
|
|
return;
|
|
}
|
|
int i=0;
|
|
struct MEMREF * memref =reinterpret_cast<struct MEMREF*>(Begin());
|
|
struct MEMREF * firstMemref =memref;
|
|
|
|
while(memref < reinterpret_cast<struct MEMREF*>(end))
|
|
{
|
|
if (memref->pc!=0)
|
|
{
|
|
i++;
|
|
firstMemref->pc += memref->pc + memref->ea;
|
|
memref->pc = 0;
|
|
}
|
|
memref++;
|
|
}
|
|
|
|
//printf ("numElements %d (full %d empty %d)\n", i+j, i, j);
|
|
_numElementsProcessed += i;
|
|
}
|
|
|
|
/*
|
|
* Reset the cursor to the beginning of the APP_THREAD_REPRESENTITVE
|
|
*/
|
|
void APP_THREAD_REPRESENTITVE::ResetCurMEMREFElement(char ** curMEMREFElement)
|
|
{
|
|
*curMEMREFElement = Begin();
|
|
}
|
|
|
|
|
|
/*
|
|
* We determined all the required instrumentation, insert the calls
|
|
*/
|
|
void TRACE_ANALYSIS_CALLS_NEEDED::InsertAnalysisCalls()
|
|
{
|
|
for (vector<ANALYSIS_CALL_INFO>::iterator c = _analysisCalls.begin();
|
|
c != _analysisCalls.end();
|
|
c++)
|
|
{
|
|
c->InsertAnalysisCall(TotalSizeOccupiedByTraceInBuffer());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Record that we need to insert an analysis call to gather the MEMREF info for this ins
|
|
*/
|
|
void TRACE_ANALYSIS_CALLS_NEEDED::RecordAnalysisCallNeeded(INS ins, UINT32 memop)
|
|
{
|
|
_analysisCalls.push_back(ANALYSIS_CALL_INFO(ins, _currentOffsetFromTraceStartInBuffer, memop));
|
|
_currentOffsetFromTraceStartInBuffer += sizeof(MEMREF);
|
|
_numAnalysisCallsNeeded++;
|
|
}
|
|
|
|
|
|
void DetermineBBLAnalysisCalls(BBL bbl, TRACE_ANALYSIS_CALLS_NEEDED * traceAnalysisCallsNeeded)
|
|
{
|
|
// Log memory references of the instruction
|
|
for(INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins=INS_Next(ins))
|
|
{
|
|
UINT32 memOperands = INS_MemoryOperandCount(ins);
|
|
|
|
// Iterate over each memory operand of the instruction.
|
|
for (UINT32 memOp = 0; memOp < memOperands; memOp++)
|
|
{
|
|
traceAnalysisCallsNeeded->RecordAnalysisCallNeeded(ins, memOp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TraceAnalysisCalls(TRACE trace, void *)
|
|
{
|
|
// Go over all BBLs of the trace and for each BBL determine and record the INSs which need
|
|
// to be instrumented - i.e. the ins requires and analysis call
|
|
//
|
|
TRACE_ANALYSIS_CALLS_NEEDED traceAnalysisCallsNeeded;
|
|
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
|
|
{
|
|
DetermineBBLAnalysisCalls(bbl, &traceAnalysisCallsNeeded);
|
|
}
|
|
|
|
// No addresses in this trace
|
|
if (traceAnalysisCallsNeeded.NumAnalysisCallsNeeded() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Now we know how bytes the analysis calls of this trace will insert into the buffer
|
|
// Each analysis call inserts a MEMREF into the buffer
|
|
|
|
// APP_THREAD_REPRESENTITVE::CheckIfNoSpaceForTraceInBuffer will determine if there are NOT enough available bytes in the buffer.
|
|
// If there are NOT then it returns TRUE and the BufferFull function is called
|
|
TRACE_InsertIfCall(trace, IPOINT_BEFORE, AFUNPTR(APP_THREAD_REPRESENTITVE::CheckIfNoSpaceForTraceInBuffer),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, endOfTraceInBufferReg, // previous trace
|
|
IARG_REG_VALUE, endOfBufferReg,
|
|
IARG_UINT32, traceAnalysisCallsNeeded.TotalSizeOccupiedByTraceInBuffer(),
|
|
IARG_END);
|
|
TRACE_InsertThenCall(trace, IPOINT_BEFORE, AFUNPTR(APP_THREAD_REPRESENTITVE::BufferFull),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, endOfTraceInBufferReg,
|
|
IARG_THREAD_ID,
|
|
IARG_RETURN_REGS, endOfTraceInBufferReg,
|
|
IARG_END);
|
|
TRACE_InsertCall(trace, IPOINT_BEFORE, AFUNPTR(APP_THREAD_REPRESENTITVE::AllocateSpaceForTraceInBuffer),
|
|
IARG_FAST_ANALYSIS_CALL,
|
|
IARG_REG_VALUE, endOfTraceInBufferReg,
|
|
IARG_UINT32, traceAnalysisCallsNeeded.TotalSizeOccupiedByTraceInBuffer(),
|
|
IARG_RETURN_REGS, endOfTraceInBufferReg,
|
|
IARG_END);
|
|
|
|
// Insert Analysis Calls for each INS on the trace that was recorded as needing one
|
|
// i.e. each INS that reads and/or writes memory
|
|
traceAnalysisCallsNeeded.InsertAnalysisCalls();
|
|
}
|
|
|
|
VOID ThreadStart(THREADID tid, CONTEXT *ctxt, INT32 flags, VOID *v)
|
|
{
|
|
// There is a new APP_THREAD_REPRESENTITVE for every thread
|
|
APP_THREAD_REPRESENTITVE * appThreadRepresentitive = new APP_THREAD_REPRESENTITVE(tid);
|
|
|
|
// A thread will need to look up its APP_THREAD_REPRESENTITVE, so save pointer in TLS
|
|
PIN_SetThreadData(appThreadRepresentitiveKey, appThreadRepresentitive, tid);
|
|
|
|
// Initialize endOfTraceInBufferReg to point at beginning of buffer
|
|
PIN_SetContextReg(ctxt, endOfTraceInBufferReg, reinterpret_cast<ADDRINT>(appThreadRepresentitive->Begin()));
|
|
|
|
// Initialize endOfBufferReg to point at end of buffer
|
|
PIN_SetContextReg(ctxt, endOfBufferReg, reinterpret_cast<ADDRINT>(appThreadRepresentitive->End()));
|
|
}
|
|
|
|
VOID ThreadFini(THREADID tid, const CONTEXT *ctxt, INT32 code, VOID *v)
|
|
{
|
|
APP_THREAD_REPRESENTITVE * appThreadRepresentitive
|
|
= static_cast<APP_THREAD_REPRESENTITVE*>(PIN_GetThreadData(appThreadRepresentitiveKey, tid));
|
|
appThreadRepresentitive->ProcessBuffer (reinterpret_cast<char *>(PIN_GetContextReg (ctxt, endOfTraceInBufferReg)));
|
|
totalBuffersFilled += appThreadRepresentitive->NumBuffersFilled();
|
|
totalElementsProcessed += appThreadRepresentitive->NumElementsProcessed();
|
|
|
|
delete appThreadRepresentitive;
|
|
|
|
PIN_SetThreadData(appThreadRepresentitiveKey, 0, tid);
|
|
}
|
|
|
|
VOID Fini(INT32 code, VOID *v)
|
|
{
|
|
return;
|
|
printf ("totalBuffersFilled %u totalElementsProcessed %14.0f\n", (totalBuffersFilled),
|
|
static_cast<double>(totalElementsProcessed));
|
|
}
|
|
|
|
|
|
INT32 Usage()
|
|
{
|
|
printf( "This tool demonstrates simple pin-tool buffer managing\n");
|
|
printf ("The following command line options are available:\n");
|
|
printf ("-num_bytes_in_buffer <num> :number of bytes allocated for each buffer, default 1048576\n");
|
|
printf ("-process_buffs <0 or 1> :specify 0 to disable processing of the buffers, default 1\n");
|
|
return -1;
|
|
}
|
|
|
|
int main(int argc, char * argv[])
|
|
{
|
|
// Initialize PIN library. Print help message if -h(elp) is specified
|
|
// in the command line or the command line is invalid
|
|
if( PIN_Init(argc,argv) )
|
|
{
|
|
return Usage();
|
|
}
|
|
|
|
appThreadRepresentitiveKey = PIN_CreateThreadDataKey(0);
|
|
|
|
// get the registers to be used in each thread for managing the
|
|
// per-thread buffer
|
|
endOfTraceInBufferReg = PIN_ClaimToolRegister();
|
|
endOfBufferReg = PIN_ClaimToolRegister();
|
|
|
|
if (! (REG_valid(endOfTraceInBufferReg) && REG_valid(endOfBufferReg)) )
|
|
{
|
|
printf ("Cannot allocate a scratch register.\n");
|
|
return 1;
|
|
}
|
|
|
|
TRACE_AddInstrumentFunction(TraceAnalysisCalls, 0);
|
|
PIN_AddThreadStartFunction(ThreadStart, 0);
|
|
PIN_AddThreadFiniFunction(ThreadFini, 0);
|
|
|
|
PIN_AddFiniFunction(Fini, 0);
|
|
|
|
//printf ("buffer size in bytes 0x%x\n", KnobNumBytesInBuffer.Value());
|
|
// fflush (stdout);
|
|
PIN_StartProgram();
|
|
|
|
return 0;
|
|
}
|