[Pkg-mono-svn-commits] [sdb] 01/02: Imported Upstream version 1.2
Jo Shields
directhex at moszumanska.debian.org
Fri Feb 7 12:52:32 UTC 2014
This is an automated email from the git hooks/post-receive script.
directhex pushed a commit to annotated tag debian/1.2-1
in repository sdb.
commit 042a7e8093aadd62544cb2f22c4cf906300e4cac
Author: Jo Shields <directhex at apebox.org>
Date: Fri Feb 7 11:31:10 2014 +0000
Imported Upstream version 1.2
---
.gitignore | 7 +
.gitmodules | 9 +
LICENSE | 21 ++
Makefile | 224 ++++++++++++
README.md | 301 ++++++++++++++++
chk/check.fs | 221 ++++++++++++
chk/cs/cwl.cs | 9 +
chk/cs/throw.cs | 15 +
chk/fs/print.fs | 4 +
mono.snk | Bin 0 -> 596 bytes
sdb.exe.config | 4 +
sdb.in | 2 +
src/AssemblyInfo.cs | 30 ++
src/Color.cs | 117 +++++++
src/Command.cs | 42 +++
src/CommandAttribute.cs | 33 ++
src/CommandLine.cs | 318 +++++++++++++++++
src/Commands/ArgumentsCommand.cs | 74 ++++
src/Commands/AttachCommand.cs | 61 ++++
src/Commands/BacktraceCommand.cs | 83 +++++
src/Commands/BreakpointCommand.cs | 531 ++++++++++++++++++++++++++++
src/Commands/CatchpointCommand.cs | 218 ++++++++++++
src/Commands/ConfigCommand.cs | 278 +++++++++++++++
src/Commands/ConnectCommand.cs | 104 ++++++
src/Commands/ContinueCommand.cs | 63 ++++
src/Commands/DatabaseCommand.cs | 185 ++++++++++
src/Commands/DecompileCommand.cs | 62 ++++
src/Commands/DirectoryCommand.cs | 73 ++++
src/Commands/DisassembleCommand.cs | 114 ++++++
src/Commands/EnvironmentCommand.cs | 305 ++++++++++++++++
src/Commands/FrameCommand.cs | 439 +++++++++++++++++++++++
src/Commands/HelpCommand.cs | 118 +++++++
src/Commands/KillCommand.cs | 65 ++++
src/Commands/ListenCommand.cs | 101 ++++++
src/Commands/PluginCommand.cs | 66 ++++
src/Commands/PrintCommand.cs | 92 +++++
src/Commands/QuitCommand.cs | 67 ++++
src/Commands/ResetCommand.cs | 72 ++++
src/Commands/RootCommand.cs | 99 ++++++
src/Commands/RunCommand.cs | 100 ++++++
src/Commands/SourceCommand.cs | 160 +++++++++
src/Commands/StepCommand.cs | 307 ++++++++++++++++
src/Commands/ThreadCommand.cs | 295 ++++++++++++++++
src/Commands/WatchCommand.cs | 256 ++++++++++++++
src/Configuration.cs | 199 +++++++++++
src/CustomLogger.cs | 50 +++
src/Debugger.cs | 702 +++++++++++++++++++++++++++++++++++++
src/LibEdit.cs | 40 +++
src/Log.cs | 105 ++++++
src/MultiCommand.cs | 159 +++++++++
src/Plugins.cs | 122 +++++++
src/Program.cs | 110 ++++++
src/SessionKind.cs | 34 ++
src/State.cs | 33 ++
src/Utilities.cs | 131 +++++++
55 files changed, 7430 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6426033
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+bin/*
+dep/*
+
+*.dll
+*.exe
+*.gz
+*.mdb
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..55310a0
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,9 @@
+[submodule "dep/debugger-libs"]
+ path = dep/debugger-libs
+ url = git://github.com/mono/debugger-libs
+[submodule "dep/cecil"]
+ path = dep/cecil
+ url = git://github.com/mono/cecil.git
+[submodule "dep/nrefactory"]
+ path = dep/nrefactory
+ url = git://github.com/icsharpcode/NRefactory.git
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fe34fff
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Alex Rønne Petersen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0f47683
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,224 @@
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2013 Alex Rønne Petersen
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+CD ?= cd
+CHMOD ?= chmod
+CP ?= cp
+FSHARPC ?= fsharpc
+GENDARME ?= gendarme
+MCS ?= mcs
+MKDIR ?= mkdir
+PKG_CONFIG ?= pkg-config
+SED ?= sed
+TAR ?= tar
+XBUILD ?= xbuild
+
+export MONO_PREFIX ?= /usr
+MODE ?= Debug
+
+ifeq ($(MODE), Debug)
+ override xb_mode = net_4_0_Debug
+ override mono_opt = --debug
+
+ FSHARPC_FLAGS += --debug+
+ MCS_FLAGS += -debug
+else
+ override xb_mode = net_4_0_Release
+ override mono_opt =
+
+ FSHARPC_FLAGS += --optimize
+ MCS_FLAGS += -optimize
+endif
+
+FSHARPC_FLAGS += --nologo --warnaserror
+GENDARME_FLAGS += --severity all --confidence all
+MCS_FLAGS += -langversion:future -unsafe -warnaserror
+XBUILD_FLAGS += /verbosity:quiet /nologo /property:Configuration=$(xb_mode)
+
+FSHARPC_TEST_FLAGS += --debug+ --nologo --warnaserror
+MCS_TEST_FLAGS += -debug -langversion:future -unsafe -warnaserror
+
+.PHONY: all check clean clean-check clean-deps gendarme
+
+override results = \
+ LICENSE \
+ README \
+ sdb.exe \
+ sdb.exe.config \
+ sdb
+
+all: $(addprefix bin/, $(results))
+
+define TargetType
+$(if $(findstring .exe, $(1)),exe,library)
+endef
+
+override cs_tests = \
+ cwl.exe \
+ throw.exe
+
+define CSharpTestTemplate
+chk/cs/$(1): chk/cs/$(basename $(1)).cs
+ $(MCS) $(MCS_TEST_FLAGS) -out:chk/cs/$(1) -target:$(call TargetType, $(1)) chk/cs/$(basename $(1)).cs
+endef
+
+$(foreach tgt, $(cs_tests), $(eval $(call CSharpTestTemplate,$(tgt))))
+
+override fs_tests = \
+ print.exe
+
+define FSharpTestTemplate
+chk/fs/$(1): chk/fs/$(basename $(1)).fs
+ $(FSHARPC) $(FSHARPC_TEST_FLAGS) --out:chk/fs/$(1) --target:$(call TargetType, $(1)) chk/fs/$(basename $(1)).fs
+endef
+
+$(foreach tgt, $(fs_tests), $(eval $(call FSharpTestTemplate,$(tgt))))
+
+override tests = \
+ $(addprefix chk/cs/, $(cs_tests)) \
+ $(addprefix chk/cs/, $(addsuffix .mdb, $(cs_tests))) \
+ $(addprefix chk/fs/, $(fs_tests)) \
+ $(addprefix chk/fs/, $(addsuffix .mdb, $(fs_tests)))
+
+check: chk/check.exe $(addprefix bin/, $(results)) $(tests)
+ $(CD) chk && MONO_PATH=. $(MONO_PREFIX)/bin/mono check.exe
+
+clean:
+ $(RM) -r bin
+
+clean-check:
+ $(RM) chk/check.exe chk/check.exe.mdb
+ $(RM) $(tests)
+
+clean-deps:
+ $(CD) dep/debugger-libs && $(XBUILD) $(XBUILD_FLAGS) /target:Clean
+
+gendarme: bin/sdb.exe
+ $(GENDARME) $(GENDARME_FLAGS) --log bin/sdb.log $<
+
+override refs = \
+ ICSharpCode.NRefactory.dll \
+ ICSharpCode.NRefactory.CSharp.dll \
+ Mono.Cecil.dll \
+ Mono.Cecil.Mdb.dll \
+ Mono.Debugger.Soft.dll \
+ Mono.Debugging.dll \
+ Mono.Debugging.Soft.dll
+
+$(addprefix bin/, $(refs)):
+ $(CD) dep/debugger-libs && $(XBUILD) $(XBUILD_FLAGS) debugger-libs.sln
+ $(MKDIR) -p bin
+ $(CP) dep/nrefactory/bin/Debug/ICSharpCode.NRefactory.dll \
+ bin/ICSharpCode.NRefactory.dll
+ $(CP) dep/nrefactory/bin/Debug/ICSharpCode.NRefactory.CSharp.dll \
+ bin/ICSharpCode.NRefactory.CSharp.dll
+ $(CP) dep/cecil/bin/$(xb_mode)/Mono.Cecil.dll \
+ bin/Mono.Cecil.dll
+ $(CP) dep/cecil/bin/$(xb_mode)/Mono.Cecil.Mdb.dll \
+ bin/Mono.Cecil.Mdb.dll
+ $(CP) dep/debugger-libs/Mono.Debugger.Soft/bin/Debug/Mono.Debugger.Soft.dll \
+ bin/Mono.Debugger.Soft.dll
+ $(CP) dep/debugger-libs/Mono.Debugging/bin/Debug/Mono.Debugging.dll \
+ bin/Mono.Debugging.dll
+ $(CP) dep/debugger-libs/Mono.Debugging.Soft/bin/Debug/Mono.Debugging.Soft.dll \
+ bin/Mono.Debugging.Soft.dll
+
+dep/Options.cs:
+ $(CP) `$(PKG_CONFIG) --variable=Sources mono-options` $@
+
+dep/getline.cs:
+ $(CP) `$(PKG_CONFIG) --variable=Sources mono-lineeditor` $@
+
+override srcs = \
+ dep/Options.cs \
+ dep/getline.cs \
+ src/Commands/AttachCommand.cs \
+ src/Commands/BacktraceCommand.cs \
+ src/Commands/BreakpointCommand.cs \
+ src/Commands/CatchpointCommand.cs \
+ src/Commands/ConfigCommand.cs \
+ src/Commands/ConnectCommand.cs \
+ src/Commands/ContinueCommand.cs \
+ src/Commands/DatabaseCommand.cs \
+ src/Commands/DecompileCommand.cs \
+ src/Commands/DirectoryCommand.cs \
+ src/Commands/DisassembleCommand.cs \
+ src/Commands/EnvironmentCommand.cs \
+ src/Commands/FrameCommand.cs \
+ src/Commands/HelpCommand.cs \
+ src/Commands/KillCommand.cs \
+ src/Commands/ListenCommand.cs \
+ src/Commands/PluginCommand.cs \
+ src/Commands/PrintCommand.cs \
+ src/Commands/QuitCommand.cs \
+ src/Commands/ResetCommand.cs \
+ src/Commands/RootCommand.cs \
+ src/Commands/RunCommand.cs \
+ src/Commands/SourceCommand.cs \
+ src/Commands/StepCommand.cs \
+ src/Commands/ThreadCommand.cs \
+ src/Commands/WatchCommand.cs \
+ src/AssemblyInfo.cs \
+ src/Color.cs \
+ src/Command.cs \
+ src/CommandAttribute.cs \
+ src/CommandLine.cs \
+ src/Configuration.cs \
+ src/CustomLogger.cs \
+ src/Debugger.cs \
+ src/LibEdit.cs \
+ src/Log.cs \
+ src/MultiCommand.cs \
+ src/Plugins.cs \
+ src/Program.cs \
+ src/SessionKind.cs \
+ src/State.cs \
+ src/Utilities.cs
+
+bin/sdb.exe: $(srcs) $(addprefix bin/, $(refs)) mono.snk
+ $(MCS) $(MCS_FLAGS) -keyfile:mono.snk -lib:bin -out:bin/sdb.exe -target:exe -r:Mono.Posix $(addprefix -r:, $(refs)) $(srcs)
+
+bin/sdb.exe.config: sdb.exe.config
+ $(MKDIR) -p bin
+ $(CP) $< $@
+
+bin/sdb: sdb.in
+ $(MKDIR) -p bin
+ $(SED) s/__MONO_OPTIONS__/$(mono_opt)/ $< > $@
+ $(CHMOD) +x $@
+
+bin/LICENSE: LICENSE
+ $(MKDIR) -p bin
+ $(CP) $< $@
+
+bin/README: README.md
+ $(MKDIR) -p bin
+ $(CP) $< $@
+
+chk/check.exe: chk/check.fs mono.snk
+ $(FSHARPC) $(FSHARPC_FLAGS) --keyfile:mono.snk --out:$@ --target:exe chk/check.fs
+
+sdb.tar.gz: $(addprefix bin/, $(results))
+ $(RM) sdb.tar.gz
+ $(CD) bin && $(TAR) -zcf ../sdb.tar.gz $(results) $(refs)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..742d882
--- /dev/null
+++ b/README.md
@@ -0,0 +1,301 @@
+# SDB: Mono Soft Debugger Client
+
+SDB is a command line client for Mono's soft debugger, a cooperative debugger
+that is part of the Mono VM. It tries to be similar in command syntax to tools
+such as GDB and LLDB.
+
+## Building
+
+Building and using SDB requires a basic POSIX-like environment, the `libedit`
+library, and an installed Mono framework.
+
+First, clone the submodules:
+
+ $ git submodule update --init --recursive
+
+To build, run:
+
+ $ make
+
+This compiles SDB and its dependencies, and puts everything in the `bin`
+directory. This directory can be moved around freely; when executed, the `sdb`
+script will set up the environment so all the dependency assemblies are found.
+
+You could, for example, just add the `bin` directory to your `PATH`:
+
+ $ export PATH=`pwd`/bin:$PATH
+ $ sdb
+ Welcome to the Mono soft debugger (sdb 1.0.5058.39468)
+ Type 'help' for a list of commands or 'quit' to exit
+
+ (sdb)
+
+You can run the SDB test suite with:
+
+ $ make check
+
+It's generally a good idea to do that to ensure that SDB works correctly on
+your system before you start using it.
+
+The following variables can be set in your environment or on the Make command
+line to affect the build:
+
+* `CD`: Path to the `cd` POSIX utility.
+* `CHMOD`: Path to the `chmod` POSIX utility.
+* `CP`: Path to the `cp` POSIX utility.
+* `FSHARPC`: Which F# compiler executable to use.
+* `FSHARPC_FLAGS`: Flags to pass to the F# compiler.
+* `FSHARPC_TEST_FLAGS`: Flags to pass to the F# compiler for tests.
+* `GENDARME`: Which Gendarme executable to use (optional).
+* `GENDARME_FLAGS`: Flags to pass to Gendarme.
+* `MCS`: Which C# compiler executable to use.
+* `MCS_FLAGS`: Flags to pass to the C# compiler.
+* `MCS_TEST_FLAGS`: Flags to pass to the C# compiler for tests.
+* `MKDIR`: Path to the `mkdir` POSIX utility.
+* `PKG_CONFIG`: Path to the `pkg-config` utility.
+* `SED`: Path to the `sed` POSIX utility.
+* `TAR`: Path to the `tar` POSIX utility.
+* `XBUILD`: Which XBuild executable to use.
+* `XBUILD_FLAGS`: Flags to pass to XBuild.
+
+Note that the F# tools are only necessary to run the test suite. Gendarme is
+also optional and is mostly used by the SDB developers. `tar` is also only used
+to package SDB releases.
+
+Additionally, `MODE` can be set to `Debug` (default) or `Release` to indicate
+the kind of build desired.
+
+Finally, `MONO_PREFIX` can be set to tell the test runner which Mono executable
+should be used. See the description of `RuntimePrefix` further down for more
+information.
+
+## Usage
+
+Running a program is simple:
+
+ $ cat test.cs
+ using System;
+ using System.Diagnostics;
+
+ static class Program
+ {
+ static void Main()
+ {
+ var str = "Foo!";
+
+ Foo(str);
+ }
+
+ static void Foo(string str)
+ {
+ Console.WriteLine(str);
+
+ Bar();
+ }
+
+ static void Bar()
+ {
+ Debugger.Break();
+ }
+ }
+ $ mcs -debug test.cs
+ $ sdb
+ Welcome to the Mono soft debugger (sdb 1.0.5060.15368)
+ Type 'help' for a list of commands or 'quit' to exit
+
+ (sdb) r test.exe
+ Inferior process '5234' ('test.exe') started
+ Foo!
+ Inferior process '5234' ('test.exe') suspended
+ #0 [0x00000001] Program.Bar at /home/alexrp/Projects/tests/cs/test.cs:22
+ Debugger.Break();
+
+A stack trace can be generated with `bt`:
+
+ (sdb) bt
+ #0 [0x00000001] Program.Bar at /home/alexrp/Projects/tests/cs/test.cs:22
+ Debugger.Break();
+ #1 [0x00000007] Program.Foo at /home/alexrp/Projects/tests/cs/test.cs:17
+ Bar();
+ #2 [0x00000008] Program.Main at /home/alexrp/Projects/tests/cs/test.cs:10
+ Foo(str);
+
+We can select a frame and inspect locals:
+
+ (sdb) f up
+ #1 [0x00000007] Program.Foo at /home/alexrp/Projects/tests/cs/test.cs:17
+ Bar();
+ (sdb) p str
+ string it = "Foo!"
+
+Or globals:
+
+ (sdb) p Environment.CommandLine
+ string it = "/home/alexrp/Projects/tests/cs/test.exe"
+
+To continue execution, do:
+
+ (sdb) c
+ Inferior process '5234' ('test.exe') resumed
+ Inferior process '5234' ('test.exe') exited
+ (sdb)
+
+We can then exit SDB:
+
+ (sdb) q
+ Bye
+
+For more commands, consult `help` in SDB.
+
+## Options
+
+SDB has a few command line options that are useful for automation. For the full
+list, issue `sdb --help`.
+
+First of all, all non-option arguments passed to SDB are treated as commands
+that SDB will execute at startup. For instance:
+
+ $ sdb "run test.exe"
+
+Or:
+
+ $ sdb "args --foo --bar baz" "run test.exe"
+
+This starts SDB and immediately executes `test.exe` with the given arguments.
+
+The first option is `-f`. This option specifies files that SDB should read
+commands from. These commands are executed before any commands specified as
+non-option arguments. This option is useful for longer command sequences that
+are easier to maintain in a separate file. Example:
+
+ $ cat cmds.txt
+ args --foo --bar baz
+ run test.exe
+ $ sdb -f cmds.txt
+
+The second option is `-b`. This runs SDB in batch mode; that is, it will exit
+as soon as all commands have finished and no inferior process is running. This
+goes well with `-f` for running programs regularly under SDB.
+
+## Settings
+
+One configuration element that you almost certainly need to alter is the
+`RuntimePrefix` string value. It is set to `/usr` by default, regardless of
+OS, which is probably not desirable everywhere. For example, on Windows, you
+will want to set it to something like `C:\Program Files (x86)\Mono-3.0.10`.
+Or if you have Mono in some other directory, you might set it to e.g.
+`/opt/mono`.
+
+You may want to set `DisableColors` to `true` if you don't want the fancy ANSI
+color codes that SDB emits.
+
+Finally, three useful settings for debugging SDB itself exist: `DebugLogging`
+can be set to `true` to make SDB spew a bunch of diagnostic information.
+`LogInternalErrors` can be set to `true` to log any internal errors that are
+encountered in the Mono debugging libraries. `LogRuntimeSpew` can be set to
+`true` to log all messages from the Mono VM.
+
+## Paths
+
+When configuration elements are changed with `config set`, SDB will store the
+configuration data in `~/.sdb.cfg`. The content of the file is the .NET binary
+serialization of the `Mono.Debugger.Client.Configuration` class. This file is
+read on startup if it exists.
+
+At startup, SDB will scan the `~/.sdb` directory for plugin assemblies. It will
+attempt to load all command and type formatter definitions.
+
+Finally, SDB will read `~/.sdb.rc` and execute any commands (one per line) from
+it. This is useful if you prefer to change your settings with commands that you
+write down manually, rather than storing the data in a binary file.
+
+## Environment
+
+The `SDB_COLORS` variable can be set to `disable` to tell SDB to not use colors
+in output. Normally, SDB will not use colors if it detects that `stdout` has
+been redirected, that `TERM` is set to `dumb` (or not set at all), or if the
+`DisableColors` configuration element is `true`.
+
+`SDB_CFG` can be set to a specific configuration file to use instead of the
+defaults `~/.sdb.cfg`. If set to the empty string (i.e. `SDB_CFG="" sdb`), SDB
+will not load any configuration file at all, and changed configuration values
+will not be saved.
+
+The `SDB_PATH` variable can be set to a list of additional directories that SDB
+will scan for plugin assemblies in. Each directory should be separated by a
+semicolon (Windows) or a colon (POSIX).
+
+`SDB_DEBUG` can be set to `enable` to make SDB print diagnostic information
+while debugging. This may be useful to debug SDB itself.
+
+## Plugins
+
+At the moment, SDB has one extension point which is the `Command` class and the
+associated `CommandAttribute` class. A class implementing `Command` that is
+tagged with `CommandAttribute` will be instantiated at startup time and put
+into the root command list.
+
+For SDB to find custom commands, they should be compiled into `.dll` assemblies
+and put in `~/.sdb` (or some other directory specified in `SDB_PATH`).
+
+Here's an example of compiling and using a test plugin:
+
+ $ cat test.cs
+ using Mono.Debugger.Client;
+
+ [Command]
+ public sealed class MyCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "mycmd" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Performs magic."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "mycmd"; }
+ }
+
+ public override string Help
+ {
+ get { return "Some sort of detailed help text goes here."; }
+ }
+
+ public override void Process(string args)
+ {
+ Log.Info("Hello! I received: {0}", args);
+ }
+ }
+ $ mcs -debug -t:library test.cs -r:`which sdb`.exe -out:$HOME/.sdb/test.dll
+ $ sdb
+ Welcome to the Mono soft debugger (sdb 1.0.5061.14716)
+ Type 'help' for a list of commands or 'quit' to exit
+
+ (sdb) h mycmd
+
+ mycmd
+
+ Some sort of detailed help text goes here.
+
+ (sdb) mycmd foo bar baz
+ Hello! I received: foo bar baz
+
+## Issues
+
+* There is no completion for commands - the default completion instead tries to
+ complete file names which is not very useful most of the time.
+* Decompilation is not implemented. The `ICSharpCode.Decompiler` library needs
+ to be separated from ILSpy for this to be practical.
+* The exit code of inferior processes is not shown. There is apparently no way
+ to get it from `Mono.Debugging`.
+* Attach support is not implemented. This requires special support in the
+ debugging libraries.
+* Some Mono versions throw a `NullReferenceException` when SDB shuts down. This
+ is because of a bug in the finalizer of `System.Timers.Timer` in Mono. This
+ bug has been fixed and should be available in whatever Mono version comes
+ after 3.2.5.
diff --git a/chk/check.fs b/chk/check.fs
new file mode 100644
index 0000000..a31008c
--- /dev/null
+++ b/chk/check.fs
@@ -0,0 +1,221 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+open System
+open System.Diagnostics
+open System.IO
+open System.Text.RegularExpressions
+
+(* ------------------------------------------------------------------- *)
+(* Initialization Logic *)
+(* ------------------------------------------------------------------- *)
+
+let mutable successes = 0
+let mutable failures = 0
+
+let mutable testName = ""
+let mutable testArgs = ""
+let mutable testLogic = fun p -> ()
+
+let recv (proc : Process) =
+ let s = proc.StandardOutput.ReadLine()
+
+ Console.WriteLine(s)
+
+ s
+
+let recvErr (proc : Process) =
+ let s = proc.StandardError.ReadLine()
+
+ Console.Error.WriteLine(s)
+
+ s
+
+let send (proc : Process) (str : string) =
+ proc.StandardInput.WriteLine(str)
+
+let runTest () =
+ let delim = String.replicate 30 "-"
+
+ Console.WriteLine("{0} {1} {0}", delim, testName)
+ Console.WriteLine()
+
+ let psi = ProcessStartInfo(Path.Combine("..", "bin", "sdb"),
+ Arguments = testArgs,
+ CreateNoWindow = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ UseShellExecute = false)
+
+ psi.EnvironmentVariables.Add("SDB_CFG", "")
+ psi.EnvironmentVariables.Add("SDB_COLORS", "disable")
+ psi.EnvironmentVariables.Add("SDB_DEBUG", "disable")
+
+ use proc = Process.Start(psi)
+
+ // Read the logo lines.
+ for _ in 1 .. 3 do
+ recv proc |> ignore
+
+ // Set the `RuntimePrefix` to `MONO_PREFIX` as
+ // specified in the `Makefile`.
+ let prefix = Environment.GetEnvironmentVariable("MONO_PREFIX")
+
+ send proc ("config set RuntimePrefix " + prefix)
+ recv proc |> ignore
+
+ let mutable ex = null
+
+ try
+ testLogic proc
+ with
+ | e ->
+ try
+ proc.Kill()
+ with
+ | :? InvalidOperationException -> ()
+
+ ex <- e
+
+ proc.WaitForExit()
+
+ let sep = String.Format("{0}-{1}-{0}", delim, String.replicate testName.Length "-")
+
+ Console.WriteLine(sep)
+ Console.Write("Result: ")
+
+ if proc.ExitCode = 0 && ex = null then
+ successes <- successes + 1
+
+ Console.Write("Success")
+ else
+ failures <- failures + 1
+
+ Console.Write("Failure")
+
+ Console.WriteLine(" ({0})", proc.ExitCode)
+
+ if ex <> null then
+ Console.WriteLine()
+ Console.WriteLine(ex)
+
+ Console.WriteLine(sep)
+ Console.WriteLine()
+
+(* ------------------------------------------------------------------- *)
+(* Assertion Functions *)
+(* ------------------------------------------------------------------- *)
+
+let assertRecv (proc : Process) (pat : string) =
+ let txt = recv proc
+
+ if not(Regex.IsMatch(txt, pat)) then
+ failwith(String.Format("stdout string '{0}' did not match pattern '{1}'", txt, pat))
+
+let assertRecvErr (proc : Process) (pat : string) =
+ let txt = recvErr proc
+
+ if not(Regex.IsMatch(txt, pat)) then
+ failwith(String.Format("stderr string '{0}' did not match pattern '{1}'", txt, pat))
+
+(* ------------------------------------------------------------------- *)
+(* Test Cases *)
+(* ------------------------------------------------------------------- *)
+
+testName <- "simple quit"
+testLogic <- fun p ->
+ send p "quit"
+runTest ()
+
+testName <- "debugger state reset"
+testLogic <- fun p ->
+ send p "reset"
+ assertRecv p "^All debugger state reset$"
+ send p "quit"
+runTest ()
+
+testName <- "configuration manipulation"
+testLogic <- fun p ->
+ send p "config get ChunkRawStrings"
+ assertRecv p "^'ChunkRawStrings' = 'False'$"
+ send p "config set ChunkRawStrings True"
+ assertRecv p "^'ChunkRawStrings' = 'True' \(was 'False'\)$"
+ send p "quit"
+runTest ()
+
+testName <- "catchpoint"
+testLogic <- fun p ->
+ send p "catchpoint add System.Exception"
+ assertRecv p "^Catchpoint for 'System.Exception' added$"
+ send p "run cs/throw.exe"
+ assertRecv p "^Inferior process '\d*' \('throw.exe'\) started$"
+ assertRecv p "^Trapped first-chance exception of type 'System.Exception'$"
+ assertRecv p "^#0 \[0x[A-Fa-f0-9]{8}\] Program.Main at .*/chk/cs/throw.cs:9$"
+ assertRecv p "^ throw new Exception\(\);$"
+ assertRecv p "^System.Exception: Exception of type 'System.Exception' was thrown.$"
+ send p "kill"
+ assertRecv p "^Inferior process '\d*' \('throw.exe'\) exited$"
+ send p "quit"
+runTest ()
+
+testName <- "source breakpoint"
+testLogic <- fun p ->
+ send p "breakpoint add location fs/print.fs 3"
+ assertRecv p "^Breakpoint '0' added at '.*/chk/fs/print.fs:3'$"
+ send p "run fs/print.exe"
+ assertRecv p "^Inferior process '\d*' \('print.exe'\) started$"
+ assertRecv p "^Hit breakpoint at '.*/chk/fs/print.fs:3'$"
+ assertRecv p "^#0 \[0x[A-Fa-f0-9]{8}\] Print.main at .*/chk/fs/print.fs:3$"
+ assertRecv p "^ printfn \"Hello World\"$"
+ send p "kill"
+ assertRecv p "^Inferior process '\d*' \('print.exe'\) exited$"
+ send p "quit"
+runTest ()
+
+testName <- "method breakpoint"
+testLogic <- fun p ->
+ send p "breakpoint add method Program.Main"
+ assertRecv p "^Breakpoint '0' added for method 'Program.Main'$"
+ send p "run cs/cwl.exe"
+ assertRecv p "^Inferior process '\d*' \('cwl.exe'\) started$"
+ assertRecv p "^Hit method breakpoint on 'Program.Main'$"
+ assertRecv p "^#0 \[0x[A-Fa-f0-9]{8}\] Program.Main at .*/chk/cs/cwl.cs:6$"
+ assertRecv p "^ {$"
+ send p "kill"
+ assertRecv p "^Inferior process '\d*' \('cwl.exe'\) exited$"
+ send p "quit"
+runTest ()
+
+(* ------------------------------------------------------------------- *)
+(* Teardown Logic *)
+(* ------------------------------------------------------------------- *)
+
+if failures = 0 then Console.ForegroundColor <- ConsoleColor.Green
+Console.WriteLine("Successes: {0}", successes)
+Console.ResetColor()
+
+if failures <> 0 then Console.ForegroundColor <- ConsoleColor.Red
+Console.WriteLine("Failures: {0}", failures)
+Console.ResetColor()
diff --git a/chk/cs/cwl.cs b/chk/cs/cwl.cs
new file mode 100644
index 0000000..f917545
--- /dev/null
+++ b/chk/cs/cwl.cs
@@ -0,0 +1,9 @@
+using System;
+
+static class Program
+{
+ static void Main()
+ {
+ Console.WriteLine("Hello World");
+ }
+}
diff --git a/chk/cs/throw.cs b/chk/cs/throw.cs
new file mode 100644
index 0000000..f111976
--- /dev/null
+++ b/chk/cs/throw.cs
@@ -0,0 +1,15 @@
+using System;
+
+static class Program
+{
+ static void Main()
+ {
+ try
+ {
+ throw new Exception();
+ }
+ catch (Exception)
+ {
+ }
+ }
+}
diff --git a/chk/fs/print.fs b/chk/fs/print.fs
new file mode 100644
index 0000000..5d934b6
--- /dev/null
+++ b/chk/fs/print.fs
@@ -0,0 +1,4 @@
+[<EntryPoint>]
+let main _ =
+ printfn "Hello World"
+ 0
diff --git a/mono.snk b/mono.snk
new file mode 100644
index 0000000..380116c
Binary files /dev/null and b/mono.snk differ
diff --git a/sdb.exe.config b/sdb.exe.config
new file mode 100644
index 0000000..931778c
--- /dev/null
+++ b/sdb.exe.config
@@ -0,0 +1,4 @@
+<configuration>
+ <dllmap dll="libedit" target="libedit.so.2" os="!windows" />
+ <dllmap dll="libedit" target="libedit.dylib" os="osx" />
+</configuration>
diff --git a/sdb.in b/sdb.in
new file mode 100644
index 0000000..2648e88
--- /dev/null
+++ b/sdb.in
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+MONO_PATH=`dirname $0`:$MONO_PATH exec `which mono` __MONO_OPTIONS__ $MONO_OPTIONS `dirname $0`/sdb.exe "$@"
diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs
new file mode 100644
index 0000000..d962754
--- /dev/null
+++ b/src/AssemblyInfo.cs
@@ -0,0 +1,30 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Reflection;
+
+[assembly: AssemblyTitle("Mono.Debugger.Client")]
+[assembly: AssemblyProduct("Mono")]
+[assembly: AssemblyCopyright("Copyright 2013 Alex Rønne Petersen")]
+[assembly: AssemblyVersion("1.2.*")]
diff --git a/src/Color.cs b/src/Color.cs
new file mode 100644
index 0000000..939b525
--- /dev/null
+++ b/src/Color.cs
@@ -0,0 +1,117 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Linq;
+
+namespace Mono.Debugger.Client
+{
+ public static class Color
+ {
+ // We need this class because the color sequences emitted by
+ // the ForegroundColor, BackgroundColor, and ResetColor helpers
+ // on System.Console mess with libedit's input and history
+ // tracker.
+
+ static readonly bool _disableColors;
+
+ static Color()
+ {
+ _disableColors = Console.IsOutputRedirected ||
+ new[] { null, "dumb" }.Contains(Environment.GetEnvironmentVariable("TERM")) ||
+ Environment.GetEnvironmentVariable("SDB_COLORS") == "disable";
+ }
+
+ static string GetColor(string modifier, string color)
+ {
+ return _disableColors || Configuration.Current.DisableColors ?
+ string.Empty : modifier + color;
+ }
+
+ public static string Red
+ {
+ get { return GetColor("\x1b[1m", "\x1b[31m"); }
+ }
+
+ public static string DarkRed
+ {
+ get { return GetColor(string.Empty, "\x1b[31m"); }
+ }
+
+ public static string Green
+ {
+ get { return GetColor("\x1b[1m", "\x1b[32m"); }
+ }
+
+ public static string DarkGreen
+ {
+ get { return GetColor(string.Empty, "\x1b[32m"); }
+ }
+
+ public static string Yellow
+ {
+ get { return GetColor("\x1b[1m", "\x1b[33m"); }
+ }
+
+ public static string DarkYellow
+ {
+ get { return GetColor(string.Empty, "\x1b[33m"); }
+ }
+
+ public static string Blue
+ {
+ get { return GetColor("\x1b[1m", "\x1b[34m"); }
+ }
+
+ public static string DarkBlue
+ {
+ get { return GetColor(string.Empty, "\x1b[34m"); }
+ }
+
+ public static string Magenta
+ {
+ get { return GetColor("\x1b[1m", "\x1b[35m"); }
+ }
+
+ public static string DarkMagenta
+ {
+ get { return GetColor(string.Empty, "\x1b[35m"); }
+ }
+
+ public static string Cyan
+ {
+ get { return GetColor("\x1b[1m", "\x1b[36m"); }
+ }
+
+ public static string DarkCyan
+ {
+ get { return GetColor(string.Empty, "\x1b[36m"); }
+ }
+
+ public static string Reset
+ {
+ get { return GetColor("\x1b[0m", string.Empty); }
+ }
+ }
+}
diff --git a/src/Command.cs b/src/Command.cs
new file mode 100644
index 0000000..93da373
--- /dev/null
+++ b/src/Command.cs
@@ -0,0 +1,42 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+namespace Mono.Debugger.Client
+{
+ public abstract class Command
+ {
+ public abstract string[] Names { get; }
+
+ public abstract string Summary { get; }
+
+ public abstract string Syntax { get; }
+
+ public virtual string Help
+ {
+ get { return "No detailed help text available."; }
+ }
+
+ public abstract void Process(string args);
+ }
+}
diff --git a/src/CommandAttribute.cs b/src/CommandAttribute.cs
new file mode 100644
index 0000000..310d4ea
--- /dev/null
+++ b/src/CommandAttribute.cs
@@ -0,0 +1,33 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Debugger.Client
+{
+ [AttributeUsage(AttributeTargets.Class, Inherited = false)]
+ public sealed class CommandAttribute : Attribute
+ {
+ }
+}
diff --git a/src/CommandLine.cs b/src/CommandLine.cs
new file mode 100644
index 0000000..34b5d83
--- /dev/null
+++ b/src/CommandLine.cs
@@ -0,0 +1,318 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Mono.Terminal;
+using Mono.Unix;
+using Mono.Unix.Native;
+using Mono.Debugger.Client.Commands;
+
+namespace Mono.Debugger.Client
+{
+ public static class CommandLine
+ {
+ internal static bool Stop { get; set; }
+
+ internal static RootCommand Root { get; private set; }
+
+ internal static AutoResetEvent ResumeEvent { get; private set; }
+
+ internal static bool InferiorExecuting { get; set; }
+
+ static readonly ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();
+
+ static readonly LineEditor _lineEditor;
+
+ static bool _windowsConsoleHandlerSet;
+
+ static Thread _signalThread;
+
+ static CommandLine()
+ {
+ Root = new RootCommand();
+ ResumeEvent = new AutoResetEvent(false);
+
+ try
+ {
+ LibEdit.Initialize();
+ }
+ catch (DllNotFoundException)
+ {
+ // Fall back to `Mono.Terminal.LineEditor`.
+ _lineEditor = new LineEditor(null);
+ }
+ }
+
+ static void Process(string cmd, bool rc)
+ {
+ if (!rc && _lineEditor == null)
+ LibEdit.AddHistory(cmd);
+
+ var args = cmd.Trim();
+
+ if (args.Length == 0)
+ return;
+
+ try
+ {
+ Root.Process(args);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Command threw an exception:");
+ Log.Error(ex.ToString());
+ }
+ }
+
+ static string GetPrompt()
+ {
+ return string.Format("{0}{1}{2} ",
+ Color.DarkMagenta,
+ Configuration.Current.InputPrompt,
+ Color.Reset);
+ }
+
+ static string GetFilePath()
+ {
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+
+ return Path.Combine(home, ".sdb.rc");
+ }
+
+ public static void RunCommands(IEnumerable<string> commands)
+ {
+ foreach (var cmd in commands)
+ _queue.Enqueue(cmd);
+ }
+
+ public static void RunFile(string path, bool swallowAndLog)
+ {
+ var commands = new List<string>();
+
+ try
+ {
+ string line;
+
+ using (var reader = new StreamReader(path))
+ while ((line = reader.ReadLine()) != null)
+ commands.Add(line);
+ }
+ catch (Exception ex)
+ {
+ if (swallowAndLog)
+ {
+ Log.Error("Could not read commands in file '{0}':", path);
+ Log.Error(ex.ToString());
+
+ return;
+ }
+ else
+ throw;
+ }
+
+ foreach (var cmd in commands)
+ _queue.Enqueue(cmd);
+ }
+
+ static void ControlCHandler()
+ {
+ Log.Info(string.Empty);
+
+ switch (Debugger.State)
+ {
+ case State.Running:
+ Debugger.Pause();
+
+ break;
+ case State.Suspended:
+ Log.Error("Inferior is already suspended");
+ Log.InfoSameLine(GetPrompt());
+
+ break;
+ case State.Exited:
+ // If `InferiorExecuting` is set while the state is
+ // `Exited`, it means that we were listening or
+ // connecting. So cancel.
+ if (InferiorExecuting)
+ Debugger.Kill();
+ else
+ {
+ Log.Error("No inferior process");
+ Log.InfoSameLine(GetPrompt());
+ }
+
+ break;
+ }
+ }
+
+ static void ConsoleControlCHandler(object sender, ConsoleCancelEventArgs e)
+ {
+ // This method is only fired on Windows.
+ ControlCHandler();
+
+ e.Cancel = true;
+ }
+
+ internal static void SetControlCHandler()
+ {
+ if (Utilities.IsWindows)
+ {
+ if (!_windowsConsoleHandlerSet)
+ Console.CancelKeyPress += ConsoleControlCHandler;
+
+ _windowsConsoleHandlerSet = true;
+ }
+ else if (_signalThread == null)
+ {
+ Stdlib.SetSignalAction(Signum.SIGINT, SignalAction.Default);
+
+ _signalThread = new Thread(() =>
+ {
+ try
+ {
+ using (var sig = new UnixSignal(Signum.SIGINT))
+ {
+ while (true)
+ {
+ sig.WaitOne();
+
+ ControlCHandler();
+ }
+ }
+ }
+ catch (ThreadAbortException)
+ {
+ }
+ });
+
+ _signalThread.Start();
+ }
+ }
+
+ internal static void UnsetControlCHandler()
+ {
+ if (Utilities.IsWindows)
+ {
+ Console.CancelKeyPress -= ConsoleControlCHandler;
+
+ _windowsConsoleHandlerSet = false;
+ }
+ else if (_signalThread != null)
+ {
+ _signalThread.Abort();
+ _signalThread.Join();
+
+ _signalThread = null;
+
+ Stdlib.SetSignalAction(Signum.SIGINT, SignalAction.Ignore);
+ }
+ }
+
+ internal static void Run(Version ver, bool batch, bool rc,
+ IEnumerable<string> commands,
+ IEnumerable<string> files)
+ {
+ if (!Configuration.Read())
+ Configuration.Defaults();
+
+ Configuration.Apply();
+
+ Log.Notice("Welcome to the Mono soft debugger (sdb {0})", ver);
+ Log.Notice("Type 'help' for a list of commands or 'quit' to exit");
+ Log.Info(string.Empty);
+
+ Root.AddCommands(Plugins.LoadDefault());
+
+ var rcFile = GetFilePath();
+
+ if (rc && File.Exists(rcFile))
+ RunFile(rcFile, true);
+
+ foreach (var file in files)
+ if (File.Exists(file))
+ RunFile(file, true);
+
+ RunCommands(commands);
+
+ while (!Stop)
+ {
+ // If the command caused the debuggee to start
+ // or resume execution, wait for it to suspend.
+ if (InferiorExecuting)
+ {
+ ResumeEvent.WaitOne();
+ InferiorExecuting = false;
+ }
+
+ string cmd;
+
+ // We use a queue here so that batch commands are
+ // also subject to the suspension check above. It
+ // also makes things easier since everything gets
+ // executed in one thread.
+ if (_queue.TryDequeue(out cmd))
+ Process(cmd, true);
+ else if (batch)
+ Stop = true;
+ else
+ {
+ cmd = _lineEditor != null ?
+ _lineEditor.Edit(GetPrompt(), string.Empty) :
+ LibEdit.ReadLine(GetPrompt());
+
+ // Did we get EOF?
+ if (cmd == null)
+ {
+ Log.Info(string.Empty);
+
+ if (Debugger.State != State.Exited)
+ Log.Error("An inferior process is active");
+ else
+ Stop = true;
+ }
+ else
+ Process(cmd, false);
+ }
+
+ }
+
+ // Clean up just in case.
+ UnsetControlCHandler();
+
+ // Let's not leave dead Mono processes behind...
+ Debugger.Pause();
+ Debugger.Kill();
+
+ while (!Debugger.DebuggeeKilled)
+ Thread.Sleep(10);
+
+ Log.Notice("Bye");
+ }
+ }
+}
diff --git a/src/Commands/ArgumentsCommand.cs b/src/Commands/ArgumentsCommand.cs
new file mode 100644
index 0000000..a841e97
--- /dev/null
+++ b/src/Commands/ArgumentsCommand.cs
@@ -0,0 +1,74 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+using System.IO;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class ArgumentsCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "arguments", "args" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Get or set the current program arguments."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "arguments|args [args]"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Without any argument, this command prints the arguments to be passed to\n" +
+ "inferior processes launched locally. If an argument is given, it is set\n" +
+ "as the arguments to be passed.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (args.Length == 0)
+ {
+ Log.Info("Program arguments: '{0}'", Debugger.Arguments);
+ return;
+ }
+ else
+ {
+ var old = Debugger.Arguments;
+
+ Debugger.Arguments = args;
+
+ Log.Info("Program arguments set to '{0}' (were '{1}')", args, old);
+ }
+ }
+ }
+}
diff --git a/src/Commands/AttachCommand.cs b/src/Commands/AttachCommand.cs
new file mode 100644
index 0000000..ef26f07
--- /dev/null
+++ b/src/Commands/AttachCommand.cs
@@ -0,0 +1,61 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class AttachCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "attach" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Attach to a running process."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "attach <id>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Attempts to attach to the given process ID.\n" +
+ "\n" +
+ "Currently unimplemented.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Log.Error("Attach support is not yet implemented.");
+ }
+ }
+}
diff --git a/src/Commands/BacktraceCommand.cs b/src/Commands/BacktraceCommand.cs
new file mode 100644
index 0000000..8328b4e
--- /dev/null
+++ b/src/Commands/BacktraceCommand.cs
@@ -0,0 +1,83 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class BacktraceCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "backtrace", "bt" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Print a backtrace of the call stack."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "backtrace|bt"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Prints a backtrace of the call stack for the active thread. Includes\n" +
+ "IL offsets, source locations, and source lines.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var bt = Debugger.ActiveBacktrace;
+
+ if (bt == null)
+ {
+ Log.Error("No active backtrace");
+ return;
+ }
+
+ if (bt.FrameCount == 0)
+ {
+ Log.Info("Backtrace for this thread is unavailable");
+ return;
+ }
+
+ for (var i = 0; i < bt.FrameCount; i++)
+ {
+ var f = bt.GetFrame(i);
+ var str = Utilities.StringizeFrame(f, true);
+
+ if (f == Debugger.ActiveFrame)
+ Log.Emphasis(str);
+ else
+ Log.Info(str);
+ }
+ }
+ }
+}
diff --git a/src/Commands/BreakpointCommand.cs b/src/Commands/BreakpointCommand.cs
new file mode 100644
index 0000000..b9f5186
--- /dev/null
+++ b/src/Commands/BreakpointCommand.cs
@@ -0,0 +1,531 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Mono.Debugging.Client;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class BreakpointCommand : MultiCommand
+ {
+ sealed class BreakpointAddCommand : MultiCommand
+ {
+ sealed class BreakpointAddLocationCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "location", "at" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add a breakpoint at a source location."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "break|bp add location <file> <line>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Adds a breakpoint to the specified source location.\n" +
+ "\n" +
+ "This is a 'normal' breakpoint associated with a file name and line\n" +
+ "which can have an optional condition expression.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var splitArgs = args.Split(' ').Where(x => x != string.Empty);
+ var count = splitArgs.Count();
+
+ if (count == 0)
+ {
+ Log.Error("No file name given");
+ return;
+ }
+
+ if (count == 1)
+ {
+ Log.Error("No line number given");
+ return;
+ }
+
+ var lineStr = splitArgs.Last();
+
+ int line;
+
+ if (!int.TryParse(lineStr, out line))
+ {
+ Log.Error("Invalid line number");
+ return;
+ }
+
+ var file = new string(args.Take(args.Length - lineStr.Length).ToArray()).Trim();
+
+ try
+ {
+ file = Path.GetFullPath(file);
+ }
+ catch (Exception ex)
+ {
+ Log.Info("Could not compute absolute path of '{0}':");
+ Log.Info(ex.ToString());
+
+ return;
+ }
+
+ foreach (var be in Debugger.Breakpoints)
+ {
+ var bp = be.Value as Breakpoint;
+
+ if (bp == null)
+ continue;
+
+ if (bp.FileName == file && bp.Line == line)
+ {
+ Log.Error("A breakpoint at '{0}:{1}' already exists ('{2}')", file, line, be.Key);
+ return;
+ }
+ }
+
+ var id = Debugger.GetBreakpointId();
+
+ Debugger.Breakpoints.Add(id, Debugger.BreakEvents.Add(file, line));
+
+ Log.Info("Breakpoint '{0}' added at '{1}:{2}'", id, file, line);
+ }
+ }
+
+ sealed class BreakpointAddMethodCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "method", "function" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add a breakpoint at a method."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "break|bp add method|function <name>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Adds a breakpoint at the specified method.\n" +
+ "\n" +
+ "A breakpint of this type has no location or condition; it simply\n" +
+ "triggers whenever execution enters the specified method.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (args.Length == 0)
+ {
+ Log.Error("No method name given");
+ return;
+ }
+
+ foreach (var be in Debugger.Breakpoints)
+ {
+ if (!(be.Value is FunctionBreakpoint))
+ continue;
+
+ if (((FunctionBreakpoint)be.Value).FunctionName == args)
+ {
+ Log.Error("A method breakpoint for '{0}' already exists ('{1}')", args, be.Key);
+ return;
+ }
+ }
+
+ // TODO: Parameter types too.
+
+ var id = Debugger.GetBreakpointId();
+ var fbp = new FunctionBreakpoint(args, "C#");
+
+ Debugger.Breakpoints.Add(id, fbp);
+ Debugger.BreakEvents.Add(fbp);
+
+ Log.Info("Breakpoint '{0}' added for method '{1}'", id, args);
+ }
+ }
+
+ public BreakpointAddCommand()
+ {
+ AddCommand<BreakpointAddLocationCommand>();
+ AddCommand<BreakpointAddMethodCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "add" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add a breakpoint at a location or method."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Adds a breakpoint at a source location or method entry.\n" +
+ "\n" +
+ "Breakpoints are initially toggled on when added.";
+ }
+ }
+
+ public override string Parent
+ {
+ get { return "break"; }
+ }
+ }
+
+ sealed class BreakpointClearCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "clear" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Clear all breakpoints."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "break|bp clear"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Removes all breakpoints.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Debugger.Breakpoints.Clear();
+ Debugger.BreakEvents.ClearBreakpoints();
+
+ Log.Info("All breakpoints cleared");
+ }
+ }
+
+ sealed class BreakpointConditionCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "condition", "expression" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Set a conditional expression for a breakpoint."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "break|bp condition|expression <id> [expr]"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Without any expression argument, unsets the condition for the given\n" +
+ "breakpoint. If an expression is given, the breakpoint's condition is\n" +
+ "set to that value, such that it will only trigger if the condition\n" +
+ "evaluates to true.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var id = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (id == null)
+ {
+ Log.Error("No breakpoint ID given");
+ return;
+ }
+
+ long num;
+
+ if (!long.TryParse(id, out num))
+ {
+ Log.Error("Invalid breakpoint ID");
+ return;
+ }
+
+ BreakEvent b;
+
+ if (!Debugger.Breakpoints.TryGetValue(num, out b))
+ {
+ Log.Error("Breakpoint '{0}' not found", num);
+ return;
+ }
+
+ if (b is FunctionBreakpoint)
+ {
+ Log.Error("Breakpoint '{0}' is a method breakpoint", num);
+ return;
+ }
+
+ var expr = new string(args.Skip(id.Length).ToArray()).Trim();
+
+ var bp = (Breakpoint)b;
+ var was = bp.ConditionExpression != null ?
+ string.Format(" (was '{0}')", bp.ConditionExpression) :
+ string.Empty;
+
+ if (expr.Length == 0)
+ {
+ bp.ConditionExpression = null;
+
+ Log.Info("Condition for breakpoint '{0}' unset{1}", num, was);
+ }
+ else
+ {
+ bp.ConditionExpression = expr;
+
+ Log.Info("Condition for breakpoint '{0}' set to '{1}'{2}", num, expr, was);
+ }
+ }
+ }
+
+ sealed class BreakpointDeleteCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "delete", "remove" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Delete a breakpoint by ID."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "break|bp delete|remove <id>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Deletes the breakpoint with the specified ID, if it exists.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ long num;
+
+ if (!long.TryParse(args, out num))
+ {
+ Log.Error("Invalid breakpoint ID");
+ return;
+ }
+
+ BreakEvent b;
+
+ if (!Debugger.Breakpoints.TryGetValue(num, out b))
+ {
+ Log.Error("Breakpoint '{0}' not found", num);
+ return;
+ }
+
+ Debugger.Breakpoints.Remove(num);
+ Debugger.BreakEvents.Remove(b);
+
+ Log.Info("Breakpoint '{0}' deleted", num);
+ }
+ }
+
+ sealed class BreakpointListCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "list" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "List all set breakpoints and their IDs."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "break|bp list"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all breakpoints, along with their IDs and settings.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.Breakpoints.Count == 0)
+ {
+ Log.Info("No breakpoints");
+ return;
+ }
+
+ foreach (var pair in Debugger.Breakpoints)
+ {
+ var bp = pair.Value as Breakpoint;
+ var fbp = pair.Value as FunctionBreakpoint;
+
+ var at = fbp != null ? fbp.FunctionName : string.Format("{0}:{1}", bp.FileName, bp.Line);
+ var expr = bp != null && bp.ConditionExpression != null ?
+ string.Format(" '{0}'", bp.ConditionExpression) :
+ string.Empty;
+
+ // TODO: Parameter types too.
+
+ Log.Info("#{0} '{1}'{2}", pair.Key, at, expr);
+ }
+ }
+ }
+
+ sealed class BreakpointToggleCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "toggle" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Toggle a breakpoint on/off."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "break|bp toggle <id>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Toggles a breakpint on or off.\n" +
+ "\n" +
+ "Toggling a breakpoint off keeps it in the breakpoint list but makes it\n" +
+ "not actually trigger, regardless of its type or condition.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ long num;
+
+ if (!long.TryParse(args, out num))
+ {
+ Log.Error("Invalid breakpoint ID");
+ return;
+ }
+
+ BreakEvent b;
+
+ if (!Debugger.Breakpoints.TryGetValue(num, out b))
+ {
+ Log.Error("Breakpoint '{0}' not found", num);
+ return;
+ }
+
+ if (Debugger.BreakEvents.Contains(b))
+ {
+ Debugger.BreakEvents.Remove(b);
+
+ Log.Info("Breakpoint '{0}' disabled", num);
+ }
+ else
+ {
+ Debugger.BreakEvents.Add(b);
+
+ Log.Info("Breakpoint '{0}' enabled", num);
+ }
+ }
+ }
+
+ public BreakpointCommand()
+ {
+ AddCommand<BreakpointAddCommand>();
+ AddCommand<BreakpointClearCommand>();
+ AddCommand<BreakpointConditionCommand>();
+ AddCommand<BreakpointDeleteCommand>();
+ AddCommand<BreakpointListCommand>();
+ AddCommand<BreakpointToggleCommand>();
+
+ Forward<BreakpointListCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "breakpoint", "bp" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add, delete, and show breakpoints."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Manipulates breakpoints.\n" +
+ "\n" +
+ "Breakpoints can be set at specific lines of source files or on the entry of\n" +
+ "managed methods. Non-method breakpoints can also have conditions set such\n" +
+ "that they only cause the debugger to break if the condition is true.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/CatchpointCommand.cs b/src/Commands/CatchpointCommand.cs
new file mode 100644
index 0000000..1f81598
--- /dev/null
+++ b/src/Commands/CatchpointCommand.cs
@@ -0,0 +1,218 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class CatchpointCommand : MultiCommand
+ {
+ sealed class CatchpointAddCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "add" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add a catchpoint for an exception type."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "catch|cp add <type>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Adds a catchpoint for the given exception type.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ foreach (var cp in Debugger.BreakEvents.GetCatchpoints())
+ {
+ if (cp.ExceptionName == args)
+ {
+ Log.Error("Catchpoint for '{0}' already exists");
+ return;
+ }
+ }
+
+ Debugger.BreakEvents.AddCatchpoint(args);
+
+ Log.Info("Catchpoint for '{0}' added", args);
+ }
+ }
+
+ sealed class CatchpointClearCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "clear" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Clear all catchpoints."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "catch|cp clear"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Removes all active catchpoints.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Debugger.BreakEvents.ClearCatchpoints();
+
+ Log.Info("All catchpoints cleared");
+ }
+ }
+
+ sealed class CatchpointDeleteCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "delete", "remove" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Delete a catchpoint by type."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "catch|cp delete|remove <type>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Deletes the catchpoint for the given exception type, if it exists.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (!Debugger.BreakEvents.GetCatchpoints().Any(x => x.ExceptionName == args))
+ {
+ Log.Error("No catchpoint for '{0}' found", args);
+ return;
+ }
+
+ Debugger.BreakEvents.RemoveCatchpoint(args);
+
+ Log.Info("Catchpoint for '{0}' deleted", args);
+ }
+ }
+
+ sealed class CatchpointListCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "list" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "List all set catchpoints."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "catch|cp list"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all currently active catchpoints.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var cps = Debugger.BreakEvents.GetCatchpoints();
+
+ if (cps.Count == 0)
+ {
+ Log.Info("No catchpoints");
+ return;
+ }
+
+ foreach (var cp in cps)
+ Log.Info("'{0}'", cp.ExceptionName);
+ }
+ }
+
+ public CatchpointCommand()
+ {
+ AddCommand<CatchpointAddCommand>();
+ AddCommand<CatchpointClearCommand>();
+ AddCommand<CatchpointDeleteCommand>();
+ AddCommand<CatchpointListCommand>();
+
+ Forward<CatchpointListCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "catchpoint", "cp" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add, delete, and show catchpoints."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Manipulates catchpoints.\n" +
+ "\n" +
+ "When a catchpoint is added for an exception type and the debuggee code\n" +
+ "actively catches it, the debugger will break on the 'throw' site of the\n" +
+ "exception.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/ConfigCommand.cs b/src/Commands/ConfigCommand.cs
new file mode 100644
index 0000000..9f74949
--- /dev/null
+++ b/src/Commands/ConfigCommand.cs
@@ -0,0 +1,278 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class ConfigCommand : MultiCommand
+ {
+ sealed class ConfigGetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "get" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Get the value of a configuration element."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "config|cfg get <name>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Gets the value of the given configuration element.\n" +
+ "\n" +
+ "This is either the default value or the value in '~/.sdb.cfg'.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var name = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (name == null)
+ {
+ Log.Error("No configuration element name given");
+ return;
+ }
+
+ var props = typeof(Configuration).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+
+ foreach (var prop in props)
+ {
+ if (prop.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ Log.Info("'{0}' = '{1}'", prop.Name, prop.GetValue(Configuration.Current));
+ return;
+ }
+ }
+
+ Log.Error("Configuration element '{0}' not found", name);
+ }
+ }
+
+ sealed class ConfigListCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "list" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "List all configuration elements and their values."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "config|cfg list"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all configuration elements and their values.\n" +
+ "\n" +
+ "These are either the default values or the values in '~/.sdb.cfg'.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var props = typeof(Configuration).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+
+ foreach (var prop in props)
+ Log.Info("'{0}' = '{1}'", prop.Name, prop.GetValue(Configuration.Current));
+ }
+ }
+
+ sealed class ConfigResetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "reset" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Reset configuration values to their defaults."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "config|cfg reset"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Resets all configuration elements to their default values.\n" +
+ "\n" +
+ "Note that this overwrites '~/.sdb.cfg' too.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Configuration.Defaults();
+ Configuration.Apply();
+
+ Log.Info("All configuration values reset");
+ }
+ }
+
+ sealed class ConfigSetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "set" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Set the value of a configuration element."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "config|cfg set <name> <value>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Sets the value of the given configuration element.\n" +
+ "\n" +
+ "If '~/.sdb.cfg' doesn't exist, this command causes it to be created.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var name = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (name == null)
+ {
+ Log.Error("No configuration element name given");
+ return;
+ }
+
+ var rest = new string(args.Skip(name.Length).ToArray()).Trim();
+
+ if (rest.Length == 0)
+ {
+ Log.Error("No configuration value given");
+ return;
+ }
+
+ var props = typeof(Configuration).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+
+ foreach (var prop in props)
+ {
+ if (prop.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ var was = prop.GetValue(Configuration.Current);
+
+ object value = null;
+
+ try
+ {
+ if (prop.PropertyType == typeof(bool))
+ value = bool.Parse(rest);
+ else if (prop.PropertyType == typeof(int))
+ value = int.Parse(rest);
+ else if (prop.PropertyType == typeof(string))
+ value = rest;
+ }
+ catch (Exception ex)
+ {
+ if (ex is FormatException || ex is OverflowException)
+ {
+ Log.Error("Invalid configuration value");
+ return;
+ }
+
+ throw;
+ }
+
+ prop.SetValue(Configuration.Current, value);
+
+ Configuration.Write();
+ Configuration.Apply();
+
+ Log.Info("'{0}' = '{1}' (was '{2}')", prop.Name, value, was);
+
+ return;
+ }
+ }
+
+ Log.Error("Configuration element '{0}' not found", name);
+ }
+ }
+
+ public ConfigCommand()
+ {
+ AddCommand<ConfigGetCommand>();
+ AddCommand<ConfigListCommand>();
+ AddCommand<ConfigResetCommand>();
+ AddCommand<ConfigSetCommand>();
+
+ Forward<ConfigListCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "config", "cfg" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Manipulate the debugger configuration."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Manipulates the debugger configuration.\n" +
+ "\n" +
+ "Configuration values are stored in '~/.sdb.cfg' and are loaded on debugger\n" +
+ "startup. The file is only created when a configuration value is set.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/ConnectCommand.cs b/src/Commands/ConnectCommand.cs
new file mode 100644
index 0000000..828aa7e
--- /dev/null
+++ b/src/Commands/ConnectCommand.cs
@@ -0,0 +1,104 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class ConnectCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "connect", "remote" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Connect to a remote virtual machine."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "connect|remote <addr> <port>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Attempts to connect to a debuggee running at the specified IP address and\n" +
+ "port.\n" +
+ "\n" +
+ "Respects the MaxConnectionAttempts and ConnectionAttemptInterval settings.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State != State.Exited)
+ {
+ Log.Error("An inferior process is already being debugged");
+ return;
+ }
+
+ var ip = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (ip == null)
+ {
+ Log.Error("No IP address given");
+ return;
+ }
+
+ IPAddress addr;
+
+ if (!IPAddress.TryParse(ip, out addr))
+ {
+ Log.Error("Invalid IP address");
+ return;
+ }
+
+ var rest = new string(args.Skip(ip.Length).ToArray()).Trim();
+
+ if (rest.Length == 0)
+ {
+ Log.Error("No port number given");
+ return;
+ }
+
+ int port;
+
+ if (!int.TryParse(rest, out port) || port <= 0)
+ {
+ Log.Error("Invalid port number");
+ return;
+ }
+
+ Log.Info("Connecting to '{0}:{1}'...", addr, port);
+
+ Debugger.Connect(addr, port);
+ }
+ }
+}
diff --git a/src/Commands/ContinueCommand.cs b/src/Commands/ContinueCommand.cs
new file mode 100644
index 0000000..5be350e
--- /dev/null
+++ b/src/Commands/ContinueCommand.cs
@@ -0,0 +1,63 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class ContinueCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "continue", "resume" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Continue execution of the inferior."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "continue|resume"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Continues execution of the inferior process after it has been paused by a\n" +
+ "breakpoint, catchpoint, unhandled exception, etc.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State == State.Exited)
+ Log.Error("No inferior process");
+ else
+ Debugger.Continue();
+ }
+ }
+}
diff --git a/src/Commands/DatabaseCommand.cs b/src/Commands/DatabaseCommand.cs
new file mode 100644
index 0000000..792dfee
--- /dev/null
+++ b/src/Commands/DatabaseCommand.cs
@@ -0,0 +1,185 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class DatabaseCommand : MultiCommand
+ {
+ sealed class DatabaseLoadCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "load", "read" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Read the database for the current inferior."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "database|db load|read <file>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Loads the debugger state from the given file.\n" +
+ "\n" +
+ "Note that this resets the active debugger state before applying the\n" +
+ "data read from the given file.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (args.Length == 0)
+ {
+ Log.Error("No file path given");
+ return;
+ }
+
+ if (!File.Exists(args))
+ {
+ Log.Error("File '{0}' does not exist", args);
+ return;
+ }
+
+ FileInfo file;
+
+ try
+ {
+ file = new FileInfo(args);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not open file '{0}':", args);
+ Log.Error(ex.ToString());
+
+ return;
+ }
+
+ Debugger.Read(file);
+
+ Log.Info("Debugger state initialized from '{0}'", args);
+ }
+ }
+
+ sealed class DatabaseSaveCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "save", "write" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Save the database for the current inferior."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "database|db save|write <file>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Saves the active debugger state to the given file.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (args.Length == 0)
+ {
+ Log.Error("No file path given");
+ return;
+ }
+
+ FileInfo file;
+
+ try
+ {
+ file = new FileInfo(args);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not open file '{0}':", args);
+ Log.Error(ex.ToString());
+
+ return;
+ }
+
+ Debugger.Write(file);
+
+ Log.Info("Debugger state saved to '{0}'", args);
+ }
+ }
+
+ public DatabaseCommand()
+ {
+ AddCommand<DatabaseLoadCommand>();
+ AddCommand<DatabaseSaveCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "database", "db" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Store and load debugger state."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Stores and loads the debugger state.\n" +
+ "\n" +
+ "This is useful for retaining things like breakpoints and environment\n" +
+ "variables across debugger runs.\n" +
+ "\n" +
+ "The following state is saved/loaded:\n" +
+ "\n" +
+ "* Working directory.\n" +
+ "* Program arguments.\n" +
+ "* Environment variables.\n" +
+ "* Watches.\n" +
+ "* Breakpoints and catchpoints.\n" +
+ "* Session options.\n" +
+ "* Evaluation options.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/DecompileCommand.cs b/src/Commands/DecompileCommand.cs
new file mode 100644
index 0000000..8aa0005
--- /dev/null
+++ b/src/Commands/DecompileCommand.cs
@@ -0,0 +1,62 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class DecompileCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "decompile" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Decompile the current stack frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "decompile"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Decompiles the IL code in the active stack frame and attempts to highlight\n" +
+ "the line closest to the current IL offset.\n" +
+ "\n" +
+ "Currently unimplemented.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Log.Error("Decompilation is not yet implemented.");
+ }
+ }
+}
diff --git a/src/Commands/DirectoryCommand.cs b/src/Commands/DirectoryCommand.cs
new file mode 100644
index 0000000..0f09571
--- /dev/null
+++ b/src/Commands/DirectoryCommand.cs
@@ -0,0 +1,73 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+using System.IO;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class DirectoryCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "directory", "cd", "cwd" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Get or set the current working directory."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "directory|cd|cwd [path]"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Without any argument, prints the current working directory for inferior\n" +
+ "processes launched locally. If an argument is given, the working directory\n" +
+ "is set to that argument.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (args.Length == 0)
+ Log.Info("Working directory: '{0}'", Debugger.WorkingDirectory);
+ else if (Directory.Exists(args))
+ {
+ var old = Debugger.WorkingDirectory;
+
+ Debugger.WorkingDirectory = args;
+
+ Log.Info("Working directory set to '{0}' (was '{1}')", args, old);
+ }
+ else
+ Log.Error("Directory '{0}' does not exist", args);
+ }
+ }
+}
diff --git a/src/Commands/DisassembleCommand.cs b/src/Commands/DisassembleCommand.cs
new file mode 100644
index 0000000..5d62bde
--- /dev/null
+++ b/src/Commands/DisassembleCommand.cs
@@ -0,0 +1,114 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class DisassembleCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "disassemble", "disasm", "dasm" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Disassemble the current stack frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "disassemble|disasm|dasm [lower] [upper]"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Disassembles the current stack frame and highlights the line corresponding\n" +
+ "to the current IL offset.\n" +
+ "\n" +
+ "If arguments are given, they specify how many lines to print before and\n" +
+ "after the line corresponding to the current IL offset.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ Log.Error("No active stack frame");
+ return;
+ }
+
+ var lower = -10;
+ var upper = 20;
+
+ var lowerStr = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (lowerStr != null)
+ {
+ if (!int.TryParse(lowerStr, out lower))
+ {
+ Log.Error("Invalid lower bound value");
+ return;
+ }
+
+ lower = -lower;
+
+ var upperStr = new string(args.Skip(lowerStr.Length).ToArray()).Trim();
+
+ if (upperStr.Length != 0)
+ {
+ if (!int.TryParse(upperStr, out upper))
+ {
+ Log.Error("Invalid upper bound value");
+ return;
+ }
+
+ upper += System.Math.Abs(lower) + 1;
+ }
+ }
+
+ var asm = f.Disassemble(lower, upper);
+
+ foreach (var line in asm)
+ {
+ if (line.IsOutOfRange)
+ continue;
+
+ var str = string.Format("0x{0:X8} {1}", line.Address, line.Code);
+
+ if (line.Address == f.Address)
+ Log.Emphasis(str);
+ else
+ Log.Info(str);
+ }
+ }
+ }
+}
diff --git a/src/Commands/EnvironmentCommand.cs b/src/Commands/EnvironmentCommand.cs
new file mode 100644
index 0000000..d5d8f56
--- /dev/null
+++ b/src/Commands/EnvironmentCommand.cs
@@ -0,0 +1,305 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class EnvironmentCommand : MultiCommand
+ {
+ sealed class EnvironmentClearCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "clear" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Clear all environment variables."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "environment clear"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Clears all environment variables.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Debugger.EnvironmentVariables.Clear();
+
+ Log.Info("All environment variables cleared");
+ }
+ }
+
+ sealed class EnvironmentDeleteCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "delete", "remove" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Delete an environment variable."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "environment delete|remove <name>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Deletes the given environment variable, if it exists.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ string val;
+
+ if (!Debugger.EnvironmentVariables.TryGetValue(args, out val))
+ {
+ Log.Error("Environment variable '{0}' not found", args);
+ return;
+ }
+
+ Debugger.EnvironmentVariables.Remove(args);
+
+ Log.Info("Environment variable '{0}' (with value '{1}') deleted", args, val);
+ }
+ }
+
+ sealed class EnvironmentGetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "get" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Get the value of an environment variable."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "environment get <name>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Gets the value of the given environment variable.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var name = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (name == null)
+ {
+ Log.Error("No environment variable name given");
+ return;
+ }
+
+ string val;
+
+ if (Debugger.EnvironmentVariables.TryGetValue(name, out val))
+ Log.Info("'{0}' = '{1}'", name, val);
+ else
+ Log.Error("Environment variable '{0}' is not set", name);
+ }
+ }
+
+ sealed class EnvironmentInheritCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "inherit" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Inherit all environment variables from the debugger."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "environment inherit"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Inherits all environment variables from the debugger host environment.\n" +
+ "\n" +
+ "This simply grabs all environment variables on the machine where the\n" +
+ "debugger is running and inserts them into the debuggee environment.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ foreach (DictionaryEntry pair in Environment.GetEnvironmentVariables())
+ Debugger.EnvironmentVariables[(string)pair.Key] = (string)pair.Value;
+ }
+ }
+
+ sealed class EnvironmentListCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "list" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "List all environment variables and their values."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "environment list"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all environment variables and their values.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.EnvironmentVariables.Count == 0)
+ {
+ Log.Info("No environment variables");
+ return;
+ }
+
+ foreach (var pair in Debugger.EnvironmentVariables)
+ Log.Info("'{0}' = '{1}'", pair.Key, pair.Value);
+ }
+ }
+
+ sealed class EnvironmentSetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "set" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Set the value of an environment variable."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "environment set <name> <value>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Sets the given environment variable to the given value.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var name = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (name == null)
+ {
+ Log.Error("No environment variable name given");
+ return;
+ }
+
+ var rest = new string(args.Skip(name.Length).ToArray()).Trim();
+
+ if (rest.Length == 0)
+ {
+ Log.Error("No environment variable value given");
+ return;
+ }
+
+ Debugger.EnvironmentVariables[name] = rest;
+ }
+ }
+
+ public EnvironmentCommand()
+ {
+ AddCommand<EnvironmentClearCommand>();
+ AddCommand<EnvironmentDeleteCommand>();
+ AddCommand<EnvironmentGetCommand>();
+ AddCommand<EnvironmentInheritCommand>();
+ AddCommand<EnvironmentListCommand>();
+ AddCommand<EnvironmentSetCommand>();
+
+ Forward<EnvironmentListCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "environment" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Manipulate inferior environment variables."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Manipulates environment variables.\n" +
+ "\n" +
+ "Environment variables set with these commands are passed on to inferior\n" +
+ "processes launched locally.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/FrameCommand.cs b/src/Commands/FrameCommand.cs
new file mode 100644
index 0000000..116cb89
--- /dev/null
+++ b/src/Commands/FrameCommand.cs
@@ -0,0 +1,439 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Mono.Debugging.Client;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class FrameCommand : MultiCommand
+ {
+ sealed class FrameArgumentsCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "arguments", "args" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Show arguments in the current stack frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "frame arguments|args"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all arguments and their values for the current frame.\n" +
+ "\n" +
+ "The 'this' reference is included if available.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ // Not really an error since it's a perfectly
+ // valid state for e.g. the finalizer thread.
+ if (Debugger.State != State.Exited)
+ Log.Info("Backtrace for this thread is unavailable");
+ else
+ Log.Error("No active stack frame");
+
+ return;
+ }
+
+ var vals = new[] { f.GetThisReference() }.Concat(f.GetParameters()).Where(x => x != null);
+
+ if (!vals.Any())
+ {
+ Log.Info("No arguments");
+ return;
+ }
+
+ foreach (var val in vals)
+ {
+ val.WaitHandle.WaitOne();
+
+ var strErr = Utilities.StringizeValue(val);
+
+ if (strErr.Item2)
+ Log.Error("{0}<error>{1} {2} = {3}", Color.DarkRed, Color.Reset,
+ val.Name, strErr.Item1);
+ else
+ Log.Info("{0}{1}{2} {3} = {4}", Color.DarkGreen, val.TypeName,
+ Color.Reset, val.Name, strErr.Item1);
+ }
+ }
+ }
+
+ sealed class FrameDownCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "down" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Go down the call stack by one frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "frame down"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Sets the active frame to the frame below the current frame.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var bt = Debugger.ActiveBacktrace;
+
+ if (bt == null)
+ {
+ Log.Error("No active backtrace");
+ return;
+ }
+
+ if (bt.FrameCount == 0)
+ {
+ Log.Info("Backtrace for this thread is unavailable");
+ return;
+ }
+
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ Log.Error("No active stack frame");
+ return;
+ }
+
+ var f2 = bt.GetFrame(f.Index - 1);
+
+ if (f2 == f)
+ {
+ Log.Error("Cannot go further down the stack");
+ return;
+ }
+
+ Debugger.ActiveFrame = f2;
+
+ Log.Emphasis(Utilities.StringizeFrame(f2, true));
+ }
+ }
+
+ sealed class FrameGetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "get" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Show the current stack frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "frame get"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Shows the currently active frame, along with its source location and\n" +
+ "and source line.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ // Not really an error since it's a perfectly
+ // valid state for e.g. the finalizer thread.
+ if (Debugger.State != State.Exited)
+ Log.Info("Backtrace for this thread is unavailable");
+ else
+ Log.Error("No active stack frame");
+
+ return;
+ }
+
+ Log.Emphasis(Utilities.StringizeFrame(f, true));
+ }
+ }
+
+ sealed class FrameLocalsCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "locals", "variables", "vars" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Show local variables in the current stack frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "frame locals|variables|vars"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all local variables and their values in the current frame.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ // Not really an error since it's a perfectly
+ // valid state for e.g. the finalizer thread.
+ if (Debugger.State != State.Exited)
+ Log.Info("Backtrace for this thread is unavailable");
+ else
+ Log.Error("No active stack frame");
+
+ return;
+ }
+
+ var vals = f.GetLocalVariables();
+
+ if (vals.Length == 0)
+ {
+ Log.Info("No locals");
+ return;
+ }
+
+ foreach (var val in vals)
+ {
+ val.WaitHandle.WaitOne();
+
+ var strErr = Utilities.StringizeValue(val);
+
+ if (strErr.Item2)
+ Log.Error("{0}<error>{1} {2} = {3}", Color.DarkRed, Color.Reset,
+ val.Name, strErr.Item1);
+ else
+ Log.Info("{0}{1}{2} {3} = {4}", Color.DarkGreen, val.TypeName,
+ Color.Reset, val.Name, strErr.Item1);
+ }
+ }
+ }
+
+ sealed class FrameSetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "set" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Switch to specific stack frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "frame set <num>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Sets the current frame to the given number." +
+ "\n" +
+ "The numbers shown in a 'backtrace' can be passed to this command.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var bt = Debugger.ActiveBacktrace;
+
+ if (bt == null)
+ {
+ Log.Error("No active backtrace");
+ return;
+ }
+
+ if (bt.FrameCount == 0)
+ {
+ // Not really an error since it's a perfectly
+ // valid state for e.g. the finalizer thread.
+ if (Debugger.State != State.Exited)
+ Log.Info("Backtrace for this thread is unavailable");
+ else
+ Log.Error("No active stack frame");
+
+ return;
+ }
+
+ int num;
+
+ if (!int.TryParse(args, out num))
+ {
+ Log.Error("Invalid frame number");
+ return;
+ }
+
+ if (num < 0 || num > bt.FrameCount - 1)
+ {
+ Log.Error("Frame number is out of bounds (0 .. {0})", bt.FrameCount - 1);
+ return;
+ }
+
+ var f = bt.GetFrame(num);
+
+ Debugger.ActiveFrame = f;
+
+ Log.Emphasis(Utilities.StringizeFrame(f, true));
+ }
+ }
+
+ sealed class FrameUpCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "up" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Go up the call stack by one frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "frame up"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Sets the active frame to the frame above the current frame.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var bt = Debugger.ActiveBacktrace;
+
+ if (bt == null)
+ {
+ Log.Error("No active backtrace");
+ return;
+ }
+
+ if (bt.FrameCount == 0)
+ {
+ Log.Info("Backtrace for this thread is unavailable");
+ return;
+ }
+
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ Log.Error("No active stack frame");
+ return;
+ }
+
+ var f2 = bt.GetFrame(f.Index + 1);
+
+ if (f2 == f)
+ {
+ Log.Error("Cannot go further up the stack");
+ return;
+ }
+
+ Debugger.ActiveFrame = f2;
+
+ Log.Emphasis(Utilities.StringizeFrame(f2, true));
+ }
+ }
+
+ public FrameCommand()
+ {
+ AddCommand<FrameArgumentsCommand>();
+ AddCommand<FrameDownCommand>();
+ AddCommand<FrameGetCommand>();
+ AddCommand<FrameLocalsCommand>();
+ AddCommand<FrameSetCommand>();
+ AddCommand<FrameUpCommand>();
+
+ Forward<FrameGetCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "frame" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Get, set, and inspect stack frames."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Interacts with the call stack.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/HelpCommand.cs b/src/Commands/HelpCommand.cs
new file mode 100644
index 0000000..f700892
--- /dev/null
+++ b/src/Commands/HelpCommand.cs
@@ -0,0 +1,118 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class HelpCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "help" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Describe available commands."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "help [cmd] [sub-cmd] [...]"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Prints detailed help for the given (sub-)command.\n" +
+ "\n" +
+ "If the command is a multi-command, its sub-commands are printed in addition\n" +
+ "to its detailed help text.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var names = args.Split(' ').Where(x => x != string.Empty).ToArray();
+ var fullName = string.Empty;
+ Command cmd = CommandLine.Root;
+
+ foreach (var name in names)
+ {
+ var mcmd = cmd as MultiCommand;
+
+ if (mcmd == null)
+ break;
+
+ var scmd = mcmd.GetCommand(name);
+
+ if (scmd == null)
+ {
+ if (cmd is RootCommand)
+ Log.Error("'{0}' is not a known command", name);
+ else
+ Log.Error("No sub-command '{0}' under '{1}'", name, fullName);
+
+ return;
+ }
+ else
+ {
+ cmd = scmd;
+
+ if (fullName != string.Empty)
+ fullName += ' ';
+
+ fullName += name;
+ }
+ }
+
+ if (!(cmd is RootCommand))
+ {
+ Log.Info(string.Empty);
+ Log.Emphasis(" {0}", cmd.Syntax);
+ }
+
+ Log.Info(string.Empty);
+ Log.Info(cmd.Help);
+ Log.Info(string.Empty);
+
+ var mcmd2 = cmd as MultiCommand;
+
+ if (mcmd2 != null)
+ {
+
+ foreach (var sub in mcmd2.Commands.Select(x => x.Item2).Distinct())
+ {
+ Log.Emphasis(" {0}", string.Join("|", sub.Names));
+ Log.Info(" {0}", sub.Summary);
+ Log.Info(string.Empty);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Commands/KillCommand.cs b/src/Commands/KillCommand.cs
new file mode 100644
index 0000000..f24c29a
--- /dev/null
+++ b/src/Commands/KillCommand.cs
@@ -0,0 +1,65 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class KillCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "kill" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Kill the inferior process."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "kill|stop"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Kills the current inferior process, regardless of its state.\n" +
+ "\n" +
+ "For remote debugging sessions, this also makes the remote virtual machine\n" +
+ "exit immediately.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State == State.Exited)
+ Log.Error("No inferior process");
+ else
+ Debugger.Kill();
+ }
+ }
+}
diff --git a/src/Commands/ListenCommand.cs b/src/Commands/ListenCommand.cs
new file mode 100644
index 0000000..72e18be
--- /dev/null
+++ b/src/Commands/ListenCommand.cs
@@ -0,0 +1,101 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class ListenCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "listen", "wait" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Listen for a remote virtual machine."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "listen|wait <addr> <port>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Listens for a remote debuggee connection on the given IP address and port.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State != State.Exited)
+ {
+ Log.Error("An inferior process is already being debugged");
+ return;
+ }
+
+ var ip = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (ip == null)
+ {
+ Log.Error("No IP address given");
+ return;
+ }
+
+ IPAddress addr;
+
+ if (!IPAddress.TryParse(ip, out addr))
+ {
+ Log.Error("Invalid IP address");
+ return;
+ }
+
+ var rest = new string(args.Skip(ip.Length).ToArray()).Trim();
+
+ if (rest.Length == 0)
+ {
+ Log.Error("No port number given");
+ return;
+ }
+
+ int port;
+
+ if (!int.TryParse(rest, out port) || port <= 0)
+ {
+ Log.Error("Invalid port number");
+ return;
+ }
+
+ Debugger.Listen(addr, port);
+
+ Log.Info("Listening on '{0}:{1}'...", addr, port);
+ }
+ }
+}
diff --git a/src/Commands/PluginCommand.cs b/src/Commands/PluginCommand.cs
new file mode 100644
index 0000000..9afb97b
--- /dev/null
+++ b/src/Commands/PluginCommand.cs
@@ -0,0 +1,66 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.IO;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class PluginCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "plugin", "addin" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Load a plugin assembly."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "plugin|addin <file>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Attempts to load the given plugin assembly. All types that are tagged with\n" +
+ "the '[Command]' attribute will be instantiated and made available.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (!File.Exists(args))
+ {
+ Log.Error("Plugin assembly '{0}' does not exist");
+ return;
+ }
+
+ CommandLine.Root.AddCommands(Plugins.Load(args));
+ }
+ }
+}
diff --git a/src/Commands/PrintCommand.cs b/src/Commands/PrintCommand.cs
new file mode 100644
index 0000000..a045b53
--- /dev/null
+++ b/src/Commands/PrintCommand.cs
@@ -0,0 +1,92 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class PrintCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "print", "evaluate" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Print (evaluate) a given expression."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "print|evaluate <expr>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Evaluates the given expression in the context of the active stack frame.\n" +
+ "The expression has access to any local variables and method arguments that\n" +
+ "are in scope.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ Log.Error("No active stack frame");
+ return;
+ }
+
+ if (args.Length == 0)
+ {
+ Log.Error("No expression given");
+ return;
+ }
+
+ if (!f.ValidateExpression(args))
+ {
+ Log.Error("Expression '{0}' is invalid", args);
+ return;
+ }
+
+ var val = f.GetExpressionValue(args, Debugger.Options.EvaluationOptions);
+ val.WaitHandle.WaitOne();
+
+ var strErr = Utilities.StringizeValue(val);
+
+ if (strErr.Item2)
+ {
+ Log.Error(strErr.Item1);
+ return;
+ }
+
+ Log.Info("{0}{1}{2} it = {3}", Color.DarkGreen, val.TypeName, Color.Reset, strErr.Item1);
+ }
+ }
+}
diff --git a/src/Commands/QuitCommand.cs b/src/Commands/QuitCommand.cs
new file mode 100644
index 0000000..3852448
--- /dev/null
+++ b/src/Commands/QuitCommand.cs
@@ -0,0 +1,67 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class QuitCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "quit", "bye", "exit" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Exit the debugger."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "quit|bye|exit [!]"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Exits the debugger. If the '!' argument is given, any active inferior\n" +
+ "process will be killed; otherwise, the command will refuse to quit if an\n" +
+ "inferior process is active.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State != State.Exited && !args.StartsWith("!"))
+ {
+ Log.Error("An inferior process is active");
+ return;
+ }
+
+ CommandLine.Stop = true;
+ }
+ }
+}
diff --git a/src/Commands/ResetCommand.cs b/src/Commands/ResetCommand.cs
new file mode 100644
index 0000000..4bad1fd
--- /dev/null
+++ b/src/Commands/ResetCommand.cs
@@ -0,0 +1,72 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class ResetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "reset" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Reset inferior environment variables, arguments, etc."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "reset"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Resets the following debugger state to defaults:\n" +
+ "\n" +
+ "* Working directory.\n" +
+ "* Program arguments.\n" +
+ "* Environment variables.\n" +
+ "* Watches.\n" +
+ "* Breakpoints and catchpoints.\n" +
+ "* Session options.\n" +
+ "* Evaluation options.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Debugger.ResetState();
+ Debugger.ResetOptions();
+
+ Configuration.Apply();
+
+ Log.Info("All debugger state reset");
+ }
+ }
+}
diff --git a/src/Commands/RootCommand.cs b/src/Commands/RootCommand.cs
new file mode 100644
index 0000000..0c32c13
--- /dev/null
+++ b/src/Commands/RootCommand.cs
@@ -0,0 +1,99 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class RootCommand : MultiCommand
+ {
+ public RootCommand()
+ {
+ AddCommandWithName<PrintCommand>("p");
+ AddCommand<AttachCommand>();
+ AddCommandWithName<BreakpointCommand>("b");
+ AddCommand<BacktraceCommand>();
+ AddCommand<BreakpointCommand>();
+ AddCommandWithName<ContinueCommand>("c");
+ AddCommand<CatchpointCommand>();
+ AddCommand<ConfigCommand>();
+ AddCommandWithName<RunCommand>("r");
+ AddCommand<ConnectCommand>();
+ AddCommand<ContinueCommand>();
+ AddCommand<DatabaseCommand>();
+ AddCommand<DecompileCommand>();
+ AddCommand<DirectoryCommand>();
+ AddCommand<DisassembleCommand>();
+ AddCommand<EnvironmentCommand>();
+ AddCommand<FrameCommand>();
+ AddCommand<HelpCommand>();
+ AddCommand<KillCommand>();
+ AddCommand<ListenCommand>();
+ AddCommand<PluginCommand>();
+ AddCommandWithName<PrintCommand>("p");
+ AddCommand<PrintCommand>();
+ AddCommand<QuitCommand>();
+ AddCommandWithName<RunCommand>("r");
+ AddCommand<ResetCommand>();
+ AddCommand<RunCommand>();
+ AddCommandWithName<StepCommand>("s");
+ AddCommand<SourceCommand>();
+ AddCommand<StepCommand>();
+ AddCommand<ThreadCommand>();
+ AddCommand<WatchCommand>();
+ }
+
+ public void AddCommands(IEnumerable<Command> commands)
+ {
+ foreach (var cmd in commands)
+ AddCommand(cmd);
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "root" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "N/A"; }
+ }
+
+ public override string Syntax
+ {
+ get { return "N/A"; }
+ }
+
+ public override string Help
+ {
+ get { return "All commands (and sub-commands) can be abbreviated.\n" +
+ "For example, 'exe' means 'execute' and 'co s' means 'config set'."; }
+ }
+
+ protected override void ProcessFallback(string args, bool invalidSubCommand)
+ {
+ Log.Error("'{0}' is not a known command", args);
+ }
+ }
+}
diff --git a/src/Commands/RunCommand.cs b/src/Commands/RunCommand.cs
new file mode 100644
index 0000000..95d7316
--- /dev/null
+++ b/src/Commands/RunCommand.cs
@@ -0,0 +1,100 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class RunCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "run", "execute", "launch" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Run a virtual machine locally."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "run|execute|launch <path>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Runs an inferior process locally. The argument must be the path to the\n" +
+ "executable file (i.e. '.exe').\n" +
+ "\n" +
+ "The following debugger state is taken into account:\n" +
+ "\n" +
+ "* Working directory.\n" +
+ "* Program arguments.\n" +
+ "* Environment variables.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State != State.Exited)
+ {
+ Log.Error("An inferior process is already being debugged");
+ return;
+ }
+
+ if (args.Length == 0)
+ {
+ Log.Error("No program path given");
+ return;
+ }
+
+ if (!File.Exists(args))
+ {
+ Log.Error("Program executable '{0}' does not exist", args);
+ return;
+ }
+
+ FileInfo file;
+
+ try
+ {
+ file = new FileInfo(args);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not open file '{0}':", args);
+ Log.Error(ex.ToString());
+
+ return;
+ }
+
+ Debugger.Run(file);
+ }
+ }
+}
diff --git a/src/Commands/SourceCommand.cs b/src/Commands/SourceCommand.cs
new file mode 100644
index 0000000..2f19bf7
--- /dev/null
+++ b/src/Commands/SourceCommand.cs
@@ -0,0 +1,160 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class SourceCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "source", "src" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Show the source for the current stack frame."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "source|src [lower] [upper]"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Prints the source for the current stack frame and highlights the current\n" +
+ "line.\n" +
+ "\n" +
+ "If arguments are given, they specify how many lines to print before and\n" +
+ "after the current line.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ Log.Error("No active stack frame");
+ return;
+ }
+
+ var lower = 10;
+ var upper = 10;
+
+ var lowerStr = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (lowerStr != null)
+ {
+ if (!int.TryParse(lowerStr, out lower))
+ {
+ Log.Error("Invalid lower bound value");
+ return;
+ }
+
+ lower = System.Math.Abs(lower);
+
+ var upperStr = new string(args.Skip(lowerStr.Length).ToArray()).Trim();
+
+ if (upperStr.Length != 0)
+ {
+ if (!int.TryParse(upperStr, out upper))
+ {
+ Log.Error("Invalid upper bound value");
+ return;
+ }
+ }
+ }
+
+ var loc = f.SourceLocation;
+ var file = loc.FileName;
+ var line = loc.Line;
+
+ if (file != null && line != -1)
+ {
+ if (!File.Exists(file))
+ {
+ Log.Error("Source file '{0}' not found", file);
+ return;
+ }
+
+ StreamReader reader;
+
+ try
+ {
+ reader = File.OpenText(file);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not open source file '{0}'", file);
+ Log.Error(ex.ToString());
+
+ return;
+ }
+
+ try
+ {
+ var exec = Debugger.CurrentExecutable;
+
+ if (exec != null && File.GetLastWriteTime(file) > exec.LastWriteTime)
+ Log.Notice("Source file '{0}' is newer than the debuggee executable", file);
+
+ var cur = 0;
+
+ while (!reader.EndOfStream)
+ {
+ var str = reader.ReadLine();
+
+ var i = line - cur;
+ var j = cur - line;
+
+ if (i > 0 && i < lower + 2 || j >= 0 && j < upper)
+ {
+ if (cur == line - 1)
+ Log.Emphasis(str);
+ else
+ Log.Info(str);
+ }
+
+ cur++;
+ }
+ }
+ finally
+ {
+ reader.Dispose();
+ }
+ }
+ else
+ Log.Error("No source information available");
+ }
+ }
+}
diff --git a/src/Commands/StepCommand.cs b/src/Commands/StepCommand.cs
new file mode 100644
index 0000000..d2087bb
--- /dev/null
+++ b/src/Commands/StepCommand.cs
@@ -0,0 +1,307 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class StepCommand : MultiCommand
+ {
+ sealed class StepOverCommand : MultiCommand
+ {
+ sealed class StepOverLineCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "line" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Step over a line."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "step over line"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Steps over a line.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State == State.Suspended)
+ Debugger.StepOverLine();
+ else
+ Log.Error("No suspended inferior process");
+ }
+ }
+
+ sealed class StepOverInstructionCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "instruction", "insn" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Step over an instruction."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "step over instruction|insn"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Steps over an instruction.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State == State.Suspended)
+ Debugger.StepOverInstruction();
+ else
+ Log.Error("No suspended inferior process");
+ }
+ }
+
+ public StepOverCommand()
+ {
+ AddCommand<StepOverLineCommand>();
+ AddCommand<StepOverInstructionCommand>();
+
+ Forward<StepOverLineCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "over" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Step over a line or an instruction."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Steps over a line or an instruction.\n" +
+ "\n" +
+ "This executes the line or instruction that's next in the source code or\n" +
+ "IL stream. If the line or instruction contains a call, the debugger\n" +
+ "will treat it as a single unit, as opposed to 'step into'.";
+ }
+ }
+
+ public override string Parent
+ {
+ get { return "step"; }
+ }
+ }
+
+ sealed class StepIntoCommand : MultiCommand
+ {
+ sealed class StepIntoLineCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "line" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Step into a line."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "step into line"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Steps into a line.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State == State.Suspended)
+ Debugger.StepIntoLine();
+ else
+ Log.Error("No suspended inferior process");
+ }
+ }
+
+ sealed class StepIntoInstructionCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "instruction", "insn" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Step into an instruction."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "step into instruction|insn"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Steps into an instruction.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State == State.Suspended)
+ Debugger.StepIntoInstruction();
+ else
+ Log.Error("No suspended inferior process");
+ }
+ }
+
+ public StepIntoCommand()
+ {
+ AddCommand<StepIntoLineCommand>();
+ AddCommand<StepIntoInstructionCommand>();
+
+ Forward<StepIntoLineCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "into" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Step into a line or an instruction."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Steps into a line or an instruction.\n" +
+ "\n" +
+ "This executes the line or instruction that's next in the source code or\n" +
+ "IL stream. If the line or instruction contains a call, the debugger\n" +
+ "will pause at the first line or instruction inside the callee.";
+ }
+ }
+
+ public override string Parent
+ {
+ get { return "step"; }
+ }
+ }
+
+ sealed class StepOutCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "out" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Step out of the current method."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "step out"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Steps out of the current method.\n" +
+ "\n" +
+ "This resumes the debuggee until it exits the method it's currently in.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ if (Debugger.State == State.Suspended)
+ Debugger.StepOutOfMethod();
+ else
+ Log.Error("No suspended inferior process");
+ }
+ }
+
+ public StepCommand()
+ {
+ AddCommandWithName<StepOverCommand>("o");
+ AddCommand<StepOverCommand>();
+ AddCommand<StepIntoCommand>();
+ AddCommand<StepOutCommand>();
+
+ Forward<StepOverCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "step" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Single-step through lines/instructions/methods."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Single-steps through lines, instructions, and methods.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/ThreadCommand.cs b/src/Commands/ThreadCommand.cs
new file mode 100644
index 0000000..20993a3
--- /dev/null
+++ b/src/Commands/ThreadCommand.cs
@@ -0,0 +1,295 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class ThreadCommand : MultiCommand
+ {
+ sealed class ThreadBacktraceCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "backtrace", "bt" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Print backtraces for all threads."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "thread backtrace|bt"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Prints a backtrace for all program threads.\n" +
+ "\n" +
+ "Functionally equivalent to applying 'backtrace' to all threads while\n" +
+ "switching through them with 'thread set'.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var p = Debugger.ActiveProcess;
+
+ if (p == null)
+ {
+ Log.Error("No active inferior process");
+ return;
+ }
+
+ var threads = p.GetThreads();
+
+ for (var i = 0; i < threads.Length; i++)
+ {
+ var t = threads[i];
+ var str = Utilities.StringizeThread(t, false);
+
+ if (t == Debugger.ActiveThread)
+ Log.Emphasis(str);
+ else
+ Log.Info(str);
+
+ var bt = t.Backtrace;
+
+ if (bt.FrameCount != 0)
+ {
+ for (var j = 0; j < bt.FrameCount; j++)
+ {
+ var f = bt.GetFrame(j);
+ var fstr = Utilities.StringizeFrame(f, true);
+
+ if (f == Debugger.ActiveFrame)
+ Log.Emphasis(fstr);
+ else
+ Log.Info(fstr);
+ }
+ }
+ else
+ Log.Info("Backtrace for this thread is unavailable");
+
+ if (i < threads.Length - 1)
+ Log.Info(string.Empty);
+ }
+ }
+ }
+
+ sealed class ThreadGetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "get" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Show the currently active thread."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "thread get"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Gets the currently active thread.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var t = Debugger.ActiveThread;
+
+ if (t == null)
+ {
+ Log.Error("No active thread");
+ return;
+ }
+
+ var str = Utilities.StringizeThread(t, true);
+
+ if (t == Debugger.ActiveThread)
+ Log.Emphasis(str);
+ else
+ Log.Info(str);
+ }
+ }
+
+ sealed class ThreadListCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "list" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "List all program threads."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "thread list"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all program threads, along with their IDs and names.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var p = Debugger.ActiveProcess;
+
+ if (p == null)
+ {
+ Log.Error("No active inferior process");
+ return;
+ }
+
+ var threads = p.GetThreads();
+
+ for (var i = 0; i < threads.Length; i++)
+ {
+ var t = threads[i];
+ var str = Utilities.StringizeThread(t, true);
+
+ if (t == Debugger.ActiveThread)
+ Log.Emphasis(str);
+ else
+ Log.Info(str);
+
+ if (i < threads.Length - 1)
+ Log.Info(string.Empty);
+ }
+ }
+ }
+
+ sealed class ThreadSetCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "set" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Set currently active thread."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "thread set <id>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Sets the currently active thread to the given thread ID.\n" +
+ "\n" +
+ "All following commands that somehow interact with the program's call\n" +
+ "stack will happen on the specified thread.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var p = Debugger.ActiveProcess;
+
+ if (p == null)
+ {
+ Log.Error("No active inferior process");
+ return;
+ }
+
+ int num;
+
+ if (!int.TryParse(args, out num) || num < 0)
+ {
+ Log.Error("Invalid thread ID");
+ return;
+ }
+
+ var threads = p.GetThreads();
+
+ foreach (var t in threads)
+ {
+ if (t.Id == num)
+ {
+ t.SetActive();
+ Debugger.ActiveFrame = null;
+
+ Log.Emphasis(Utilities.StringizeThread(t, true));
+
+ return;
+ }
+ }
+
+ Log.Error("Thread ID '{0}' not found", num);
+ }
+ }
+
+ public ThreadCommand()
+ {
+ AddCommand<ThreadBacktraceCommand>();
+ AddCommand<ThreadGetCommand>();
+ AddCommand<ThreadListCommand>();
+ AddCommand<ThreadSetCommand>();
+
+ Forward<ThreadGetCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "thread" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Get, set, and inspect program threads."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Interacts with program threads.";
+ }
+ }
+ }
+}
diff --git a/src/Commands/WatchCommand.cs b/src/Commands/WatchCommand.cs
new file mode 100644
index 0000000..c6e2258
--- /dev/null
+++ b/src/Commands/WatchCommand.cs
@@ -0,0 +1,256 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Collections.Generic;
+using Mono.Debugging.Client;
+
+namespace Mono.Debugger.Client.Commands
+{
+ sealed class WatchCommand : MultiCommand
+ {
+ sealed class WatchAddCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "add" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add a watch expression."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "watch add <expr>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Adds a watch with the given expression.\n" +
+ "\n" +
+ "Watches and their values can be shown with the 'watch list' command.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var id = Debugger.GetWatchId();
+
+ Debugger.Watches.Add(id, args);
+
+ Log.Info("Added watch '{0}' with expression '{1}'", id, args);
+ }
+ }
+
+ sealed class WatchClearCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "clear" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Delete all watches."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "watch clear"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Clears all active watches.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ Debugger.Watches.Clear();
+
+ Log.Info("All watches cleared");
+ }
+ }
+
+ sealed class WatchDeleteCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "delete", "remove" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Delete a watch by ID."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "watch delete|remove <id>"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Deletes the watch with the specified ID, if it exists.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ long num;
+
+ if (!long.TryParse(args, out num))
+ {
+ Log.Error("Invalid watch ID");
+ return;
+ }
+
+ string expr;
+
+ if (!Debugger.Watches.TryGetValue(num, out expr))
+ {
+ Log.Error("Watch '{0}' not found", num);
+ return;
+ }
+
+ Debugger.Watches.Remove(num);
+
+ Log.Info("Watch '{0}' (with expression '{1}') deleted", num, expr);
+ }
+ }
+
+ sealed class WatchListCommand : Command
+ {
+ public override string[] Names
+ {
+ get { return new[] { "list" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "List all set watches and their IDs."; }
+ }
+
+ public override string Syntax
+ {
+ get { return "watch list"; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Lists all watches, along with their IDs, expressions, and values.";
+ }
+ }
+
+ public override void Process(string args)
+ {
+ var f = Debugger.ActiveFrame;
+
+ if (f == null)
+ {
+ Log.Error("No active stack frame");
+ return;
+ }
+
+ if (Debugger.Watches.Count == 0)
+ {
+ Log.Info("No watches");
+ return;
+ }
+
+ foreach (var pair in Debugger.Watches)
+ {
+ ObjectValue obj = null;
+ string value;
+ bool error;
+
+ if (!f.ValidateExpression(pair.Value))
+ {
+ value = "Expression is invalid";
+ error = true;
+ }
+ else
+ {
+ obj = f.GetExpressionValue(pair.Value, Debugger.Options.EvaluationOptions);
+ obj.WaitHandle.WaitOne();
+
+ var strErr = Utilities.StringizeValue(obj);
+
+ value = strErr.Item1;
+ error = strErr.Item2;
+ }
+
+ var prefix = string.Format("#{0} '{1}': ", pair.Key, pair.Value);
+
+ if (error)
+ Log.Error("{0}{1}", prefix, value);
+ else
+ Log.Info("{0}{1}{2}{3} it = {4}", prefix, Color.DarkGreen, obj.TypeName, Color.Reset, value);
+ }
+ }
+ }
+
+ public WatchCommand()
+ {
+ AddCommand<WatchAddCommand>();
+ AddCommand<WatchClearCommand>();
+ AddCommand<WatchDeleteCommand>();
+ AddCommand<WatchListCommand>();
+
+ Forward<WatchListCommand>();
+ }
+
+ public override string[] Names
+ {
+ get { return new[] { "watch" }; }
+ }
+
+ public override string Summary
+ {
+ get { return "Add, delete, and show watches."; }
+ }
+
+ public override string Help
+ {
+ get
+ {
+ return "Manipulates watches.\n" +
+ "\n" +
+ "Watches are simply expressions that can be saved and queried at any point\n" +
+ "when the debuggee is paused. They are useful for easily observing global\n" +
+ "program state.";
+ }
+ }
+ }
+}
diff --git a/src/Configuration.cs b/src/Configuration.cs
new file mode 100644
index 0000000..37134f7
--- /dev/null
+++ b/src/Configuration.cs
@@ -0,0 +1,199 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.Serialization.Formatters.Binary;
+using Mono.Debugging.Client;
+
+namespace Mono.Debugger.Client
+{
+ [Serializable]
+ public sealed class Configuration
+ {
+ public bool AllowMethodEvaluation { get; set; }
+
+ public bool AllowTargetInvoke { get; set; }
+
+ public bool AllowToStringCalls { get; set; }
+
+ public bool ChunkRawStrings { get; set; }
+
+ public int ConnectionAttemptInterval { get; set; }
+
+ public bool DebugLogging { get; set; }
+
+ public bool DisableColors { get; set; }
+
+ public bool EllipsizeStrings { get; set; }
+
+ public int EllipsizeThreshold { get; set; }
+
+ public bool EnableControlC { get; set; }
+
+ public int EvaluationTimeout { get; set; }
+
+ public string ExceptionIdentifier { get; set; }
+
+ public bool FlattenHierarchy { get; set; }
+
+ public bool HexadecimalIntegers { get; set; }
+
+ public string InputPrompt { get; set; }
+
+ public bool LogInternalErrors { get; set; }
+
+ public bool LogRuntimeSpew { get; set; }
+
+ public int MaxConnectionAttempts { get; set; }
+
+ public int MemberEvaluationTimeout { get; set; }
+
+ public string RuntimePrefix { get; set; }
+
+ public bool StepOverPropertiesAndOperators { get; set; }
+
+ public static Configuration Current { get; private set; }
+
+ static Configuration()
+ {
+ Current = new Configuration();
+ }
+
+ static string GetFilePath()
+ {
+ var cfg = Environment.GetEnvironmentVariable("SDB_CFG");
+
+ if (cfg != null)
+ return cfg == string.Empty ? null : cfg;
+
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+
+ return Path.Combine(home, ".sdb.cfg");
+ }
+
+ public static void Write()
+ {
+ var file = GetFilePath();
+
+ if (file == null)
+ return;
+
+ try
+ {
+ using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write))
+ new BinaryFormatter().Serialize(stream, Current);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not write configuration file '{0}':", file);
+ Log.Error(ex.ToString());
+ }
+ }
+
+ public static bool Read()
+ {
+ var file = GetFilePath();
+
+ if (file == null)
+ return false;
+
+ try
+ {
+ using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
+ Current = (Configuration)new BinaryFormatter().Deserialize(stream);
+ }
+ catch (Exception ex)
+ {
+ // If it's an FNFE, chances are the file just
+ // hasn't been written yet.
+ if (!(ex is FileNotFoundException))
+ {
+ Log.Error("Could not read configuration file '{0}':", file);
+ Log.Error(ex.ToString());
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void Defaults()
+ {
+ // Cute hack to set all properties to their default values.
+ foreach (var prop in typeof(Configuration).GetProperties(BindingFlags.Public |
+ BindingFlags.Instance))
+ prop.SetValue(Configuration.Current, null);
+
+ Current.AllowMethodEvaluation = true;
+ Current.AllowTargetInvoke = true;
+ Current.AllowToStringCalls = true;
+ Current.ConnectionAttemptInterval = 500;
+ Current.EllipsizeStrings = true;
+ Current.EllipsizeThreshold = 100;
+ Current.EnableControlC = true;
+ Current.EvaluationTimeout = 1000;
+ Current.ExceptionIdentifier = "$exception";
+ Current.FlattenHierarchy = true;
+ Current.InputPrompt = "(sdb)";
+ Current.MaxConnectionAttempts = 1;
+ Current.MemberEvaluationTimeout = 5000;
+ Current.RuntimePrefix = "/usr";
+ Current.StepOverPropertiesAndOperators = true;
+ }
+
+ public static void Apply()
+ {
+ // We can only apply a limited set of options here since some
+ // are set at session creation time.
+
+ var opt = Debugger.Options;
+
+ opt.StepOverPropertiesAndOperators = Current.StepOverPropertiesAndOperators;
+
+ var eval = opt.EvaluationOptions;
+
+ eval.AllowMethodEvaluation = Current.AllowMethodEvaluation;
+ eval.AllowTargetInvoke = Current.AllowTargetInvoke;
+ eval.AllowToStringCalls = Current.AllowToStringCalls;
+ eval.ChunkRawStrings = Current.ChunkRawStrings;
+ eval.CurrentExceptionTag = Current.ExceptionIdentifier;
+ eval.EllipsizeStrings = Current.EllipsizeStrings;
+ eval.EllipsizedLength = Current.EllipsizeThreshold;
+ eval.EvaluationTimeout = Current.EvaluationTimeout;
+ eval.FlattenHierarchy = Current.FlattenHierarchy;
+ eval.IntegerDisplayFormat = Current.HexadecimalIntegers ?
+ IntegerDisplayFormat.Hexadecimal :
+ IntegerDisplayFormat.Decimal;
+ eval.MemberEvaluationTimeout = Current.MemberEvaluationTimeout;
+
+ if (Current.EnableControlC)
+ CommandLine.SetControlCHandler();
+ else
+ CommandLine.UnsetControlCHandler();
+ }
+ }
+}
diff --git a/src/CustomLogger.cs b/src/CustomLogger.cs
new file mode 100644
index 0000000..8e38dc5
--- /dev/null
+++ b/src/CustomLogger.cs
@@ -0,0 +1,50 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using Mono.Debugging.Client;
+
+namespace Mono.Debugger.Client
+{
+ sealed class CustomLogger : ICustomLogger
+ {
+ public void LogError(string message, Exception ex)
+ {
+ Log.Error(message);
+
+ if (ex != null)
+ Log.Error(ex.ToString());
+ }
+
+ public void LogAndShowException(string message, Exception ex)
+ {
+ LogError(message, ex);
+ }
+
+ public void LogMessage(string format, params object[] args)
+ {
+ Log.Info(format, args);
+ }
+ }
+}
diff --git a/src/Debugger.cs b/src/Debugger.cs
new file mode 100644
index 0000000..721274b
--- /dev/null
+++ b/src/Debugger.cs
@@ -0,0 +1,702 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Threading;
+using Mono.Debugging.Client;
+using Mono.Debugging.Soft;
+
+namespace Mono.Debugger.Client
+{
+ public static class Debugger
+ {
+ static readonly object _lock = new object();
+
+ static Debugger()
+ {
+ EnsureCreated();
+ ResetOptions();
+ ResetState();
+
+ _debuggeeKilled = true;
+
+ DebuggerLoggingService.CustomLogger = new CustomLogger();
+ }
+
+ static SoftDebuggerSession _session;
+
+ public static FileInfo CurrentExecutable { get; private set; }
+
+ public static IPAddress CurrentAddress { get; private set; }
+
+ public static int CurrentPort { get; private set; }
+
+ public static DebuggerSessionOptions Options { get; private set; }
+
+ public static string WorkingDirectory { get; set; }
+
+ public static string Arguments { get; set; }
+
+ public static Dictionary<string, string> EnvironmentVariables { get; private set; }
+
+ public static SortedDictionary<long, string> Watches { get; private set; }
+
+ public static SortedDictionary<long, BreakEvent> Breakpoints { get; private set; }
+
+ public static BreakpointStore BreakEvents { get; private set; }
+
+ public static bool DebuggeeKilled
+ {
+ get { return _debuggeeKilled; }
+ set { _debuggeeKilled = value; }
+ }
+
+ static volatile bool _debuggeeKilled;
+
+ static long _nextWatchId;
+
+ static long _nextBreakpointId;
+
+ static volatile bool _showResumeMessage;
+
+ public static State State
+ {
+ get
+ {
+ lock (_lock)
+ {
+ if (_session == null || _session.HasExited || !_session.IsConnected)
+ return State.Exited;
+
+ return _session.IsRunning ? State.Running : State.Suspended;
+ }
+ }
+ }
+
+ static volatile SessionKind _kind;
+
+ public static SessionKind Kind
+ {
+ get { return _kind; }
+ set { _kind = value; }
+ }
+
+ static ProcessInfo _activeProcess;
+
+ public static ProcessInfo ActiveProcess
+ {
+ get { return _activeProcess; }
+ }
+
+ public static ThreadInfo ActiveThread
+ {
+ get
+ {
+ lock (_lock)
+ return _session == null ? null : _session.ActiveThread;
+ }
+ }
+
+ public static Backtrace ActiveBacktrace
+ {
+ get
+ {
+ var thr = ActiveThread;
+
+ return thr == null ? null : thr.Backtrace;
+ }
+ }
+
+ static StackFrame _activeFrame;
+
+ public static StackFrame ActiveFrame
+ {
+ get
+ {
+ var f = _activeFrame;
+
+ if (f != null)
+ return f;
+
+ var bt = ActiveBacktrace;
+
+ if (bt != null)
+ return _activeFrame = bt.GetFrame(0);
+
+ return null;
+ }
+ set { _activeFrame = value; }
+ }
+
+ public static ExceptionInfo ActiveException
+ {
+ get
+ {
+ var bt = ActiveBacktrace;
+
+ return bt == null ? null : bt.GetFrame(0).GetException();
+ }
+ }
+
+ static void PrintException(string prefix, ExceptionInfo ex)
+ {
+ // HACK: Until we get a `WaitHandle` property on the
+ // `Mono.Debugging.Client.ExceptionInfo` type...
+ ex.Message.Discard();
+
+ while (ex.Message == "Loading...")
+ Thread.Sleep(10);
+
+ Log.Error("{0}{1}: {2}", prefix, ex.Type, ex.Message);
+ }
+
+ static void PrintException(ExceptionInfo ex)
+ {
+ PrintException(string.Empty, ex);
+
+ var prefix = "> ";
+ var inner = ex;
+
+ while ((inner = inner.InnerException) != null)
+ {
+ PrintException(prefix, inner);
+
+ prefix = "--" + prefix;
+ }
+ }
+
+ static string StringizeTarget()
+ {
+ if (CurrentExecutable != null)
+ return CurrentExecutable.Name;
+
+ if (CurrentAddress != null)
+ return string.Format("{0}:{1}", CurrentAddress, CurrentPort);
+
+ return "<none>";
+ }
+
+ static void EnsureCreated()
+ {
+ lock (_lock)
+ {
+ if (_session != null)
+ return;
+
+ _session = new SoftDebuggerSession();
+ _session.Breakpoints = BreakEvents;
+
+ _session.ExceptionHandler = ex =>
+ {
+ if (Configuration.Current.LogInternalErrors)
+ {
+ Log.Error("Internal debugger error:", ex.GetType());
+ Log.Error(ex.ToString());
+ }
+
+ return true;
+ };
+
+ _session.LogWriter = (isStdErr, text) =>
+ {
+ if (Configuration.Current.LogRuntimeSpew)
+ Log.NoticeSameLine("[Mono] {0}", text); // The string already has a line feed.
+ };
+
+ _session.OutputWriter = (isStdErr, text) =>
+ {
+ lock (Log.Lock)
+ {
+ if (isStdErr)
+ Console.Error.Write(text);
+ else
+ Console.Write(text);
+ }
+ };
+
+ _session.TypeResolverHandler += (identifier, location) =>
+ {
+ // I honestly have no idea how correct this is. I suspect you
+ // could probably break it in some corner cases. It does make
+ // something like `p Android.Runtime.JNIEnv.Handle` work,
+ // though, which would otherwise have required `global::` to
+ // be explicitly prepended.
+
+ if (identifier == "__EXCEPTION_OBJECT__")
+ return null;
+
+ foreach (var loc in ActiveFrame.GetAllLocals())
+ if (loc.Name == identifier)
+ return null;
+
+ return identifier;
+ };
+
+ _session.TargetEvent += (sender, e) =>
+ {
+ Log.Debug("Event: '{0}'", e.Type);
+ };
+
+ _session.TargetStarted += (sender, e) =>
+ {
+ _activeFrame = null;
+
+ if (_showResumeMessage)
+ Log.Notice("Inferior process '{0}' ('{1}') resumed",
+ ActiveProcess.Id, StringizeTarget());
+ };
+
+ _session.TargetReady += (sender, e) =>
+ {
+ _showResumeMessage = true;
+ _activeProcess = _session.GetProcesses().SingleOrDefault();
+
+ // The inferior process has launched, so we can safely
+ // set our `SIGINT` handler without it interfering with
+ // the inferior.
+ CommandLine.SetControlCHandler();
+
+ Log.Notice("Inferior process '{0}' ('{1}') started",
+ ActiveProcess.Id, StringizeTarget());
+ };
+
+ _session.TargetStopped += (sender, e) =>
+ {
+ Log.Notice("Inferior process '{0}' ('{1}') suspended",
+ ActiveProcess.Id, StringizeTarget());
+ Log.Emphasis(Utilities.StringizeFrame(ActiveFrame, true));
+
+ CommandLine.ResumeEvent.Set();
+ };
+
+ _session.TargetInterrupted += (sender, e) =>
+ {
+ Log.Notice("Inferior process '{0}' ('{1}') interrupted",
+ ActiveProcess.Id, StringizeTarget());
+ Log.Emphasis(Utilities.StringizeFrame(ActiveFrame, true));
+
+ CommandLine.ResumeEvent.Set();
+ };
+
+ _session.TargetHitBreakpoint += (sender, e) =>
+ {
+ var bp = e.BreakEvent as Breakpoint;
+ var fbp = e.BreakEvent as FunctionBreakpoint;
+
+ if (fbp != null)
+ Log.Notice("Hit method breakpoint on '{0}'", fbp.FunctionName);
+ else
+ {
+ var cond = bp.ConditionExpression != null ?
+ string.Format(" (condition '{0}' met)", bp.ConditionExpression) :
+ string.Empty;
+
+ Log.Notice("Hit breakpoint at '{0}:{1}'{2}", bp.FileName, bp.Line, cond);
+ }
+
+ Log.Emphasis(Utilities.StringizeFrame(ActiveFrame, true));
+
+ CommandLine.ResumeEvent.Set();
+ };
+
+ _session.TargetExited += (sender, e) =>
+ {
+ var p = ActiveProcess;
+
+ // Can happen when a remote connection attempt fails.
+ if (p == null)
+ {
+ if (_kind == SessionKind.Listening)
+ Log.Notice("Listening socket closed");
+ else if (_kind == SessionKind.Connected)
+ Log.Notice("Connection attempt terminated");
+ else
+ Log.Notice("Failed to connect to '{0}'", StringizeTarget());
+ }
+ else
+ Log.Notice("Inferior process '{0}' ('{1}') exited", ActiveProcess.Id, StringizeTarget());
+
+ // Make sure we clean everything up on a normal exit.
+ Kill();
+
+ _debuggeeKilled = true;
+ _kind = SessionKind.Disconnected;
+
+ CommandLine.ResumeEvent.Set();
+ };
+
+ _session.TargetExceptionThrown += (sender, e) =>
+ {
+ var ex = ActiveException;
+
+ Log.Notice("Trapped first-chance exception of type '{0}'", ex.Type);
+ Log.Emphasis(Utilities.StringizeFrame(ActiveFrame, true));
+
+ PrintException(ex);
+
+ CommandLine.ResumeEvent.Set();
+ };
+
+ _session.TargetUnhandledException += (sender, e) =>
+ {
+ var ex = ActiveException;
+
+ Log.Notice("Trapped unhandled exception of type '{0}'", ex.Type);
+ Log.Emphasis(Utilities.StringizeFrame(ActiveFrame, true));
+
+ PrintException(ex);
+
+ CommandLine.ResumeEvent.Set();
+ };
+
+ _session.TargetThreadStarted += (sender, e) =>
+ {
+ Log.Notice("Inferior thread '{0}' ('{1}') started",
+ e.Thread.Id, e.Thread.Name);
+ };
+
+ _session.TargetThreadStopped += (sender, e) =>
+ {
+ Log.Notice("Inferior thread '{0}' ('{1}') exited",
+ e.Thread.Id, e.Thread.Name);
+ };
+ }
+ }
+
+ public static void Run(FileInfo file)
+ {
+ lock (_lock)
+ {
+ EnsureCreated();
+
+ CurrentExecutable = file;
+ CurrentAddress = null;
+ CurrentPort = -1;
+
+ _showResumeMessage = false;
+ _debuggeeKilled = false;
+ _kind = SessionKind.Launched;
+
+ var info = new SoftDebuggerStartInfo(Configuration.Current.RuntimePrefix,
+ EnvironmentVariables)
+ {
+ Command = file.FullName,
+ Arguments = Arguments,
+ WorkingDirectory = WorkingDirectory,
+ StartArgs =
+ {
+ MaxConnectionAttempts = Configuration.Current.MaxConnectionAttempts,
+ TimeBetweenConnectionAttempts = Configuration.Current.ConnectionAttemptInterval
+ }
+ };
+
+ // We need to ignore `SIGINT` while we start the inferior
+ // process so that it inherits that signal disposition.
+ CommandLine.UnsetControlCHandler();
+
+ _session.Run(info, Options);
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+
+ public static void Connect(IPAddress address, int port)
+ {
+ lock (_lock)
+ {
+ EnsureCreated();
+
+ CurrentExecutable = null;
+ CurrentAddress = address;
+ CurrentPort = port;
+
+ _showResumeMessage = false;
+ _debuggeeKilled = false;
+ _kind = SessionKind.Connected;
+
+ var args = new SoftDebuggerConnectArgs(string.Empty, address, port)
+ {
+ MaxConnectionAttempts = Configuration.Current.MaxConnectionAttempts,
+ TimeBetweenConnectionAttempts = Configuration.Current.ConnectionAttemptInterval
+ };
+
+ _session.Run(new SoftDebuggerStartInfo(args), Options);
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+
+ public static void Listen(IPAddress address, int port)
+ {
+ lock (_lock)
+ {
+ EnsureCreated();
+
+ CurrentExecutable = null;
+ CurrentAddress = address;
+ CurrentPort = port;
+
+ _showResumeMessage = false;
+ _debuggeeKilled = false;
+ _kind = SessionKind.Listening;
+
+ var args = new SoftDebuggerListenArgs(string.Empty, address, port);
+
+ _session.Run(new SoftDebuggerStartInfo(args), Options);
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+
+ public static void Pause()
+ {
+ lock (_lock)
+ if (_session != null && _session.IsRunning)
+ _session.Stop();
+ }
+
+ public static void Continue()
+ {
+ lock (_lock)
+ {
+ if (_session != null && !_session.IsRunning && !_session.HasExited)
+ {
+ _session.Continue();
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+ }
+
+ public static void Kill()
+ {
+ lock (_lock)
+ {
+ if (_session == null)
+ return;
+
+ CommandLine.InferiorExecuting = true;
+
+ if (!_session.HasExited)
+ _session.Exit();
+
+ _session.Dispose();
+ _session = null;
+ }
+ }
+
+ public static void StepOverLine()
+ {
+ lock (_lock)
+ {
+ if (_session != null && !_session.IsRunning && !_session.HasExited)
+ {
+ _session.StepLine();
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+ }
+
+ public static void StepOverInstruction()
+ {
+ lock (_lock)
+ {
+ if (_session != null && !_session.IsRunning && !_session.HasExited)
+ {
+ _session.StepInstruction();
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+ }
+
+ public static void StepIntoLine()
+ {
+ lock (_lock)
+ {
+ if (_session != null && !_session.IsRunning && !_session.HasExited)
+ {
+ _session.NextLine();
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+ }
+
+ public static void StepIntoInstruction()
+ {
+ lock (_lock)
+ {
+ if (_session != null && !_session.IsRunning && !_session.HasExited)
+ {
+ _session.NextInstruction();
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+ }
+
+ public static void StepOutOfMethod()
+ {
+ lock (_lock)
+ {
+ if (_session != null && !_session.IsRunning && !_session.HasExited)
+ {
+ _session.Finish();
+
+ CommandLine.InferiorExecuting = true;
+ }
+ }
+ }
+
+ public static long GetWatchId()
+ {
+ return _nextWatchId++;
+ }
+
+ public static long GetBreakpointId()
+ {
+ return _nextBreakpointId++;
+ }
+
+ public static void ResetState()
+ {
+ // No need to lock on this data.
+ WorkingDirectory = Environment.CurrentDirectory;
+ Arguments = string.Empty;
+ EnvironmentVariables = new Dictionary<string, string>();
+ Watches = new SortedDictionary<long, string>();
+ _nextWatchId = 0;
+ Breakpoints = new SortedDictionary<long, BreakEvent>();
+ BreakEvents = new BreakpointStore();
+
+ // Make sure breakpoints/catchpoints take effect.
+ lock (_lock)
+ if (_session != null)
+ _session.Breakpoints = BreakEvents;
+ }
+
+ public static void ResetOptions()
+ {
+ // No need to lock on this data.
+
+ Options = new DebuggerSessionOptions
+ {
+ EvaluationOptions = EvaluationOptions.DefaultOptions
+ };
+
+ Options.EvaluationOptions.UseExternalTypeResolver = true;
+ }
+
+ [Serializable]
+ sealed class DebuggerState
+ {
+ public string WorkingDirectory { get; set; }
+
+ public string Arguments { get; set; }
+
+ public Dictionary<string, string> EnvironmentVariables { get; set; }
+
+ public SortedDictionary<long, string> Watches { get; set; }
+
+ public long NextWatchId { get; set; }
+
+ public Dictionary<long, Tuple<BreakEvent, bool>> Breakpoints { get; set; }
+
+ public long NextBreakpointId { get; set; }
+
+ public ReadOnlyCollection<Catchpoint> Catchpoints { get; set; }
+ }
+
+ public static void Write(FileInfo file)
+ {
+ var state = new DebuggerState
+ {
+ WorkingDirectory = WorkingDirectory,
+ Arguments = Arguments,
+ EnvironmentVariables = EnvironmentVariables,
+ Watches = Watches,
+ NextWatchId = _nextWatchId,
+ Breakpoints = Breakpoints.Select(x => Tuple.Create(x.Key, x.Value, BreakEvents.Contains(x.Value)))
+ .ToDictionary(x => x.Item1, x => Tuple.Create(x.Item2, x.Item3)),
+ NextBreakpointId = _nextBreakpointId,
+ Catchpoints = BreakEvents.GetCatchpoints()
+ };
+
+ try
+ {
+ using (var stream = file.Open(FileMode.Create, FileAccess.Write))
+ new BinaryFormatter().Serialize(stream, state);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not write database file '{0}':", file);
+ Log.Error(ex.ToString());
+ }
+ }
+
+ public static void Read(FileInfo file)
+ {
+ DebuggerState state;
+
+ try
+ {
+ using (var stream = file.Open(FileMode.Open, FileAccess.Read))
+ state = (DebuggerState)new BinaryFormatter().Deserialize(stream);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not read database file '{0}':", file);
+ Log.Error(ex.ToString());
+
+ return;
+ }
+
+ ResetState();
+
+ WorkingDirectory = state.WorkingDirectory;
+ Arguments = state.Arguments;
+ EnvironmentVariables = state.EnvironmentVariables;
+ Watches = state.Watches;
+
+ foreach (var kvp in state.Breakpoints)
+ {
+ Breakpoints.Add(kvp.Key, kvp.Value.Item1);
+
+ if (kvp.Value.Item2)
+ BreakEvents.Add(kvp.Value.Item1);
+ }
+
+ foreach (var cp in state.Catchpoints)
+ BreakEvents.Add(cp);
+ }
+ }
+}
diff --git a/src/LibEdit.cs b/src/LibEdit.cs
new file mode 100644
index 0000000..1479ffb
--- /dev/null
+++ b/src/LibEdit.cs
@@ -0,0 +1,40 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System.Runtime.InteropServices;
+
+namespace Mono.Debugger.Client
+{
+ static class LibEdit
+ {
+ [DllImport("libedit", EntryPoint = "rl_initialize")]
+ public static extern int Initialize();
+
+ [DllImport("libedit", EntryPoint = "readline")]
+ public static extern string ReadLine(string prompt);
+
+ [DllImport("libedit", EntryPoint = "add_history")]
+ public static extern void AddHistory(string line);
+ }
+}
diff --git a/src/Log.cs b/src/Log.cs
new file mode 100644
index 0000000..6e05fe8
--- /dev/null
+++ b/src/Log.cs
@@ -0,0 +1,105 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Debugger.Client
+{
+ public static class Log
+ {
+ static readonly bool _debug = Environment.GetEnvironmentVariable("SDB_DEBUG") == "enable";
+
+ public static object Lock { get; private set; }
+
+ static Log()
+ {
+ Lock = new object();
+ }
+
+ static void Output(bool nl, string color, string format, object[] args)
+ {
+ var str = color + (args.Length == 0 ? format : string.Format(format, args)) + Color.Reset;
+
+ lock (Lock)
+ {
+ if (nl)
+ Console.WriteLine(str);
+ else
+ Console.Write(str);
+ }
+ }
+
+ public static void InfoSameLine(string format, params object[] args)
+ {
+ Output(false, string.Empty, format, args);
+ }
+
+ public static void Info(string format, params object[] args)
+ {
+ Output(true, string.Empty, format, args);
+ }
+
+ public static void NoticeSameLine(string format, params object[] args)
+ {
+ Output(false, Color.DarkCyan, format, args);
+ }
+
+ public static void Notice(string format, params object[] args)
+ {
+ Output(true, Color.DarkCyan, format, args);
+ }
+
+ public static void EmphasisSameLine(string format, params object[] args)
+ {
+ Output(false, Color.DarkGreen, format, args);
+ }
+
+ public static void Emphasis(string format, params object[] args)
+ {
+ Output(true, Color.DarkGreen, format, args);
+ }
+
+ public static void ErrorSameLine(string format, params object[] args)
+ {
+ Output(false, Color.DarkRed, format, args);
+ }
+
+ public static void Error(string format, params object[] args)
+ {
+ Output(true, Color.DarkRed, format, args);
+ }
+
+ public static void DebugSameLine(string format, params object[] args)
+ {
+ if (_debug || Configuration.Current.DebugLogging)
+ Output(false, Color.DarkYellow, format, args);
+ }
+
+ public static void Debug(string format, params object[] args)
+ {
+ if (_debug || Configuration.Current.DebugLogging)
+ Output(true, Color.DarkYellow, format, args);
+ }
+ }
+}
diff --git a/src/MultiCommand.cs b/src/MultiCommand.cs
new file mode 100644
index 0000000..a04f492
--- /dev/null
+++ b/src/MultiCommand.cs
@@ -0,0 +1,159 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Mono.Debugger.Client
+{
+ public abstract class MultiCommand : Command
+ {
+ readonly Dictionary<Type, Command> _instantiations = new Dictionary<Type, Command>();
+
+ readonly List<Tuple<string, Command>> _allCommands = new List<Tuple<string, Command>>();
+
+ readonly List<Tuple<string, Command>> _commands = new List<Tuple<string, Command>>();
+
+ Command _forwardTarget;
+
+ public IEnumerable<Tuple<string, Command>> Commands
+ {
+ get { return _commands; }
+ }
+
+ public virtual string Parent
+ {
+ get { return null; }
+ }
+
+ public override string Syntax
+ {
+ get
+ {
+ var names = string.Join("|", Names);
+ var subs = string.Join("|", _commands.Select(x => x.Item2).Distinct().Select(x => x.Names[0]));
+
+ return string.Format("{0}{1} {2} ...", Parent != null ? Parent + " " : string.Empty, names, subs);
+ }
+ }
+
+ Command Instantiate(Type type)
+ {
+ Command cmd;
+
+ if (!_instantiations.TryGetValue(type, out cmd))
+ {
+ cmd = (Command)Activator.CreateInstance(type);
+
+ _instantiations.Add(type, cmd);
+ }
+
+ return cmd;
+ }
+
+ protected void AddCommand(Command command)
+ {
+ foreach (var name in command.Names)
+ {
+ var tup = Tuple.Create(name, command);
+
+ _allCommands.Add(tup);
+ _commands.Add(tup);
+ }
+ }
+
+ protected void AddCommand(Type type)
+ {
+ AddCommand(Instantiate(type));
+ }
+
+ protected void AddCommand<T>()
+ where T : Command
+ {
+ AddCommand(typeof(T));
+ }
+
+ protected void AddCommandWithName(Command command, string name)
+ {
+ _allCommands.Add(Tuple.Create(name, command));
+ }
+
+ protected void AddCommandWithName(Type type, string name)
+ {
+ AddCommandWithName(Instantiate(type), name);
+ }
+
+ protected void AddCommandWithName<T>(string name)
+ where T : Command
+ {
+ AddCommandWithName(typeof(T), name);
+ }
+
+ protected void Forward<T>()
+ {
+ _forwardTarget = Instantiate(typeof(T));
+ }
+
+ public Command GetCommand(string name)
+ {
+ foreach (var cmd in _allCommands)
+ if (cmd.Item1.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
+ return cmd.Item2;
+
+ return null;
+ }
+
+ public override sealed void Process(string args)
+ {
+ var name = args.Split(' ').Where(x => x != string.Empty).FirstOrDefault();
+
+ if (name != null)
+ {
+ var cmd = GetCommand(name);
+
+ if (cmd != null)
+ {
+ cmd.Process(new string(args.Skip(name.Length).ToArray()).Trim());
+ return;
+ }
+ }
+
+ ProcessFallback(args, name != null);
+ }
+
+ protected virtual void ProcessFallback(string args, bool invalidSubCommand)
+ {
+ if (invalidSubCommand)
+ Log.Error("Invalid sub-command given to '{0}'", Names[0]);
+ else
+ {
+ if (_forwardTarget != null)
+ _forwardTarget.Process(args);
+ else
+ Log.Error("No '{0}' sub-command specified", Names[0]);
+ }
+ }
+ }
+}
diff --git a/src/Plugins.cs b/src/Plugins.cs
new file mode 100644
index 0000000..d1944b6
--- /dev/null
+++ b/src/Plugins.cs
@@ -0,0 +1,122 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace Mono.Debugger.Client
+{
+ static class Plugins
+ {
+ static string GetFolderPath()
+ {
+ var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+
+ return Path.Combine(home, ".sdb");
+ }
+
+ public static IEnumerable<Command> Load(string file)
+ {
+ Log.Debug("Attempting to load plugin '{0}'", file);
+
+ Assembly asm;
+
+ try
+ {
+ asm = Assembly.LoadFile(file);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not load plugin '{0}':", file);
+ Log.Error(ex.ToString());
+
+ yield break;
+ }
+
+ Type[] types;
+
+ try
+ {
+ types = asm.GetTypes();
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not load types from plugin '{0}':", file);
+ Log.Error(ex.ToString());
+
+ yield break;
+ }
+
+ foreach (var type in types)
+ {
+ Command cmd = null;
+
+ try
+ {
+ if (type.IsDefined(typeof(CommandAttribute), false))
+ cmd = (Command)Activator.CreateInstance(type);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not load command type '{0}':", type);
+ Log.Error(ex.ToString());
+ }
+
+ if (cmd != null)
+ yield return cmd;
+ }
+ }
+
+ public static IEnumerable<Command> LoadDirectory(string path)
+ {
+ try
+ {
+ return Directory.EnumerateFiles(path, "*.dll").Select(x => Load(x)).SelectMany(x => x);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Could not iterate plugin directory '{0}':", path);
+ Log.Error(ex.ToString());
+ }
+
+ return Enumerable.Empty<Command>();
+ }
+
+ public static IEnumerable<Command> LoadDefault()
+ {
+ var env = Environment.GetEnvironmentVariable("SDB_PATH") ?? string.Empty;
+ var paths = new[] { GetFolderPath() }.Concat(env.Split(Path.PathSeparator).Where(x => x != string.Empty));
+ var list = new List<Command>();
+
+ foreach (var p in paths)
+ if (Directory.Exists(p))
+ list.AddRange(LoadDirectory(p));
+
+ return list;
+ }
+ }
+}
diff --git a/src/Program.cs b/src/Program.cs
new file mode 100644
index 0000000..dfe9a71
--- /dev/null
+++ b/src/Program.cs
@@ -0,0 +1,110 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using Mono.Options;
+using Mono.Unix.Native;
+
+namespace Mono.Debugger.Client
+{
+ static class Program
+ {
+ static void Help(OptionSet set)
+ {
+ Console.WriteLine("This is the Mono soft debugger.");
+ Console.WriteLine();
+ Console.WriteLine("Usage:");
+ Console.WriteLine();
+ Console.WriteLine(" sdb [options]");
+ Console.WriteLine(" sdb [options] \"run prog.exe\"");
+ Console.WriteLine(" sdb [options] \"args --foo --bar baz\" \"run prog.exe\"");
+ Console.WriteLine();
+ Console.WriteLine("Options:");
+ Console.WriteLine();
+
+ set.WriteOptionDescriptions(Console.Out);
+
+ Console.WriteLine();
+ Console.WriteLine("All non-option arguments are treated as commands that are executed");
+ Console.WriteLine("at startup. Files are executed before individual commands.");
+ }
+
+ static int Main(string[] args)
+ {
+ var version = false;
+ var help = false;
+ var batch = false;
+ var rc = true;
+
+ var files = new List<string>();
+
+ var p = new OptionSet()
+ {
+ {"v|version", "Show version information and exit.", v => version = v != null},
+ {"h|help", "Show this help message and exit.", v => help = v != null},
+ {"b|batch", "Exit after running commands.", v => batch = v != null},
+ {"n|norc", "Don't run commands in '~/.sdb.rc'.", v => rc = v == null},
+ {"f|file=", "Execute commands in the given file at startup.", f => files.Add(f)}
+ };
+
+ List<string> cmds;
+
+ try
+ {
+ cmds = p.Parse(args);
+ }
+ catch (OptionException ex)
+ {
+ Console.WriteLine(ex.Message);
+ return 1;
+ }
+
+ var ver = typeof(Program).Assembly.GetName().Version;
+
+ if (version)
+ {
+ Console.WriteLine("Mono soft debugger (sdb) {0}", ver);
+ return 0;
+ }
+
+ if (help)
+ {
+ Help(p);
+ return 0;
+ }
+
+ // This ugly hack is necessary because we don't want the
+ // debuggee to die if the user hits Ctrl-C. We set our signal
+ // disposition to `SIG_IGN` so that the debuggee inherits
+ // this and doesn't die.
+ if (!Utilities.IsWindows && !batch)
+ Stdlib.SetSignalAction(Signum.SIGINT, SignalAction.Ignore);
+
+ CommandLine.Run(ver, batch, rc, cmds, files);
+
+ return 0;
+ }
+ }
+}
diff --git a/src/SessionKind.cs b/src/SessionKind.cs
new file mode 100644
index 0000000..ae8b648
--- /dev/null
+++ b/src/SessionKind.cs
@@ -0,0 +1,34 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+namespace Mono.Debugger.Client
+{
+ public enum SessionKind : byte
+ {
+ Disconnected,
+ Launched,
+ Listening,
+ Connected,
+ }
+}
diff --git a/src/State.cs b/src/State.cs
new file mode 100644
index 0000000..08fc405
--- /dev/null
+++ b/src/State.cs
@@ -0,0 +1,33 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+namespace Mono.Debugger.Client
+{
+ public enum State : byte
+ {
+ Running,
+ Suspended,
+ Exited
+ }
+}
diff --git a/src/Utilities.cs b/src/Utilities.cs
new file mode 100644
index 0000000..85179c9
--- /dev/null
+++ b/src/Utilities.cs
@@ -0,0 +1,131 @@
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2013 Alex Rønne Petersen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using Mono.Debugging.Client;
+
+namespace Mono.Debugger.Client
+{
+ public static class Utilities
+ {
+ public static bool IsWindows
+ {
+ get { return (int)Environment.OSVersion.Platform < (int)PlatformID.Unix; }
+ }
+
+ public static void Discard<T>(this T value)
+ {
+ }
+
+ public static string StringizeFrame(StackFrame frame, bool includeIndex)
+ {
+ var loc = string.Empty;
+ string src = null;
+
+ if (frame.SourceLocation.FileName != null)
+ {
+ loc = " at " + frame.SourceLocation.FileName;
+
+ if (frame.SourceLocation.Line != -1)
+ {
+ loc += ":" + frame.SourceLocation.Line;
+
+ StreamReader reader = null;
+
+ try
+ {
+ reader = File.OpenText(frame.SourceLocation.FileName);
+
+ var cur = 1;
+
+ while (!reader.EndOfStream)
+ {
+ var str = reader.ReadLine();
+
+ if (cur == frame.SourceLocation.Line)
+ {
+ src = str;
+ break;
+ }
+
+ cur++;
+ }
+ }
+ catch (Exception)
+ {
+ }
+ finally
+ {
+ if (reader != null)
+ reader.Dispose();
+ }
+ }
+ }
+
+ var idx = includeIndex ? string.Format("#{0} ", frame.Index) : string.Empty;
+ var srcStr = src != null ? Environment.NewLine + src : string.Empty;
+
+ return string.Format("{0}[0x{1:X8}] {2}{3}{4}", idx, frame.Address,
+ frame.SourceLocation.MethodName, loc, srcStr);
+ }
+
+ public static string StringizeThread(ThreadInfo thread, bool includeFrame)
+ {
+ var f = includeFrame ? thread.Backtrace.GetFrame(0) : null;
+
+ var fstr = f == null ? string.Empty : Environment.NewLine + StringizeFrame(f, false);
+ var tstr = string.Format("Thread #{0} '{1}'", thread.Id, thread.Name);
+
+ return string.Format("{0}{1}", tstr, fstr);
+ }
+
+ public static Tuple<string, bool> StringizeValue(ObjectValue value)
+ {
+ string str;
+ bool err;
+
+ if (value.IsError)
+ {
+ str = value.DisplayValue;
+ err = true;
+ }
+ else if (value.IsUnknown)
+ {
+ str = "Result is unrepresentable";
+ err = true;
+ }
+ else
+ {
+ str = value.DisplayValue;
+ err = false;
+ }
+
+ if (Configuration.Current.DebugLogging)
+ str += string.Format(" ({0})", value.Flags);
+
+ return Tuple.Create(str, err);
+ }
+ }
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mono/packages/sdb.git
More information about the Pkg-mono-svn-commits
mailing list