/* * 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 #include #include #include #include #include #include "pin.H" using std::cerr; using std::string; using std::endl; // Command line switches for this tool. // KNOB KnobStackBreak(KNOB_MODE_WRITEONCE, "pintool", "stackbreak", "0", "Stop at breakpoint when thread uses this much stack (use with -appdebug_enable"); KNOB KnobOut(KNOB_MODE_WRITEONCE, "pintool", "o", "", "When using -stackbreak, debugger connection information is printed to this file (default stderr)"); KNOB 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 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 -- Break when any thread stack usage exceeds 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(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(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(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; }