/* * 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 the implementation file for the "debugger-shell" tool extensions. * See the header file "debugger-shell.H" for a description of what these * extensions are and how to use them in your tool. */ /* * This tool implements a number of extended debugger breakpoints, which use * Pin instrumentation to trigger the breakpoint condition. This comment * provides an overview of the instrumentation strategy. * * Most of the extended debugger breakpoints insert instrumentation at * IPOINT_BEFORE, which tests the breakpoint condition. We use if / then * instrumentation, where the "if" part tests the breakpoint condition and * the "then" part triggers the breakpoint. The inserted analysis code follows * this pattern: * * if TestCondition(....) * { * if (REG_SKIP_ONE == REG_INST_PTR) * return; * REG_SKIP_ONE = REG_INST_PTR; * PIN_ApplicationBreakpoint(....); * } * [Original Instruction] * REG_SKIP_ONE = 0 * * The TestCondition() is a short in-lined "if" analysis call that returns * TRUE if the breakpoint condition is true, and the remainder of the code * before [Original Instruction] is in the "then" analysis call. Usually, * all breakpoint types share the same "then" code, but use a different * TestCondition(). * * The usage of REG_SKIP_ONE is a bit tricky. When the debugger continues * from a breakpoint, it re-executes all the instrumentation on the [Original * Instruction], including the TestCondition() call. Of course, we don't want * the breakpoint to immediately re-trigger, though, or the application would * never make forward progress. We use a Pin virtual register (denoted by * REG_SKIP_ONE above) to solve this by skipping the next occurence of the * breakpoint when the application resumes. Clearing REG_SKIP_ONE at * IPOINT_AFTER / IPOINT_TAKEN_BRANCH ensures that the breakpoint will * re-trigger if the application loops back to the same instruction. * * If more than one breakpoint is placed on the same instruction, each * breakpoint inserts its own instrumentation like this: * * if TestCondition1(....) * { * if (REG_SKIP_ONE == REG_INST_PTR) * return; * REG_SKIP_ONE = REG_INST_PTR; * PIN_ApplicationBreakpoint(....); * } * if TestCondition2(....) * { * if (REG_SKIP_ONE == REG_INST_PTR) * return; * REG_SKIP_ONE = REG_INST_PTR; * PIN_ApplicationBreakpoint(....); * } * [Original Instruction] * REG_SKIP_ONE = 0 * * One breakpoint ("break if store to == ") is checked at * IPOINT_AFTER or IPOINT_TAKEN_BRANCH. This instrumentation looks like this: * * REG_RECORD_EA = IARG_MEMORYWRITE_EA * [Original Store Instruction] * if (REG_RECORD_EA == && *REG_RECORD_EA == ) * { * PIN_ApplicationBreakpoint(....); * } * * Here, we record the value of the effective address in a Pin virtual register * because IARG_MEMORYWRITE_EA cannot be computed at IPOINT_AFTER. The * instrumentation at IPOINT_AFTER tests the breakpoint condition and triggers * the breakpoint. If the breakpoint does trigger, the PC will point to the * next instruction after [Original Store Instruction]. Therefore, when the * debugger continues, execution immediately resumes at the next instruction * and there is no need to use the REG_SKIP_ONE technique. * * The tool also implements tracepoints. A tracepoint is like a breakpoint, * except that instead of stopping when a condition is met, a trace record is * recorded. The instrumentation for a tracepoint uses the same "if" * instrumentation to check the condition, but the "then" instrumentation is * different. A typical example looks like this: * * if TestCondition(....) * { * PIN_GetLock(....); * TraceLog.push_back(....); * PIN_ReleaseLock(....); * } */ #include #include #include #include #include #include #include #include #include #include "debugger-shell.H" using std::string; // These are all the registers that can be used in breakpoint conditions, etc. // struct REG_INFO { REG _reg; const char *_name; }; #if defined(TARGET_IA32E) static const REG_INFO AllRegisters[] = { {REG_GAX, "rax"}, {REG_GBX, "rbx"}, {REG_GCX, "rcx"}, {REG_GDX, "rdx"}, {REG_GSI, "rsi"}, {REG_GDI, "rdi"}, {REG_GBP, "rbp"}, {REG_RSP, "rsp"}, {REG_R8, "r8"}, {REG_R9, "r9"}, {REG_R10, "r10"}, {REG_R11, "r11"}, {REG_R12, "r12"}, {REG_R13, "r13"}, {REG_R14, "r14"}, {REG_R15, "r15"} }; #elif defined(TARGET_IA32) static const REG_INFO AllRegisters[] = { {REG_GAX, "eax"}, {REG_GBX, "ebx"}, {REG_GCX, "ecx"}, {REG_GDX, "edx"}, {REG_GSI, "esi"}, {REG_GDI, "edi"}, {REG_GBP, "ebp"}, {REG_ESP, "esp"} }; #endif class SHELL : public DEBUGGER_SHELL::ISHELL { private: BOOL _isEnabled; DEBUGGER_SHELL::STARTUP_ARGUMENTS _clientArgs; // Describes the help messages for a single command. // struct HELP { HELP(const char *cmd, const char *desc) : _command(cmd), _description(desc) {} HELP(const std::string &cmd, const std::string &desc) : _command(cmd), _description(desc) {} std::string _command; // Name of the extended debugger command. std::string _description; // Help string for the command. }; typedef std::vector HELPS; // Describes the help messages for all commands in a category. // struct HELP_CATEGORY { HELP_CATEGORY() {} HELP_CATEGORY(const char *name, const char *desc, const char *intro) : _name(name), _description(desc), _intro(intro) {} HELP_CATEGORY(const std::string &name, const std::string &desc, const std::string &intro) : _name(name), _description(desc), _intro(intro) {} // These are printed when the user types "help", showing a brief description // of the entire category. // std::string _name; // Name of this category of commands. std::string _description; // Description of this category. // These are printed when the user types "help ", describing each // command in the category. // std::string _intro; // Introductory paragraph for all commands in this category. HELPS _helpStrings; // Help messages for all commands in this category. // If not empty, this is a cached copy of the formatted help message // containing all the command descriptions in '_intro' and '_helpStrings'. // std::string _formattedHelp; }; typedef std::vector HELP_CATEGORIES; HELP_CATEGORIES _helpCategories; unsigned _nextHelpCategory; // Index of the next available help category. // If not empty, this is a cached copy of the formatted top-level help message, // which describes all the command categories. // std::string _formattedCategoryHelp; // These are used when formatting help messages. // typedef std::pair FORMAT_PAIR; typedef std::vector FORMAT_PAIRS; typedef std::vector WORDS; // These are the register numbers for the REG_SKIP_ONE and REG_RECORD_EA // virtual registers. Each is one of the REG_INST_Gn registers. // REG _regSkipOne; REG _regThreadData; REG _regRecordEa; // Possible trigger conditions for breakpoints or tracepoints. // enum TRIGGER { TRIGGER_AT, // Trigger before PC. TRIGGER_STORE_TO, // Trigger before store to address. TRIGGER_STORE_VALUE_TO, // Trigger after store of value to address. TRIGGER_LOAD_FROM, // Trigger before load from address. TRIGGER_LOAD_VALUE_FROM,// Trigger before load of value from address. TRIGGER_AT_ICOUNT, // Trigger before instruction count TRIGGER_AT_MCOUNT, // Trigger before memory instruction count TRIGGER_JUMP_TO, // Trigger before jump to PC. TRIGGER_REG_IS // Trigger before PC if register has value. }; // Possible event types. // enum ETYPE { ETYPE_BREAKPOINT, ETYPE_TRACEPOINT }; // Data for TRIGGER_AT_ICOUNT event struct AT_ICOUNT { // TRIGGER_AT_ICOUNT UINT64 _icount; // instruction count THREADID _tid; // thread to trigger the event }; // Data for TRIGGER_AT_MCOUNT event struct AT_MCOUNT { // TRIGGER_AT_MCOUNT UINT64 _mcount; // memory instruction count THREADID _tid; // thread to trigger the event }; struct EVENT { ETYPE _type; // Breakpoint vs. tracepoint. TRIGGER _trigger; // Trigger condition. std::string _listMsg; // String printed when event is listed. std::string _triggerMsg; // Message printed when breakpoint triggers or when tracepoint is printed. // Data specific to ETYPE_TRACEPOINT. // REG _reg; // If not REG_INVALID, trace this register. BOOL _isDeleted; // TRUE: User has deleted, but may be referenced in TRACEREC. BOOL _isEnabled; // TRUE if tracepoint is enabled. // Data specific to the trigger condition. // union { ADDRINT _ea; // TRIGGER_STORE_TO: EA of store. ADDRINT _pc; // TRIGGER_AT: PC of trigger location. // TRIGGER_JUMP_TO: PC of jump. AT_ICOUNT _atIcount; // TRIGGER_AT_ICOUNT AT_MCOUNT _atMcount; // TRIGGER_AT_ICOUNT struct { // TRIGGER_STORE_VALUE_TO: ADDRINT _ea; // EA of store. UINT64 _value; // value stored. } _storeValueTo; struct { // TRIGGER_LOAD_VALUE_FROM: ADDRINT _ea; // EA of load. UINT64 _value; // value loaded. } _loadValueFrom; struct { // TRIGGER_REG_IS: ADDRINT _pc; // PC of breakpoint. REG _reg; // register ID. ADDRINT _value; // value of register. } _regIs; }; }; // All events, indexed by their ID. // typedef std::map EVENTS; EVENTS _events; unsigned _nextEventId; // A trace record collected when executing a tracepoint. // struct TRACEREC { unsigned _id; // Index of EVENT in '_events'. ADDRINT _pc; // PC where tracepoint triggered. ADDRINT _regValue; // If tracepoints traces a register, it's value. }; // Log of all the tracepoint data. Access is protected by the lock. // PIN_LOCK _traceLock; typedef std::vector TRACERECS; TRACERECS _traceLog; // Instruction count and memory instruction count used if any // TRIGGER_AT_ICOUNT or TRIGGER_AT_MCOUNT breakpoints are setup State // is maintained on a per thread basis. We use the _regThreadData // virtual register to keep a pointer to this data structure for each // thread. // struct THREAD_DATA { THREAD_DATA() : _tid(0), _icount(0), _mcount(0) {} THREADID _tid; UINT64 _icount; UINT64 _mcount; }; // Help messages are formatted to be no wider than this number of characters. // static const unsigned MaxHelpWidth = 80; public: // ----- constructor ----- /* * This method completes construction of the object. We do it here, * so we can return an error indication more easily. * * @return TRUE on success. */ BOOL Construct() { _regSkipOne = PIN_ClaimToolRegister(); _regRecordEa = PIN_ClaimToolRegister(); _regThreadData = PIN_ClaimToolRegister(); if (!REG_valid(_regSkipOne) || !REG_valid(_regRecordEa) || !REG_valid(_regThreadData)) { PrintError("Unable to allocate Pin virtual register"); return FALSE; } PIN_InitLock(&_traceLock); _nextHelpCategory = DEBUGGER_SHELL::HELP_CATEGORY_END; _nextEventId = 1; _isEnabled = FALSE; return TRUE; } // ----- DEBUGGER_SHELL::ISHELL ----- BOOL Enable(const DEBUGGER_SHELL::STARTUP_ARGUMENTS &args) { if (_isEnabled) { PrintError("Do not call ISHELL::Enable() twice"); return FALSE; } _clientArgs = args; ConstructHelpStrings(); // Callbacks for start and end of threads. // PIN_AddThreadStartFunction(ThreadStart, this); PIN_AddThreadFiniFunction(ThreadFini, this); // Debugger interpreter, to process debugger commands. // PIN_AddDebugInterpreter(DebugInterpreter, this); // Trace instrumentation, to handle debugger commands. // TRACE_AddInstrumentFunction(InstrumentTrace, this); _isEnabled = TRUE; return TRUE; } unsigned AddExtendedHelpCategory(const std::string &name, const std::string &description, BOOL *alreadyExists) { // See if this category name already exists, if so return that element (ignoring the // new description). // HELP_CATEGORIES::iterator it = FindHelpCategory(name); if (it != _helpCategories.end()) { if (alreadyExists) *alreadyExists = TRUE; return it - _helpCategories.begin(); } // No existing element, so add a new one. // if (_helpCategories.size() <= _nextHelpCategory) _helpCategories.resize(_nextHelpCategory+1); _formattedCategoryHelp.clear(); _helpCategories[_nextHelpCategory] = HELP_CATEGORY(name, description, ""); if (alreadyExists) *alreadyExists = FALSE; return _nextHelpCategory++; } void AddExtendedHelpMessage(unsigned category, const std::string &cmd, const std::string &description) { ASSERTX(category < _helpCategories.size()); HELP_CATEGORY &entry = _helpCategories[category]; entry._formattedHelp.clear(); entry._helpStrings.push_back(HELP(cmd, description)); } REG GetSkipOneRegister() { return _regSkipOne; } private: /* * Pin call-back that is invoked when a thread starts * * @param[in] tid The tid of the starting thread * @param[in,out] ctxt Register state for the thread * @param[in] flags OS specific flags * @param[in] v Any tool specific value */ static VOID ThreadStart(THREADID tid, CONTEXT * ctxt, INT32 flags, VOID * v) { THREAD_DATA * td = new THREAD_DATA(); SHELL * ds = static_cast(v); td->_tid = tid; PIN_SetContextReg(ctxt, ds->_regThreadData, ADDRINT(td)); } /* * Pin call-back that is invoked when a thread ends. Note that in some cases this * callback is not invoked (e.g. windows threadpool) * * @param[in] tid The tid of the starting thread * @param[in] ctxt Register state for the thread * @param[in] code OS specific exit code * @param[in] v Any tool specific value */ static VOID ThreadFini(THREADID tid, const CONTEXT * ctxt, INT32 code, VOID * v) { THREAD_DATA * td = NULL; SHELL * ds = static_cast(v); td = reinterpret_cast(PIN_GetContextReg(ctxt, ds->_regThreadData)); if (td) delete td; } /* * Pin call-back that implements an extended debugger command. * * @param[in] tid The debugger focus thread. * @param[in,out] ctxt Register state for the debugger's "focus" thread. * @param[in] cmd Text of the extended command. * @param[out] result Text that the debugger prints when the command finishes. * @param[in] vme Pointer to ISHELL instance. * * @return TRUE if we recognize this extended command. */ static BOOL DebugInterpreter(THREADID tid, CONTEXT *ctxt, const string &cmd, string *result, VOID *vme) { SHELL *me = static_cast(vme); /* * General Commands: * * help * help * * Breakpoint Commands: * * break if store to * break after store to == * break if load from * break before load from == * break if icount * break if mcount * break if jump to * break at if == * list breakpoints * delete breakpoint * * Tracing Commands: * * trace [] at * trace [] if load from * trace [] before load from == * trace [] if store to * trace [] after store to == * trace enable [] * trace disable [] * trace clear * trace print [to ] * list tracepoints * delete tracepoint * * Example Trace Output: * * 0x1234: rax = 0x5678 * 0x1234: * 0x1234: if store to 0x89abc: rax = 0x5678 * 0x1234: if store to 0x89abc * 0x1234: after store to 0x89abc = 0xdef00: rax = 0x5678 * 0x1234: after store to 0x89abc = 0xdef00 */ WORDS words; me->SplitWords(cmd, &words); size_t nWords = words.size(); if (nWords == 1 && words[0] == "help") { // help // *result = me->GetFormattedCategoryHelp(); return TRUE; } if (nWords == 2 && words[0] == "help") { // help // *result = me->GetFormattedHelp(words[1]); return TRUE; } else if (nWords == 2 && words[0] == "list" && words[1] == "breakpoints") { // list breakpoints // *result = me->ListBreakpoints(); return TRUE; } else if (nWords == 2 && words[0] == "list" && words[1] == "tracepoints") { // list tracepoints // *result = me->ListTracepoints(); return TRUE; } else if (nWords == 3 && words[0] == "delete" && words[1] == "breakpoint") { // delete breakpoint // *result = me->DeleteEvent(ETYPE_BREAKPOINT, words[2]); return TRUE; } else if (nWords == 3 && words[0] == "delete" && words[1] == "tracepoint") { // delete tracepoint // *result = me->DeleteEvent(ETYPE_TRACEPOINT, words[2]); return TRUE; } else if (nWords == 2 && words[0] == "trace" && words[1] == "enable") { // trace enable // *result = me->EnableDisableAllTraces(TRUE); return TRUE; } else if (nWords == 3 && words[0] == "trace" && words[1] == "enable") { // trace enable // *result = me->EnableDisableTrace(words[2], TRUE); return TRUE; } else if (nWords == 2 && words[0] == "trace" && words[1] == "disable") { // trace disable // *result = me->EnableDisableAllTraces(FALSE); return TRUE; } else if (nWords == 3 && words[0] == "trace" && words[1] == "disable") { // trace disable // *result = me->EnableDisableTrace(words[2], FALSE); return TRUE; } else if (nWords == 2 && words[0] == "trace" && words[1] == "clear") { // trace clear // *result = me->ClearTraceLog(); return TRUE; } else if (nWords == 2 && words[0] == "trace" && words[1] == "print") { // trace print // *result = me->PrintTraceLog(""); return TRUE; } else if (nWords == 4 && words[0] == "trace" && words[1] == "print" && words[2] == "to") { // trace print to // *result = me->PrintTraceLog(words[3]); return TRUE; } else if (nWords == 3 && words[0] == "trace" && words[1] == "at") { // trace at // *result = me->ParseTriggerAtEvent(ETYPE_TRACEPOINT, words[2], ""); return TRUE; } else if (nWords == 4 && words[0] == "trace" && words[2] == "at") { // trace at // *result = me->ParseTriggerAtEvent(ETYPE_TRACEPOINT, words[3], words[1]); return TRUE; } else if (nWords == 5 && words[0] == "break" && words[1] == "if" && words[2] == "store" && words[3] == "to") { // break if store to // *result = me->ParseTriggerStoreToEvent(ETYPE_BREAKPOINT, words[4], ""); return TRUE; } else if (nWords == 5 && words[0] == "break" && words[1] == "if" && words[2] == "load" && words[3] == "from") { // break if load from // *result = me->ParseTriggerLoadFromEvent(ETYPE_BREAKPOINT, words[4], ""); return TRUE; } else if (nWords == 5 && words[0] == "trace" && words[1] == "if" && words[2] == "store" && words[3] == "to") { // trace if store to // *result = me->ParseTriggerStoreToEvent(ETYPE_TRACEPOINT, words[4], ""); return TRUE; } else if (nWords == 6 && words[0] == "trace" && words[2] == "if" && words[3] == "store" && words[4] == "to") { // trace if store to // *result = me->ParseTriggerStoreToEvent(ETYPE_TRACEPOINT, words[5], words[1]); return TRUE; } else if (nWords == 5 && words[0] == "trace" && words[1] == "if" && words[2] == "load" && words[3] == "from") { // trace if load from // *result = me->ParseTriggerLoadFromEvent(ETYPE_TRACEPOINT, words[4], ""); return TRUE; } else if (nWords == 6 && words[0] == "trace" && words[2] == "if" && words[3] == "load" && words[4] == "from") { // trace if load from // *result = me->ParseTriggerLoadFromEvent(ETYPE_TRACEPOINT, words[5], words[1]); return TRUE; } else if (nWords == 7 && words[0] == "break" && words[1] == "before" && words[2] == "load" && words[3] == "from" && words[5] == "==") { // break before load from == // *result = me->ParseTriggerLoadValueFromEvent(ETYPE_BREAKPOINT, words[4], words[6], ""); return TRUE; } else if (nWords == 7 && words[0] == "break" && words[1] == "after" && words[2] == "store" && words[3] == "to" && words[5] == "==") { // break after store to == // *result = me->ParseTriggerStoreValueToEvent(ETYPE_BREAKPOINT, words[4], words[6], ""); return TRUE; } else if (nWords == 7 && words[0] == "trace" && words[1] == "after" && words[2] == "store" && words[3] == "to" && words[5] == "==") { // trace after store to == // *result = me->ParseTriggerStoreValueToEvent(ETYPE_TRACEPOINT, words[4], words[6], ""); return TRUE; } else if (nWords == 8 && words[0] == "trace" && words[2] == "after" && words[3] == "store" && words[4] == "to" && words[6] == "==") { // trace after store to == // *result = me->ParseTriggerStoreValueToEvent(ETYPE_TRACEPOINT, words[5], words[7], words[1]); return TRUE; } else if (nWords == 7 && words[0] == "trace" && words[1] == "before" && words[2] == "load" && words[3] == "from" && words[5] == "==") { // trace before load from == // *result = me->ParseTriggerLoadValueFromEvent(ETYPE_TRACEPOINT, words[4], words[6], ""); return TRUE; } else if (nWords == 8 && words[0] == "trace" && words[2] == "before" && words[3] == "load" && words[4] == "from" && words[6] == "==") { // trace before load from == // *result = me->ParseTriggerLoadValueFromEvent(ETYPE_TRACEPOINT, words[5], words[7], words[1]); return TRUE; } else if (me->_clientArgs._enableIcountBreakpoints && nWords == 4 && words[0] == "break" && words[1] == "if" && words[2] == "icount") { // break if icount // *result = me->ParseTriggerAtCount(ETYPE_BREAKPOINT, words[3], TRIGGER_AT_ICOUNT, tid); return TRUE; } else if (me->_clientArgs._enableIcountBreakpoints && nWords == 4 && words[0] == "break" && words[1] == "if" && words[2] == "mcount") { // break if mcount // *result = me->ParseTriggerAtCount(ETYPE_BREAKPOINT, words[3], TRIGGER_AT_MCOUNT, tid); return TRUE; } else if (nWords == 5 && words[0] == "break" && words[1] == "if" && words[2] == "jump" && words[3] == "to") { // break if jump to // *result = me->ParseTriggerJumpToEvent(ETYPE_BREAKPOINT, words[4], ""); return TRUE; } else if (nWords == 7 && words[0] == "break" && words[1] == "at" && words[3] == "if" && words[5] == "==") { // break at if == // *result = me->ParseTriggerRegIsEvent(ETYPE_BREAKPOINT, words[2], words[4], words[6], ""); return TRUE; } return FALSE; } /* * Flush the code cache. */ VOID Flush() { PIN_RemoveInstrumentation(); } /* * Split an input command into a series of whitespace-separated words. Leading * and trailing whitespace is ignored. * * @param[in] cmd The input command. * @param[out] words An STL container that receives the parsed words. Each * word is added with the push_back() method. */ VOID SplitWords(const std::string &cmd, WORDS *words) { size_t pos = cmd.find_first_not_of(' '); while (pos != std::string::npos) { size_t end = cmd.find_first_of(' ', pos+1); if (end == std::string::npos) { words->push_back(cmd.substr(pos)); pos = end; } else { words->push_back(cmd.substr(pos, end-pos)); pos = cmd.find_first_not_of(' ', end+1); } } } /* * Attempt to parse an unsigned integral number from a string. The string's prefix * determines the radix: "0x" for hex, "0" for octal, otherwise decimal. * * @param[in] val The string to parse. * @param[out] number On success, receives the parsed number. * * @return TRUE if a number is successfully parsed. */ template BOOL ParseNumber(const std::string &val, T *number) { std::istringstream is(val); T num; if (val.compare(0, 2, "0x") == 0) is >> std::hex >> num; else if (val.compare(0, 1, "0") == 0) is >> std::oct >> num; else is >> std::dec >> num; if (is.fail() || !is.eof()) return FALSE; *number = num; return TRUE; } /* * Attempt to parse a "full" register name. * * @param[in] name String which possibly names a register. * * @return If \a name is a register we recognize, returns that register ID. Otherwise, * returns REG_INVALID(). */ REG ParseRegName(const std::string &name) { if (name.empty() || (name[0] != '$' && name[0] != '%')) return REG_INVALID(); // Translate the string to lower case and remove the leading "$" or "%". // std::string reg = name.substr(1); std::transform(reg.begin(), reg.end(), reg.begin(), ::tolower); const unsigned nRegs = sizeof(AllRegisters) / sizeof(AllRegisters[0]); for (unsigned i = 0; i < nRegs; i++) { if (reg == AllRegisters[i]._name) return AllRegisters[i]._reg; } return REG_INVALID(); } /* * Get the name for a Pin register. * * @param[in] reg The register. * * @return The name of \a reg. */ std::string GetRegName(REG reg) { const unsigned nRegs = sizeof(AllRegisters) / sizeof(AllRegisters[0]); for (unsigned i = 0; i < nRegs; i++) { if (reg == AllRegisters[i]._reg) return std::string("$") + AllRegisters[i]._name; } ASSERTX(0); return "???"; } /* * Construct help strings for all the extended debugger commands. */ void ConstructHelpStrings() { if (_helpCategories.size() < DEBUGGER_SHELL::HELP_CATEGORY_END) _helpCategories.resize(DEBUGGER_SHELL::HELP_CATEGORY_END); _formattedCategoryHelp.clear(); // General commands. // HELP_CATEGORY *category = &_helpCategories[DEBUGGER_SHELL::HELP_CATEGORY_GENERAL]; *category = HELP_CATEGORY("general", "General commands.", ""); HELPS *helpCommands = &category->_helpStrings; category->_formattedHelp.clear(); helpCommands->push_back(HELP("help", "Print help summary of available commands.")); helpCommands->push_back(HELP("help ", "Print help on commands in .")); // Breakpoint commands. // category = &_helpCategories[DEBUGGER_SHELL::HELP_CATEGORY_BREAKPOINTS]; *category = HELP_CATEGORY("breakpoints", "Breakpoint commands.", ""); helpCommands = &category->_helpStrings; category->_formattedHelp.clear(); helpCommands->push_back(HELP("list breakpoints", "List all extended breakpoints.")); helpCommands->push_back(HELP("delete breakpoint ", "Delete extended breakpoint .")); helpCommands->push_back(HELP("break if load from ", "Break before any load from .")); helpCommands->push_back(HELP("break if store to ", "Break before any store to .")); helpCommands->push_back(HELP("break before load from == ", "Break before load if loaded from .")); helpCommands->push_back(HELP("break after store to == ", "Break after store if stored to .")); if (_clientArgs._enableIcountBreakpoints) { helpCommands->push_back(HELP("break if icount ", "Break current thread before it reaches instructions from the start of execution.")); helpCommands->push_back(HELP("break if mcount ", "Break current thread before it reaches memory instructions from the start of execution.")); } helpCommands->push_back(HELP("break if jump to ", "Break before any jump to .")); helpCommands->push_back(HELP("break at if == ", "Break before if contains .")); // Tracepoint commands. // category = &_helpCategories[DEBUGGER_SHELL::HELP_CATEGORY_TRACEPOINTS]; *category = HELP_CATEGORY("tracepoints", "Tracepoint commands.", ""); helpCommands = &category->_helpStrings; category->_formattedHelp.clear(); helpCommands->push_back(HELP("list tracepoints", "List all extended tracepoints.")); helpCommands->push_back(HELP("delete tracepoint ", "Delete extended tracepoint .")); helpCommands->push_back(HELP("trace print [to ]", "Print contents of trace log to screen, or to .")); helpCommands->push_back(HELP("trace clear", "Clear contents of trace log.")); helpCommands->push_back(HELP("trace disable []", "Disable all tracepoints, or only tracepoint .")); helpCommands->push_back(HELP("trace enable []", "Enable all tracepoints, or only tracepoint .")); helpCommands->push_back(HELP("trace [] at ", "Record trace entry before executing instruction at . If is " "specified, record that register's value too.")); helpCommands->push_back(HELP("trace [] if store to ", "Record trace entry before executing any store to . If is " "specified, record that register's value too.")); helpCommands->push_back(HELP("trace [] after store to == ", "Record trace entry after any store of to . If is " "specified, record that register's value too.")); helpCommands->push_back(HELP("trace [] if load from ", "Record trace entry before executing any load to . If is " "specified, record that register's value too.")); helpCommands->push_back(HELP("trace [] before load from == ", "Record trace entry before any load of from . If is " "specified, record that register's value too.")); // Register names. // std::string regLower = AllRegisters[0]._name; std::string regUpper = regLower; std::transform(regUpper.begin(), regUpper.end(), regUpper.begin(), ::toupper); std::string intro = "Some of the extended debugger commands accept a parameter. You can specify a registers name " "using either \"$\" or \"%\" followed by the name of the register. For example, all of these strings " "specify the same register: \"$" + regLower + "\", \"%" + regLower + "\", \"$" + regUpper + "\", \"%" + regUpper + "\". The list of available register names is:"; category = &_helpCategories[DEBUGGER_SHELL::HELP_CATEGORY_REGISTERS]; *category = HELP_CATEGORY("registers", "Register names.", intro); helpCommands = &category->_helpStrings; category->_formattedHelp.clear(); const unsigned nRegs = sizeof(AllRegisters) / sizeof(AllRegisters[0]); for (unsigned i = 0; i < nRegs; i++) helpCommands->push_back(HELP(std::string("$") + AllRegisters[i]._name, "")); } /* * @return The formatted text for the "help" command. */ std::string GetFormattedCategoryHelp() { if (!_formattedCategoryHelp.empty()) return _formattedCategoryHelp; FORMAT_PAIRS textPairs; for (HELP_CATEGORIES::iterator it = _helpCategories.begin(); it != _helpCategories.end(); ++it) textPairs.push_back(std::make_pair(it->_name, it->_description)); std::string text = FormatHelpText(textPairs); text.append("\n"); text.append("type \"help \" for help on commands in that category.\n"); _formattedCategoryHelp = text; return _formattedCategoryHelp; } /* * Get the formatted text for the "help " command. * * @param[in] categoryName Possibly the name of a category. * * @return The formatted text for the command. If \a categoryName is not valid, an * error message is returned. */ std::string GetFormattedHelp(const std::string &categoryName) { HELP_CATEGORIES::iterator it = FindHelpCategory(categoryName); if (it == _helpCategories.end()) return "Unknown category '" + categoryName + "'\n"; HELP_CATEGORY &category = *it; if (!category._formattedHelp.empty()) return category._formattedHelp; FORMAT_PAIRS textPairs; for (HELPS::iterator it2 = category._helpStrings.begin(); it2 != category._helpStrings.end(); ++it2) textPairs.push_back(std::make_pair(it2->_command, it2->_description)); std::string text; if (!category._intro.empty()) { text.append(SplitToMultipleLines(category._intro, MaxHelpWidth, 0)); text.append("\n"); text.append("\n"); } text.append(FormatHelpText(textPairs)); if (text.empty()) text = "(none)\n"; category._formattedHelp = text; return category._formattedHelp; } /* * Attempt to find an existing help category by name. * * @param[in] name Names a potential category. * * @return An iterator to the category's entry if \a name exists, otherwise the "end" iterator. */ HELP_CATEGORIES::iterator FindHelpCategory(const std::string &name) { HELP_CATEGORIES::iterator it; for (it = _helpCategories.begin(); it != _helpCategories.end(); ++it) { if (it->_name == name) break; } return it; } /* * Format text for a help message. * * @param[in] textPairs A container of text pairs. The first element in the pair is * a command name, the second is a longer description. * * @return The formatted text. */ std::string FormatHelpText(const FORMAT_PAIRS &textPairs) { const size_t longCommandSize = 25; // Description for long command is printed on separate line. // The description text starts 2 spaces to the right of the longest "short" command. // const size_t dashColumn = longCommandSize+2; std::string result; BOOL newLineBeforeNext = FALSE; for (FORMAT_PAIRS::const_iterator it = textPairs.begin(); it != textPairs.end(); ++it) { std::string thisMessage = it->first; BOOL isLongCommand = FALSE; if (it->second.empty()) { // There is no description, so the line just contains the command. } else if (it->first.size() < longCommandSize) { // This is a "short" command. The description starts on the same line as // the command, but may continue on subsequent lines. // size_t pad = dashColumn - it->first.size(); thisMessage.append(pad, ' '); thisMessage.append("- "); thisMessage.append(it->second); if (thisMessage.size() > MaxHelpWidth) thisMessage = SplitToMultipleLines(thisMessage, MaxHelpWidth, dashColumn+2); } else { // This is a "long" command. The description starts on the next line. // thisMessage.append("\n"); std::string desc(dashColumn+2, ' '); desc.append(it->second); if (desc.size() > MaxHelpWidth) desc = SplitToMultipleLines(desc, MaxHelpWidth, dashColumn+2); thisMessage.append(desc); isLongCommand = TRUE; } // It seems more readable if there is a blank line separating "long" commands // from their neighbors. // if (newLineBeforeNext || isLongCommand) result.append("\n"); result.append(thisMessage); result.append("\n"); newLineBeforeNext = isLongCommand; } return result; } /* * Split a line of text into multiple indented lines. * * @param[in] str The line of text to split. * @param[in] maxWidth None of the output lines will be wider than this limit. * @param[in] indent If the line is split, all line other than the first are indented * with this many spaces. * * @return The formated text lines. */ std::string SplitToMultipleLines(const std::string &str, size_t maxWidth, size_t indent) { BOOL isFirst = TRUE; std::string ret; std::string input = str; while (input.size() > maxWidth) { BOOL needHyphen = FALSE; size_t posBreakAfter = std::string::npos; // Point 'posBreakAfter' to the last character of the last word that fits // before 'maxWidth'. We assume that words are separated by spaces. // size_t posSpace = input.rfind(' ', maxWidth-1); if (posSpace != std::string::npos) posBreakAfter = input.find_last_not_of(' ', posSpace); // If there's a really long word is itself longer than 'maxWidth', break // the word and put a hyphen in. // if (posBreakAfter == std::string::npos) { posBreakAfter = maxWidth-2; needHyphen = TRUE; } // Split the line, add indenting for all but the first one. // if (!isFirst) ret.append(indent, ' '); ret.append(input.substr(0, posBreakAfter+1)); if (needHyphen) ret.append("-"); ret.append("\n"); // Strip off any spaces from the start of the next line. // size_t posNextWord = input.find_first_not_of(' ', posSpace); input.erase(0, posNextWord); // Lines after the first are indented, so this reduces the effective width // of the line. // if (isFirst) { isFirst = FALSE; maxWidth -= indent; } } if (input.size()) { if (!isFirst) ret.append(indent, ' '); ret.append(input); } return ret; } /* * @return A single string showing all the active breakpoints. */ std::string ListBreakpoints() { std::string ret; for (EVENTS::iterator it = _events.begin(); it != _events.end(); ++it) { if (it->second._type == ETYPE_BREAKPOINT) { ret += it->second._listMsg; ret += "\n"; } } return ret; } /* * @return A single string showing all the active tracepoints. */ std::string ListTracepoints() { std::string ret; for (EVENTS::iterator it = _events.begin(); it != _events.end(); ++it) { if (it->second._type == ETYPE_TRACEPOINT && !it->second._isDeleted) { ret += it->second._listMsg; if (!it->second._isEnabled) ret += " (disabled)"; ret += "\n"; } } return ret; } /* * Delete an event. * * @param[in] type The type of event to delete. * @param[in] idStr The event ID. * * @return A string to return to the debugger prompt. */ std::string DeleteEvent(ETYPE type, const std::string &idStr) { unsigned id; std::string ret = ValidateId(type, idStr, &id); if (!ret.empty()) return ret; // The trace log may contain a pointer to the tracepoint, so don't really // delete it if the trace log is non-empty. // if (type == ETYPE_TRACEPOINT && !_traceLog.empty()) _events[id]._isDeleted = TRUE; else _events.erase(id); Flush(); return ""; } /* * Enable or disable all "trace" events. * * @param[in] enable TRUE if events should be enabled. * * @return A string to return to the debugger prompt. */ std::string EnableDisableAllTraces(BOOL enable) { BOOL needFlush = FALSE; for (EVENTS::iterator it = _events.begin(); it != _events.end(); ++it) { if (it->second._type == ETYPE_TRACEPOINT && !it->second._isDeleted && it->second._isEnabled != enable) { it->second._isEnabled = enable; needFlush = TRUE; } } if (needFlush) Flush(); return ""; } /* * Enable or disable a single trace event. * * @param[in] idStr ID of the trace event to enable. * @param[in] enable TRUE if the event should be enabled. * * @return A string to return to the debugger prompt. */ std::string EnableDisableTrace(const std::string &idStr, BOOL enable) { unsigned id; std::string ret = ValidateId(ETYPE_TRACEPOINT, idStr, &id); if (!ret.empty()) return ret; if (_events[id]._isEnabled != enable) { _events[id]._isEnabled = enable; Flush(); } return ""; } /* * Clear the trace log. * * @return A string to return to the debugger prompt. */ std::string ClearTraceLog() { if (_traceLog.empty()) return ""; _traceLog.clear(); // Now that the trace log is cleared, there's no danger that there are any // references to deleted "trace" events. So, we can really delete them. // EVENTS::iterator it = _events.begin(); while (it != _events.end()) { EVENTS::iterator thisIt = it++; if (thisIt->second._type == ETYPE_TRACEPOINT && thisIt->second._isDeleted) _events.erase(thisIt); } return ""; } /* * Print the contents of the trace log. * * @param[in] file If not empty, the file to print the log to. Otherwise, the * content of the log is returned (and printed to the debugger prompt). * * @return A string to return to the debugger prompt. */ std::string PrintTraceLog(const std::string &file) { std::ostringstream ss; std::ofstream fs; std::ostream *os; // We print the log either to a file, or to the "ss" buffer. // if (!file.empty()) { fs.open(file.c_str()); os = &fs; } else { os = &ss; } // We want to pad out the "pc" field with leading zeros. // os->fill('0'); size_t width = 2*sizeof(ADDRINT); for (TRACERECS::iterator it = _traceLog.begin(); it != _traceLog.end(); ++it) { const EVENT &evnt = _events[it->_id]; (*os) << "0x" << std::hex << std::setw(width) << it->_pc << std::setw(0); if (!evnt._triggerMsg.empty()) (*os) << ": " << evnt._triggerMsg; if (REG_valid(evnt._reg)) *os << ": " << GetRegName(evnt._reg) << " = 0x" << std::hex << it->_regValue; (*os) << "\n"; } // If printing to the debugger prompt, this returns the output. If not, the output // is flushed to the file when the 'fs' goes out of scope and the return statement // returns the empty string. // return ss.str(); } /* * Parse an event ID and check that it is valid. * * @param[in] type The type of event that this ID should correspond to. * @param[in] idStr The candidate ID. * @param[out] id On success, receives the ID. * * @return On success, the empty string. On failure, an error message. */ std::string ValidateId(ETYPE type, const std::string &idStr, unsigned *id) { if (!ParseNumber(idStr, id)) { std::ostringstream os; os << "Invalid " << GetEventName(type) << " ID " << idStr << "\n"; return os.str(); } EVENTS::iterator it = _events.find(*id); if (it == _events.end() || it->second._type != type || (type == ETYPE_TRACEPOINT && it->second._isDeleted)) { std::ostringstream os; os << "Invalid " << GetEventName(type) << " ID " << idStr << "\n"; return os.str(); } return ""; } /* * Get the name of an event type. * * @param[in] type The event type. * * @return The name for \a type. */ std::string GetEventName(ETYPE type) { switch (type) { case ETYPE_BREAKPOINT: return "breakpoint"; case ETYPE_TRACEPOINT: return "tracepoint"; default: ASSERTX(0); return ""; } } /* * Parse an event with trigger type TRIGGER_AT. * * @param[in] type The type of event. * @param[in] pcStr The trigger's PC address. * @param[in] regStr If not empty, the register to trace. * * @return A string to return to the debugger prompt. */ std::string ParseTriggerAtEvent(ETYPE type, const std::string &pcStr, const std::string ®Str) { ADDRINT pc; if (!ParseNumber(pcStr, &pc)) { std::ostringstream os; os << "Invalid address " << pcStr << "\n"; return os.str(); } REG reg = REG_INVALID(); if (type == ETYPE_TRACEPOINT && !regStr.empty()) { reg = ParseRegName(regStr); if (!REG_valid(reg)) { std::ostringstream os; os << "Invalid register " << regStr << " (see \"help registers\")\n"; return os.str(); } } unsigned id = _nextEventId++; std::ostringstream os; os << "at 0x" << std::hex << pc; EVENT evnt; std::string ret; if (type == ETYPE_BREAKPOINT) { std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; } else { std::ostringstream osList; osList << "#" << std::dec << id << ": trace"; if (REG_valid(reg)) osList << " " << GetRegName(reg); osList << " " << os.str(); evnt._listMsg = osList.str(); evnt._triggerMsg = ""; evnt._reg = reg; evnt._isDeleted = FALSE; evnt._isEnabled = TRUE; ret = "Tracepoint " + osList.str() + "\n"; } evnt._type = type; evnt._trigger = TRIGGER_AT; evnt._pc = pc; _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Parse an event with trigger type TRIGGER_LOAD_FROM * * @param[in] type The type of event. * @param[in] addrStr The trigger's load address. * @param[in] regStr If not empty, the register to trace. * * @return A string to return to the debugger prompt. */ std::string ParseTriggerLoadFromEvent(ETYPE type, const std::string &addrStr, const std::string ®Str) { ADDRINT addr; string outStr; if (!ParseNumber(addrStr, &addr)) { std::ostringstream os; os << "Invalid address " << addrStr << "\n"; return os.str(); } REG reg = REG_INVALID(); if (type == ETYPE_TRACEPOINT && !regStr.empty()) { reg = ParseRegName(regStr); if (!REG_valid(reg)) { std::ostringstream os; os << "Invalid register " << regStr << " (see \"help registers\")\n"; return os.str(); } } unsigned id = _nextEventId++; std::ostringstream os; os << "if load from 0x" << std::hex << addr; EVENT evnt; std::string ret; if (type == ETYPE_BREAKPOINT) { std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; } else { std::ostringstream osList; osList << "#" << std::dec << id << ": trace"; if (REG_valid(reg)) osList << " " << GetRegName(reg); osList << " " << os.str(); evnt._listMsg = osList.str(); evnt._triggerMsg = os.str(); evnt._reg = reg; evnt._isDeleted = FALSE; evnt._isEnabled = TRUE; ret = "Tracepoint " + osList.str() + "\n"; } // fill in information specific to this trigger event evnt._type = type; evnt._trigger = TRIGGER_LOAD_FROM; evnt._ea = addr; _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Parse an event with trigger type TRIGGER_LOAD_VALUE_FROM. * * @param[in] type The type of event. * @param[in] addrStr The trigger's load address. * @param[in] valueStr The trigger's load value. * @param[in] regStr If not empty, the register to trace. * * @return A string to return to the debugger prompt. */ std::string ParseTriggerLoadValueFromEvent(ETYPE type, const std::string &addrStr, const std::string &valueStr, const std::string ®Str) { ADDRINT addr; if (!ParseNumber(addrStr, &addr)) { std::ostringstream os; os << "Invalid address " << addrStr << "\n"; return os.str(); } UINT64 value = 0; if (!ParseNumber(valueStr, &value)) { std::ostringstream os; os << "Invalid value " << valueStr << "\n"; return os.str(); } REG reg = REG_INVALID(); if (type == ETYPE_TRACEPOINT && !regStr.empty()) { reg = ParseRegName(regStr); if (!REG_valid(reg)) { std::ostringstream os; os << "Invalid register " << regStr << " (see \"help registers\")\n"; return os.str(); } } unsigned id = _nextEventId++; std::ostringstream os; os << "before load from 0x" << std::hex << addr << " == 0x" << std::hex << value; EVENT evnt; std::string ret; if (type == ETYPE_BREAKPOINT) { std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; } else { std::ostringstream osList; osList << "#" << std::dec << id << ": trace"; if (REG_valid(reg)) osList << " " << GetRegName(reg); osList << " " << os.str(); evnt._listMsg = osList.str(); evnt._triggerMsg = os.str(); evnt._reg = reg; evnt._isDeleted = FALSE; evnt._isEnabled = TRUE; ret = "Tracepoint " + osList.str() + "\n"; } evnt._type = type; evnt._trigger = TRIGGER_LOAD_VALUE_FROM; evnt._loadValueFrom._ea = addr; evnt._loadValueFrom._value = value; _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Parse an event with trigger type TRIGGER_STORE_TO. * * @param[in] type The type of event. * @param[in] addrStr The trigger's store address. * @param[in] regStr If not empty, the register to trace. * * @return A string to return to the debugger prompt. */ std::string ParseTriggerStoreToEvent(ETYPE type, const std::string &addrStr, const std::string ®Str) { ADDRINT addr; if (!ParseNumber(addrStr, &addr)) { std::ostringstream os; os << "Invalid address " << addrStr << "\n"; return os.str(); } REG reg = REG_INVALID(); if (type == ETYPE_TRACEPOINT && !regStr.empty()) { reg = ParseRegName(regStr); if (!REG_valid(reg)) { std::ostringstream os; os << "Invalid register " << regStr << " (see \"help registers\")\n"; return os.str(); } } unsigned id = _nextEventId++; std::ostringstream os; os << "if store to 0x" << std::hex << addr; EVENT evnt; std::string ret; if (type == ETYPE_BREAKPOINT) { std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; } else { std::ostringstream osList; osList << "#" << std::dec << id << ": trace"; if (REG_valid(reg)) osList << " " << GetRegName(reg); osList << " " << os.str(); evnt._listMsg = osList.str(); evnt._triggerMsg = os.str(); evnt._reg = reg; evnt._isDeleted = FALSE; evnt._isEnabled = TRUE; ret = "Tracepoint " + osList.str() + "\n"; } evnt._type = type; evnt._trigger = TRIGGER_STORE_TO; evnt._ea = addr; _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Parse an event with trigger type TRIGGER_STORE_VALUE_TO. * * @param[in] type The type of event. * @param[in] addrStr The trigger's store address. * @param[in] valueStr The trigger's store value. * @param[in] regStr If not empty, the register to trace. * * @return A string to return to the debugger prompt. */ std::string ParseTriggerStoreValueToEvent(ETYPE type, const std::string &addrStr, const std::string &valueStr, const std::string ®Str) { ADDRINT addr; if (!ParseNumber(addrStr, &addr)) { std::ostringstream os; os << "Invalid address " << addrStr << "\n"; return os.str(); } UINT64 value = 0; if (!ParseNumber(valueStr, &value)) { std::ostringstream os; os << "Invalid value " << valueStr << "\n"; return os.str(); } REG reg = REG_INVALID(); if (type == ETYPE_TRACEPOINT && !regStr.empty()) { reg = ParseRegName(regStr); if (!REG_valid(reg)) { std::ostringstream os; os << "Invalid register " << regStr << " (see \"help registers\")\n"; return os.str(); } } unsigned id = _nextEventId++; std::ostringstream os; os << "after store to 0x" << std::hex << addr << " == 0x" << std::hex << value; EVENT evnt; std::string ret; if (type == ETYPE_BREAKPOINT) { std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; } else { std::ostringstream osList; osList << "#" << std::dec << id << ": trace"; if (REG_valid(reg)) osList << " " << GetRegName(reg); osList << " " << os.str(); evnt._listMsg = osList.str(); evnt._triggerMsg = os.str(); evnt._reg = reg; evnt._isDeleted = FALSE; evnt._isEnabled = TRUE; ret = "Tracepoint " + osList.str() + "\n"; } evnt._type = type; evnt._trigger = TRIGGER_STORE_VALUE_TO; evnt._storeValueTo._ea = addr; evnt._storeValueTo._value = value; _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Parse an event with trigger type TRIGGER_AT_ICOUNT or TRIGGER_AT_MCOUNT * * @param[in] type The type of event. * @param[in] countStr The trigger's count. * @param[in] trigger The type of trigger (either icount or mcount). * @param[in} tid The id of the thread to trigger the breakpoint * * @return A string to return to the debugger prompt. */ std::string ParseTriggerAtCount(ETYPE type, const std::string &countStr, TRIGGER trigger, THREADID tid) { ASSERTX(type == ETYPE_BREAKPOINT); ASSERTX(trigger == TRIGGER_AT_ICOUNT || trigger == TRIGGER_AT_MCOUNT); UINT64 count = 0; if (!ParseNumber(countStr, &count)) { std::ostringstream os; os << "Invalid value " << countStr << "\n"; return os.str(); } unsigned id = _nextEventId++; std::ostringstream os; os << "thread " << std::dec << tid << " if " << (trigger==TRIGGER_AT_ICOUNT ? "icount" : "mcount") << " " << std::dec << count; EVENT evnt; std::string ret; std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; evnt._type = type; evnt._trigger = trigger; if (trigger == TRIGGER_AT_ICOUNT) { evnt._atIcount._icount = count; evnt._atIcount._tid = tid; } else { evnt._atMcount._mcount = count; evnt._atMcount._tid = tid; } _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Parse an event with trigger type TRIGGER_JUMP_TO. * * @param[in] type The type of event. * @param[in] addrStr The trigger's jump address. * @param[in] regStr If not empty, the register to trace. * * @return A string to return to the debugger prompt. */ std::string ParseTriggerJumpToEvent(ETYPE type, const std::string &addrStr, const std::string ®Str) { ADDRINT addr; if (!ParseNumber(addrStr, &addr)) { std::ostringstream os; os << "Invalid address " << addrStr << "\n"; return os.str(); } REG reg = REG_INVALID(); if (type == ETYPE_TRACEPOINT && !regStr.empty()) { reg = ParseRegName(regStr); if (!REG_valid(reg)) { std::ostringstream os; os << "Invalid register " << regStr << " (see \"help registers\")\n"; return os.str(); } } unsigned id = _nextEventId++; std::ostringstream os; os << "if jump to 0x" << std::hex << addr; EVENT evnt; std::string ret; if (type == ETYPE_BREAKPOINT) { std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; } else { std::ostringstream osList; osList << "#" << std::dec << id << ": trace"; if (REG_valid(reg)) osList << " " << GetRegName(reg); osList << " " << os.str(); evnt._listMsg = osList.str(); evnt._triggerMsg = os.str(); evnt._reg = reg; evnt._isDeleted = FALSE; evnt._isEnabled = TRUE; ret = "Tracepoint " + osList.str() + "\n"; } evnt._type = type; evnt._trigger = TRIGGER_JUMP_TO; evnt._pc = addr; _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Parse an event with trigger type TRIGGER_REG_IS. * * @param[in] type The type of event. * @param[in] pcStr The trigger's PC address. * @param[in] regCheckStr The trigger's register name. * @param[in] valueStr The trigger's register value. * @param[in] regTraceStr If not empty, the register to trace. * * @return A string to return to the debugger prompt. */ std::string ParseTriggerRegIsEvent(ETYPE type, const std::string &pcStr, const std::string ®CheckStr, const std::string &valueStr, const std::string ®TraceStr) { ADDRINT pc; if (!ParseNumber(pcStr, &pc)) { std::ostringstream os; os << "Invalid address " << pcStr << "\n"; return os.str(); } REG regCheck = ParseRegName(regCheckStr); if (!REG_valid(regCheck)) { std::ostringstream os; os << "Invalid register " << regCheckStr << " (see \"help registers\")\n"; return os.str(); } ADDRINT value; if (!ParseNumber(valueStr, &value)) { std::ostringstream os; os << "Invalid value " << valueStr << "\n"; return os.str(); } REG regTrace = REG_INVALID(); if (type == ETYPE_TRACEPOINT && !regTraceStr.empty()) { regTrace = ParseRegName(regTraceStr); if (!REG_valid(regTrace)) { std::ostringstream os; os << "Invalid register " << regTraceStr << " (see \"help registers\")\n"; return os.str(); } } unsigned id = _nextEventId++; std::ostringstream os; os << " at 0x" << std::hex << pc << " if " << GetRegName(regCheck) << " == 0x" << std::hex << value; EVENT evnt; std::string ret; if (type == ETYPE_BREAKPOINT) { std::ostringstream osTrigger; osTrigger << "Triggered breakpoint #" << std::dec << id << ": break " << os.str(); evnt._triggerMsg = osTrigger.str(); std::ostringstream osList; osList << "#" << std::dec << id << ": break " << os.str(); evnt._listMsg = osList.str(); ret = "Breakpoint " + osList.str() + "\n"; } else { std::ostringstream osList; osList << "#" << std::dec << id << ": trace"; if (REG_valid(regTrace)) osList << " " << GetRegName(regTrace); osList << " " << os.str(); evnt._listMsg = osList.str(); evnt._triggerMsg = os.str(); evnt._reg = regTrace; evnt._isDeleted = FALSE; evnt._isEnabled = TRUE; ret = "Tracepoint " + osList.str() + "\n"; } evnt._type = type; evnt._trigger = TRIGGER_REG_IS; evnt._regIs._pc = pc; evnt._regIs._reg = regCheck; evnt._regIs._value = value; _events.insert(std::make_pair(id, evnt)); Flush(); return ret; } /* * Print an error message. * * @param[in] message Text of the error message. */ void PrintError(const std::string &message) { PIN_WriteErrorMessage(message.c_str(), 1000, PIN_ERR_NONFATAL, 0); } /* -------------- Instrumentation Functions -------------- */ /* * Pin call-back to instrument a trace. * * @param[in] trace Trace to instrument. * @param[in] vme Pointer to ISHELL instance. */ static VOID InstrumentTrace(TRACE trace, void *vme) { SHELL *me = static_cast(vme); for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins)) { // Insert breakpoints before tracepoints because we don't want a tracepoint // to log anything until after execution resumes from the breakpoint. // BOOL insertSkipClear = FALSE; BOOL insertRecordEa = FALSE; me->InstrumentIns(ins, bbl, ETYPE_BREAKPOINT, &insertSkipClear, &insertRecordEa); me->InstrumentIns(ins, bbl, ETYPE_TRACEPOINT, &insertSkipClear, &insertRecordEa); // If there are any events with TRIGGER_STORE_VALUE_TO, record the store's effective address // at IPOINT_BEFORE. We only need to do this once, even if there are many such events. // if (insertRecordEa) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)ReturnAddrint, IARG_CALL_ORDER, me->_clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_MEMORYWRITE_EA, IARG_RETURN_REGS, me->_regRecordEa, IARG_END); } // If there are any "before" breakpoints, we need to clear the REG_SKIP_ONE // virtual register. // if (insertSkipClear) me->InsertSkipClear(ins); if (me->_clientArgs._enableIcountBreakpoints) me->InsertCountingInstrumentation(ins); } } } /* * Instrument an instruction. * * @param[in] ins Instruction to instrument. * @param[in] bbl Basic block containing \a ins. * @param[in] type Only insert instrumentation for events of this type. * @param[out] insertSkipClear If this instructions needs instrumentation to clear the * REG_SKIP_ONE register, \a insertSkipClear is set TRUE. * @param[out] insertRecordEa If this instructions needs instrumentation to record a * store's effective address, \a insertRecordEa is set TRUE. */ VOID InstrumentIns(INS ins, BBL bbl, ETYPE type, BOOL *insertSkipClear, BOOL *insertRecordEa) { for (EVENTS::iterator it = _events.begin(); it != _events.end(); ++it) { if (it->second._type != type) continue; if (type == ETYPE_TRACEPOINT && (it->second._isDeleted || !it->second._isEnabled)) continue; switch (it->second._trigger) { case TRIGGER_AT: if (INS_Address(ins) == it->second._pc) { if (type == ETYPE_BREAKPOINT) { InsertBreakpoint(ins, bbl, FALSE, IPOINT_BEFORE, it->second); *insertSkipClear = TRUE; } else { InsertTracepoint(ins, bbl, FALSE, IPOINT_BEFORE, it->first, it->second); } } break; case TRIGGER_AT_ICOUNT: if (type == ETYPE_BREAKPOINT) { InsertIcountBreakpoint(ins, bbl, it->second); *insertSkipClear = TRUE; } break; case TRIGGER_AT_MCOUNT: if (type == ETYPE_BREAKPOINT) { if (INS_IsMemoryRead(ins)||INS_IsMemoryWrite(ins)) { InsertMcountBreakpoint(ins, bbl, it->second); *insertSkipClear = TRUE; } } break; case TRIGGER_LOAD_FROM: if (INS_IsMemoryRead(ins)) { INS_InsertIfCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckAddrint, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_MEMORYREAD_EA, IARG_ADDRINT, it->second._ea, IARG_END); if (type == ETYPE_BREAKPOINT) { InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, it->second); *insertSkipClear = TRUE; } else { InsertTracepoint(ins, bbl, TRUE, IPOINT_BEFORE, it->first, it->second); } } break; case TRIGGER_STORE_TO: if (INS_IsMemoryWrite(ins)) { INS_InsertIfCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckAddrint, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_MEMORYWRITE_EA, IARG_ADDRINT, it->second._ea, IARG_END); if (type == ETYPE_BREAKPOINT) { InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, it->second); *insertSkipClear = TRUE; } else { InsertTracepoint(ins, bbl, TRUE, IPOINT_BEFORE, it->first, it->second); } } break; case TRIGGER_LOAD_VALUE_FROM: if (INS_IsMemoryRead(ins)) { if (type == ETYPE_BREAKPOINT) *insertSkipClear = TRUE; InstrumentLoadValueFrom(ins, bbl, it->first, it->second); } break; case TRIGGER_STORE_VALUE_TO: if (INS_IsMemoryWrite(ins)) { *insertRecordEa = TRUE; InstrumentStoreValueTo(ins, bbl, it->first, it->second); } break; case TRIGGER_JUMP_TO: if (INS_IsControlFlow(ins)) { INS_InsertIfCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckAddrint, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_BRANCH_TARGET_ADDR, IARG_ADDRINT, it->second._pc, IARG_END); if (type == ETYPE_BREAKPOINT) { InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, it->second); *insertSkipClear = TRUE; } else { InsertTracepoint(ins, bbl, TRUE, IPOINT_BEFORE, it->first, it->second); } } break; case TRIGGER_REG_IS: if (INS_Address(ins) == it->second._regIs._pc) { INS_InsertIfCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckAddrint, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, it->second._regIs._reg, IARG_ADDRINT, it->second._regIs._value, IARG_END); if (type == ETYPE_BREAKPOINT) { InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, it->second); *insertSkipClear = TRUE; } else { InsertTracepoint(ins, bbl, TRUE, IPOINT_BEFORE, it->first, it->second); } } break; } } } /* * Instrument an instruction with a TRIGGER_AT_ICOUNT event. * * @param[in] ins The instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] evnt The event descrption. */ VOID InsertIcountBreakpoint(INS ins, BBL bbl, const EVENT &evnt) { ASSERTX(_clientArgs._enableIcountBreakpoints); INS_InsertIfCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckIcount, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regThreadData, IARG_PTR, &evnt._atIcount, IARG_END); InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, evnt); } /* * Instrument a memory instruction with a TRIGGER_AT_MCOUNT event. * * @param[in] ins The memory instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] evnt The event description. */ VOID InsertMcountBreakpoint(INS ins, BBL bbl, const EVENT &evnt) { ASSERTX(_clientArgs._enableIcountBreakpoints); if (INS_HasRealRep(ins) && !_clientArgs._countZeroRepAsMemOp) { INS_InsertIfPredicatedCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckPredMcount, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regThreadData, IARG_PTR, &evnt._atMcount, IARG_EXECUTING, IARG_END); } else { INS_InsertIfCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckMcount, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regThreadData, IARG_PTR, &evnt._atMcount, IARG_END); } InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, evnt); } VOID InsertCountingInstrumentation(INS ins) { BOOL isMemory = INS_IsMemoryRead(ins) || INS_IsMemoryWrite(ins); if (INS_IsPrefetch(ins) && !_clientArgs._countPrefetchAsMemOp) isMemory = FALSE; if (isMemory) { if (INS_HasRealRep(ins) && !_clientArgs._countZeroRepAsMemOp) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)IncrementIcount, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regThreadData, IARG_END); INS_InsertPredicatedCall(ins, IPOINT_BEFORE, (AFUNPTR)IncrementMcount, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regThreadData, IARG_END); } else { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)IncrementIMcount, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regThreadData, IARG_END); } } else { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)IncrementIcount, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regThreadData, IARG_END); } } /* * Instrument a load instruction with a TRIGGER_LOAD_VALUE_FROM event. * * @param[in] ins The load instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] id The event ID. * @param[in] evnt The event descrption. */ VOID InstrumentLoadValueFrom(INS ins, BBL bbl, unsigned id, const EVENT &evnt) { switch (INS_MemoryReadSize(ins)) { case 1: InstrumentLoadValueFromForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue8); break; case 2: InstrumentLoadValueFromForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue16); break; case 4: InstrumentLoadValueFromForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue32); break; case 8: if (sizeof(ADDRINT) >= sizeof(UINT64)) InstrumentLoadValueFromForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValueAddrint); else InstrumentLoadValue64HiLo(ins, bbl, id, evnt); break; } } /* * Instrument a store instruction with a TRIGGER_STORE_VALUE_TO event. * * @param[in] ins The instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] id The event ID. * @param[in] evnt The event descrption. */ VOID InstrumentStoreValueTo(INS ins, BBL bbl, unsigned id, const EVENT &evnt) { switch (INS_MemoryWriteSize(ins)) { case 1: InstrumentStoreValueToForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue8); break; case 2: InstrumentStoreValueToForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue16); break; case 4: InstrumentStoreValueToForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue32); break; case 8: if (sizeof(ADDRINT) >= sizeof(UINT64)) InstrumentStoreValueToForSize(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValueAddrint); else InstrumentStoreValue64HiLo(ins, bbl, id, evnt); break; } } /* * Instrument a load instruction with a TRIGGER_LOAD_VALUE_FROM event. * * @tparam UINTX One of the UINT types, matching the size of the store. * There is an assumption that sizeof(UINTX) <= sizeof(ADDRINT). * @param[in] ins The load instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] id The event ID. * @param[in] evnt The event descrption. * @param[in] CheckLoadX One of the CheckAddressAndValue() analysis functions, matching the * size of the load. */ template VOID InstrumentLoadValueFromForSize(INS ins, BBL bbl, unsigned id, const EVENT &evnt, AFUNPTR CheckLoadX) { UINT64 value = evnt._loadValueFrom._value; ETYPE type = evnt._type; if (static_cast(value) == value) { INS_InsertIfCall(ins, IPOINT_BEFORE, CheckLoadX, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_MEMORYREAD_EA, IARG_ADDRINT, evnt._loadValueFrom._ea, IARG_ADDRINT, static_cast(value), IARG_END); if (type == ETYPE_BREAKPOINT) InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, evnt); else InsertTracepoint(ins, bbl, TRUE, IPOINT_BEFORE, id, evnt); } } /* * Instrument a store instruction with a TRIGGER_STORE_VALUE_TO event. * * @tparam UINTX One of the UINT types, matching the size of the store. * There is an assumption that sizeof(UINTX) <= sizeof(ADDRINT). * @param[in] ins The store instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] id The event ID. * @param[in] evnt The event descrption. * @param[in] CheckStoreX One of the CheckAddressAndValue() analysis functions, matching the * size of the store. */ template VOID InstrumentStoreValueToForSize(INS ins, BBL bbl, unsigned id, const EVENT &evnt, AFUNPTR CheckStoreX) { UINT64 value = evnt._storeValueTo._value; ETYPE type = evnt._type; if (static_cast(value) == value) { if (INS_IsValidForIpointAfter(ins)) { INS_InsertIfCall(ins, IPOINT_AFTER, CheckStoreX, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regRecordEa, IARG_ADDRINT, evnt._storeValueTo._ea, IARG_ADDRINT, static_cast(value), IARG_END); if (type == ETYPE_BREAKPOINT) InsertBreakpoint(ins, bbl, TRUE, IPOINT_AFTER, evnt); else InsertTracepoint(ins, bbl, TRUE, IPOINT_AFTER, id, evnt); } if (INS_IsValidForIpointTakenBranch(ins)) { INS_InsertIfCall(ins, IPOINT_TAKEN_BRANCH, CheckStoreX, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regRecordEa, IARG_ADDRINT, evnt._storeValueTo._ea, IARG_ADDRINT, static_cast(value), IARG_END); if (type == ETYPE_BREAKPOINT) InsertBreakpoint(ins, bbl, TRUE, IPOINT_TAKEN_BRANCH, evnt); else InsertTracepoint(ins, bbl, TRUE, IPOINT_TAKEN_BRANCH, id, evnt); } } } /* * Instrument a 64-bit load instruction with a TRIGGER_LOAD_VALUE_FROM event. * The value is checked using high and low ADDRINT parts (where ADDRINT is assumed * to be 32-bits). * * @param[in] ins The load instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] id The event ID. * @param[in] evnt The event descrption. */ VOID InstrumentLoadValue64HiLo(INS ins, BBL bbl, unsigned id, const EVENT &evnt) { UINT64 value = evnt._loadValueFrom._value; ETYPE type = evnt._type; ADDRINT hi = static_cast(value >> 32); ADDRINT lo = static_cast(value); INS_InsertIfCall(ins, IPOINT_BEFORE, (AFUNPTR)CheckAddressAndValue64, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_MEMORYREAD_EA, IARG_ADDRINT, evnt._loadValueFrom._ea, IARG_ADDRINT, hi, IARG_ADDRINT, lo, IARG_END); if (type == ETYPE_BREAKPOINT) InsertBreakpoint(ins, bbl, TRUE, IPOINT_BEFORE, evnt); else InsertTracepoint(ins, bbl, TRUE, IPOINT_BEFORE, id, evnt); } /* * Instrument a 64-bit store instruction with a TRIGGER_STORE_VALUE_TO event. * The value is checked using high and low ADDRINT parts (where ADDRINT is assumed * to be 32-bits). * * @param[in] ins The store instruction. * @param[in] bbl The basic block containing \a ins. * @param[in] id The event ID. * @param[in] evnt The event descrption. */ VOID InstrumentStoreValue64HiLo(INS ins, BBL bbl, unsigned id, const EVENT &evnt) { UINT64 value = evnt._storeValueTo._value; ETYPE type = evnt._type; ADDRINT hi = static_cast(value >> 32); ADDRINT lo = static_cast(value); if (INS_IsValidForIpointAfter(ins)) { INS_InsertIfCall(ins, IPOINT_AFTER, (AFUNPTR)CheckAddressAndValue64, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regRecordEa, IARG_ADDRINT, evnt._storeValueTo._ea, IARG_ADDRINT, hi, IARG_ADDRINT, lo, IARG_END); if (type == ETYPE_BREAKPOINT) InsertBreakpoint(ins, bbl, TRUE, IPOINT_AFTER, evnt); else InsertTracepoint(ins, bbl, TRUE, IPOINT_AFTER, id, evnt); } if (INS_IsValidForIpointTakenBranch(ins)) { INS_InsertIfCall(ins, IPOINT_TAKEN_BRANCH, (AFUNPTR)CheckAddressAndValue64, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_REG_VALUE, _regRecordEa, IARG_ADDRINT, evnt._storeValueTo._ea, IARG_ADDRINT, hi, IARG_ADDRINT, lo, IARG_END); if (type == ETYPE_BREAKPOINT) InsertBreakpoint(ins, bbl, TRUE, IPOINT_TAKEN_BRANCH, evnt); else InsertTracepoint(ins, bbl, TRUE, IPOINT_TAKEN_BRANCH, id, evnt); } } /* * Add the instrumentation for a call to the breakpoint analysis routine. * * @param[in] ins The instruction being instrumented. * @param[in] bbl The basic block containing \a ins. * @param[in] isThen TRUE if this should be a "then" instrumentation call. * @param[in] ipoint Where to place the instrumentation. * @param[in] evnt The ETYPE_BREAKPOINT event description. */ VOID InsertBreakpoint(INS ins, BBL bbl, BOOL isThen, IPOINT ipoint, const EVENT &evnt) { ASSERTX(evnt._type == ETYPE_BREAKPOINT); // Breakpoints alwas use "then" instrumentation currently. If that ever changes, // we need to extend the ICUSTOM_INSTRUMENTOR interface to communicate "then" // vs. non-"then" instrumentation to the client. // ASSERTX(isThen); if (_clientArgs._customInstrumentor) { if (ipoint == IPOINT_BEFORE) { _clientArgs._customInstrumentor->InsertBreakpointBefore(ins, bbl, _clientArgs._callOrderBefore, evnt._triggerMsg); } else { _clientArgs._customInstrumentor->InsertBreakpointAfter(ins, bbl, ipoint, _clientArgs._callOrderAfter, evnt._triggerMsg); } return; } if (ipoint == IPOINT_BEFORE) { INS_InsertThenCall(ins, ipoint, (AFUNPTR)TriggerBreakpointBefore, IARG_CALL_ORDER, _clientArgs._callOrderBefore, IARG_CONST_CONTEXT, // IARG_CONST_CONTEXT has much lower overhead // than IARG_CONTEX fog passing the CONTEXT* // to the analysis routine. Note that IARG_CONST_CONTEXT // passes a read-only CONTEXT* to the analysis routine IARG_THREAD_ID, IARG_UINT32, static_cast(_regSkipOne), IARG_PTR, evnt._triggerMsg.c_str(), IARG_END); } else { INS_InsertThenCall(ins, ipoint, (AFUNPTR)TriggerBreakpointAfter, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_CONST_CONTEXT, // IARG_CONST_CONTEXT has much lower overhead // than IARG_CONTEX fog passing the CONTEXT* // to the analysis routine. Note that IARG_CONST_CONTEXT // passes a read-only CONTEXT* to the analysis routine IARG_INST_PTR, IARG_THREAD_ID, IARG_PTR, evnt._triggerMsg.c_str(), IARG_END); } } /* * Add the instrumentation for a call to the tracepoint analysis routine. * * @param[in] ins The instruction being instrumented. * @param[in] bbl The basic block containing \a ins. * @param[in] isThen TRUE if this should be a "then" instrumentation call. * @param[in] ipoint Where to place the instrumentation. * @param[in] id The event ID. * @param[in] evnt The ETYPE_TRACEPOINT event description. */ VOID InsertTracepoint(INS ins, BBL bbl, BOOL isThen, IPOINT ipoint, unsigned id, const EVENT &evnt) { ASSERTX(evnt._type == ETYPE_TRACEPOINT); CALL_ORDER order; if (ipoint == IPOINT_BEFORE) order = _clientArgs._callOrderBefore; else order = _clientArgs._callOrderAfter; if (REG_valid(evnt._reg)) { if (isThen) { INS_InsertThenCall(ins, ipoint, (AFUNPTR)RecordTracepointAndReg, IARG_CALL_ORDER, order, IARG_PTR, this, IARG_UINT32, static_cast(id), IARG_INST_PTR, IARG_REG_VALUE, evnt._reg, IARG_END); } else { INS_InsertCall(ins, ipoint, (AFUNPTR)RecordTracepointAndReg, IARG_CALL_ORDER, order, IARG_PTR, this, IARG_UINT32, static_cast(id), IARG_INST_PTR, IARG_REG_VALUE, evnt._reg, IARG_END); } } else { if (isThen) { INS_InsertThenCall(ins, ipoint, (AFUNPTR)RecordTracepoint, IARG_CALL_ORDER, order, IARG_PTR, this, IARG_UINT32, static_cast(id), IARG_INST_PTR, IARG_END); } else { INS_InsertCall(ins, ipoint, (AFUNPTR)RecordTracepoint, IARG_CALL_ORDER, order, IARG_PTR, this, IARG_UINT32, static_cast(id), IARG_INST_PTR, IARG_END); } } } /* * Insert instrumentation after an instruction to clear the "skip one" flag. * * @param[in] ins The instruction. */ VOID InsertSkipClear(INS ins) { if (INS_IsValidForIpointAfter(ins)) { INS_InsertCall(ins, IPOINT_AFTER, (AFUNPTR)ReturnZero, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_RETURN_REGS, _regSkipOne, IARG_END); } if (INS_IsValidForIpointTakenBranch(ins)) { INS_InsertCall(ins, IPOINT_TAKEN_BRANCH, (AFUNPTR)ReturnZero, IARG_CALL_ORDER, _clientArgs._callOrderAfter, IARG_FAST_ANALYSIS_CALL, IARG_RETURN_REGS, _regSkipOne, IARG_END); } } /* -------------- Analysis Functions -------------- */ /* * These are all analysis routines that check for a trigger condition. They * should all be fast, and we expect Pin to inline them. */ static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddrint(ADDRINT a, ADDRINT b) { return (a == b); } static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValue8(ADDRINT ea, ADDRINT expect, ADDRINT value) { return (ea == expect) && (*reinterpret_cast(ea) == static_cast(value)); } static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValue16(ADDRINT ea, ADDRINT expect, ADDRINT value) { return (ea == expect) && (*reinterpret_cast(ea) == static_cast(value)); } static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValue32(ADDRINT ea, ADDRINT expect, ADDRINT value) { return (ea == expect) && (*reinterpret_cast(ea) == static_cast(value)); } static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValueAddrint(ADDRINT ea, ADDRINT expect, ADDRINT value) { return (ea == expect) && (*reinterpret_cast(ea) == value); } static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValue64(ADDRINT ea, ADDRINT expect, ADDRINT valueHi, ADDRINT valueLo) { UINT64 value = (static_cast(valueHi) << 32) | valueLo; return (ea == expect) && (*reinterpret_cast(ea) == value); } // check if the icount and the thread match the expected by the breakpoint static ADDRINT PIN_FAST_ANALYSIS_CALL CheckIcount(THREAD_DATA * td, AT_ICOUNT * expected) { // bit-wise "and" because logical "and" does not produce inline-able code return (td->_icount == expected->_icount) & (td->_tid == expected->_tid); } // check if the mcount and the thread match the expected by the breakpoint static ADDRINT PIN_FAST_ANALYSIS_CALL CheckMcount(THREAD_DATA * td, AT_MCOUNT * expected) { // bit-wise "and" because logical "and" does not produce inline-able code return (td->_mcount == expected->_mcount) & (td->_tid == expected->_tid); } static ADDRINT PIN_FAST_ANALYSIS_CALL CheckPredMcount(THREAD_DATA * td, AT_MCOUNT * expected, BOOL executing) { // bit-wise "and" because logical "and" does not produce inline-able code return executing & static_cast(CheckMcount(td, expected)); } /* * These are utility analysis routines that return values to be stored in a Pin virtual * register. They are meant to be used with IARG_RETURN_REGS. */ static ADDRINT PIN_FAST_ANALYSIS_CALL ReturnZero() { return 0; } static ADDRINT PIN_FAST_ANALYSIS_CALL ReturnAddrint(ADDRINT a) { return a; } /* * Analysis routine to keep track of the debugger shell state, such as instruction * count or memory count */ static VOID PIN_FAST_ANALYSIS_CALL IncrementIcount(THREAD_DATA * td) { td->_icount++; } static VOID PIN_FAST_ANALYSIS_CALL IncrementMcount(THREAD_DATA * td) { td->_mcount++; } static VOID PIN_FAST_ANALYSIS_CALL IncrementIMcount(THREAD_DATA * td) { td->_icount++; td->_mcount++; } /* * Trigger a breakpoint that occurs before an instruction. * * @param[in] ctxt Register state before the instruction. * NOTE that since IARG_CONST_CONTEXT was specified * this ctxt is read-only * @param[in] tid The calling thread. * @param[in] regSkipOne The REG_SKIP_ONE Pin virtual register. * @param[in] message Tells what breakpoint was triggered. */ static VOID TriggerBreakpointBefore(CONTEXT *ctxt, THREADID tid, UINT32 regSkipOne, const char *message) { // When we resume from the breakpoint, this analysis routine is re-executed. // This logic prevents the breakpoint from being re-triggered when we resume. // The REG_SKIP_ONE virtual register is cleared in the instruction's "after" // analysis function. // ADDRINT skipPc = PIN_GetContextReg(ctxt, static_cast(regSkipOne)); ADDRINT pc = PIN_GetContextReg(ctxt, REG_INST_PTR); if (skipPc == pc) return; CONTEXT writableContext; // since IARG_CONST_CONTEXT was specified ctxt is read-only // need to copy the ctxt into a writable context in order to do // the following PIN_SetContextReg PIN_SaveContext(ctxt, &writableContext); PIN_SetContextReg(&writableContext, static_cast(regSkipOne), pc); PIN_ApplicationBreakpoint(&writableContext, tid, FALSE, message); } /* * Trigger a breakpoint that occurs after an instruction. * * @param[in] ctxt Register state after the instruction (PC points to next instruction). * @param[in] pc PC of instruction that triggered the breakpoint. * @param[in] tid The calling thread. * @param[in] message Tells what breakpoint was triggered. */ static VOID TriggerBreakpointAfter(CONTEXT *ctxt, ADDRINT pc, THREADID tid, const char *message) { // Note, we don't need any special logic to prevent re-triggering this breakpoint // when we resume because 'ctxt' points at the next instruction. When resuming, we // start executing at the next instruction, so avoid re-evaluating the breakpoint // condition. // Tell the user the PC of the instruction that triggered the breakpoint because // the PC in 'ctxt' points at the next instruction. Otherwise, the triggering instruction // might not be obvious if it was a CALL or branch instruction. // std::ostringstream os; os << message << "\n"; os << "Breakpoint triggered after instruction at 0x" << std::hex << pc; PIN_ApplicationBreakpoint(ctxt, tid, FALSE, os.str()); } /* * Record a tracepoint with no register value. * * @param[in] me Points to our SHELL object. * @param[in] id Event ID for the tracepoint description. * @param[in] pc Trigger PC for tracepoint. */ static VOID RecordTracepoint(SHELL *me, UINT32 id, ADDRINT pc) { TRACEREC rec; rec._id = static_cast(id); rec._pc = pc; PIN_GetLock(&me->_traceLock, 1); me->_traceLog.push_back(rec); PIN_ReleaseLock(&me->_traceLock); } /* * Record a tracepoint with a register value. * * @param[in] me Points to our SHELL object. * @param[in] id Event ID for the tracepoint description. * @param[in] pc Trigger PC for tracepoint. * @param[in] regValue Trigger PC for tracepoint. */ static VOID RecordTracepointAndReg(SHELL *me, UINT32 id, ADDRINT pc, ADDRINT regValue) { TRACEREC rec; rec._id = static_cast(id); rec._pc = pc; rec._regValue = regValue; PIN_GetLock(&me->_traceLock, 1); me->_traceLog.push_back(rec); PIN_ReleaseLock(&me->_traceLock); } }; DEBUGGER_SHELL::ISHELL *DEBUGGER_SHELL::CreateShell() { SHELL *shell = new SHELL(); if (!shell->Construct()) { delete shell; return 0; } return shell; }