#include <fstream>
#include "typeprinter.h"
#include "logger.h"
+#include "cputil.h"
-
-
+using std::string;
static HRESULT IsSameFunctionBreakpoint(
ICorDebugFunctionBreakpoint *pBreakpoint1,
return true;
}
-void ManagedDebugger::InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint)
-{
- LogFuncEntry();
-
- m_breakpoints.InsertExceptionBreakpoint(name, breakpoint);
-}
-
-void Breakpoints::InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint)
-{
- std::lock_guard<std::mutex> lock(m_breakpointsMutex);
- breakpoint.id = m_nextBreakpointId++;
-}
-
void Breakpoints::DeleteAllBreakpoints()
{
std::lock_guard<std::mutex> lock(m_breakpointsMutex);
return S_OK;
}
-#include "cputil.h"
-
HRESULT Breakpoints::ResolveFunctionBreakpointInModule(ICorDebugModule *pModule, ManagedFunctionBreakpoint &fbp)
{
HRESULT Status;
}
+ return S_OK;
+}
+
+HRESULT Breakpoints::InsertExceptionBreakpoint(const ExceptionBreakMode &mode,
+ const string &name, uint32_t &rid)
+{
+ std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+ HRESULT Status;
+ IfFailRet(m_exceptionBreakpoints.Insert(m_nextBreakpointId, mode, name));
+ rid = m_nextBreakpointId;
+ ++m_nextBreakpointId;
+ return S_OK;
+}
+
+HRESULT Breakpoints::DeleteExceptionBreakpoint(const uint32_t id)
+{
+ std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+ return m_exceptionBreakpoints.Delete(id);
+}
+
+HRESULT Breakpoints::GetExceptionBreakMode(ExceptionBreakMode &mode,
+ const string &name)
+{
+ std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+ return m_exceptionBreakpoints.GetExceptionBreakMode(mode, name);
+}
+
+bool Breakpoints::MatchExceptionBreakpoint(const string &name,
+ const ExceptionBreakCategory category)
+{
+ std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+ return m_exceptionBreakpoints.Match(name, category);
+}
+
+HRESULT ExceptionBreakpointStorage::Insert(uint32_t id,
+ const ExceptionBreakMode &mode, const string &name)
+{
+ HRESULT Status = S_OK;
+ // vsdbg each time creates a new exception breakpoint id.
+ // But, for "*" name, the last `id' silently are deleted by vsdbg.
+ if (name.compare("*") == 0) {
+ if (bp.current_asterix_id != 0) {
+ // Silent remove for global filter
+ Status = Delete(bp.current_asterix_id);
+ }
+ bp.current_asterix_id = id;
+ }
+
+ bp.exceptionBreakpoints.insert(std::make_pair(name, mode));
+ bp.table[id] = name;
+
+ return Status;
+}
+
+HRESULT ExceptionBreakpointStorage::Delete(uint32_t id) {
+ const auto it = bp.table.find(id);
+ if (it == bp.table.end()) {
+ return E_FAIL;
+ }
+ const string name = it->second;
+ if (name.compare("*") == 0) {
+ bp.current_asterix_id = 0;
+ }
+ bp.exceptionBreakpoints.erase(name);
+ bp.table.erase(id);
+
+ return S_OK;
+}
+
+bool ExceptionBreakpointStorage::Match(const string &exceptionName,
+ const ExceptionBreakCategory category) const
+{
+ // Try to match exactly by name after check global name "*"
+ // ExceptionBreakMode can be specialized by explicit filter.
+ ExceptionBreakMode mode;
+ GetExceptionBreakMode(mode, "*");
+ GetExceptionBreakMode(mode, exceptionName);
+ if (category == ExceptionBreakCategory::ANY ||
+ category == mode.category)
+ {
+ if (mode.BothUnhandledAndUserUnhandled())
+ {
+ const string SystemPrefix = "System.";
+ if (exceptionName.compare(0, SystemPrefix.size(), SystemPrefix) == 0)
+ {
+ // Expected user-applications exceptions from throw(), but get
+ // explicit/implicit exception from `System.' clases.
+ return false;
+ }
+ }
+
+ return mode.Any();
+ }
+
+ return false;
+}
+
+HRESULT ExceptionBreakpointStorage::GetExceptionBreakMode(ExceptionBreakMode &out,
+ const string &name) const
+{
+ auto p = bp.exceptionBreakpoints.equal_range(name);
+ if (p.first == bp.exceptionBreakpoints.end()) {
+ return E_FAIL;
+ }
+
+ out.category = p.first->second.category;
+ out.flags |= p.first->second.flags;
+ ++p.first;
+ while (p.first != p.second) {
+ if (out.category == ExceptionBreakCategory::ANY ||
+ out.category == p.first->second.category)
+ out.flags |= p.first->second.flags;
+ ++p.first;
+ }
+
return S_OK;
}
virtual HRESULT GetThreads(std::vector<Thread> &threads) = 0;
virtual HRESULT SetBreakpoints(std::string filename, const std::vector<SourceBreakpoint> &srcBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
virtual HRESULT SetFunctionBreakpoints(const std::vector<FunctionBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
- virtual void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint) = 0;
virtual HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector<StackFrame> &stackFrames, int &totalFrames) = 0;
virtual HRESULT StepCommand(int threadId, StepType stepType) = 0;
virtual HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) = 0;
virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) = 0;
virtual HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) = 0;
virtual HRESULT SetVariableByExpression(uint64_t frameId, const std::string &name, const std::string &value, std::string &output) = 0;
+ virtual HRESULT GetExceptionInfoResponse(int threadId, ExceptionInfoResponse &exceptionResponse) = 0;
+ virtual HRESULT DeleteExceptionBreakpoint(const uint32_t id) = 0;
+ virtual HRESULT InsertExceptionBreakpoint(const ExceptionBreakMode &mode, const std::string& names, uint32_t &id) = 0;
};
class Protocol
#include "frames.h"
#include "logger.h"
+using std::string;
+
// From dbgshim.h
struct dbgshim_t
{
{
LogFuncEntry();
- std::string excType;
- std::string excModule;
+ string excType;
+ string excModule;
GetExceptionInfo(pThread, excType, excModule);
- if (unhandled)
+ if (unhandled || m_debugger.MatchExceptionBreakpoint(excType, ExceptionBreakCategory::CLR))
{
DWORD threadId = 0;
pThread->GetID(&threadId);
m_debugger.SetLastStoppedThread(pThread);
- std::string details = "An unhandled exception of type '" + excType + "' occurred in " + excModule;
+ string details;
+ if (unhandled)
+ details = "An unhandled exception of type '" + excType + "' occurred in " + excModule;
+ else
+ details = "Exception thrown: '" + excType + "' in " + excModule;
ToRelease<ICorDebugValue> pExceptionValue;
- auto emitFunc = [=](const std::string &message) {
+ auto emitFunc = [=](const string &message) {
StoppedEvent event(StopException, threadId);
event.text = excType;
event.description = message.empty() ? details : message;
{
emitFunc(details);
}
+
}
else
{
- std::string text = "Exception thrown: '" + excType + "' in " + excModule + "\n";
+ string text = "Exception thrown: '" + excType + "' in " + excModule + "\n";
OutputEvent event(OutputConsole, text);
event.source = "target-exception";
m_debugger.m_protocol->EmitOutputEvent(event);
/* [in] */ ICorDebugThread *pThread,
/* [in] */ ICorDebugMDA *pMDA)
{
+ // TODO: MDA notification should be supported with exception breakpoint feature
+ // https://docs.microsoft.com/ru-ru/dotnet/framework/unmanaged-api/debugging/icordebugmanagedcallback2-mdanotification-method
+ // https://docs.microsoft.com/ru-ru/dotnet/framework/debug-trace-profile/diagnosing-errors-with-managed-debugging-assistants#enable-and-disable-mdas
+ //
+
LogFuncEntry();
return E_NOTIMPL;
}
return Status;
if (!running)
return S_OK;
-
+
Status = m_pProcess->Stop(0);
if (Status != S_OK)
return Status;
m_unregisterToken = nullptr;
return Startup(pCordb, pid);
}
+
+// VSCode
+HRESULT ManagedDebugger::GetExceptionInfoResponse(int threadId,
+ ExceptionInfoResponse &exceptionInfoResponse)
+{
+ LogFuncEntry();
+
+ HRESULT Status;
+ ExceptionBreakMode mode;
+ IfFailRet(m_breakpoints.GetExceptionBreakMode(mode, "*"));
+ return m_variables.GetExceptionInfoResponseData(m_pProcess, threadId, mode, exceptionInfoResponse);
+}
+
+// MI
+HRESULT ManagedDebugger::InsertExceptionBreakpoint(const ExceptionBreakMode &mode,
+ const string &name, uint32_t &id)
+{
+ LogFuncEntry();
+ return m_breakpoints.InsertExceptionBreakpoint(mode, name, id);
+}
+
+// MI
+HRESULT ManagedDebugger::DeleteExceptionBreakpoint(const uint32_t id)
+{
+ LogFuncEntry();
+ return m_breakpoints.DeleteExceptionBreakpoint(id);
+}
+
+// MI and VSCode
+bool ManagedDebugger::MatchExceptionBreakpoint(const string &exceptionName,
+ const ExceptionBreakCategory category)
+{
+ LogFuncEntry();
+ return m_breakpoints.MatchExceptionBreakpoint(exceptionName, category);
+}
std::mutex m_breakpointsMutex;
std::unordered_map<std::string, std::unordered_map<int, ManagedBreakpoint> > m_breakpoints;
std::unordered_map<std::string, ManagedFunctionBreakpoint > m_funcBreakpoints;
+ ExceptionBreakpointStorage m_exceptionBreakpoints;
HRESULT ResolveBreakpointInModule(ICorDebugModule *pModule, ManagedBreakpoint &bp);
HRESULT ResolveBreakpoint(ManagedBreakpoint &bp);
void TryResolveBreakpointsForModule(ICorDebugModule *pModule, std::vector<BreakpointEvent> &events);
- void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint);
-
HRESULT SetBreakpoints(
ICorDebugProcess *pProcess,
std::string filename,
std::vector<Breakpoint> &breakpoints);
void SetStopAtEntry(bool stopAtEntry);
+
+ HRESULT InsertExceptionBreakpoint(const ExceptionBreakMode &mode, const std::string &name, uint32_t &output);
+ HRESULT DeleteExceptionBreakpoint(const uint32_t id);
+ HRESULT GetExceptionBreakMode(ExceptionBreakMode &mode, const std::string &name);
+ bool MatchExceptionBreakpoint(const std::string &exceptionName, const ExceptionBreakCategory category);
};
class Variables
};
Evaluator &m_evaluator;
+ struct Member;
std::unordered_map<uint32_t, VariableReference> m_variables;
uint32_t m_nextVariableReference;
void AddVariableReference(Variable &variable, uint64_t frameId, ICorDebugValue *value, ValueKind valueKind);
+ HRESULT GetExceptionInfoResponseDetailsMembers(
+ ICorDebugProcess *pProcess,
+ ICorDebugThread *pThread,
+ uint64_t frameId,
+ Variable &var,
+ ExceptionDetails &details,
+ bool &isFoundInnerException,
+ std::vector<Member> &members);
+
+ HRESULT GetICorDebugValueMembers(
+ ICorDebugProcess *pProcess,
+ ICorDebugThread *pThread,
+ uint64_t frameId,
+ ICorDebugValue *value,
+ bool fetchOnlyStatic,
+ std::vector<Member> &members);
+
+ HRESULT GetVariableMembers(
+ ICorDebugProcess *pProcess,
+ ICorDebugThread *pThread,
+ uint64_t frameId,
+ Variable &var,
+ std::vector<Member> &members);
+
+ HRESULT GetExceptionVariable(
+ uint64_t frameId,
+ ICorDebugThread *pThread,
+ Variable &variable);
+
HRESULT GetStackVariables(
uint64_t frameId,
ICorDebugThread *pThread,
int count,
std::vector<Variable> &variables);
- struct Member;
-
static void FixupInheritedFieldNames(std::vector<Member> &members);
HRESULT FetchFieldsAndProperties(
static BOOL VarGetChild(void *opaque, uint32_t varRef, const char* name, int *typeId, void **data);
bool GetChildDataByName(uint32_t varRef, const std::string &name, int *typeId, void **data);
+ void FillValueAndType(Member &member, Variable &var, bool escape = true);
public:
- Variables(Evaluator &evaluator) : m_evaluator(evaluator), m_nextVariableReference(1) {}
+ Variables(Evaluator &evaluator) :
+ m_evaluator(evaluator),
+ m_nextVariableReference(1)
+ {}
int GetNamedVariables(uint32_t variablesReference);
- HRESULT Variables::GetVariables(
+ HRESULT GetVariables(
ICorDebugProcess *pProcess,
uint32_t variablesReference,
VariablesFilter filter,
ICorDebugValue **ppResult);
void Clear() { m_variables.clear(); m_nextVariableReference = 1; }
+
+ HRESULT GetExceptionInfoResponseData(ICorDebugProcess *pProcess, int threadId, const ExceptionBreakMode &mode, ExceptionInfoResponse &exceptionInfoResponse);
};
class ManagedCallback;
HRESULT GetThreads(std::vector<Thread> &threads) override;
HRESULT SetBreakpoints(std::string filename, const std::vector<SourceBreakpoint> &srcBreakpoints, std::vector<Breakpoint> &breakpoints) override;
HRESULT SetFunctionBreakpoints(const std::vector<FunctionBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) override;
- void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint) override;
HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector<StackFrame> &stackFrames, int &totalFrames) override;
HRESULT StepCommand(int threadId, StepType stepType) override;
HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) override;
HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) override;
HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) override;
HRESULT SetVariableByExpression(uint64_t frameId, const std::string &expression, const std::string &value, std::string &output) override;
+ HRESULT GetExceptionInfoResponse(int threadId, ExceptionInfoResponse &exceptionResponse) override;
+ HRESULT InsertExceptionBreakpoint(const ExceptionBreakMode &mode, const std::string &name, uint32_t &output) override;
+ HRESULT DeleteExceptionBreakpoint(const uint32_t id) override;
+private:
+ bool MatchExceptionBreakpoint(const std::string &exceptionName, const ExceptionBreakCategory category);
};
#include "logger.h"
-
using namespace std::placeholders;
+using std::unordered_set;
+using std::string;
+using std::vector;
typedef std::function<HRESULT(
const std::vector<std::string> &args,
m_debugger->SetFunctionBreakpoints(remainingFuncBreakpoints, tmpBreakpoints);
}
+HRESULT MIProtocol::InsertExceptionBreakpoints(const ExceptionBreakMode &mode,
+ const vector<string>& names, string &output)
+{
+ if (names.empty())
+ return E_FAIL;
+
+ HRESULT Status;
+ string buf = "";
+ uint32_t id = 0;
+ for (const auto &name : names) {
+ Status = m_debugger->InsertExceptionBreakpoint(mode, name, id);
+ if (S_OK != Status) {
+ return Status;
+ }
+ buf += "{number=\"" + std::to_string(id) + "\"},";
+ }
+ if (!buf.empty())
+ buf.pop_back();
+
+ // This line fixes double comma ',,' in output
+ if (names.size() > 1) {
+ output = "^done,bkpt=[" + buf + "]";
+ }
+ else {
+ // This sensitive for current CI Runner.cs
+ output = "^done,bkpt=" + buf;
+ }
+
+ return S_OK;
+}
+
+HRESULT MIProtocol::DeleteExceptionBreakpoints(const std::unordered_set<uint32_t> &ids,
+ string &output)
+{
+ HRESULT Status;
+ for (const auto &id : ids) {
+ Status = m_debugger->DeleteExceptionBreakpoint(id);
+ if (S_OK != Status) {
+ output = "Cannot delete exception breakpoint by id=:'" + std::to_string(id) + "'";
+ return Status;
+ }
+ }
+ return S_OK;
+}
+
void MIProtocol::EmitStoppedEvent(StoppedEvent event)
{
LogFuncEntry();
{ "interpreter-exec", [](const std::vector<std::string> &args, std::string &output) -> HRESULT {
return S_OK;
}},
- { "break-exception-insert", [this](const std::vector<std::string> &args, std::string &output) -> HRESULT {
- if (args.empty())
+ { "break-exception-insert", [this](const vector<string> &args, string &output) -> HRESULT {
+ // That's all info about MI "-break-exception-insert" feature:
+ // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI
+ // https://raw.githubusercontent.com/gregg-miskelly/MIEngine/f5f22f53908644aacffdc3f843fba20b639d07bb/src/MICore/MICommandFactory.cs
+ // https://github.com/OmniSharp/omnisharp-vscode/files/626936/vscodelog.txt
+ if (args.size() < 2) {
+ output = "Command usage: -break-exception-insert [--mda] <unhandled|user-unhandled|throw|throw+user-unhandled> *|<Exception names>";
+ return E_INVALIDARG;
+ }
+
+ size_t i = 0;
+ ExceptionBreakMode filterValue;
+ if (args.at(i) == "--mda") {
+ filterValue.category = ExceptionBreakCategory::MDA;
+ ++i;
+ }
+
+ // Unavailale for changing by user
+ if (args.at(i).compare("unhandled") == 0) {
+ return S_OK;
+ }
+
+ if (args.at(i).compare("user-unhandled") == 0) {
+ filterValue.setUserUnhandled();
+ }
+
+ if (args.at(i).compare("throw") == 0 ) {
+ filterValue.setThrow();
+ }
+
+ if (args.at(i).compare("throw+user-unhandled") == 0) {
+ filterValue.setAll();
+ }
+
+ if (!filterValue.Any()) {
+ output = "Command requires only:'unhandled','user-unhandled','throw','throw+user-unhandled' arguments as an exception stages";
return E_FAIL;
- size_t i = 1;
- if (args.at(0) == "--mda")
- i = 2;
+ }
- std::ostringstream ss;
- const char *sep = "";
- ss << "bkpt=[";
- for (; i < args.size(); i++)
+ // Exception names example:
+ // And vsdbg have common numbers for all type of breakpoints
+ //-break-exception-insert throw A B C
+ //^done,bkpt=[{number="1"},{number="2"},{number="3"}]
+ //(gdb)
+ //-break-insert Class1.cs:1
+ //^done,bkpt={number="4",type="breakpoint",disp="keep",enabled="y"}
+ //(gdb)
+ const vector<string> names(args.begin() + (i + 1), args.end());
+ return InsertExceptionBreakpoints(filterValue, names, output);
+ }},
+ { "break-exception-delete", [this](const vector<string> &args, string &output) -> HRESULT {
+ if (args.empty()) {
+ output = "Command usage: -break-exception-delete <Exception indexes>";
+ return E_INVALIDARG;
+ }
+ unordered_set<uint32_t> indexes;
+ for (const string &id : args)
{
- Breakpoint b;
- m_debugger->InsertExceptionBreakpoint(args.at(i), b);
- ss << sep;
- sep = ",";
- ss << "{number=\"" << b.id << "\"}";
+ bool isTrue = false;
+ int value = ParseInt(id, isTrue);
+ if (isTrue) {
+ indexes.insert(value);
+ }
+ else {
+ output = "Invalid argument:'"+ id + "'";
+ return E_INVALIDARG;
+ }
}
- ss << "]";
- output = ss.str();
-
- return S_OK;
+ return DeleteExceptionBreakpoints(indexes, output);
}},
{ "var-show-attributes", [this](const std::vector<std::string> &args, std::string &output) -> HRESULT {
HRESULT Status;
void DeleteBreakpoints(const std::unordered_set<uint32_t> &ids);
void DeleteFunctionBreakpoints(const std::unordered_set<uint32_t> &ids);
static HRESULT PrintFrameLocation(const StackFrame &stackFrame, std::string &output);
+ HRESULT InsertExceptionBreakpoints(const ExceptionBreakMode &mode, const std::vector<std::string>& names, std::string &output);
+ HRESULT DeleteExceptionBreakpoints(const std::unordered_set<uint32_t> &ids, std::string &output);
};
#include <string>
#include <vector>
+#include <bitset>
+#include <unordered_map>
+
#include "platform.h"
// From https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts
enum SymbolStatus
{
SymbolsSkipped, // "Skipped loading symbols."
- SymbolsLoaded, // "Symbols loaded."
+ SymbolsLoaded, // "Symbols loaded."
SymbolsNotFound
};
std::string name;
std::string value;
std::string type;
+ std::string module;
VariablePresentationHint presentationHint;
std::string evaluateName;
uint32_t variablesReference;
condition(cond)
{}
};
+
+enum struct ExceptionBreakCategory : int
+{
+ CLR = 0,
+ MDA = 1,
+
+ ANY, // CLR or MDA does not matter
+};
+
+class ExceptionBreakMode
+{
+private:
+ enum Flag : int
+ {
+ F_Unhandled = 0, // Always enabled for catch of System Exceptions.
+ F_Throw = 1, // All events raised by 'throw new' operator.
+ F_UserUnhandled, // Break on unhandled exception in user code.
+
+ COUNT // Flag enum element counter
+ };
+
+public:
+ std::bitset<Flag::COUNT> flags;
+
+ ExceptionBreakCategory category;
+
+ ExceptionBreakMode() : category(ExceptionBreakCategory::CLR) {
+ flags.set(Flag::F_Unhandled);
+ }
+
+ bool Unhandled() const {
+ return flags.test(Flag::F_Unhandled);
+ }
+
+ bool Throw() const {
+ return flags.test(Flag::F_Throw);
+ }
+
+ bool UserUnhandled() const {
+ return flags.test(Flag::F_UserUnhandled);
+ }
+
+ void setThrow() {
+ flags.set(Flag::F_Throw);
+ }
+
+ void setUserUnhandled() {
+ flags.set(Flag::F_UserUnhandled);
+ }
+
+ void resetThrow() {
+ flags.reset(Flag::F_Throw);
+ }
+
+ void resetUserUnhandled() {
+ flags.reset(Flag::F_UserUnhandled);
+ }
+
+ // 'All', 'Never' values for VScode
+ void setAll() {
+ // setUnhandled() not supported
+ // its looks as a problem with unconsistent state
+ setThrow();
+ setUserUnhandled();
+ }
+
+ void resetAll() {
+ // resetUnhandled() not supported
+ // its looks as a problem with unconsistent state
+ resetThrow();
+ resetUserUnhandled();
+ }
+
+ bool All() const {
+ return Unhandled() && Throw() && UserUnhandled();
+ }
+
+ bool Never() const {
+ // Always false because Unhandled() always throw
+ return !Unhandled() && !Throw() && !UserUnhandled();
+ }
+
+ // Logical extentions for freindly using of class
+ bool Any() const {
+ return Throw() || UserUnhandled();
+ }
+
+ bool OnlyUnhandled() const {
+ return Unhandled() && !Throw() && !UserUnhandled();
+ }
+
+ bool BothUnhandledAndUserUnhandled() const {
+ return Unhandled() && !Throw() && UserUnhandled();
+ }
+
+};
+
+struct ExceptionBreakpointStorage
+{
+private:
+ // vsdbg not supported list of exception breakpoint command
+ struct ExceptionBreakpoint {
+ ExceptionBreakpoint() : current_asterix_id(0) {}
+ std::unordered_map<uint32_t, std::string> table;
+ // For global filter (*) we need to know last id
+ uint32_t current_asterix_id;
+ // vsdbg supports any clones of named exception filters, but
+ // for customers its will to come some difficult for matching.
+ // For netcoredbg approach based on single unique name for each
+ // next of user exception.
+ //std::unordered_map<std::string, ExceptionBreakMode> exceptionBreakpoints;
+ std::unordered_multimap<std::string, ExceptionBreakMode> exceptionBreakpoints;
+ };
+
+ ExceptionBreakpoint bp;
+
+public:
+ HRESULT Insert(uint32_t id, const ExceptionBreakMode &mode, const std::string &name);
+ HRESULT Delete(uint32_t id);
+ bool Match(const std::string &exceptionName, const ExceptionBreakCategory category) const;
+ HRESULT GetExceptionBreakMode(ExceptionBreakMode &out, const std::string &name) const;
+
+ ExceptionBreakpointStorage() = default;
+ ExceptionBreakpointStorage(ExceptionBreakpointStorage &&that) = default;
+ ExceptionBreakpointStorage(const ExceptionBreakpointStorage &that) = delete;
+};
+
+// An ExceptionPathSegment represents a segment in a path that is used to match
+// leafs or nodes in a tree of exceptions. If a segment consists of more than
+// one name, it matches the names provided if 'negate' is false or missing
+// or it matches anything except the names provided if 'negate' is true.
+struct ExceptionPathSegment
+{
+ // If false or missing this segment matches the names provided, otherwise
+ // it matches anything except the names provided.
+ bool negate;
+ // Depending on the value of 'negate' the names
+ // that should match or not match.
+ std::vector<std::string> names;
+};
+
+// An ExceptionOptions assigns configuration options to a set of exceptions.
+struct ExceptionOptions
+{
+ // A path that selects a single or multiple exceptions in a tree.
+ // If 'path' is missing, the whole tree is selected.
+ // By convention the first segment of the path is a category that is used to
+ // group exceptions in the UI.
+ std::vector<ExceptionPathSegment> path;
+ // Condition when a thrown exception should result in a break.
+ ExceptionBreakMode breakMode;
+};
+
+// The request configures the debuggers response to thrown exceptions.
+// If an exception is configured to break, a 'stopped' event is fired
+// (with reason 'exception').
+struct SetExceptionBreakpointsRequest
+{
+ // IDs of checked exception options. The set of IDs is returned via the
+ // 'exceptionBreakpointFilters' capability.
+ std::vector<std::string> filters;
+ // Configuration options for selected exceptions.
+ std::vector<ExceptionOptions> exceptionOptions;
+};
+
+struct ExceptionBreakpointsFilter
+{
+ // The internal ID of the filter. This value is passed to the
+ // setExceptionBreakpoints request.
+ std::string filter;
+ // The name of the filter. This will be shown in the UI.
+ std::string label;
+ // Initial value of the filter. If not specified a value 'false' is assumed.
+ bool default_value;
+
+ ExceptionBreakpointsFilter(const std::string &fr, const std::string &ll,
+ bool df = false) : filter(fr), label(ll), default_value(df) {}
+};
+
+struct Capabilities
+{
+ // Available filters or options for the setExceptionBreakpoints request.
+ std::vector<ExceptionBreakpointsFilter> exceptionBreakpointFilters;
+ // The debug adapter supports 'exceptionOptions' on the
+ // setExceptionBreakpoints request.
+ bool supportsExceptionOptions;
+ // The debug adapter supports the 'exceptionInfo' request.
+ bool supportsExceptionInfoRequest;
+ //
+ // ... many other features
+};
+
+struct ExceptionDetails
+{
+ // Message contained in the exception.
+ std::string message;
+ // Short type name of the exception object.
+ std::string typeName;
+ // Fully-qualified type name of the exception object.
+ std::string fullTypeName;
+ // Optional expression that can be evaluated in the current scope
+ // to obtain the exception object.
+ std::string evaluateName;
+ // Stack trace at the time the exception was thrown.
+ std::string stackTrace;
+ // Details of the exception contained by this exception, if any.
+ std::vector<ExceptionDetails> innerException;
+};
+
+struct ExceptionInfoResponse
+{
+ // ID of the exception that was thrown.
+ std::string exceptionId;
+ // Descriptive text for the exception provided by the debug adapter.
+ std::string description;
+ // Mode that caused the exception notification to be raised.
+ ExceptionBreakMode breakMode;
+ // Detailed information about the exception.
+ ExceptionDetails details;
+
+ std::string getVSCodeBreakMode() const;
+};
#include "frames.h"
#include "symbolreader.h"
#include "logger.h"
+#include "cputil.h"
+#include "protocol.h"
+using std::string;
+using std::vector;
HRESULT Variables::GetNumChild(
ICorDebugValue *pValue,
Member(const Member &that) = delete;
};
+void Variables::FillValueAndType(Member &member, Variable &var, bool escape)
+{
+ if (member.value == nullptr)
+ {
+ var.value = "<error>";
+ return;
+ }
+ PrintValue(member.value, var.value, escape);
+ TypePrinter::GetTypeOfValue(member.value, var.type);
+}
+
HRESULT Variables::FetchFieldsAndProperties(
ICorDebugValue *pInputValue,
ICorDebugThread *pThread,
m_variables.emplace(std::make_pair(variable.variablesReference, std::move(variableReference)));
}
+HRESULT Variables::GetICorDebugValueMembers(
+ ICorDebugProcess *pProcess,
+ ICorDebugThread *pThread,
+ uint64_t frameId,
+ ICorDebugValue *value,
+ bool fetchOnlyStatic,
+ vector<Member> &members)
+{
+ if (pProcess == nullptr || pThread == nullptr || value == nullptr)
+ return E_FAIL;
+
+ HRESULT Status;
+
+ StackFrame stackFrame(frameId);
+ ToRelease<ICorDebugFrame> pFrame;
+ IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+
+ ToRelease<ICorDebugILFrame> pILFrame;
+ if (pFrame)
+ IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*)&pILFrame));
+
+ bool hasStaticMembers = false;
+ IfFailRet(FetchFieldsAndProperties(value,
+ pThread,
+ pILFrame,
+ members,
+ fetchOnlyStatic,
+ hasStaticMembers,
+ 0,
+ INT_MAX));
+
+ return S_OK;
+}
+
+HRESULT Variables::GetVariableMembers(
+ ICorDebugProcess *pProcess,
+ ICorDebugThread *pThread,
+ uint64_t frameId,
+ Variable &var,
+ vector<Member> &members)
+{
+ if (pProcess == nullptr || pThread == nullptr)
+ return E_FAIL;
+
+ VariableReference &ref = m_variables.at(var.variablesReference);
+ if (ref.IsScope())
+ return E_INVALIDARG;
+
+ if (!ref.value)
+ return E_FAIL;
+
+ bool fetchOnlyStatic = ref.valueKind == ValueIsClass;
+ return GetICorDebugValueMembers(pProcess, pThread, frameId, ref.value,
+ fetchOnlyStatic, members);
+}
+
+static HRESULT GetModuleName(ICorDebugThread *pThread, std::string &module) {
+ HRESULT Status;
+ ToRelease<ICorDebugFrame> pFrame;
+ IfFailRet(pThread->GetActiveFrame(&pFrame));
+
+ if (pFrame == nullptr)
+ return E_FAIL;
+
+ ToRelease<ICorDebugFunction> pFunc;
+ IfFailRet(pFrame->GetFunction(&pFunc));
+
+ ToRelease<ICorDebugModule> pModule;
+ IfFailRet(pFunc->GetModule(&pModule));
+
+ ToRelease<IUnknown> pMDUnknown;
+ ToRelease<IMetaDataImport> pMDImport;
+ IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+ IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*)&pMDImport));
+
+ WCHAR mdName[mdNameLen];
+ ULONG nameLen;
+ IfFailRet(pMDImport->GetScopeProps(mdName, _countof(mdName), &nameLen, nullptr));
+ module = to_utf8(mdName);
+
+ return S_OK;
+}
+
+HRESULT Variables::GetExceptionInfoResponseDetailsMembers(
+ ICorDebugProcess *pProcess,
+ ICorDebugThread *pThread,
+ uint64_t frameId,
+ Variable &varRoot,
+ ExceptionDetails &details,
+ bool &isFoundInnerException,
+ vector<Member> &members)
+{
+ HRESULT Status;
+
+ details.evaluateName = varRoot.name;
+ details.typeName = varRoot.type;
+ details.fullTypeName = varRoot.type;
+
+ bool isMessage, isStackTrace, isInnerException;
+ for (auto &it : members)
+ {
+ isMessage = isStackTrace = isInnerException = false;
+ if ( !(isMessage = (it.name == "Message")) &&
+ !(isStackTrace = (it.name == "StackTrace")) &&
+ !(isInnerException = (it.name == "InnerException")))
+ continue;
+
+ Variable var;
+ var.name = it.name;
+ FillValueAndType(it, var);
+
+ if (isMessage)
+ {
+ details.message = var.value;
+ continue;
+ }
+
+ if (isStackTrace)
+ {
+ details.stackTrace = var.value;
+ continue;
+ }
+
+ if (isInnerException)
+ {
+ vector<Member> mem;
+ if ((Status = GetICorDebugValueMembers(pProcess, pThread, frameId,
+ it.value, false, mem)) != S_OK)
+ return Status;
+
+ if (!mem.empty())
+ {
+ isFoundInnerException = true;
+ details.innerException.emplace_back(std::move(ExceptionDetails()));
+ bool dummy;
+ if ((Status = GetExceptionInfoResponseDetailsMembers(pProcess, pThread,
+ frameId, var, details.innerException.back(), dummy, mem)) != S_OK)
+ {
+ details.innerException.pop_back();
+ return Status;
+ }
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT Variables::GetExceptionInfoResponseData(
+ ICorDebugProcess *pProcess,
+ int threadId,
+ const ExceptionBreakMode &mode,
+ ExceptionInfoResponse &exceptionInfoResponse)
+{
+ HRESULT Status;
+
+ uint64_t frameId = StackFrame(threadId, 0, "").id;
+ ToRelease<ICorDebugThread> pThread;
+ IfFailRet(pProcess->GetThread(threadId, &pThread));
+
+ Variable varException;
+ if (GetExceptionVariable(frameId, pThread, varException))
+ return E_FAIL;
+
+ vector<Member> members;
+ if ((Status = GetVariableMembers(pProcess, pThread, frameId, varException, members)) != S_OK)
+ return Status;
+
+ if (mode.OnlyUnhandled() || mode.UserUnhandled())
+ {
+ exceptionInfoResponse.description = "An unhandled exception of type '" + varException.type +
+ "' occurred in " + varException.module;
+ }
+ else
+ {
+ exceptionInfoResponse.description = "Exception thrown: '" + varException.type +
+ "' in " + varException.module;
+ }
+
+ exceptionInfoResponse.exceptionId = varException.type;
+ exceptionInfoResponse.breakMode = mode;
+
+ bool isFoundInnerException = false;
+ if ((Status = GetExceptionInfoResponseDetailsMembers(pProcess, pThread, frameId,
+ varException, exceptionInfoResponse.details, isFoundInnerException, members)) != S_OK)
+ return Status;
+
+ if (isFoundInnerException)
+ exceptionInfoResponse.description += "\n Inner exception found, see $exception in variables window for more details.";
+
+ return S_OK;
+}
+
+HRESULT Variables::GetExceptionVariable(
+ uint64_t frameId,
+ ICorDebugThread *pThread,
+ Variable &var)
+{
+ HRESULT Status;
+ ToRelease<ICorDebugValue> pExceptionValue;
+ if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr)
+ {
+ var.name = "$exception";
+ var.evaluateName = var.name;
+
+ bool escape = true;
+ PrintValue(pExceptionValue, var.value, escape);
+ TypePrinter::GetTypeOfValue(pExceptionValue, var.type);
+
+ // AddVariableReference is re-interable function.
+ AddVariableReference(var, frameId, pExceptionValue, ValueIsVariable);
+
+ string excModule;
+ IfFailRet(GetModuleName(pThread, excModule));
+ var.module = excModule;
+
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
HRESULT Variables::GetStackVariables(
uint64_t frameId,
ICorDebugThread *pThread,
std::vector<Variable> &variables)
{
HRESULT Status;
-
int currentIndex = -1;
-
- ToRelease<ICorDebugValue> pExceptionValue;
- if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr)
- {
+ Variable var;
+ if (GetExceptionVariable(frameId, pThread, var) == S_OK) {
+ variables.push_back(var);
++currentIndex;
- bool outOfRange = currentIndex < start || (count != 0 && currentIndex >= start + count);
- if (!outOfRange)
- {
- Variable var;
- var.name = "$exception";
- var.evaluateName = var.name;
- bool escape = true;
- PrintValue(pExceptionValue, var.value, escape);
- TypePrinter::GetTypeOfValue(pExceptionValue, var.type);
- AddVariableReference(var, frameId, pExceptionValue, ValueIsVariable);
- variables.push_back(var);
- }
}
IfFailRet(m_evaluator.WalkStackVars(pFrame, [&](
bool isIndex = !it.name.empty() && it.name.at(0) == '[';
if (var.name.find('(') == std::string::npos) // expression evaluator does not support typecasts
var.evaluateName = ref.evaluateName + (isIndex ? "" : ".") + var.name;
- bool escape = true;
- if (it.value == nullptr)
- {
- var.value = "<error>";
- }
- else
- {
- PrintValue(it.value, var.value, escape);
- TypePrinter::GetTypeOfValue(it.value, var.type);
- }
+ FillValueAndType(it, var);
AddVariableReference(var, ref.frameId, it.value, ValueIsVariable);
variables.push_back(var);
}
#include "cputil.h"
#include "logger.h"
+using std::string;
+using std::vector;
+using std::min;
+
// for convenience
using json = nlohmann::json;
}
}
+static json getVSCode(const ExceptionDetails &self) {
+ json details = json({});
+
+ details["message"] = self.message;
+ details["typeName"] = self.typeName;
+ details["fullTypeName"] = self.fullTypeName;
+ details["evaluateName"] = self.evaluateName;
+ details["stackTrace"] = self.stackTrace;
+
+ json arr = json::array();
+ if (!self.innerException.empty()) {
+ // INFO: Visual Studio Code does not display inner exception,
+ // but vsdbg fill all nested InnerExceptions in Response.
+ const auto it = self.innerException.begin();
+ json inner = getVSCode(*it);
+ arr.push_back(inner);
+ }
+ details["innerException"] = arr;
+
+ return details;
+}
+
void VSCodeProtocol::EmitStoppedEvent(StoppedEvent event)
{
LogFuncEntry();
capabilities["supportsFunctionBreakpoints"] = true;
capabilities["supportsConditionalBreakpoints"] = true;
capabilities["supportTerminateDebuggee"] = true;
+ capabilities["supportsExceptionInfoRequest"] = true;
}
HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &arguments, json &body)
EmitCapabilitiesEvent();
m_debugger->Initialize();
+
AddCapabilitiesTo(body);
return S_OK;
} },
+ { "setExceptionBreakpoints", [this](const json &arguments, json &body) {
+ vector<string> filters = arguments.value("filters", vector<string>());
+ ExceptionBreakMode mode;
+
+ namespace KW = VSCodeExceptionBreakModeKeyWord;
+
+ for (unsigned i = 0; i < filters.size(); i++)
+ {
+ if (filters[i].compare(KW::ALL) == 0 ||
+ filters[i].compare(KW::ALWAYS) == 0) {
+ mode.setAll();
+ }
+ if (filters[i].compare(KW::USERUNHANDLED) == 0 ||
+ filters[i].compare(KW::USERUNHANDLED_A) == 0) {
+ mode.setUserUnhandled();
+ }
+ // Nothing to do for "unhandled"
+ if (filters[i].compare(KW::NEVER) == 0) {
+ mode.resetAll();
+ }
+ }
+
+ const string globalExceptionBreakpoint = "*";
+ uint32_t id;
+ m_debugger->InsertExceptionBreakpoint(mode, globalExceptionBreakpoint, id);
+
+ //
+ // TODO:
+ // - implement options support. options not supported in
+ // current vscode 1.31.1 with C# plugin 1.17.1
+ // - use ExceptionBreakpointStorage type for support options feature
+ body["supportsExceptionOptions"] = false;
+
+ return S_OK;
+ } },
{ "configurationDone", [this](const json &arguments, json &body){
return m_debugger->ConfigurationDone();
} },
+ { "exceptionInfo", [this](const json &arguments, json &body) {
+ int threadId = arguments.at("threadId");
+ ExceptionInfoResponse exceptionResponse;
+ if (!m_debugger->GetExceptionInfoResponse(threadId, exceptionResponse))
+ {
+ body["breakMode"] = exceptionResponse.getVSCodeBreakMode();
+ body["exceptionId"] = exceptionResponse.exceptionId;
+ body["description"] = exceptionResponse.description;
+ body["details"] = getVSCode(exceptionResponse.details);
+
+ return S_OK;
+ }
+
+ return E_FAIL;
+ } },
{ "setBreakpoints", [this](const json &arguments, json &body){
HRESULT Status;
}
}
}
+
+string ExceptionInfoResponse::getVSCodeBreakMode() const
+{
+ namespace KW = VSCodeExceptionBreakModeKeyWord;
+
+ if (breakMode.Never())
+ return KW::NEVER;
+
+ if (breakMode.All())
+ return KW::ALWAYS;
+
+ if (breakMode.OnlyUnhandled())
+ return KW::UNHANDLED;
+
+ // Throw() not supported for VSCode
+ // - description of "always: always breaks".
+ // if (breakMode.Throw())
+
+ if (breakMode.UserUnhandled())
+ return KW::USERUNHANDLED;
+
+ // Logical Error
+ return "undefined";
+}
#include "json/json.hpp"
#include "debugger.h"
+namespace VSCodeExceptionBreakModeKeyWord
+{
+ static const std::string ALL = "all";
+ static const std::string ALWAYS = "always";
+ static const std::string NEVER = "never";
+ static const std::string USERUNHANDLED = "userUnhandled";
+ static const std::string USERUNHANDLED_A = "user-unhandled";
+ static const std::string UNHANDLED = "unhandled";
+}
class VSCodeProtocol : public Protocol
{
--- /dev/null
+/*
+using System.IO;
+Send("1-file-exec-and-symbols dotnet");
+Send("2-exec-arguments " + TestBin);
+
+string filename = Path.GetFileName(TestSource);
+
+Send("3-break-exception-insert throw+user-unhandled A B C");
+var r = Expect("3^done");
+Send("4-break-exception-delete 3 2 1");
+r = Expect("4^done");
+
+//////// Silent removing of previous global exception filter
+Send("200-break-exception-insert throw *");
+r = Expect("200^done");
+int be200 = r.Find("bkpt").FindInt("number");
+
+Send("201-break-exception-insert throw+user-unhandled *");
+r = Expect("201^done");
+int be201 = r.Find("bkpt").FindInt("number");
+
+Send(String.Format("202-break-exception-delete {0}", be200));
+r = Expect("202^error");
+
+Send(String.Format("203-break-exception-delete {0}", be201));
+r = Expect("203^done");
+////////
+
+Send(String.Format("5-break-insert -f {0}:{1}", filename, Lines["PIT_STOP_A"]));
+r = Expect("5^done");
+int b1 = r.Find("bkpt").FindInt("number");
+
+Send(String.Format("6-break-insert -f {0}:{1}", filename, Lines["PIT_STOP_B"]));
+r = Expect("6^done");
+int b2 = r.Find("bkpt").FindInt("number");
+
+Send("7-exec-run");
+
+r = Expect("*stopped");
+Assert.Equal("entry-point-hit", r.FindString("reason"));
+Assert.Equal(Lines["MAIN"], r.Find("frame").FindInt("line"));
+
+//////// Expected result => Not found any exception breakpoits.
+//////// "State: !Thow() && !UserUnhandled() name := '*'";
+Send("8-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+//////// Expected result => Not found any exception breakpoits.
+//////// "State: !Thow() && UserUnhandled() name := '*'";
+Send("10-break-exception-insert user-unhandled *");
+r = Expect("10^done");
+int be1 = r.Find("bkpt").FindInt("number");
+
+Send("11-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("12-break-exception-delete {0}", be1));
+r = Expect("12^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && !UserUnhandled() name := '*'";
+Send("13-break-exception-insert throw *");
+r = Expect("13^done");
+int be2 = r.Find("bkpt").FindInt("number");
+
+Send("14-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("15-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("17-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("18-break-exception-delete {0}", be2));
+r = Expect("18^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled() name := '*'";
+Send("19-break-exception-insert throw+user-unhandled *");
+r = Expect("19^done");
+int be3 = r.Find("bkpt").FindInt("number");
+
+Send("20-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("21-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("22-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("23-break-exception-delete {0}", be3));
+r = Expect("23^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled() name := '*'";
+Send("19-break-exception-insert throw+user-unhandled *");
+r = Expect("19^done");
+int be4 = r.Find("bkpt").FindInt("number");
+
+Send("20-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("21-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("22-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("23-break-exception-delete {0}", be4));
+r = Expect("23^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled()";
+//////// "name := System.DivideByZeroException";
+Send("24-break-exception-insert throw+user-unhandled System.DivideByZeroException");
+r = Expect("24^done");
+int be5 = r.Find("bkpt").FindInt("number");
+
+Send("25-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("26-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("27-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("28-break-exception-delete {0}", be5));
+r = Expect("28^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow()";
+//////// "name := System.DivideByZeroException";
+//////// "State: UserUnhandled()";
+//////// "name := System.DivideByZeroException";
+Send("29-break-exception-insert throw System.DivideByZeroException");
+r = Expect("29^done");
+int be6 = r.Find("bkpt").FindInt("number");
+
+Send("30-break-exception-insert user-unhandled System.DivideByZeroException");
+r = Expect("30^done");
+int be7 = r.Find("bkpt").FindInt("number");
+
+Send("31-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("32-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("33-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("34-break-exception-delete {0}", be7));
+r = Expect("34^done");
+
+Send(String.Format("35-break-exception-delete {0}", be6));
+r = Expect("35^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled()";
+//////// "name := System.DivideByZeroExceptionWrong and *";
+Send("36-break-exception-insert throw+user-unhandled *");
+r = Expect("36^done");
+int be8 = r.Find("bkpt").FindInt("number");
+
+Send("37-break-exception-insert throw+user-unhandled DivideByZeroExceptionWrong");
+r = Expect("37^done");
+int be9 = r.Find("bkpt").FindInt("number");
+
+Send("38-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("39-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("40-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("41-break-exception-delete {0}", be8));
+r = Expect("41^done");
+
+Send(String.Format("42-break-exception-delete {0}", be9));
+r = Expect("42^done");
+
+//////// Expected result => Not found any exception breakpoits.
+//////// "State: Thow() && UserUnhandled()";
+//////// "name := System.DivideByZeroExceptionWrong";
+
+Send("36-break-exception-insert throw+user-unhandled System.DivideByZeroExceptionWrong");
+r = Expect("36^done");
+int be10 = r.Find("bkpt").FindInt("number");
+
+Send("37-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_B"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("38-break-exception-delete {0}", be10));
+r = Expect("38^done");
+
+//////// Expected result => Raised EXCEPTION_C, EXCEPTION_D, EXCEPTION_E and exit after unhandled EXCEPTION_E.
+//////// "State: !Thow() && UserUnhandled()";
+//////// "name := AppException";
+
+//////// Test of setting unhandled - read only mode.
+Send("39-break-exception-insert unhandled AppException");
+r = Expect("39^done");
+
+Send("40-break-exception-insert user-unhandled AppException");
+r = Expect("40^done");
+int be11 = r.Find("bkpt").FindInt("number");
+
+Send("41-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_C"], r.Find("frame").FindInt("line"));
+
+Send("42-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_D"], r.Find("frame").FindInt("line"));
+
+Send("43-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_E"], r.Find("frame").FindInt("line"));
+
+Send("44-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_E"], r.Find("frame").FindInt("line"));
+
+Send("45-exec-continue");
+r = Expect("*stopped");
+Assert.Equal("exited", r.FindString("reason"));
+
+Send(String.Format("46-break-exception-delete {0}", be11));
+r = Expect("46^done");
+
+Send(String.Format("99-break-delete {0}", b1));
+Expect("99^done");
+Send(String.Format("100-break-delete {0}", b2));
+Expect("100^done");
+
+Send("-gdb-exit");
+*/
+
+using System;
+using System.Threading;
+
+public class AppException : Exception
+{
+ public AppException(String message) : base(message) { }
+ public AppException(String message, Exception inner) : base(message, inner) { }
+}
+
+public class Test
+{
+ public void TestSystemException()
+ { // //@PIT_STOP_A@
+ int zero = 1 - 1;
+ try
+ {
+ int a = 1 / zero; // //@EXCEPTION_A@
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Implicit Exception: {e.Message}");
+ }
+ try
+ {
+ throw new System.DivideByZeroException(); // //@EXCEPTION_B@
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Explicit Exception: {e.Message}");
+ }
+ Console.WriteLine("Complete system exception test\n");
+ }
+ public void TestAppException()
+ { // //@PIT_STOP_B@
+ try
+ {
+ CatchInner();
+ }
+ catch (AppException e)
+ {
+ Console.WriteLine("Caught: {0}", e.Message);
+ if (e.InnerException != null)
+ {
+ Console.WriteLine("Inner exception: {0}", e.InnerException);
+ }
+ throw new AppException("Error again in CatchInner caused by calling the ThrowInner method.", e); // //@EXCEPTION_E@
+ }
+ Console.WriteLine("Complete application exception test\n");
+ }
+ public void ThrowInner()
+ {
+ throw new AppException("Exception in ThrowInner method."); // //@EXCEPTION_C@
+ }
+ public void CatchInner()
+ {
+ try
+ {
+ this.ThrowInner();
+ }
+ catch (AppException e)
+ {
+ throw new AppException("Error in CatchInner caused by calling the ThrowInner method.", e); // //@EXCEPTION_D@
+ }
+ }
+}
+
+public class MainClass
+{
+ public static void Main()
+ { // //@MAIN@
+ Test test = new Test();
+
+ //////// "State: !Thow() && !UserUnhandled() name := '*'";
+ test.TestSystemException();
+
+ //////// "State: !Thow() && UserUnhandled() name := '*'";
+ test.TestSystemException();
+
+ //////// "State: Thow() && !UserUnhandled() name := '*'";
+ test.TestSystemException();
+
+ //////// "State: Thow() && UserUnhandled() name := '*'";
+ test.TestSystemException();
+
+ //////// "State: Thow() && UserUnhandled()";
+ //////// "name := System.DivideByZeroException";
+ test.TestSystemException();
+
+ //////// "State: Thow()";
+ //////// "name := System.DivideByZeroException";
+ //////// "State: UserUnhandled()";
+ //////// "name := System.DivideByZeroException";
+ test.TestSystemException();
+
+ //////// "State: Thow() && UserUnhandled()";
+ //////// "name := System.DivideByZeroExceptionWrong and *";
+ test.TestSystemException();
+
+ //////// "State: Thow() && UserUnhandled()";
+ //////// "name := System.DivideByZeroExceptionWrong";
+ test.TestSystemException();
+
+ //////// "TODO:\n"
+ //////// "Test for check forever waiting:\n"
+ //////// "threads in NetcoreDBG (unsupported now)\n"
+ //////// "Thread threadA = new Thread(new ThreadStart(test.TestSystemException));\n"
+ //////// "Thread threadB = new Thread(new ThreadStart(test.TestSystemException));\n"
+ //////// "threadA.Start();\n"
+ //////// "threadB.Start();\n"
+ //////// "threadA.Join();\n"
+ //////// "threadB.Join();\n"
+
+ //////// "Test with process exit at the end, need re-run"
+ //////// "INFO: State: !Thow() && UserUnhandled() name := AppException";
+ test.TestAppException();
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp2.0</TargetFramework>
+ </PropertyGroup>
+
+</Project>
[Fact]
public void BreakpointAddRemoveTest() => ExecuteTest();
+ [Fact]
+ public void ExceptionBreakpointTest() => ExecuteTest();
+
[Fact]
public void SetValuesTest() => ExecuteTest();
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LambdaTest", "LambdaTest\LambdaTest.csproj", "{70F91400-AB4E-4A19-A137-9B5BF36C8543}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExceptionBreakpointTest", "ExceptionBreakpointTest\ExceptionBreakpointTest.csproj", "{361771B0-7948-412E-86ED-D41157F6DE6B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
{70F91400-AB4E-4A19-A137-9B5BF36C8543}.Release|x64.Build.0 = Release|x64
{70F91400-AB4E-4A19-A137-9B5BF36C8543}.Release|x86.ActiveCfg = Release|x86
{70F91400-AB4E-4A19-A137-9B5BF36C8543}.Release|x86.Build.0 = Release|x86
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x64.ActiveCfg = Debug|x64
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x64.Build.0 = Debug|x64
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x86.ActiveCfg = Debug|x86
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x86.Build.0 = Debug|x86
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x64.ActiveCfg = Release|x64
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x64.Build.0 = Release|x64
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x86.ActiveCfg = Release|x86
+ {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x86.Build.0 = Release|x86
EndGlobalSection
EndGlobal