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.
2989 lines
103 KiB
2989 lines
103 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 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 <addr> == <value>") 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 == <addr> && *REG_RECORD_EA == <value>)
|
|
* {
|
|
* 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 <iostream>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#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<HELP> 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 <category>", 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_CATEGORY> 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<std::string, std::string> FORMAT_PAIR;
|
|
typedef std::vector<FORMAT_PAIR> FORMAT_PAIRS;
|
|
|
|
typedef std::vector<std::string> 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<unsigned, EVENT> 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<TRACEREC> 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<SHELL *>(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<SHELL *>(v);
|
|
|
|
td = reinterpret_cast<THREAD_DATA *>(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<SHELL *>(vme);
|
|
|
|
/*
|
|
* General Commands:
|
|
*
|
|
* help
|
|
* help <category>
|
|
*
|
|
* Breakpoint Commands:
|
|
*
|
|
* break if store to <addr>
|
|
* break after store to <addr> == <value>
|
|
* break if load from <addr>
|
|
* break before load from <addr> == <value>
|
|
* break if icount <count>
|
|
* break if mcount <count>
|
|
* break if jump to <pc>
|
|
* break at <pc> if <reg> == <value>
|
|
* list breakpoints
|
|
* delete breakpoint <id>
|
|
*
|
|
* Tracing Commands:
|
|
*
|
|
* trace [<reg>] at <pc>
|
|
* trace [<reg>] if load from <addr>
|
|
* trace [<reg>] before load from <addr> == <value>
|
|
* trace [<reg>] if store to <addr>
|
|
* trace [<reg>] after store to <addr> == <value>
|
|
* trace enable [<id>]
|
|
* trace disable [<id>]
|
|
* trace clear
|
|
* trace print [to <file>]
|
|
* list tracepoints
|
|
* delete tracepoint <id>
|
|
*
|
|
* 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 <category>
|
|
//
|
|
*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 <id>
|
|
//
|
|
*result = me->DeleteEvent(ETYPE_BREAKPOINT, words[2]);
|
|
return TRUE;
|
|
}
|
|
else if (nWords == 3 && words[0] == "delete" && words[1] == "tracepoint")
|
|
{
|
|
// delete tracepoint <id>
|
|
//
|
|
*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 <id>
|
|
//
|
|
*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 <id>
|
|
//
|
|
*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 <file>
|
|
//
|
|
*result = me->PrintTraceLog(words[3]);
|
|
return TRUE;
|
|
}
|
|
else if (nWords == 3 && words[0] == "trace" && words[1] == "at")
|
|
{
|
|
// trace at <pc>
|
|
//
|
|
*result = me->ParseTriggerAtEvent(ETYPE_TRACEPOINT, words[2], "");
|
|
return TRUE;
|
|
}
|
|
else if (nWords == 4 && words[0] == "trace" && words[2] == "at")
|
|
{
|
|
// trace <reg> at <pc>
|
|
//
|
|
*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 <addr>
|
|
//
|
|
*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 <addr>
|
|
//
|
|
*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 <addr>
|
|
//
|
|
*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 <reg> if store to <addr>
|
|
//
|
|
*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 <addr>
|
|
//
|
|
*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 <reg> if load from <addr>
|
|
//
|
|
*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 <addr> == <value>
|
|
//
|
|
*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 <addr> == <value>
|
|
//
|
|
*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 <addr> == <value>
|
|
//
|
|
*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 <reg> after store to <addr> == <value>
|
|
//
|
|
*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 <addr> == <value>
|
|
//
|
|
*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 <reg> before load from <addr> == <value>
|
|
//
|
|
*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 <count>
|
|
//
|
|
*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 <count>
|
|
//
|
|
*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 <pc>
|
|
//
|
|
*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 <pc> if <reg> == <value>
|
|
//
|
|
*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<typename T> 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 <category>",
|
|
"Print help on commands in <category>."));
|
|
|
|
|
|
// 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 <id>",
|
|
"Delete extended breakpoint <id>."));
|
|
helpCommands->push_back(HELP("break if load from <addr>",
|
|
"Break before any load from <addr>."));
|
|
helpCommands->push_back(HELP("break if store to <addr>",
|
|
"Break before any store to <addr>."));
|
|
helpCommands->push_back(HELP("break before load from <addr> == <value>",
|
|
"Break before load if <value> loaded from <addr>."));
|
|
helpCommands->push_back(HELP("break after store to <addr> == <value>",
|
|
"Break after store if <value> stored to <addr>."));
|
|
|
|
if (_clientArgs._enableIcountBreakpoints)
|
|
{
|
|
helpCommands->push_back(HELP("break if icount <count>",
|
|
"Break current thread before it reaches <count> instructions from the start of execution."));
|
|
helpCommands->push_back(HELP("break if mcount <count>",
|
|
"Break current thread before it reaches <count> memory instructions from the start of execution."));
|
|
}
|
|
|
|
helpCommands->push_back(HELP("break if jump to <pc>",
|
|
"Break before any jump to <pc>."));
|
|
helpCommands->push_back(HELP("break at <pc> if <reg> == <value>",
|
|
"Break before <pc> if <reg> contains <value>."));
|
|
|
|
|
|
// 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 <id>",
|
|
"Delete extended tracepoint <id>."));
|
|
helpCommands->push_back(HELP("trace print [to <file>]",
|
|
"Print contents of trace log to screen, or to <file>."));
|
|
helpCommands->push_back(HELP("trace clear",
|
|
"Clear contents of trace log."));
|
|
helpCommands->push_back(HELP("trace disable [<id>]",
|
|
"Disable all tracepoints, or only tracepoint <id>."));
|
|
helpCommands->push_back(HELP("trace enable [<id>]",
|
|
"Enable all tracepoints, or only tracepoint <id>."));
|
|
helpCommands->push_back(HELP("trace [<reg>] at <pc>",
|
|
"Record trace entry before executing instruction at <pc>. If <reg> is "
|
|
"specified, record that register's value too."));
|
|
helpCommands->push_back(HELP("trace [<reg>] if store to <addr>",
|
|
"Record trace entry before executing any store to <addr>. If <reg> is "
|
|
"specified, record that register's value too."));
|
|
helpCommands->push_back(HELP("trace [<reg>] after store to <addr> == <value>",
|
|
"Record trace entry after any store of <value> to <addr>. If <reg> is "
|
|
"specified, record that register's value too."));
|
|
helpCommands->push_back(HELP("trace [<reg>] if load from <addr>",
|
|
"Record trace entry before executing any load to <addr>. If <reg> is "
|
|
"specified, record that register's value too."));
|
|
helpCommands->push_back(HELP("trace [<reg>] before load from <addr> == <value>",
|
|
"Record trace entry before any load of <value> from <addr>. If <reg> 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 <reg> 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 <category>\" for help on commands in that category.\n");
|
|
_formattedCategoryHelp = text;
|
|
return _formattedCategoryHelp;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the formatted text for the "help <category>" 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<SHELL *>(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<UINT8>(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue8);
|
|
break;
|
|
case 2:
|
|
InstrumentLoadValueFromForSize<UINT16>(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue16);
|
|
break;
|
|
case 4:
|
|
InstrumentLoadValueFromForSize<UINT32>(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue32);
|
|
break;
|
|
case 8:
|
|
if (sizeof(ADDRINT) >= sizeof(UINT64))
|
|
InstrumentLoadValueFromForSize<UINT64>(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<UINT8>(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue8);
|
|
break;
|
|
case 2:
|
|
InstrumentStoreValueToForSize<UINT16>(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue16);
|
|
break;
|
|
case 4:
|
|
InstrumentStoreValueToForSize<UINT32>(ins, bbl, id, evnt, (AFUNPTR)CheckAddressAndValue32);
|
|
break;
|
|
case 8:
|
|
if (sizeof(ADDRINT) >= sizeof(UINT64))
|
|
InstrumentStoreValueToForSize<UINT64>(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<typename UINTX> 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<UINTX>(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<ADDRINT>(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<typename UINTX> 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<UINTX>(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<ADDRINT>(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<ADDRINT>(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<ADDRINT>(value >> 32);
|
|
ADDRINT lo = static_cast<ADDRINT>(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<ADDRINT>(value >> 32);
|
|
ADDRINT lo = static_cast<ADDRINT>(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<UINT32>(_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<UINT32>(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<UINT32>(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<UINT32>(id),
|
|
IARG_INST_PTR,
|
|
IARG_END);
|
|
}
|
|
else
|
|
{
|
|
INS_InsertCall(ins, ipoint, (AFUNPTR)RecordTracepoint,
|
|
IARG_CALL_ORDER, order,
|
|
IARG_PTR, this,
|
|
IARG_UINT32, static_cast<UINT32>(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<UINT8 *>(ea) == static_cast<UINT8>(value));
|
|
}
|
|
|
|
static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValue16(ADDRINT ea, ADDRINT expect, ADDRINT value)
|
|
{
|
|
return (ea == expect) && (*reinterpret_cast<UINT16 *>(ea) == static_cast<UINT16>(value));
|
|
}
|
|
|
|
static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValue32(ADDRINT ea, ADDRINT expect, ADDRINT value)
|
|
{
|
|
return (ea == expect) && (*reinterpret_cast<UINT32 *>(ea) == static_cast<UINT32>(value));
|
|
}
|
|
|
|
static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValueAddrint(ADDRINT ea, ADDRINT expect, ADDRINT value)
|
|
{
|
|
return (ea == expect) && (*reinterpret_cast<ADDRINT *>(ea) == value);
|
|
}
|
|
|
|
static ADDRINT PIN_FAST_ANALYSIS_CALL CheckAddressAndValue64(ADDRINT ea, ADDRINT expect,
|
|
ADDRINT valueHi, ADDRINT valueLo)
|
|
{
|
|
UINT64 value = (static_cast<UINT64>(valueHi) << 32) | valueLo;
|
|
return (ea == expect) && (*reinterpret_cast<UINT64 *>(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<BOOL>(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<REG>(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<REG>(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<unsigned>(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<unsigned>(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;
|
|
}
|