Debugger: Watch list

This commit is contained in:
Sour 2019-02-27 20:33:56 -05:00
parent 802bd75df1
commit 26e90e90a1
19 changed files with 2144 additions and 12 deletions

View file

@ -57,6 +57,7 @@
<ClInclude Include="ControlDeviceState.h" />
<ClInclude Include="ControlManager.h" />
<ClInclude Include="Cpu.h" />
<ClInclude Include="ExpressionEvaluator.h" />
<ClInclude Include="RegisterHandlerB.h" />
<ClInclude Include="CpuTypes.h" />
<ClInclude Include="Debugger.h" />
@ -123,6 +124,7 @@
<ClCompile Include="Disassembler.cpp" />
<ClCompile Include="DisassemblyInfo.cpp" />
<ClCompile Include="DmaController.cpp" />
<ClCompile Include="ExpressionEvaluator.cpp" />
<ClCompile Include="InternalRegisters.cpp" />
<ClCompile Include="KeyManager.cpp" />
<ClCompile Include="MemoryDumper.cpp" />

View file

@ -185,6 +185,9 @@
<ClInclude Include="RegisterHandlerA.h">
<Filter>SNES</Filter>
</ClInclude>
<ClInclude Include="ExpressionEvaluator.h">
<Filter>Debugger</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp" />
@ -287,6 +290,9 @@
<ClCompile Include="MemoryManager.cpp">
<Filter>SNES</Filter>
</ClCompile>
<ClCompile Include="ExpressionEvaluator.cpp">
<Filter>Debugger</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">

View file

@ -1,5 +1,13 @@
#pragma once
#include "stdafx.h"
#include "CpuTypes.h"
#include "PpuTypes.h"
struct DebugState
{
CpuState Cpu;
PpuState Ppu;
};
enum class SnesMemoryType
{
@ -18,6 +26,13 @@ struct AddressInfo
SnesMemoryType Type;
};
struct MemoryOperationInfo
{
uint32_t Address;
int32_t Value;
MemoryOperationType OperationType;
};
namespace CdlFlags
{
enum CdlFlags : uint8_t

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "Debugger.h"
#include "DebugTypes.h"
#include "Console.h"
#include "Cpu.h"
#include "Ppu.h"
@ -12,6 +13,7 @@
#include "MemoryDumper.h"
#include "CodeDataLogger.h"
#include "Disassembler.h"
#include "ExpressionEvaluator.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/FolderUtilities.h"
@ -22,6 +24,7 @@ Debugger::Debugger(shared_ptr<Console> console)
_ppu = console->GetPpu();
_memoryManager = console->GetMemoryManager();
_watchExpEval.reset(new ExpressionEvaluator(this));
_codeDataLogger.reset(new CodeDataLogger(console->GetCartridge()->DebugGetPrgRomSize()));
_disassembler.reset(new Disassembler(console, _codeDataLogger));
_traceLogger.reset(new TraceLogger(this, _memoryManager));
@ -109,6 +112,19 @@ void Debugger::ProcessCpuWrite(uint32_t addr, uint8_t value, MemoryOperationType
}
}
int32_t Debugger::EvaluateExpression(string expression, EvalResultType &resultType, bool useCache)
{
DebugState state;
MemoryOperationInfo operationInfo { 0, 0, MemoryOperationType::DummyRead };
GetState(&state);
if(useCache) {
return _watchExpEval->Evaluate(expression, state, resultType, operationInfo);
} else {
ExpressionEvaluator expEval(this);
return expEval.Evaluate(expression, state, resultType, operationInfo);
}
}
void Debugger::Run()
{
_cpuStepCount = -1;

View file

@ -11,16 +11,12 @@ class MemoryManager;
class CodeDataLogger;
enum class MemoryOperationType;
enum class EvalResultType : int32_t;
class TraceLogger;
class ExpressionEvaluator;
class MemoryDumper;
class Disassembler;
struct DebugState
{
CpuState Cpu;
PpuState Ppu;
//ApuState apuState;
};
struct DebugState;
class Debugger
{
@ -36,6 +32,8 @@ private:
shared_ptr<CodeDataLogger> _codeDataLogger;
shared_ptr<Disassembler> _disassembler;
unique_ptr<ExpressionEvaluator> _watchExpEval;
atomic<int32_t> _cpuStepCount;
uint8_t _prevOpCode = 0;
@ -46,6 +44,8 @@ public:
void ProcessCpuRead(uint32_t addr, uint8_t value, MemoryOperationType type);
void ProcessCpuWrite(uint32_t addr, uint8_t value, MemoryOperationType type);
int32_t EvaluateExpression(string expression, EvalResultType &resultType, bool useCache);
void Run();
void Step(int32_t stepCount);
bool IsExecutionStopped();

View file

@ -0,0 +1,604 @@
#include "stdafx.h"
#include <climits>
#include <algorithm>
#include "DebugTypes.h"
#include "ExpressionEvaluator.h"
#include "Console.h"
#include "Debugger.h"
#include "MemoryDumper.h"
#include "Disassembler.h"
#include "../Utilities/HexUtilities.h"
const vector<string> ExpressionEvaluator::_binaryOperators = { { "*", "/", "%", "+", "-", "<<", ">>", "<", "<=", ">", ">=", "==", "!=", "&", "^", "|", "&&", "||" } };
const vector<int> ExpressionEvaluator::_binaryPrecedence = { { 10, 10, 10, 9, 9, 8, 8, 7, 7, 7, 7, 6, 6, 5, 4, 3, 2, 1 } };
const vector<string> ExpressionEvaluator::_unaryOperators = { { "+", "-", "~", "!" } };
const vector<int> ExpressionEvaluator::_unaryPrecedence = { { 11, 11, 11, 11 } };
const std::unordered_set<string> ExpressionEvaluator::_operators = { { "*", "/", "%", "+", "-", "<<", ">>", "<", "<=", ">", ">=", "==", "!=", "&", "^", "|", "&&", "||", "~", "!", "(", ")", "{", "}", "[", "]" } };
bool ExpressionEvaluator::IsOperator(string token, int &precedence, bool unaryOperator)
{
if(unaryOperator) {
for(size_t i = 0, len = _unaryOperators.size(); i < len; i++) {
if(token.compare(_unaryOperators[i]) == 0) {
precedence = _unaryPrecedence[i];
return true;
}
}
} else {
for(size_t i = 0, len = _binaryOperators.size(); i < len; i++) {
if(token.compare(_binaryOperators[i]) == 0) {
precedence = _binaryPrecedence[i];
return true;
}
}
}
return false;
}
EvalOperators ExpressionEvaluator::GetOperator(string token, bool unaryOperator)
{
if(unaryOperator) {
for(size_t i = 0, len = _unaryOperators.size(); i < len; i++) {
if(token.compare(_unaryOperators[i]) == 0) {
return (EvalOperators)(EvalOperators::Plus + i);
}
}
} else {
for(size_t i = 0, len = _binaryOperators.size(); i < len; i++) {
if(token.compare(_binaryOperators[i]) == 0) {
return (EvalOperators)(EvalOperators::Multiplication + i);
}
}
}
return EvalOperators::Addition;
}
bool ExpressionEvaluator::CheckSpecialTokens(string expression, size_t &pos, string &output, ExpressionData &data)
{
string token;
size_t initialPos = pos;
size_t len = expression.size();
do {
char c = std::tolower(expression[pos]);
if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '@') {
//Only letters, numbers and underscore are allowed in code labels
token += c;
pos++;
} else {
break;
}
} while(pos < len);
if(token == "a") {
output += std::to_string((int64_t)EvalValues::RegA);
} else if(token == "x") {
output += std::to_string((int64_t)EvalValues::RegX);
} else if(token == "y") {
output += std::to_string((int64_t)EvalValues::RegY);
} else if(token == "ps") {
output += std::to_string((int64_t)EvalValues::RegPS);
} else if(token == "sp") {
output += std::to_string((int64_t)EvalValues::RegSP);
} else if(token == "pc") {
output += std::to_string((int64_t)EvalValues::RegPC);
} else if(token == "oppc") {
output += std::to_string((int64_t)EvalValues::RegOpPC);
} else if(token == "previousoppc") {
output += std::to_string((int64_t)EvalValues::PreviousOpPC);
} else if(token == "frame") {
output += std::to_string((int64_t)EvalValues::PpuFrameCount);
} else if(token == "cycle") {
output += std::to_string((int64_t)EvalValues::PpuCycle);
} else if(token == "scanline") {
output += std::to_string((int64_t)EvalValues::PpuScanline);
} else if(token == "irq") {
output += std::to_string((int64_t)EvalValues::Irq);
} else if(token == "nmi") {
output += std::to_string((int64_t)EvalValues::Nmi);
} else if(token == "verticalblank") {
output += std::to_string((int64_t)EvalValues::VerticalBlank);
} else if(token == "sprite0hit") {
output += std::to_string((int64_t)EvalValues::Sprite0Hit);
} else if(token == "spriteoverflow") {
output += std::to_string((int64_t)EvalValues::SpriteOverflow);
} else if(token == "value") {
output += std::to_string((int64_t)EvalValues::Value);
} else if(token == "address") {
output += std::to_string((int64_t)EvalValues::Address);
} else if(token == "romaddress") {
output += std::to_string((int64_t)EvalValues::AbsoluteAddress);
} else if(token == "iswrite") {
output += std::to_string((int64_t)EvalValues::IsWrite);
} else if(token == "isread") {
output += std::to_string((int64_t)EvalValues::IsRead);
} else if(token == "branched") {
output += std::to_string((int64_t)EvalValues::Branched);
} else {
return false;
//TODO LABELS
/*string originalExpression = expression.substr(initialPos, pos - initialPos);
bool validLabel = _debugger->GetLabelManager()->ContainsLabel(originalExpression);
if(!validLabel) {
//Check if a multi-byte label exists for this name
string label = originalExpression + "+0";
validLabel = _debugger->GetLabelManager()->ContainsLabel(label);
}
if(validLabel) {
data.Labels.push_back(originalExpression);
output += std::to_string(EvalValues::FirstLabelIndex + data.Labels.size() - 1);
} else {
return false;
}*/
}
return true;
}
string ExpressionEvaluator::GetNextToken(string expression, size_t &pos, ExpressionData &data, bool &success)
{
string output;
success = true;
char c = std::tolower(expression[pos]);
if(c == '$') {
//Hex numbers
pos++;
for(size_t len = expression.size(); pos < len; pos++) {
c = std::tolower(expression[pos]);
if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
output += c;
} else {
break;
}
}
if(output.empty()) {
//No numbers followed the hex mark, this isn't a valid expression
success = false;
}
output = std::to_string((uint32_t)HexUtilities::FromHex(output));
} else if(c == '%') {
//Binary numbers
pos++;
for(size_t len = expression.size(); pos < len; pos++) {
c = std::tolower(expression[pos]);
if(c == '0' || c <= '1') {
output += c;
} else {
break;
}
}
if(output.empty()) {
//No numbers followed the binary mark, this isn't a valid expression
success = false;
}
uint32_t value = 0;
for(size_t i = 0; i < output.size(); i++) {
value <<= 1;
value |= output[i] == '1' ? 1 : 0;
}
output = std::to_string(value);
} else if(c >= '0' && c <= '9') {
//Regular numbers
for(size_t len = expression.size(); pos < len; pos++) {
c = std::tolower(expression[pos]);
if(c >= '0' && c <= '9') {
output += c;
} else {
break;
}
}
} else if((c < 'a' || c > 'z') && c != '_' && c != '@') {
//Operators
string operatorToken;
for(size_t len = expression.size(); pos < len; pos++) {
c = std::tolower(expression[pos]);
operatorToken += c;
if(output.empty() || _operators.find(operatorToken) != _operators.end()) {
//If appending the next char results in a valid operator, append it (or if this is the first character)
output += c;
} else {
//Reached the end of the operator, return
break;
}
}
} else {
//Special tokens and labels
success = CheckSpecialTokens(expression, pos, output, data);
}
return output;
}
bool ExpressionEvaluator::ProcessSpecialOperator(EvalOperators evalOp, std::stack<EvalOperators> &opStack, std::stack<int> &precedenceStack, vector<int64_t> &outputQueue)
{
if(opStack.empty()) {
return false;
}
while(opStack.top() != evalOp) {
outputQueue.push_back(opStack.top());
opStack.pop();
precedenceStack.pop();
if(opStack.empty()) {
return false;
}
}
if(evalOp != EvalOperators::Parenthesis) {
outputQueue.push_back(opStack.top());
}
opStack.pop();
precedenceStack.pop();
return true;
}
bool ExpressionEvaluator::ToRpn(string expression, ExpressionData &data)
{
std::stack<EvalOperators> opStack = std::stack<EvalOperators>();
std::stack<int> precedenceStack;
size_t position = 0;
int parenthesisCount = 0;
int bracketCount = 0;
int braceCount = 0;
bool previousTokenIsOp = true;
bool operatorExpected = false;
bool operatorOrEndTokenExpected = false;
while(true) {
bool success = true;
string token = GetNextToken(expression, position, data, success);
if(!success) {
return false;
}
if(token.empty()) {
break;
}
bool requireOperator = operatorExpected;
bool requireOperatorOrEndToken = operatorOrEndTokenExpected;
bool unaryOperator = previousTokenIsOp;
operatorExpected = false;
operatorOrEndTokenExpected = false;
previousTokenIsOp = false;
int precedence = 0;
if(IsOperator(token, precedence, unaryOperator)) {
EvalOperators op = GetOperator(token, unaryOperator);
bool rightAssociative = unaryOperator;
while(!opStack.empty() && ((rightAssociative && precedence < precedenceStack.top()) || (!rightAssociative && precedence <= precedenceStack.top()))) {
//Pop operators from the stack until we find something with higher priority (or empty the stack)
data.RpnQueue.push_back(opStack.top());
opStack.pop();
precedenceStack.pop();
}
opStack.push(op);
precedenceStack.push(precedence);
previousTokenIsOp = true;
} else if(requireOperator) {
//We needed an operator, and got something else, this isn't a valid expression (e.g "(3)4" or "[$00]22")
return false;
} else if(requireOperatorOrEndToken && token[0] != ')' && token[0] != ']' && token[0] != '}') {
//We needed an operator or close token - this isn't a valid expression (e.g "%1134")
return false;
} else if(token[0] == '(') {
parenthesisCount++;
opStack.push(EvalOperators::Parenthesis);
precedenceStack.push(0);
previousTokenIsOp = true;
} else if(token[0] == ')') {
parenthesisCount--;
if(!ProcessSpecialOperator(EvalOperators::Parenthesis, opStack, precedenceStack, data.RpnQueue)) {
return false;
}
operatorExpected = true;
} else if(token[0] == '[') {
bracketCount++;
opStack.push(EvalOperators::Bracket);
precedenceStack.push(0);
} else if(token[0] == ']') {
bracketCount--;
if(!ProcessSpecialOperator(EvalOperators::Bracket, opStack, precedenceStack, data.RpnQueue)) {
return false;
}
operatorExpected = true;
} else if(token[0] == '{') {
braceCount++;
opStack.push(EvalOperators::Braces);
precedenceStack.push(0);
} else if(token[0] == '}') {
braceCount--;
if(!ProcessSpecialOperator(EvalOperators::Braces, opStack, precedenceStack, data.RpnQueue)){
return false;
}
operatorExpected = true;
} else {
if(token[0] < '0' || token[0] > '9') {
return false;
} else {
data.RpnQueue.push_back(std::stoll(token));
operatorOrEndTokenExpected = true;
}
}
}
if(braceCount || bracketCount || parenthesisCount) {
//Mismatching number of brackets/braces/parenthesis
return false;
}
while(!opStack.empty()) {
data.RpnQueue.push_back(opStack.top());
opStack.pop();
}
return true;
}
int32_t ExpressionEvaluator::Evaluate(ExpressionData &data, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo)
{
if(data.RpnQueue.empty()) {
resultType = EvalResultType::Invalid;
return 0;
}
int pos = 0;
int64_t right = 0;
int64_t left = 0;
resultType = EvalResultType::Numeric;
for(size_t i = 0, len = data.RpnQueue.size(); i < len; i++) {
int64_t token = data.RpnQueue[i];
if(token >= EvalValues::RegA) {
//Replace value with a special value
if(token >= EvalValues::FirstLabelIndex) {
resultType = EvalResultType::Invalid;
return 0;
//TODO
/*int64_t labelIndex = token - EvalValues::FirstLabelIndex;
if((size_t)labelIndex < data.Labels.size()) {
token = _debugger->GetLabelManager()->GetLabelRelativeAddress(data.Labels[(uint32_t)labelIndex]);
if(token < -1) {
//Label doesn't exist, try to find a matching multi-byte label
string label = data.Labels[(uint32_t)labelIndex] + "+0";
token = _debugger->GetLabelManager()->GetLabelRelativeAddress(label);
}
} else {
token = -2;
}
if(token < 0) {
//Label is no longer valid
resultType = token == -1 ? EvalResultType::OutOfScope : EvalResultType::Invalid;
return 0;
}*/
} else {
switch(token) {
case EvalValues::RegA: token = state.Cpu.A; break;
case EvalValues::RegX: token = state.Cpu.X; break;
case EvalValues::RegY: token = state.Cpu.Y; break;
case EvalValues::RegSP: token = state.Cpu.SP; break;
case EvalValues::RegPS: token = state.Cpu.PS; break;
case EvalValues::RegPC: token = state.Cpu.PC; break;
//TODO
/*case EvalValues::RegOpPC: token = state.Cpu.DebugPC; break;*/
case EvalValues::PpuFrameCount: token = state.Ppu.FrameCount; break;
case EvalValues::PpuCycle: token = state.Ppu.Cycle; break;
case EvalValues::PpuScanline: token = state.Ppu.Scanline; break;
//TODO
/*case EvalValues::Nmi: token = state.CPU.NMIFlag; resultType = EvalResultType::Boolean; break;
case EvalValues::Irq: token = state.CPU.IRQFlag; resultType = EvalResultType::Boolean; break;
case EvalValues::Value: token = operationInfo.Value; break;
case EvalValues::Address: token = operationInfo.Address; break;
case EvalValues::AbsoluteAddress: token = _debugger->GetAbsoluteAddress(operationInfo.Address); break;
case EvalValues::IsWrite: token = operationInfo.OperationType == MemoryOperationType::Write || operationInfo.OperationType == MemoryOperationType::DummyWrite; break;
case EvalValues::IsRead: token = operationInfo.OperationType == MemoryOperationType::Read || operationInfo.OperationType == MemoryOperationType::DummyRead; break;
case EvalValues::PreviousOpPC: token = state.CPU.PreviousDebugPC; break;
case EvalValues::Sprite0Hit: token = state.PPU.StatusFlags.Sprite0Hit; resultType = EvalResultType::Boolean; break;
case EvalValues::SpriteOverflow: token = state.PPU.StatusFlags.SpriteOverflow; resultType = EvalResultType::Boolean; break;
case EvalValues::VerticalBlank: token = state.PPU.StatusFlags.VerticalBlank; resultType = EvalResultType::Boolean; break;
case EvalValues::Branched: token = Disassembler::IsJump(_debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, state.CPU.PreviousDebugPC, true)); resultType = EvalResultType::Boolean; break;*/
}
}
} else if(token >= EvalOperators::Multiplication) {
right = operandStack[--pos];
if(pos > 0 && token <= EvalOperators::LogicalOr) {
//Only do this for binary operators
left = operandStack[--pos];
}
resultType = EvalResultType::Numeric;
switch(token) {
case EvalOperators::Multiplication: token = left * right; break;
case EvalOperators::Division:
if(right == 0) {
resultType = EvalResultType::DivideBy0;
return 0;
}
token = left / right; break;
case EvalOperators::Modulo:
if(right == 0) {
resultType = EvalResultType::DivideBy0;
return 0;
}
token = left % right;
break;
case EvalOperators::Addition: token = left + right; break;
case EvalOperators::Substration: token = left - right; break;
case EvalOperators::ShiftLeft: token = left << right; break;
case EvalOperators::ShiftRight: token = left >> right; break;
case EvalOperators::SmallerThan: token = left < right; resultType = EvalResultType::Boolean; break;
case EvalOperators::SmallerOrEqual: token = left <= right; resultType = EvalResultType::Boolean; break;
case EvalOperators::GreaterThan: token = left > right; resultType = EvalResultType::Boolean; break;
case EvalOperators::GreaterOrEqual: token = left >= right; resultType = EvalResultType::Boolean; break;
case EvalOperators::Equal: token = left == right; resultType = EvalResultType::Boolean; break;
case EvalOperators::NotEqual: token = left != right; resultType = EvalResultType::Boolean; break;
case EvalOperators::BinaryAnd: token = left & right; break;
case EvalOperators::BinaryXor: token = left ^ right; break;
case EvalOperators::BinaryOr: token = left | right; break;
case EvalOperators::LogicalAnd: token = left && right; resultType = EvalResultType::Boolean; break;
case EvalOperators::LogicalOr: token = left || right; resultType = EvalResultType::Boolean; break;
//Unary operators
case EvalOperators::Plus: token = right; break;
case EvalOperators::Minus: token = -right; break;
case EvalOperators::BinaryNot: token = ~right; break;
case EvalOperators::LogicalNot: token = !right; break;
case EvalOperators::Bracket: token = _debugger->GetMemoryDumper()->GetMemoryValue(SnesMemoryType::CpuMemory, (uint32_t)right); break;
case EvalOperators::Braces: token = _debugger->GetMemoryDumper()->GetMemoryValueWord(SnesMemoryType::CpuMemory, (uint32_t)right); break;
default: throw std::runtime_error("Invalid operator");
}
}
operandStack[pos++] = token;
}
return (int32_t)operandStack[0];
}
ExpressionEvaluator::ExpressionEvaluator(Debugger* debugger)
{
_debugger = debugger;
}
ExpressionData ExpressionEvaluator::GetRpnList(string expression, bool &success)
{
ExpressionData* cachedData = PrivateGetRpnList(expression, success);
if(cachedData) {
return *cachedData;
} else {
return ExpressionData();
}
}
ExpressionData* ExpressionEvaluator::PrivateGetRpnList(string expression, bool& success)
{
ExpressionData *cachedData = nullptr;
{
LockHandler lock = _cacheLock.AcquireSafe();
auto result = _cache.find(expression);
if(result != _cache.end()) {
cachedData = &(result->second);
}
}
if(cachedData == nullptr) {
string fixedExp = expression;
fixedExp.erase(std::remove(fixedExp.begin(), fixedExp.end(), ' '), fixedExp.end());
ExpressionData data;
success = ToRpn(fixedExp, data);
if(success) {
LockHandler lock = _cacheLock.AcquireSafe();
_cache[expression] = data;
cachedData = &_cache[expression];
}
} else {
success = true;
}
return cachedData;
}
int32_t ExpressionEvaluator::PrivateEvaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo, bool& success)
{
success = true;
ExpressionData *cachedData = PrivateGetRpnList(expression, success);
if(!success) {
resultType = EvalResultType::Invalid;
return 0;
}
return Evaluate(*cachedData, state, resultType, operationInfo);
}
int32_t ExpressionEvaluator::Evaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo)
{
try {
bool success;
int32_t result = PrivateEvaluate(expression, state, resultType, operationInfo, success);
if(success) {
return result;
}
} catch(std::exception e) {
}
resultType = EvalResultType::Invalid;
return 0;
}
bool ExpressionEvaluator::Validate(string expression)
{
try {
DebugState state;
EvalResultType type;
MemoryOperationInfo operationInfo;
bool success;
PrivateEvaluate(expression, state, type, operationInfo, success);
return success;
} catch(std::exception e) {
return false;
}
}
#if _DEBUG
#include <assert.h>
void ExpressionEvaluator::RunTests()
{
//Some basic unit tests to run in debug mode
auto test = [=](string expr, EvalResultType expectedType, int expectedResult) {
DebugState state = { 0 };
OperationInfo opInfo = { 0 };
EvalResultType type;
int32_t result = Evaluate(expr, state, type, opInfo);
assert(type == expectedType);
assert(result == expectedResult);
};
test("1 - -1", EvalResultType::Numeric, 2);
test("1 - (-1)", EvalResultType::Numeric, 2);
test("1 - -(-1)", EvalResultType::Numeric, 0);
test("(0 - 1) == -1 && 5 < 10", EvalResultType::Boolean, true);
test("(0 - 1) == 0 || 5 < 10", EvalResultType::Boolean, true);
test("(0 - 1) == 0 || 5 < -10", EvalResultType::Boolean, false);
test("(0 - 1) == 0 || 15 < 10", EvalResultType::Boolean, false);
test("10 != $10", EvalResultType::Boolean, true);
test("10 == $A", EvalResultType::Boolean, true);
test("10 == $0A", EvalResultType::Boolean, true);
test("(0 - 1 == 0 || 15 < 10", EvalResultType::Invalid, 0);
test("10 / 0", EvalResultType::DivideBy0, 0);
test("x + 5", EvalResultType::Numeric, 5);
test("x == 0", EvalResultType::Boolean, true);
test("x == y", EvalResultType::Boolean, true);
test("x == y == scanline", EvalResultType::Boolean, false); //because (x == y) is true, and true != scanline
test("x == y && !(a == x)", EvalResultType::Boolean, false);
test("(~0 & ~1) & $FFF == $FFE", EvalResultType::Numeric, 0); //because of operator priority (& is done after ==)
test("((~0 & ~1) & $FFF) == $FFE", EvalResultType::Boolean, true);
test("1+3*3+10/(3+4)", EvalResultType::Numeric, 11);
test("(1+3*3+10)/(3+4)", EvalResultType::Numeric, 2);
test("(1+3*3+10)/3+4", EvalResultType::Numeric, 10);
test("{$4500}", EvalResultType::Numeric, 0x4545);
test("[$4500]", EvalResultType::Numeric, 0x45);
test("[$45]3", EvalResultType::Invalid, 0);
test("($45)3", EvalResultType::Invalid, 0);
test("($45]", EvalResultType::Invalid, 0);
test("%11", EvalResultType::Numeric, 3);
test("%011", EvalResultType::Numeric, 3);
test("%1011", EvalResultType::Numeric, 11);
test("%12", EvalResultType::Invalid, 0);
}
#endif

137
Core/ExpressionEvaluator.h Normal file
View file

@ -0,0 +1,137 @@
#pragma once
#include "stdafx.h"
#include <stack>
#include <deque>
#include <unordered_map>
#include <unordered_set>
#include "DebugTypes.h"
#include "../Utilities/SimpleLock.h"
class Debugger;
enum EvalOperators : int64_t
{
//Binary operators
Multiplication = 20000000000,
Division = 20000000001,
Modulo = 20000000002,
Addition = 20000000003,
Substration = 20000000004,
ShiftLeft = 20000000005,
ShiftRight = 20000000006,
SmallerThan = 20000000007,
SmallerOrEqual = 20000000008,
GreaterThan = 20000000009,
GreaterOrEqual = 20000000010,
Equal = 20000000011,
NotEqual = 20000000012,
BinaryAnd = 20000000013,
BinaryXor = 20000000014,
BinaryOr = 20000000015,
LogicalAnd = 20000000016,
LogicalOr = 20000000017,
//Unary operators
Plus = 20000000050,
Minus = 20000000051,
BinaryNot = 20000000052,
LogicalNot = 20000000053,
//Used to read ram address
Bracket = 20000000054, //Read byte
Braces = 20000000055, //Read word
//Special value, not used as an operator
Parenthesis = 20000000100,
};
enum EvalValues : int64_t
{
RegA = 20000000100,
RegX = 20000000101,
RegY = 20000000102,
RegSP = 20000000103,
RegPS = 20000000104,
RegPC = 20000000105,
RegOpPC = 20000000106,
PpuFrameCount = 20000000107,
PpuCycle = 20000000108,
PpuScanline = 20000000109,
Nmi = 20000000110,
Irq = 20000000111,
Value = 20000000112,
Address = 20000000113,
AbsoluteAddress = 20000000114,
IsWrite = 20000000115,
IsRead = 20000000116,
PreviousOpPC = 20000000117,
Sprite0Hit = 20000000118,
SpriteOverflow = 20000000119,
VerticalBlank = 20000000120,
Branched = 20000000121,
FirstLabelIndex = 20000002000,
};
enum class EvalResultType : int32_t
{
Numeric = 0,
Boolean = 1,
Invalid = 2,
DivideBy0 = 3,
OutOfScope = 4
};
class StringHasher
{
public:
size_t operator()(const std::string& t) const
{
//Quick hash for expressions - most are likely to have different lengths, and not expecting dozens of breakpoints, either, so this should be fine.
return t.size();
}
};
struct ExpressionData
{
std::vector<int64_t> RpnQueue;
std::vector<string> Labels;
};
class ExpressionEvaluator
{
private:
static const vector<string> _binaryOperators;
static const vector<int> _binaryPrecedence;
static const vector<string> _unaryOperators;
static const vector<int> _unaryPrecedence;
static const std::unordered_set<string> _operators;
std::unordered_map<string, ExpressionData, StringHasher> _cache;
SimpleLock _cacheLock;
int64_t operandStack[1000];
Debugger* _debugger;
bool IsOperator(string token, int &precedence, bool unaryOperator);
EvalOperators GetOperator(string token, bool unaryOperator);
bool CheckSpecialTokens(string expression, size_t &pos, string &output, ExpressionData &data);
string GetNextToken(string expression, size_t &pos, ExpressionData &data, bool &success);
bool ProcessSpecialOperator(EvalOperators evalOp, std::stack<EvalOperators> &opStack, std::stack<int> &precedenceStack, vector<int64_t> &outputQueue);
bool ToRpn(string expression, ExpressionData &data);
int32_t PrivateEvaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo, bool &success);
ExpressionData* PrivateGetRpnList(string expression, bool& success);
public:
ExpressionEvaluator(Debugger* debugger);
int32_t Evaluate(ExpressionData &data, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo);
int32_t Evaluate(string expression, DebugState &state, EvalResultType &resultType, MemoryOperationInfo &operationInfo);
ExpressionData GetRpnList(string expression, bool &success);
bool Validate(string expression);
#if _DEBUG
void RunTests();
#endif
};

View file

@ -111,3 +111,11 @@ uint8_t MemoryDumper::GetMemoryValue(SnesMemoryType memoryType, uint32_t address
return 0;
}
uint8_t MemoryDumper::GetMemoryValueWord(SnesMemoryType memoryType, uint32_t address)
{
uint32_t memorySize = GetMemorySize(memoryType);
uint8_t lsb = GetMemoryValue(memoryType, address);
uint8_t msb = GetMemoryValue(memoryType, (address + 1) & (memorySize - 1));
return (msb << 8) | lsb;
}

View file

@ -22,6 +22,7 @@ public:
void GetMemoryState(SnesMemoryType type, uint8_t *buffer);
uint8_t GetMemoryValue(SnesMemoryType memoryType, uint32_t address, bool disableSideEffects = true);
uint8_t GetMemoryValueWord(SnesMemoryType memoryType, uint32_t address);
void SetMemoryValue(SnesMemoryType memoryType, uint32_t address, uint8_t value, bool disableSideEffects = true);
void SetMemoryValues(SnesMemoryType memoryType, uint32_t address, uint8_t* data, uint32_t length);
void SetMemoryState(SnesMemoryType type, uint8_t *buffer, uint32_t length);

View file

@ -46,6 +46,8 @@ extern "C"
DllExport void __stdcall StopTraceLogger() { GetDebugger()->GetTraceLogger()->StopLogging(); }
DllExport const char* GetExecutionTrace(uint32_t lineCount) { return GetDebugger()->GetTraceLogger()->GetExecutionTrace(lineCount); }
DllExport int32_t __stdcall EvaluateExpression(char* expression, EvalResultType *resultType, bool useCache) { return GetDebugger()->EvaluateExpression(expression, *resultType, useCache); }
DllExport void __stdcall GetState(DebugState *state) { GetDebugger()->GetState(state); }
DllExport void __stdcall SetMemoryState(SnesMemoryType type, uint8_t *buffer, int32_t length) { GetDebugger()->GetMemoryDumper()->SetMemoryState(type, buffer, length); }

View file

@ -39,6 +39,8 @@ namespace Mesen.GUI.Config
public XmlColor CodeReadBreakpointColor = Color.FromArgb(40, 40, 200);
public XmlColor CodeActiveStatementColor = Color.Yellow;
public WatchFormatStyle WatchFormat = WatchFormatStyle.Hex;
public DebugInfo()
{
}

View file

@ -0,0 +1,515 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Mesen.GUI.Config;
using Mesen.GUI.Controls;
using System.Text.RegularExpressions;
using System.Globalization;
using Mesen.GUI.Forms;
namespace Mesen.GUI.Debugger
{
public partial class ctrlWatch : BaseControl
{
private Color _updatedColor = Color.Red;
private Color _normalColor = SystemColors.ControlText;
private static Regex _watchAddressOrLabel = new Regex(@"^(\[|{)(\s*((\$[0-9A-Fa-f]+)|(\d+)|([@_a-zA-Z0-9]+)))\s*[,]{0,1}\d*\s*(\]|})$", RegexOptions.Compiled);
private int _previousMaxLength = -1;
private int _selectedAddress = -1;
//private CodeLabel _selectedLabel = null;
private List<WatchValueInfo> _previousValues = new List<WatchValueInfo>();
private bool _isEditing = false;
ListViewItem _keyDownItem = null;
public ctrlWatch()
{
InitializeComponent();
this.DoubleBuffered = true;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if(!IsDesignMode) {
WatchManager.WatchChanged += WatchManager_WatchChanged;
mnuRemoveWatch.InitShortcut(this, nameof(DebuggerShortcutsConfig.WatchList_Delete));
mnuEditInMemoryViewer.InitShortcut(this, nameof(DebuggerShortcutsConfig.CodeWindow_EditInMemoryViewer));
mnuViewInDisassembly.InitShortcut(this, nameof(DebuggerShortcutsConfig.MemoryViewer_ViewInDisassembly));
mnuMoveUp.InitShortcut(this, nameof(DebuggerShortcutsConfig.WatchList_MoveUp));
mnuMoveDown.InitShortcut(this, nameof(DebuggerShortcutsConfig.WatchList_MoveDown));
}
}
public string GetTooltipText()
{
return "";
/*return (
frmBreakpoint.GetConditionTooltip(true) + Environment.NewLine + Environment.NewLine +
"Additionally, the watch window supports a syntax to display X bytes starting from a specific address. e.g:" + Environment.NewLine +
"[$10, 16]: Display 16 bytes starting from address $10" + Environment.NewLine +
"[MyLabel, 4]: Display 4 bytes starting from the address the specified label (MyLabel) refers to"
);*/
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if(lstWatch.SelectedItems.Count > 0) {
//Used to prevent a Mono issue where pressing a key will change the selected item before we get a chance to edit it
_keyDownItem = lstWatch.SelectedItems[0];
if((_isEditing && keyData == Keys.Escape) || keyData == Keys.Enter) {
if(keyData == Keys.Enter) {
if(_isEditing) {
ApplyEdit();
} else {
StartEdit(lstWatch.SelectedItems[0].Text);
}
} else if(keyData == Keys.Escape) {
CancelEdit();
}
return true;
}
} else {
_keyDownItem = null;
}
UpdateActions();
return base.ProcessCmdKey(ref msg, keyData);
}
private void contextMenuWatch_Opening(object sender, CancelEventArgs e)
{
UpdateActions();
}
private void WatchManager_WatchChanged(object sender, EventArgs e)
{
if(this.InvokeRequired) {
this.BeginInvoke((Action)(() => this.UpdateWatch()));
} else {
this.UpdateWatch();
}
}
public void UpdateWatch(bool autoResizeColumns = true)
{
List<WatchValueInfo> watchContent = WatchManager.GetWatchContent(_previousValues);
_previousValues = watchContent;
bool updating = false;
if(watchContent.Count != lstWatch.Items.Count - 1) {
int currentFocus = lstWatch.FocusedItem?.Selected == true ? (lstWatch.FocusedItem?.Index ?? -1) : -1;
lstWatch.BeginUpdate();
lstWatch.Items.Clear();
List<ListViewItem> itemsToAdd = new List<ListViewItem>();
foreach(WatchValueInfo watch in watchContent) {
ListViewItem item = new ListViewItem(watch.Expression);
item.UseItemStyleForSubItems = false;
item.SubItems.Add(watch.Value).ForeColor = watch.HasChanged ? _updatedColor : _normalColor;
itemsToAdd.Add(item);
}
var lastItem = new ListViewItem("");
lastItem.SubItems.Add("");
itemsToAdd.Add(lastItem);
lstWatch.Items.AddRange(itemsToAdd.ToArray());
if(currentFocus >= 0 && currentFocus < lstWatch.Items.Count) {
SetSelectedItem(currentFocus);
}
updating = true;
} else {
for(int i = 0; i < watchContent.Count; i++) {
ListViewItem item = lstWatch.Items[i];
bool needUpdate = (
item.SubItems[0].Text != watchContent[i].Expression ||
item.SubItems[1].Text != watchContent[i].Value ||
item.SubItems[1].ForeColor != (watchContent[i].HasChanged ? _updatedColor : _normalColor)
);
if(needUpdate) {
updating = true;
item.SubItems[0].Text = watchContent[i].Expression;
item.SubItems[1].Text = watchContent[i].Value;
item.SubItems[1].ForeColor = watchContent[i].HasChanged ? _updatedColor : _normalColor;
}
}
}
if(updating) {
if(watchContent.Count > 0) {
int maxLength = watchContent.Select(info => info.Value.Length).Max();
if(_previousMaxLength != maxLength) {
if(autoResizeColumns) {
lstWatch.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.ColumnContent);
}
if(colValue.Width < 100) {
colValue.Width = 100;
}
_previousMaxLength = maxLength;
}
}
lstWatch.EndUpdate();
}
}
private void lstWatch_SelectedIndexChanged(object sender, EventArgs e)
{
mnuRemoveWatch.Enabled = lstWatch.SelectedItems.Count >= 1;
UpdateActions();
}
private void UpdateActions()
{
mnuHexDisplay.Checked = ConfigManager.Config.Debug.WatchFormat == WatchFormatStyle.Hex;
mnuDecimalDisplay.Checked = ConfigManager.Config.Debug.WatchFormat == WatchFormatStyle.Signed;
mnuBinaryDisplay.Checked = ConfigManager.Config.Debug.WatchFormat == WatchFormatStyle.Binary;
mnuRowDisplayFormat.Enabled = lstWatch.SelectedItems.Count > 0;
mnuEditInMemoryViewer.Enabled = false;
mnuViewInDisassembly.Enabled = false;
mnuMoveUp.Enabled = false;
mnuMoveDown.Enabled = false;
if(lstWatch.SelectedItems.Count == 1) {
Match match = _watchAddressOrLabel.Match(lstWatch.SelectedItems[0].Text);
if(match.Success) {
string address = match.Groups[3].Value;
if(address[0] >= '0' && address[0] <= '9' || address[0] == '$') {
//CPU Address
_selectedAddress = Int32.Parse(address[0] == '$' ? address.Substring(1) : address, address[0] == '$' ? NumberStyles.AllowHexSpecifier : NumberStyles.None);
//TODO
//_selectedLabel = null;
mnuEditInMemoryViewer.Enabled = true;
mnuViewInDisassembly.Enabled = true;
} else {
//Label
_selectedAddress = -1;
//TODO
/*_selectedLabel = LabelManager.GetLabel(address);
if(_selectedLabel != null) {
mnuEditInMemoryViewer.Enabled = true;
mnuViewInDisassembly.Enabled = true;
}*/
}
}
mnuMoveUp.Enabled = lstWatch.SelectedIndices[0] > 0 && lstWatch.SelectedIndices[0] < lstWatch.Items.Count - 1;
mnuMoveDown.Enabled = lstWatch.SelectedIndices[0] < lstWatch.Items.Count - 2;
}
}
private void mnuRemoveWatch_Click(object sender, EventArgs e)
{
if(lstWatch.SelectedItems.Count >= 1) {
var itemsToRemove = new List<int>();
foreach(ListViewItem item in lstWatch.SelectedItems) {
itemsToRemove.Add(item.Index);
}
WatchManager.RemoveWatch(itemsToRemove.ToArray());
}
}
private void mnuViewInDisassembly_Click(object sender, EventArgs e)
{
//TODO
/*if(lstWatch.SelectedItems.Count != 1) {
return;
}
if(_selectedAddress >= 0) {
DebugWindowManager.GetDebugger().ScrollToAddress(_selectedAddress);
} else if(_selectedLabel != null) {
int relAddress = _selectedLabel.GetRelativeAddress();
if(relAddress >= 0) {
DebugWindowManager.GetDebugger().ScrollToAddress(relAddress);
}
}*/
}
private void mnuEditInMemoryViewer_Click(object sender, EventArgs e)
{
//TODO
/*if(lstWatch.SelectedItems.Count != 1) {
return;
}
if(_selectedAddress >= 0) {
DebugWindowManager.OpenMemoryViewer(_selectedAddress, DebugMemoryType.CpuMemory);
} else if(_selectedLabel != null) {
DebugWindowManager.OpenMemoryViewer((int)_selectedLabel.Address, _selectedLabel.AddressType.ToMemoryType());
}*/
}
private void StartEdit(string text, ListViewItem selectedItem = null)
{
if(selectedItem == null) {
selectedItem = lstWatch.SelectedItems[0];
}
SetSelectedItem(selectedItem.Index);
txtEdit.Location = selectedItem.Position;
txtEdit.Width = selectedItem.Bounds.Width;
txtEdit.Text = text;
txtEdit.SelectionLength = 0;
txtEdit.SelectionStart = text.Length;
txtEdit.Visible = true;
txtEdit.Focus();
_isEditing = true;
}
private void lstWatch_Click(object sender, EventArgs e)
{
if(lstWatch.SelectedItems.Count == 1 && string.IsNullOrWhiteSpace(lstWatch.SelectedItems[0].Text)) {
StartEdit("");
}
}
private void lstWatch_DoubleClick(object sender, EventArgs e)
{
if(lstWatch.SelectedItems.Count == 1) {
StartEdit(lstWatch.SelectedItems[0].Text);
}
}
private void ApplyEdit()
{
if(lstWatch.SelectedItems.Count > 0) {
lstWatch.SelectedItems[0].Text = txtEdit.Text;
WatchManager.UpdateWatch(lstWatch.SelectedIndices[0], txtEdit.Text);
}
lstWatch.Focus();
}
private void CancelEdit()
{
if(lstWatch.SelectedItems.Count > 0) {
txtEdit.Text = lstWatch.SelectedItems[0].Text;
}
lstWatch.Focus();
}
private void lstWatch_KeyPress(object sender, KeyPressEventArgs e)
{
if(lstWatch.SelectedItems.Count > 0) {
if(e.KeyChar >= ' ' && e.KeyChar < 128) {
e.Handled = true;
StartEdit(e.KeyChar.ToString(), _keyDownItem);
_keyDownItem = null;
}
}
}
private void txtEdit_Leave(object sender, EventArgs e)
{
_isEditing = false;
txtEdit.Visible = false;
lstWatch.Focus();
ApplyEdit();
}
private void mnuMoveUp_Click(object sender, EventArgs e)
{
MoveUp(false);
}
private void mnuMoveDown_Click(object sender, EventArgs e)
{
MoveDown();
}
private void SetSelectedItem(int index)
{
if(index < lstWatch.Items.Count) {
lstWatch.FocusedItem = lstWatch.Items[index];
foreach(ListViewItem item in lstWatch.Items) {
item.Selected = lstWatch.FocusedItem == item;
}
}
}
private void MoveUp(bool fromUpDownArrow)
{
if(lstWatch.SelectedIndices.Count == 0) {
return;
}
int index = lstWatch.SelectedIndices[0];
if(Program.IsMono && fromUpDownArrow) {
//Mono appears to move the selection up before processing this
index++;
}
if(index > 0 && index < lstWatch.Items.Count - 1) {
string currentEntry = lstWatch.Items[index].SubItems[0].Text;
string entryAbove = lstWatch.Items[index - 1].SubItems[0].Text;
SetSelectedItem(index - 1);
WatchManager.UpdateWatch(index - 1, currentEntry);
WatchManager.UpdateWatch(index, entryAbove);
} else {
SetSelectedItem(index);
}
}
private void MoveDown()
{
if(lstWatch.SelectedIndices.Count == 0) {
return;
}
int index = lstWatch.SelectedIndices[0];
if(index < lstWatch.Items.Count - 2) {
string currentEntry = lstWatch.Items[index].SubItems[0].Text;
string entryBelow = lstWatch.Items[index + 1].SubItems[0].Text;
SetSelectedItem(index + 1);
WatchManager.UpdateWatch(index + 1, currentEntry);
WatchManager.UpdateWatch(index, entryBelow);
} else {
SetSelectedItem(index);
}
}
private void lstWatch_OnMoveUpDown(Keys keyData, ref bool processed)
{
if(keyData == ConfigManager.Config.Debug.Shortcuts.WatchList_MoveUp) {
MoveUp(true);
processed = true;
} else if(keyData == ConfigManager.Config.Debug.Shortcuts.WatchList_MoveDown) {
MoveDown();
processed = true;
}
}
private void mnuImport_Click(object sender, EventArgs e)
{
using(OpenFileDialog ofd = new OpenFileDialog()) {
ofd.SetFilter("Watch files (*.mwf)|*.mwf");
if(ofd.ShowDialog() == DialogResult.OK) {
WatchManager.Import(ofd.FileName);
}
}
}
private void mnuExport_Click(object sender, EventArgs e)
{
using(SaveFileDialog sfd = new SaveFileDialog()) {
sfd.SetFilter("Watch files (*.mwf)|*.mwf");
if(sfd.ShowDialog() == DialogResult.OK) {
WatchManager.Export(sfd.FileName);
}
}
}
private void mnuHexDisplay_Click(object sender, EventArgs e)
{
ConfigManager.Config.Debug.WatchFormat = WatchFormatStyle.Hex;
ConfigManager.ApplyChanges();
UpdateWatch();
}
private void mnuDecimalDisplay_Click(object sender, EventArgs e)
{
ConfigManager.Config.Debug.WatchFormat = WatchFormatStyle.Signed;
ConfigManager.ApplyChanges();
UpdateWatch();
}
private void mnuBinaryDisplay_Click(object sender, EventArgs e)
{
ConfigManager.Config.Debug.WatchFormat = WatchFormatStyle.Binary;
ConfigManager.ApplyChanges();
UpdateWatch();
}
private string GetFormatString(WatchFormatStyle format, int byteLength)
{
string formatString = ", ";
switch(format) {
case WatchFormatStyle.Binary: formatString += "B"; break;
case WatchFormatStyle.Hex: formatString += "H"; break;
case WatchFormatStyle.Signed: formatString += "S"; break;
case WatchFormatStyle.Unsigned: formatString += "U"; break;
default: throw new Exception("Unsupported type");
}
if(byteLength > 1) {
formatString += byteLength.ToString();
}
return formatString;
}
private void SetSelectionFormat(WatchFormatStyle format, int byteLength)
{
SetSelectionFormat(GetFormatString(format, byteLength));
}
private void SetSelectionFormat(string formatString)
{
List<string> entries = WatchManager.WatchEntries;
foreach(int i in lstWatch.SelectedIndices) {
if(i < entries.Count) {
Match match = WatchManager.FormatSuffixRegex.Match(entries[i]);
if(match.Success) {
WatchManager.UpdateWatch(i, match.Groups[1].Value + formatString);
} else {
WatchManager.UpdateWatch(i, entries[i] + formatString);
}
}
}
}
private void mnuRowBinary_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Binary, 1);
}
private void mnuRowHex1_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Hex, 1);
}
private void mnuRowHex2_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Hex, 2);
}
private void mnuRowHex3_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Hex, 3);
}
private void mnuRowSigned1_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Signed, 1);
}
private void mnuRowSigned2_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Signed, 2);
}
private void mnuRowSigned3_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Unsigned, 1);
}
private void mnuRowUnsigned_Click(object sender, EventArgs e)
{
SetSelectionFormat(WatchFormatStyle.Unsigned, 1);
}
private void mnuRowClearFormat_Click(object sender, EventArgs e)
{
SetSelectionFormat("");
}
}
}

396
UI/Debugger/Controls/ctrlWatch.designer.cs generated Normal file
View file

@ -0,0 +1,396 @@
namespace Mesen.GUI.Debugger
{
partial class ctrlWatch
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if(disposing && (components != null)) {
components.Dispose();
}
WatchManager.WatchChanged -= WatchManager_WatchChanged;
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem("");
this.lstWatch = new Mesen.GUI.Controls.WatchListView();
this.colName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.colValue = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.contextMenuWatch = new System.Windows.Forms.ContextMenuStrip(this.components);
this.mnuRemoveWatch = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();
this.mnuEditInMemoryViewer = new System.Windows.Forms.ToolStripMenuItem();
this.mnuViewInDisassembly = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator();
this.mnuMoveUp = new System.Windows.Forms.ToolStripMenuItem();
this.mnuMoveDown = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();
this.mnuDecimalDisplay = new System.Windows.Forms.ToolStripMenuItem();
this.mnuHexDisplay = new System.Windows.Forms.ToolStripMenuItem();
this.mnuBinaryDisplay = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator();
this.mnuRowDisplayFormat = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRowBinary = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem8 = new System.Windows.Forms.ToolStripSeparator();
this.mnuRowHex1 = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRowHex2 = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRowHex3 = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem6 = new System.Windows.Forms.ToolStripSeparator();
this.mnuRowSigned1 = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRowSigned2 = new System.Windows.Forms.ToolStripMenuItem();
this.mnuRowSigned3 = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator();
this.mnuRowUnsigned = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem9 = new System.Windows.Forms.ToolStripSeparator();
this.mnuRowClearFormat = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
this.mnuImport = new System.Windows.Forms.ToolStripMenuItem();
this.mnuExport = new System.Windows.Forms.ToolStripMenuItem();
this.txtEdit = new System.Windows.Forms.TextBox();
this.contextMenuWatch.SuspendLayout();
this.SuspendLayout();
//
// lstWatch
//
this.lstWatch.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.colName,
this.colValue});
this.lstWatch.ContextMenuStrip = this.contextMenuWatch;
this.lstWatch.Dock = System.Windows.Forms.DockStyle.Fill;
this.lstWatch.FullRowSelect = true;
this.lstWatch.GridLines = true;
this.lstWatch.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
this.lstWatch.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
listViewItem1});
this.lstWatch.Location = new System.Drawing.Point(0, 0);
this.lstWatch.Name = "lstWatch";
this.lstWatch.Size = new System.Drawing.Size(378, 112);
this.lstWatch.TabIndex = 6;
this.lstWatch.UseCompatibleStateImageBehavior = false;
this.lstWatch.View = System.Windows.Forms.View.Details;
this.lstWatch.OnMoveUpDown += new Mesen.GUI.Controls.WatchListView.MoveUpDownHandler(this.lstWatch_OnMoveUpDown);
this.lstWatch.SelectedIndexChanged += new System.EventHandler(this.lstWatch_SelectedIndexChanged);
this.lstWatch.Click += new System.EventHandler(this.lstWatch_Click);
this.lstWatch.DoubleClick += new System.EventHandler(this.lstWatch_DoubleClick);
this.lstWatch.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.lstWatch_KeyPress);
//
// colName
//
this.colName.Text = "Name";
this.colName.Width = 180;
//
// colValue
//
this.colValue.Text = "Value";
this.colValue.Width = 110;
//
// contextMenuWatch
//
this.contextMenuWatch.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.mnuRemoveWatch,
this.toolStripMenuItem1,
this.mnuEditInMemoryViewer,
this.mnuViewInDisassembly,
this.toolStripMenuItem4,
this.mnuMoveUp,
this.mnuMoveDown,
this.toolStripMenuItem2,
this.mnuRowDisplayFormat,
this.toolStripMenuItem3,
this.mnuDecimalDisplay,
this.mnuHexDisplay,
this.mnuBinaryDisplay,
this.toolStripMenuItem5,
this.mnuImport,
this.mnuExport});
this.contextMenuWatch.Name = "contextMenuWatch";
this.contextMenuWatch.Size = new System.Drawing.Size(194, 298);
this.contextMenuWatch.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuWatch_Opening);
//
// mnuRemoveWatch
//
this.mnuRemoveWatch.Image = global::Mesen.GUI.Properties.Resources.Close;
this.mnuRemoveWatch.Name = "mnuRemoveWatch";
this.mnuRemoveWatch.Size = new System.Drawing.Size(193, 22);
this.mnuRemoveWatch.Text = "Remove";
this.mnuRemoveWatch.Click += new System.EventHandler(this.mnuRemoveWatch_Click);
//
// toolStripMenuItem1
//
this.toolStripMenuItem1.Name = "toolStripMenuItem1";
this.toolStripMenuItem1.Size = new System.Drawing.Size(190, 6);
//
// mnuEditInMemoryViewer
//
this.mnuEditInMemoryViewer.Image = global::Mesen.GUI.Properties.Resources.CheatCode;
this.mnuEditInMemoryViewer.Name = "mnuEditInMemoryViewer";
this.mnuEditInMemoryViewer.Size = new System.Drawing.Size(193, 22);
this.mnuEditInMemoryViewer.Text = "Edit in Memory Viewer";
this.mnuEditInMemoryViewer.Click += new System.EventHandler(this.mnuEditInMemoryViewer_Click);
//
// mnuViewInDisassembly
//
this.mnuViewInDisassembly.Image = global::Mesen.GUI.Properties.Resources.Debugger;
this.mnuViewInDisassembly.Name = "mnuViewInDisassembly";
this.mnuViewInDisassembly.Size = new System.Drawing.Size(193, 22);
this.mnuViewInDisassembly.Text = "View in disassembly";
this.mnuViewInDisassembly.Click += new System.EventHandler(this.mnuViewInDisassembly_Click);
//
// toolStripMenuItem4
//
this.toolStripMenuItem4.Name = "toolStripMenuItem4";
this.toolStripMenuItem4.Size = new System.Drawing.Size(190, 6);
//
// mnuMoveUp
//
this.mnuMoveUp.Image = global::Mesen.GUI.Properties.Resources.MoveUp;
this.mnuMoveUp.Name = "mnuMoveUp";
this.mnuMoveUp.Size = new System.Drawing.Size(193, 22);
this.mnuMoveUp.Text = "Move up";
this.mnuMoveUp.Click += new System.EventHandler(this.mnuMoveUp_Click);
//
// mnuMoveDown
//
this.mnuMoveDown.Image = global::Mesen.GUI.Properties.Resources.MoveDown;
this.mnuMoveDown.Name = "mnuMoveDown";
this.mnuMoveDown.Size = new System.Drawing.Size(193, 22);
this.mnuMoveDown.Text = "Move down";
this.mnuMoveDown.Click += new System.EventHandler(this.mnuMoveDown_Click);
//
// toolStripMenuItem2
//
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
this.toolStripMenuItem2.Size = new System.Drawing.Size(190, 6);
//
// mnuDecimalDisplay
//
this.mnuDecimalDisplay.Name = "mnuDecimalDisplay";
this.mnuDecimalDisplay.Size = new System.Drawing.Size(193, 22);
this.mnuDecimalDisplay.Text = "Decimal Display";
this.mnuDecimalDisplay.Click += new System.EventHandler(this.mnuDecimalDisplay_Click);
//
// mnuHexDisplay
//
this.mnuHexDisplay.Checked = true;
this.mnuHexDisplay.CheckState = System.Windows.Forms.CheckState.Checked;
this.mnuHexDisplay.Name = "mnuHexDisplay";
this.mnuHexDisplay.Size = new System.Drawing.Size(193, 22);
this.mnuHexDisplay.Text = "Hexadecimal Display";
this.mnuHexDisplay.Click += new System.EventHandler(this.mnuHexDisplay_Click);
//
// mnuBinaryDisplay
//
this.mnuBinaryDisplay.Name = "mnuBinaryDisplay";
this.mnuBinaryDisplay.Size = new System.Drawing.Size(193, 22);
this.mnuBinaryDisplay.Text = "Binary Display";
this.mnuBinaryDisplay.Click += new System.EventHandler(this.mnuBinaryDisplay_Click);
//
// toolStripMenuItem5
//
this.toolStripMenuItem5.Name = "toolStripMenuItem5";
this.toolStripMenuItem5.Size = new System.Drawing.Size(190, 6);
//
// mnuRowDisplayFormat
//
this.mnuRowDisplayFormat.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.mnuRowBinary,
this.toolStripMenuItem8,
this.mnuRowHex1,
this.mnuRowHex2,
this.mnuRowHex3,
this.toolStripMenuItem6,
this.mnuRowSigned1,
this.mnuRowSigned2,
this.mnuRowSigned3,
this.toolStripMenuItem7,
this.mnuRowUnsigned,
this.toolStripMenuItem9,
this.mnuRowClearFormat});
this.mnuRowDisplayFormat.Name = "mnuRowDisplayFormat";
this.mnuRowDisplayFormat.Size = new System.Drawing.Size(193, 22);
this.mnuRowDisplayFormat.Text = "Row Display Format";
//
// mnuRowBinary
//
this.mnuRowBinary.Name = "mnuRowBinary";
this.mnuRowBinary.Size = new System.Drawing.Size(197, 22);
this.mnuRowBinary.Text = "Binary";
this.mnuRowBinary.Click += new System.EventHandler(this.mnuRowBinary_Click);
//
// toolStripMenuItem8
//
this.toolStripMenuItem8.Name = "toolStripMenuItem8";
this.toolStripMenuItem8.Size = new System.Drawing.Size(194, 6);
//
// mnuRowHex1
//
this.mnuRowHex1.Name = "mnuRowHex1";
this.mnuRowHex1.Size = new System.Drawing.Size(197, 22);
this.mnuRowHex1.Text = "Hexadecimal (8-bit)";
this.mnuRowHex1.Click += new System.EventHandler(this.mnuRowHex1_Click);
//
// mnuRowHex2
//
this.mnuRowHex2.Name = "mnuRowHex2";
this.mnuRowHex2.Size = new System.Drawing.Size(197, 22);
this.mnuRowHex2.Text = "Hexadecimal (16-bit)";
this.mnuRowHex2.Click += new System.EventHandler(this.mnuRowHex2_Click);
//
// mnuRowHex3
//
this.mnuRowHex3.Name = "mnuRowHex3";
this.mnuRowHex3.Size = new System.Drawing.Size(197, 22);
this.mnuRowHex3.Text = "Hexadecimal (24-bit)";
this.mnuRowHex3.Click += new System.EventHandler(this.mnuRowHex3_Click);
//
// toolStripMenuItem6
//
this.toolStripMenuItem6.Name = "toolStripMenuItem6";
this.toolStripMenuItem6.Size = new System.Drawing.Size(194, 6);
//
// mnuRowSigned1
//
this.mnuRowSigned1.Name = "mnuRowSigned1";
this.mnuRowSigned1.Size = new System.Drawing.Size(197, 22);
this.mnuRowSigned1.Text = "Signed decimal (8-bit)";
this.mnuRowSigned1.Click += new System.EventHandler(this.mnuRowSigned1_Click);
//
// mnuRowSigned2
//
this.mnuRowSigned2.Name = "mnuRowSigned2";
this.mnuRowSigned2.Size = new System.Drawing.Size(197, 22);
this.mnuRowSigned2.Text = "Signed decimal (16-bit)";
this.mnuRowSigned2.Click += new System.EventHandler(this.mnuRowSigned2_Click);
//
// mnuRowSigned3
//
this.mnuRowSigned3.Name = "mnuRowSigned3";
this.mnuRowSigned3.Size = new System.Drawing.Size(197, 22);
this.mnuRowSigned3.Text = "Signed decimal (24-bit)";
this.mnuRowSigned3.Click += new System.EventHandler(this.mnuRowSigned3_Click);
//
// toolStripMenuItem7
//
this.toolStripMenuItem7.Name = "toolStripMenuItem7";
this.toolStripMenuItem7.Size = new System.Drawing.Size(194, 6);
//
// mnuRowUnsigned
//
this.mnuRowUnsigned.Name = "mnuRowUnsigned";
this.mnuRowUnsigned.Size = new System.Drawing.Size(197, 22);
this.mnuRowUnsigned.Text = "Unsigned decimal";
this.mnuRowUnsigned.Click += new System.EventHandler(this.mnuRowUnsigned_Click);
//
// toolStripMenuItem9
//
this.toolStripMenuItem9.Name = "toolStripMenuItem9";
this.toolStripMenuItem9.Size = new System.Drawing.Size(194, 6);
//
// mnuRowClearFormat
//
this.mnuRowClearFormat.Image = global::Mesen.GUI.Properties.Resources.Close;
this.mnuRowClearFormat.Name = "mnuRowClearFormat";
this.mnuRowClearFormat.Size = new System.Drawing.Size(197, 22);
this.mnuRowClearFormat.Text = "Clear";
this.mnuRowClearFormat.Click += new System.EventHandler(this.mnuRowClearFormat_Click);
//
// toolStripMenuItem3
//
this.toolStripMenuItem3.Name = "toolStripMenuItem3";
this.toolStripMenuItem3.Size = new System.Drawing.Size(190, 6);
//
// mnuImport
//
this.mnuImport.Image = global::Mesen.GUI.Properties.Resources.Import;
this.mnuImport.Name = "mnuImport";
this.mnuImport.Size = new System.Drawing.Size(193, 22);
this.mnuImport.Text = "Import...";
this.mnuImport.Click += new System.EventHandler(this.mnuImport_Click);
//
// mnuExport
//
this.mnuExport.Image = global::Mesen.GUI.Properties.Resources.Export;
this.mnuExport.Name = "mnuExport";
this.mnuExport.Size = new System.Drawing.Size(193, 22);
this.mnuExport.Text = "Export...";
this.mnuExport.Click += new System.EventHandler(this.mnuExport_Click);
//
// txtEdit
//
this.txtEdit.AcceptsReturn = true;
this.txtEdit.Location = new System.Drawing.Point(3, 24);
this.txtEdit.Name = "txtEdit";
this.txtEdit.Size = new System.Drawing.Size(177, 20);
this.txtEdit.TabIndex = 7;
this.txtEdit.Visible = false;
this.txtEdit.Leave += new System.EventHandler(this.txtEdit_Leave);
//
// ctrlWatch
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.txtEdit);
this.Controls.Add(this.lstWatch);
this.Name = "ctrlWatch";
this.Size = new System.Drawing.Size(378, 112);
this.contextMenuWatch.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private Mesen.GUI.Controls.WatchListView lstWatch;
private System.Windows.Forms.ColumnHeader colName;
private System.Windows.Forms.ColumnHeader colValue;
private System.Windows.Forms.ContextMenuStrip contextMenuWatch;
private System.Windows.Forms.ToolStripMenuItem mnuRemoveWatch;
private System.Windows.Forms.ToolStripMenuItem mnuHexDisplay;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;
private System.Windows.Forms.ToolStripMenuItem mnuEditInMemoryViewer;
private System.Windows.Forms.ToolStripMenuItem mnuViewInDisassembly;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2;
private System.Windows.Forms.TextBox txtEdit;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem4;
private System.Windows.Forms.ToolStripMenuItem mnuMoveUp;
private System.Windows.Forms.ToolStripMenuItem mnuMoveDown;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3;
private System.Windows.Forms.ToolStripMenuItem mnuImport;
private System.Windows.Forms.ToolStripMenuItem mnuExport;
private System.Windows.Forms.ToolStripMenuItem mnuDecimalDisplay;
private System.Windows.Forms.ToolStripMenuItem mnuBinaryDisplay;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem5;
private System.Windows.Forms.ToolStripMenuItem mnuRowDisplayFormat;
private System.Windows.Forms.ToolStripMenuItem mnuRowSigned1;
private System.Windows.Forms.ToolStripMenuItem mnuRowSigned2;
private System.Windows.Forms.ToolStripMenuItem mnuRowSigned3;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem6;
private System.Windows.Forms.ToolStripMenuItem mnuRowHex1;
private System.Windows.Forms.ToolStripMenuItem mnuRowHex2;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7;
private System.Windows.Forms.ToolStripMenuItem mnuRowUnsigned;
private System.Windows.Forms.ToolStripMenuItem mnuRowBinary;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem8;
private System.Windows.Forms.ToolStripMenuItem mnuRowHex3;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem9;
private System.Windows.Forms.ToolStripMenuItem mnuRowClearFormat;
}
}

View file

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="contextMenuWatch.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

226
UI/Debugger/WatchManager.cs Normal file
View file

@ -0,0 +1,226 @@
using Mesen.GUI.Config;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Mesen.GUI.Debugger
{
class WatchManager
{
public static event EventHandler WatchChanged;
private static List<string> _watchEntries = new List<string>();
private static Regex _arrayWatchRegex = new Regex(@"\[((\$[0-9A-Fa-f]+)|(\d+)|([@_a-zA-Z0-9]+))\s*,\s*(\d+)\]", RegexOptions.Compiled);
public static Regex FormatSuffixRegex = new Regex(@"^(.*),\s*([B|H|S|U])([\d]){0,1}$", RegexOptions.Compiled);
public static List<string> WatchEntries
{
get { return _watchEntries; }
set
{
_watchEntries = new List<string>(value);
WatchChanged?.Invoke(null, EventArgs.Empty);
}
}
public static List<WatchValueInfo> GetWatchContent(List<WatchValueInfo> previousValues)
{
WatchFormatStyle defaultStyle = ConfigManager.Config.Debug.WatchFormat;
int defaultByteLength = 1;
if(defaultStyle == WatchFormatStyle.Signed) {
defaultByteLength = 4;
}
var list = new List<WatchValueInfo>();
for(int i = 0; i < _watchEntries.Count; i++) {
string expression = _watchEntries[i].Trim();
string newValue = "";
EvalResultType resultType;
string exprToEvaluate = expression;
WatchFormatStyle style = defaultStyle;
int byteLength = defaultByteLength;
if(expression.StartsWith("{") && expression.EndsWith("}")) {
//Default to 2-byte values when using {} syntax
byteLength = 2;
}
ProcessFormatSpecifier(ref exprToEvaluate, ref style, ref byteLength);
bool forceHasChanged = false;
Match match = _arrayWatchRegex.Match(expression);
if(match.Success) {
//Watch expression matches the array display syntax (e.g: [$300,10] = display 10 bytes starting from $300)
newValue = ProcessArrayDisplaySyntax(style, ref forceHasChanged, match);
} else {
Int32 result = DebugApi.EvaluateExpression(exprToEvaluate, out resultType, true);
switch(resultType) {
case EvalResultType.Numeric: newValue = FormatValue(result, style, byteLength); break;
case EvalResultType.Boolean: newValue = result == 0 ? "false" : "true"; break;
case EvalResultType.Invalid: newValue = "<invalid expression>"; forceHasChanged = true; break;
case EvalResultType.DivideBy0: newValue = "<division by zero>"; forceHasChanged = true; break;
case EvalResultType.OutOfScope: newValue = "<label out of scope>"; forceHasChanged = true; break;
}
}
list.Add(new WatchValueInfo() { Expression = expression, Value = newValue, HasChanged = forceHasChanged || (i < previousValues.Count ? (previousValues[i].Value != newValue) : false) });
}
return list;
}
private static string FormatValue(int value, WatchFormatStyle style, int byteLength)
{
switch(style) {
case WatchFormatStyle.Unsigned: return ((UInt32)value).ToString();
case WatchFormatStyle.Hex: return "$" + value.ToString("X" + byteLength * 2);
case WatchFormatStyle.Binary:
string binary = Convert.ToString(value, 2).PadLeft(byteLength * 8, '0');
for(int i = binary.Length - 4; i > 0; i -= 4) {
binary = binary.Insert(i, ".");
}
return "%" + binary;
case WatchFormatStyle.Signed:
int bitCount = byteLength * 8;
if(bitCount < 32) {
if(((value >> (bitCount - 1)) & 0x01) == 0x01) {
//Negative value
return (value | (-(1 << bitCount))).ToString();
} else {
//Position value
return value.ToString();
}
} else {
return value.ToString();
}
default: throw new Exception("Unsupported format");
}
}
public static bool IsArraySyntax(string expression)
{
return _arrayWatchRegex.IsMatch(expression);
}
private static bool ProcessFormatSpecifier(ref string expression, ref WatchFormatStyle style, ref int byteLength)
{
Match match = WatchManager.FormatSuffixRegex.Match(expression);
if(!match.Success) {
return false;
}
string format = match.Groups[2].Value.ToUpperInvariant();
switch(format[0]) {
case 'S': style = WatchFormatStyle.Signed; break;
case 'H': style = WatchFormatStyle.Hex; break;
case 'B': style = WatchFormatStyle.Binary; break;
case 'U': style = WatchFormatStyle.Unsigned; break;
default: throw new Exception("Invalid format");
}
if(match.Groups[3].Success) {
byteLength = Math.Max(Math.Min(Int32.Parse(match.Groups[3].Value), 4), 1);
} else {
byteLength = 1;
}
expression = match.Groups[1].Value;
return true;
}
private static string ProcessArrayDisplaySyntax(WatchFormatStyle style, ref bool forceHasChanged, Match match)
{
string newValue;
int address;
if(match.Groups[2].Value.Length > 0) {
address = int.Parse(match.Groups[2].Value.Substring(1), System.Globalization.NumberStyles.HexNumber);
} else if(match.Groups[3].Value.Length > 0) {
address = int.Parse(match.Groups[3].Value);
} else {
return "<invalid expression>";
/*CodeLabel label = LabelManager.GetLabel(match.Groups[4].Value);
if(label == null) {
forceHasChanged = true;
return "<invalid label>";
}
address = label.GetRelativeAddress();*/
}
int elemCount = int.Parse(match.Groups[5].Value);
if(address >= 0) {
List<string> values = new List<string>(elemCount);
for(int j = address, end = address + elemCount; j < end; j++) {
int memValue = DebugApi.GetMemoryValue(SnesMemoryType.CpuMemory, (uint)j);
values.Add(FormatValue(memValue, style, 1));
}
newValue = string.Join(" ", values);
} else {
newValue = "<label out of scope>";
forceHasChanged = true;
}
return newValue;
}
public static void AddWatch(params string[] expressions)
{
foreach(string expression in expressions) {
_watchEntries.Add(expression);
}
WatchChanged?.Invoke(null, EventArgs.Empty);
}
public static void UpdateWatch(int index, string expression)
{
if(string.IsNullOrWhiteSpace(expression)) {
RemoveWatch(index);
} else {
if(index >= _watchEntries.Count) {
_watchEntries.Add(expression);
} else {
_watchEntries[index] = expression;
}
WatchChanged?.Invoke(null, EventArgs.Empty);
}
}
public static void RemoveWatch(params int[] indexes)
{
HashSet<int> set = new HashSet<int>(indexes);
_watchEntries = _watchEntries.Where((el, index) => !set.Contains(index)).ToList();
//_previousValues = _previousValues.Where((el, index) => !set.Contains(index)).ToList();
WatchChanged?.Invoke(null, EventArgs.Empty);
}
public static void Import(string filename)
{
if(File.Exists(filename)) {
WatchManager.WatchEntries = new List<string>(File.ReadAllLines(filename));
}
}
public static void Export(string filename)
{
File.WriteAllLines(filename, WatchManager.WatchEntries);
}
}
public class WatchValueInfo
{
public string Expression { get; set; }
public string Value { get; set; }
public bool HasChanged { get; set; }
}
public enum WatchFormatStyle
{
Unsigned,
Signed,
Hex,
Binary
}
}

View file

@ -55,15 +55,23 @@
this.toolStripMenuItem8 = new System.Windows.Forms.ToolStripSeparator();
this.mnuBreakIn = new System.Windows.Forms.ToolStripMenuItem();
this.mnuBreakOn = new System.Windows.Forms.ToolStripMenuItem();
this.ctrlSplitContainer = new Mesen.GUI.Controls.ctrlSplitContainer();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.ctrlWatch = new Mesen.GUI.Debugger.ctrlWatch();
this.ctrlMesenMenuStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.ctrlSplitContainer)).BeginInit();
this.ctrlSplitContainer.Panel1.SuspendLayout();
this.ctrlSplitContainer.Panel2.SuspendLayout();
this.ctrlSplitContainer.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// ctrlDisassemblyView
//
this.ctrlDisassemblyView.Dock = System.Windows.Forms.DockStyle.Left;
this.ctrlDisassemblyView.Location = new System.Drawing.Point(0, 24);
this.ctrlDisassemblyView.Dock = System.Windows.Forms.DockStyle.Fill;
this.ctrlDisassemblyView.Location = new System.Drawing.Point(0, 0);
this.ctrlDisassemblyView.Name = "ctrlDisassemblyView";
this.ctrlDisassemblyView.Size = new System.Drawing.Size(559, 620);
this.ctrlDisassemblyView.Size = new System.Drawing.Size(852, 439);
this.ctrlDisassemblyView.TabIndex = 0;
//
// ctrlMesenMenuStrip1
@ -243,17 +251,63 @@
this.mnuBreakOn.Size = new System.Drawing.Size(212, 22);
this.mnuBreakOn.Text = "Break on...";
//
// ctrlSplitContainer
//
this.ctrlSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
this.ctrlSplitContainer.HidePanel2 = false;
this.ctrlSplitContainer.Location = new System.Drawing.Point(0, 24);
this.ctrlSplitContainer.Name = "ctrlSplitContainer";
this.ctrlSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal;
//
// ctrlSplitContainer.Panel1
//
this.ctrlSplitContainer.Panel1.Controls.Add(this.ctrlDisassemblyView);
//
// ctrlSplitContainer.Panel2
//
this.ctrlSplitContainer.Panel2.Controls.Add(this.tableLayoutPanel1);
this.ctrlSplitContainer.Size = new System.Drawing.Size(852, 620);
this.ctrlSplitContainer.SplitterDistance = 439;
this.ctrlSplitContainer.TabIndex = 2;
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.Controls.Add(this.ctrlWatch, 0, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 1;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(852, 177);
this.tableLayoutPanel1.TabIndex = 0;
//
// ctrlWatch
//
this.ctrlWatch.Dock = System.Windows.Forms.DockStyle.Fill;
this.ctrlWatch.Location = new System.Drawing.Point(3, 3);
this.ctrlWatch.Name = "ctrlWatch";
this.ctrlWatch.Size = new System.Drawing.Size(420, 171);
this.ctrlWatch.TabIndex = 0;
//
// frmDebugger
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(852, 644);
this.Controls.Add(this.ctrlDisassemblyView);
this.Controls.Add(this.ctrlSplitContainer);
this.Controls.Add(this.ctrlMesenMenuStrip1);
this.Name = "frmDebugger";
this.Text = "frmDebugger";
this.Text = "Debugger";
this.ctrlMesenMenuStrip1.ResumeLayout(false);
this.ctrlMesenMenuStrip1.PerformLayout();
this.ctrlSplitContainer.Panel1.ResumeLayout(false);
this.ctrlSplitContainer.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.ctrlSplitContainer)).EndInit();
this.ctrlSplitContainer.ResumeLayout(false);
this.tableLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
@ -285,5 +339,8 @@
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem8;
private System.Windows.Forms.ToolStripMenuItem mnuBreakIn;
private System.Windows.Forms.ToolStripMenuItem mnuBreakOn;
private GUI.Controls.ctrlSplitContainer ctrlSplitContainer;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private ctrlWatch ctrlWatch;
}
}

View file

@ -76,6 +76,7 @@ namespace Mesen.GUI.Debugger
this.BeginInvoke((MethodInvoker)(() => {
ctrlDisassemblyView.SetActiveAddress(activeAddress);
ctrlWatch.UpdateWatch(true);
}));
break;
}

View file

@ -53,6 +53,8 @@ namespace Mesen.GUI
return state;
}
[DllImport(DllPath)] public static extern Int32 EvaluateExpression([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string expression, out EvalResultType resultType, [MarshalAs(UnmanagedType.I1)]bool useCache);
[DllImport(DllPath)] public static extern Int32 GetMemorySize(SnesMemoryType type);
[DllImport(DllPath)] public static extern Byte GetMemoryValue(SnesMemoryType type, UInt32 address);
[DllImport(DllPath)] public static extern void SetMemoryValue(SnesMemoryType type, UInt32 address, byte value);
@ -131,4 +133,13 @@ namespace Mesen.GUI
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1000)]
public byte[] Format;
}
public enum EvalResultType
{
Numeric = 0,
Boolean = 1,
Invalid = 2,
DivideBy0 = 3,
OutOfScope = 4
}
}

View file

@ -301,6 +301,12 @@
<Compile Include="Debugger\Controls\ctrlHexViewer.designer.cs">
<DependentUpon>ctrlHexViewer.cs</DependentUpon>
</Compile>
<Compile Include="Debugger\Controls\ctrlWatch.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Debugger\Controls\ctrlWatch.designer.cs">
<DependentUpon>ctrlWatch.cs</DependentUpon>
</Compile>
<Compile Include="Debugger\frmDbgPreferences.cs">
<SubType>Form</SubType>
</Compile>
@ -384,6 +390,7 @@
<Compile Include="Debugger\HexBox\Util.cs" />
<Compile Include="Debugger\TblLoader.cs" />
<Compile Include="Debugger\DebugWindowManager.cs" />
<Compile Include="Debugger\WatchManager.cs" />
<Compile Include="Forms\BaseConfigForm.Designer.cs">
<DependentUpon>BaseConfigForm.cs</DependentUpon>
</Compile>
@ -456,6 +463,9 @@
<EmbeddedResource Include="Debugger\Controls\ctrlHexViewer.resx">
<DependentUpon>ctrlHexViewer.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger\Controls\ctrlWatch.resx">
<DependentUpon>ctrlWatch.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger\frmDbgPreferences.resx">
<DependentUpon>frmDbgPreferences.cs</DependentUpon>
</EmbeddedResource>