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.

433 lines
14 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 is an example tool that adds extended debugger commands. See the
* Pin User Manual section "Debugging the Application while Running Under Pin"
* for a tutorial about this tool.
*
* This tool adds extended commands to the debugger that allow you to set
* breakpoints which trigger when the application's stack usage crosses a
* user-specified threshold. It also keeps track of stack usage statistics,
* which can be displayed from within the debugger. In GDB, type "monitor help"
* to show a list of the commands this tool adds.
*/
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <cctype>
#include <map>
#include "pin.H"
using std::cerr;
using std::string;
using std::endl;
// Command line switches for this tool.
//
KNOB<ADDRINT> KnobStackBreak(KNOB_MODE_WRITEONCE, "pintool",
"stackbreak", "0",
"Stop at breakpoint when thread uses this much stack (use with -appdebug_enable");
KNOB<std::string> KnobOut(KNOB_MODE_WRITEONCE, "pintool",
"o", "",
"When using -stackbreak, debugger connection information is printed to this file (default stderr)");
KNOB<UINT32> KnobTimeout(KNOB_MODE_WRITEONCE, "pintool",
"timeout", "0",
"When using -stackbreak, wait for this many seconds for debugger to connect (zero means wait forever)");
// Virtual register we use to point to each thread's TINFO structure.
//
static REG RegTinfo;
// Information about each thread.
//
struct TINFO
{
TINFO(ADDRINT base) : _stackBase(base), _max(0), _maxReported(0) {}
ADDRINT _stackBase; // Base (highest address) of stack.
size_t _max; // Maximum stack usage so far.
size_t _maxReported; // Maximum stack usage reported at breakpoint.
std::ostringstream _os; // Used to format messages.
};
typedef std::map<THREADID, TINFO *> TINFO_MAP;
static TINFO_MAP ThreadInfos;
static std::ostream *Output = &std::cerr;
static bool EnableInstrumentation = false;
static bool BreakOnNewMax = false;
static ADDRINT BreakOnSize = 0;
static VOID OnThreadStart(THREADID, CONTEXT *, INT32, VOID *);
static VOID OnThreadEnd(THREADID, const CONTEXT *, INT32, VOID *);
static VOID Instruction(INS, VOID *);
static ADDRINT OnStackChangeIf(ADDRINT, ADDRINT);
static VOID DoBreakpoint(const CONTEXT *, THREADID);
static void ConnectDebugger();
static BOOL DebugInterpreter(THREADID, CONTEXT *, const string &, string *, VOID *);
static std::string TrimWhitespace(const std::string &);
/* ===================================================================== */
/* Print Help Message */
/* ===================================================================== */
INT32 Usage()
{
cerr << "This tool demonstrates the use of extended debugger commands" << endl;
cerr << endl << KNOB_BASE::StringKnobSummary() << endl;
return -1;
}
/* ===================================================================== */
/* Main */
/* ===================================================================== */
int main(int argc, char *argv[])
{
PIN_InitSymbols();
if (PIN_Init(argc, argv)) return Usage();
if (PIN_GetDebugStatus() == DEBUG_STATUS_DISABLED)
{
std::cerr << "Application level debugging must be enabled to use this tool.\n";
std::cerr << "Start Pin with either -appdebug or -appdebug_enable.\n";
std::cerr << std::flush;
return 1;
}
if (!KnobOut.Value().empty())
Output = new std::ofstream(KnobOut.Value().c_str());
if (KnobStackBreak.Value())
{
EnableInstrumentation = true;
BreakOnSize = KnobStackBreak.Value();
}
// Allocate a virtual register that each thread uses to point to its
// TINFO data. Threads can use this virtual register to quickly access
// their own thread-private data.
//
RegTinfo = PIN_ClaimToolRegister();
if (!REG_valid(RegTinfo))
{
std::cerr << "Cannot allocate a scratch register.\n";
std::cerr << std::flush;
return 1;
}
PIN_AddDebugInterpreter(DebugInterpreter, 0);
PIN_AddThreadStartFunction(OnThreadStart, 0);
PIN_AddThreadFiniFunction(OnThreadEnd, 0);
INS_AddInstrumentFunction(Instruction, 0);
PIN_StartProgram();
return 0;
}
/*
* This call-back implements the extended debugger commands.
*
* tid[in] Pin thread ID for debugger's "focus" thread.
* ctxt[in,out] Register state for the debugger's "focus" thread.
* cmd[in] Text of the extended command.
* result[out] Text that the debugger prints when the command finishes.
*
* Returns: TRUE if we recognize this extended command.
*/
static BOOL DebugInterpreter(THREADID tid, CONTEXT *ctxt, const string &cmd, string *result, VOID *)
{
TINFO_MAP::iterator it = ThreadInfos.find(tid);
if (it == ThreadInfos.end())
return FALSE;
TINFO *tinfo = it->second;
std::string line = TrimWhitespace(cmd);
*result = "";
if (line == "help")
{
result->append("stacktrace on -- Enable tracing of stack usage.\n");
result->append("stacktrace off -- Disable tracing of stack usage.\n");
result->append("stats -- Show stack usage for current thread.\n");
result->append("stackbreak newmax -- Break when any thread stack reaches new maximum usage.\n");
result->append("stackbreak <number> -- Break when any thread stack usage exceeds <number> bytes.\n");
result->append("stackbreak off -- Disable stack breakpoints.\n");
return TRUE;
}
else if (line == "stats")
{
ADDRINT sp = PIN_GetContextReg(ctxt, REG_STACK_PTR);
tinfo->_os.str("");
if (sp <= tinfo->_stackBase)
tinfo->_os << "Current stack usage: " << std::dec << (tinfo->_stackBase - sp) << " bytes.\n";
else
tinfo->_os << "Current stack usage: -" << std::dec << (sp - tinfo->_stackBase) << " bytes.\n";
tinfo->_os << "Maximum stack usage: " << tinfo->_max << " bytes.\n";
*result = tinfo->_os.str();
return TRUE;
}
else if (line == "stacktrace on")
{
if (!EnableInstrumentation)
{
PIN_RemoveInstrumentation();
EnableInstrumentation = true;
*result = "Stack tracing enabled.\n";
}
return TRUE;
}
else if (line == "stacktrace off")
{
if (EnableInstrumentation)
{
PIN_RemoveInstrumentation();
EnableInstrumentation = false;
*result = "Stack tracing disabled.\n";
}
return TRUE;
}
else if (line == "stackbreak newmax")
{
if (!EnableInstrumentation)
{
PIN_RemoveInstrumentation();
EnableInstrumentation = true;
}
BreakOnNewMax = true;
BreakOnSize = 0;
*result = "Will break when thread reaches new stack usage max.\n";
return TRUE;
}
else if (line == "stackbreak off")
{
BreakOnNewMax = false;
BreakOnSize = 0;
return TRUE;
}
else if (line.find("stackbreak ") == 0)
{
std::istringstream is(&line.c_str()[sizeof("stackbreak ")-1]);
size_t size;
is >> size;
if (!is)
{
*result = "Please specify a numeric size (in bytes)\n";
return TRUE;
}
if (!EnableInstrumentation)
{
PIN_RemoveInstrumentation();
EnableInstrumentation = true;
}
BreakOnNewMax = false;
BreakOnSize = size;
tinfo->_os.str("");
tinfo->_os << "Will break when thread uses more than " << size << " bytes of stack.\n";
*result = tinfo->_os.str();
return TRUE;
}
return FALSE; /* Unknown command */
}
static VOID OnThreadStart(THREADID tid, CONTEXT *ctxt, INT32, VOID *)
{
TINFO *tinfo = new TINFO(PIN_GetContextReg(ctxt, REG_STACK_PTR));
ThreadInfos.insert(std::make_pair(tid, tinfo));
PIN_SetContextReg(ctxt, RegTinfo, reinterpret_cast<ADDRINT>(tinfo));
}
static VOID OnThreadEnd(THREADID tid, const CONTEXT *ctxt, INT32, VOID *)
{
TINFO_MAP::iterator it = ThreadInfos.find(tid);
if (it != ThreadInfos.end())
{
delete it->second;
ThreadInfos.erase(it);
}
}
static VOID Instruction(INS ins, VOID *)
{
if (!EnableInstrumentation) return;
if (INS_RegWContain(ins, REG_STACK_PTR))
{
if (INS_IsSysenter(ins)) return; // no need to instrument system calls
IPOINT where = IPOINT_AFTER;
if (!INS_IsValidForIpointAfter(ins))
{
if (INS_IsValidForIpointTakenBranch(ins))
{
where = IPOINT_TAKEN_BRANCH;
}
else
{
return;
}
}
INS_InsertIfCall(ins, where, (AFUNPTR)OnStackChangeIf, IARG_REG_VALUE, REG_STACK_PTR, IARG_REG_VALUE, RegTinfo, IARG_END);
// We use IARG_CONST_CONTEXT here instead of IARG_CONTEXT because it is faster.
//
INS_InsertThenCall(ins, where, (AFUNPTR)DoBreakpoint, IARG_CONST_CONTEXT, IARG_THREAD_ID, IARG_END);
}
}
/*
* Analysis routine called after each instruction that modifies the stack pointer.
*
* sp[in] Value of the stack pointer (after it is updated).
* addrInfo[in] Address of the thread's TINFO structure.
*
* Returns: TRUE if the debugger should stop at a breakpoint.
*/
static ADDRINT OnStackChangeIf(ADDRINT sp, ADDRINT addrInfo)
{
TINFO *tinfo = reinterpret_cast<TINFO *>(addrInfo);
// The stack pointer may go above the base slightly. (For example, the application's dynamic
// loader does this briefly during start-up.)
//
if (sp > tinfo->_stackBase)
return 0;
// Keep track of the maximum stack usage.
//
size_t size = tinfo->_stackBase - sp;
if (size > tinfo->_max)
tinfo->_max = size;
// See if we need to trigger a breakpoint.
//
if (BreakOnNewMax && size > tinfo->_maxReported)
return 1;
if (BreakOnSize && size >= BreakOnSize)
return 1;
return 0;
}
/*
* Analysis routine called if we should stop at a breakpoint.
*
* ctxt[in,out] Application register state immediately after the instruction that change $SP.
* tid[in] Thread ID for thread that triggers the breakpoint.
*/
static VOID DoBreakpoint(const CONTEXT *ctxt, THREADID tid)
{
TINFO *tinfo = reinterpret_cast<TINFO *>(PIN_GetContextReg(ctxt, RegTinfo));
// Keep track of the maximum reported stack usage for "stackbreak newmax".
//
size_t size = tinfo->_stackBase - PIN_GetContextReg(ctxt, REG_STACK_PTR);
if (size > tinfo->_maxReported)
tinfo->_maxReported = size;
ConnectDebugger(); // Ask the user to connect a debugger, if it is not already connected.
// Construct a string that the debugger will print when it stops. If a debugger is
// not connected, no breakpoint is triggered and execution resumes immediately.
//
tinfo->_os.str("");
tinfo->_os << "Thread " << std::dec << tid << " uses " << size << " bytes of stack.";
PIN_ApplicationBreakpoint(ctxt, tid, FALSE, tinfo->_os.str());
}
/*
* If a debugger is not already connected, ask the user to connect one now. Upon
* return, a debugger may or may not be connected.
*/
static void ConnectDebugger()
{
if (PIN_GetDebugStatus() != DEBUG_STATUS_UNCONNECTED)
return;
DEBUG_CONNECTION_INFO info;
if (!PIN_GetDebugConnectionInfo(&info) || info._type != DEBUG_CONNECTION_TYPE_TCP_SERVER)
return;
*Output << "Triggered stack-limit breakpoint.\n";
#if defined(TARGET_MAC)
*Output << "Start LLDB and enter this command:\n";
*Output << " gdb-remote " << std::dec << info._tcpServer._tcpPort << "\n";
#else
*Output << "Start GDB and enter this command:\n";
*Output << " target remote :" << std::dec << info._tcpServer._tcpPort << "\n";
#endif
*Output << std::flush;
if (PIN_WaitForDebuggerToConnect(1000*KnobTimeout.Value()))
return;
*Output << "No debugger attached after " << KnobTimeout.Value() << " seconds.\n";
*Output << "Resuming application without stopping.\n";
*Output << std::flush;
}
/*
* Trim whitespace from a line of text. Leading and trailing whitespace is removed.
* Any internal whitespace is replaced with a single space (' ') character.
*
* inLine[in] Input text line.
*
* Returns: A string with the whitespace trimmed.
*/
static std::string TrimWhitespace(const std::string &inLine)
{
std::string outLine = inLine;
bool skipNextSpace = true;
for (std::string::iterator it = outLine.begin(); it != outLine.end(); ++it)
{
if (std::isspace(*it))
{
if (skipNextSpace)
{
it = outLine.erase(it);
if (it == outLine.end())
break;
}
else
{
*it = ' ';
skipNextSpace = true;
}
}
else
{
skipNextSpace = false;
}
}
if (!outLine.empty())
{
std::string::reverse_iterator it = outLine.rbegin();
if (std::isspace(*it))
outLine.erase(outLine.size()-1);
}
return outLine;
}