├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── examples
├── angularview
│ ├── angularview.py
│ └── static
│ │ ├── css
│ │ ├── solarized.css
│ │ └── style.css
│ │ ├── index.html
│ │ └── js
│ │ ├── app.js
│ │ ├── controllers.js
│ │ └── services.js
├── client.py
├── command.py
└── view.py
├── install.sh
├── setup.cfg
├── setup.py
├── tests
├── Vagrantfile
├── __init__.py
├── api_message_tests.py
├── common.py
├── frontend_tests.py
├── gdb_cli_tests.py
├── http_api_tests.py
├── inferior.c
├── lldb_api_tests.py
├── lldb_cli_tests.py
├── test.c
└── testinit.lldb
└── voltron
├── __init__.py
├── __main__.py
├── api.py
├── colour.py
├── config
└── default.cfg
├── core.py
├── dbg.py
├── entry.py
├── lexers.py
├── main.py
├── plugin.py
├── plugins
├── __init__.py
├── api
│ ├── __init__.py
│ ├── backtrace.py
│ ├── breakpoints.py
│ ├── command.py
│ ├── dereference.py
│ ├── disassemble.py
│ ├── memory.py
│ ├── null.py
│ ├── plugins.py
│ ├── registers.py
│ ├── stack.py
│ ├── state.py
│ ├── targets.py
│ ├── version.py
│ └── write_memory.py
├── debugger
│ ├── __init__.py
│ ├── dbg_gdb.py
│ ├── dbg_lldb.py
│ ├── dbg_mock.py
│ ├── dbg_vdb.py
│ └── dbg_windbg.py
└── view
│ ├── __init__.py
│ ├── backtrace.py
│ ├── breakpoints.py
│ ├── command.py
│ ├── disasm.py
│ ├── memory.py
│ └── register.py
├── rdb.py
├── repl.py
├── styles.py
└── view.py
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Python ###
2 | *.py[cod]
3 |
4 | # C extensions
5 | *.so
6 |
7 | # Packages
8 | *.egg
9 | *.egg-info
10 | dist
11 | build
12 | eggs
13 | parts
14 | bin
15 | var
16 | sdist
17 | develop-eggs
18 | .installed.cfg
19 | lib
20 | lib64
21 | __pycache__
22 |
23 | # Installer logs
24 | pip-log.txt
25 |
26 | # Unit test / coverage reports
27 | .coverage
28 | .tox
29 | nosetests.xml
30 |
31 | # Translations
32 | *.mo
33 |
34 | # Mr Developer
35 | .mr.developer.cfg
36 | .project
37 | .pydevproject
38 |
39 | # Rope
40 | .ropeproject
41 |
42 | .DS_Store
43 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | matrix:
4 | include:
5 | - os: linux
6 | sudo: required
7 | dist: trusty
8 | python: "3.3"
9 | # - os: linux
10 | # sudo: required
11 | # dist: trusty
12 | # python: "3.4"
13 | # - os: linux
14 | # sudo: required
15 | # dist: trusty
16 | # python: "3.5"
17 | # - os: osx
18 | # osx_image: xcode7.2
19 | # language: generic
20 |
21 | addons:
22 | apt:
23 | packages:
24 | - build-essential
25 | - gdb
26 |
27 | before_install:
28 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; brew install python ; fi
29 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq ; sudo apt-get install lldb-3.4 ; fi
30 |
31 | install:
32 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pip install mock pexpect nose ; fi
33 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install mock pexpect nose --user; fi
34 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pip install . ; fi
35 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install . --user; fi
36 |
37 | script:
38 | - python --version
39 | - mkdir ~/.voltron
40 | - echo '{"general":{"debug_logging":true}}' >~/.voltron/config
41 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then python -m nose -sv tests/gdb_cli_tests.py ; fi
42 | - python -m nose -sv tests/lldb_cli_tests.py
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 snare
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Voltron
2 | =======
3 |
4 | [](https://travis-ci.org/snare/voltron/)
5 |
6 | Voltron is an extensible debugger UI toolkit written in Python. It aims to improve the user experience of various debuggers (LLDB, GDB, VDB and WinDbg) by enabling the attachment of utility views that can retrieve and display data from the debugger host. By running these views in other TTYs, you can build a customised debugger user interface to suit your needs.
7 |
8 | Voltron does not aim to be everything to everyone. It's not a wholesale replacement for your debugger's CLI. Rather, it aims to complement your existing setup and allow you to extend your CLI debugger as much or as little as you like. If you just want a view of the register contents in a window alongside your debugger, you can do that. If you want to go all out and have something that looks more like OllyDbg, you can do that too.
9 |
10 | Built-in views are provided for:
11 |
12 | - Registers
13 | - Disassembly
14 | - Stack
15 | - Memory
16 | - Breakpoints
17 | - Backtrace
18 |
19 | The author's setup looks something like this:
20 |
21 | 
22 |
23 | Any debugger command can be split off into a view and highlighted with a specified Pygments lexer:
24 |
25 | 
26 |
27 | More screenshots are [here](https://github.com/snare/voltron/wiki/Screenshots).
28 |
29 | Support
30 | -------
31 |
32 | Voltron supports LLDB, GDB, VDB and WinDbg/CDB (via [PyKD](https://pykd.codeplex.com/)) and runs on macOS, Linux and Windows.
33 |
34 | WinDbg support is still fairly new, please [open an issue](https://github.com/snare/voltron/issues) if you have problems.
35 |
36 | The following architectures are supported:
37 |
38 | | | lldb | gdb | vdb | windbg |
39 | |---------|------|-----|-----|--------|
40 | | x86 | ✓ | ✓ | ✓ | ✓ |
41 | | x86_64 | ✓ | ✓ | ✓ | ✓ |
42 | | arm | ✓ | ✓ | ✓ | ✗ |
43 | | arm64 | ✓ | ✗ | ✗ | ✗ |
44 | | powerpc | ✗ | ✓ | ✗ | ✗ |
45 |
46 | Installation
47 | ------------
48 |
49 | **Note:** Only macOS and Debian derivatives are fully supported by the install script. It should hopefully not fail on other Linux distros, but it won't try to install package dependencies. If you're using another distro, have a look at `install.sh` to work out what dependencies you might need to install before running it.
50 |
51 | Download the source and run the install script:
52 |
53 | $ git clone https://github.com/snare/voltron
54 | $ cd voltron
55 | $ ./install.sh
56 |
57 | By default, the install script will install into the user's `site-packages` directory. If you want to install into the system `site-packages`, use the `-s` flag:
58 |
59 | $ ./install.sh -s
60 |
61 | You can also install into a virtual environment (for LLDB only) like this:
62 |
63 | $ ./install.sh -v /path/to/venv -b lldb
64 |
65 | If you are on Windows without a shell, have problems installing, or would prefer to install manually, please see the [manual installation documentation](https://github.com/snare/voltron/wiki/Installation).
66 |
67 | Quick Start
68 | -----------
69 |
70 | 1. If your debugger has an init script (`.lldbinit` for LLDB or `.gdbinit` for GDB) configure it to load Voltron when it starts by sourcing the `entry.py` entry point script. The full path will be inside the `voltron` package. For example, on macOS it might be */Library/Python/2.7/site-packages/voltron/entry.py*. The `install.sh` script will add this to your `.gdbinit` or `.lldbinit` file automatically if it detects GDB or LLDB in your path.
71 |
72 | LLDB:
73 |
74 | command script import /path/to/voltron/entry.py
75 |
76 | GDB:
77 |
78 | source /path/to/voltron/entry.py
79 |
80 | 2. Start your debugger and initialise Voltron manually if necessary.
81 |
82 | On recent versions of LLDB you do not need to initialise Voltron manually:
83 |
84 | $ lldb target_binary
85 |
86 | On older versions of LLDB you need to call `voltron init` after you load the inferior:
87 |
88 | $ lldb target_binary
89 | (lldb) voltron init
90 |
91 | GDB:
92 |
93 | $ gdb target_binary
94 |
95 | VDB:
96 |
97 | $ ./vdbbin target_binary
98 | > script /path/to/voltron/entry.py
99 |
100 | WinDbg/CDB is only supported run via Bash with a Linux userland. The author tests with [Git Bash](https://git-for-windows.github.io) and [ConEmu](http://conemu.github.io). PyKD and Voltron can be loaded in one command when launching the debugger:
101 |
102 | $ cdb -c '.load C:\path\to\pykd.pyd ; !py --global C:\path\to\voltron\entry.py' target_binary
103 |
104 | 3. In another terminal (I use iTerm panes) start one of the UI views. On LLDB, WinDbg and GDB the views will update immediately. On VDB they will not update until the inferior stops (at a breakpoint, after a step, etc):
105 |
106 | $ voltron view register
107 | $ voltron view stack
108 | $ voltron view disasm
109 | $ voltron view backtrace
110 |
111 | 4. Set a breakpoint and run your inferior.
112 |
113 | (*db) b main
114 | (*db) run
115 |
116 | 5. When the debugger hits the breakpoint, the views will be updated to reflect the current state of registers, stack, memory, etc. Views are updated after each command is executed in the debugger CLI, using the debugger's "stop hook" mechanism. So each time you step, or continue and hit a breakpoint, the views will update.
117 |
118 | Documentation
119 | -------------
120 |
121 | See the [wiki](https://github.com/snare/voltron/wiki) on github.
122 |
123 | FAQ
124 | ---
125 |
126 | **Q.** Why am I getting an `ImportError` loading Voltron?
127 |
128 | **A.** You might have multiple versions of Python installed and have installed Voltron using the wrong one. See the more detailed [installation instructions](https://github.com/snare/voltron/wiki/Installation).
129 |
130 | **Q.** [GEF](https://github.com/hugsy/gef)? [PEDA](https://github.com/longld/peda)? [PwnDbg](https://github.com/pwndbg/pwndbg)? [fG's gdbinit](https://github.com/gdbinit/gdbinit)?
131 |
132 | **A.** All super great extensions for GDB. These tools primarily provide sets of additional commands for exploitation tasks, but each also provides a "context" display with a view of registers, stack, code, etc, like Voltron. These tools print their context display in the debugger console each time the debugger stops. Voltron takes a different approach by embedding an RPC server implant in the debugger and enabling the attachment of views from other terminals (or even web browsers, or now [synchronising with Binary Ninja](https://github.com/snare/binja)), which allows the user to build a cleaner multi-window interface to their debugger. Voltron works great alongside all of these tools. You can just disable the context display in your GDB extension of choice and hook up some Voltron views, while still getting all the benefits of the useful commands added by these tools.
133 |
134 | Bugs and Errata
135 | ---------------
136 |
137 | See the [issue tracker](https://github.com/snare/voltron/issues) on github for more information or to submit issues.
138 |
139 | If you're experiencing an `ImportError` loading Voltron, please ensure you've followed the [installation instructions](https://github.com/snare/voltron/wiki/Installation) for your platform.
140 |
141 | ### LLDB
142 |
143 | On older versions of LLDB, the `voltron init` command must be run manually after loading the debug target, as a target must be loaded before Voltron's hooks can be installed. Voltron will attempt to automatically register its event handler, and it will inform the user if `voltron init` is required.
144 |
145 | ### WinDbg
146 |
147 | More information about WinDbg/CDB support [here](https://github.com/snare/voltron/wiki/Installation#windbg).
148 |
149 | ### Misc
150 |
151 | The authors primarily use Voltron with the most recent version of LLDB on macOS. We will try to test everything on as many platforms and architectures as possible before releases, but LLDB/macOS/x64 is going to be by far the most frequently-used combination. Hopefully Voltron doesn't set your pets on fire, but YMMV.
152 |
153 | License
154 | -------
155 |
156 | See the [LICENSE](https://github.com/snare/voltron/blob/master/LICENSE) file.
157 |
158 | If you use this and don't hate it, buy me a beer at a conference some time. This license also extends to other contributors - [richo](http://github.com/richo) definitely deserves a few beers for his contributions.
159 |
160 | Credits
161 | -------
162 |
163 | Thanks to my former employers Assurance and Azimuth Security for giving me time to spend working on this.
164 |
165 | Props to [richo](http://github.com/richo) for all his contributions to Voltron.
166 |
167 | [fG!](http://github.com/gdbinit)'s gdbinit was the original inspiration for this project.
168 |
169 | Thanks to [Willi](http://github.com/williballenthin) for implementing the VDB support.
170 |
171 | Voltron now uses [Capstone](http://www.capstone-engine.org) for disassembly as well as the debugger hosts' internal disassembly mechanism. [Capstone](http://www.capstone-engine.org) is a powerful, open source, multi-architecture disassembler upon which the next generation of reverse engineering and debugging tools are being built. Check it out.
172 |
173 | Thanks to [grazfather](http://github.com/grazfather) for ongoing contributions.
174 |
--------------------------------------------------------------------------------
/examples/angularview/angularview.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import pygments
3 | from voltron.plugin import *
4 | from voltron.lexers import *
5 | from voltron.api import *
6 |
7 | from flask import *
8 |
9 | log = logging.getLogger('api')
10 |
11 |
12 | class AngularViewPlugin(WebPlugin):
13 | name = 'angularview'
14 |
15 |
16 | class FormatDisassemblyRequest(APIRequest):
17 | _fields = {'disassembly': True}
18 |
19 | def dispatch(self):
20 | try:
21 | res = FormatDisassemblyResponse(
22 | disassembly=pygments.highlight(self.disassembly.strip(), LLDBIntelLexer(), pygments.formatters.HtmlFormatter()))
23 | except Exception as e:
24 | msg = "Exception formatting disassembly: {}".format(repr(e))
25 | log.exception(msg)
26 | res = APIGenericErrorResponse(msg)
27 |
28 | return res
29 |
30 |
31 | class FormatDisassemblyResponse(APIResponse):
32 | _fields = {'disassembly': True}
33 |
34 |
35 | class FormatDisassemblyPlugin(APIPlugin):
36 | request = "format_disasm"
37 | request_class = FormatDisassemblyRequest
38 | response_class = FormatDisassemblyResponse
39 |
--------------------------------------------------------------------------------
/examples/angularview/static/css/solarized.css:
--------------------------------------------------------------------------------
1 | /* Solarized Dark
2 |
3 | For use with Jekyll and Pygments
4 |
5 | http://ethanschoonover.com/solarized
6 |
7 | SOLARIZED HEX ROLE
8 | --------- -------- ------------------------------------------
9 | base03 #002b36 background
10 | base01 #586e75 comments / secondary content
11 | base1 #93a1a1 body text / default code / primary content
12 | orange #cb4b16 constants
13 | red #dc322f regex, special keywords
14 | blue #268bd2 reserved keywords
15 | cyan #2aa198 strings, numbers
16 | green #859900 operators, other keywords
17 | */
18 |
19 | /*.highlight { background-color: #002b36; color: #93a1a1 }*/
20 | .highlight { background-color: #073642; color: #93a1a1 }
21 | .highlight .c { color: #586e75 } /* Comment */
22 | .highlight .err { color: #93a1a1 } /* Error */
23 | .highlight .g { color: #93a1a1 } /* Generic */
24 | .highlight .k { color: #859900 } /* Keyword */
25 | .highlight .l { color: #93a1a1 } /* Literal */
26 | .highlight .n { color: #93a1a1 } /* Name */
27 | .highlight .o { color: #859900 } /* Operator */
28 | .highlight .x { color: #cb4b16 } /* Other */
29 | .highlight .p { color: #93a1a1 } /* Punctuation */
30 | .highlight .cm { color: #586e75 } /* Comment.Multiline */
31 | .highlight .cp { color: #859900 } /* Comment.Preproc */
32 | .highlight .c1 { color: #586e75 } /* Comment.Single */
33 | .highlight .cs { color: #859900 } /* Comment.Special */
34 | .highlight .gd { color: #2aa198 } /* Generic.Deleted */
35 | .highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */
36 | .highlight .gr { color: #dc322f } /* Generic.Error */
37 | .highlight .gh { color: #cb4b16 } /* Generic.Heading */
38 | .highlight .gi { color: #859900 } /* Generic.Inserted */
39 | .highlight .go { color: #93a1a1 } /* Generic.Output */
40 | .highlight .gp { color: #93a1a1 } /* Generic.Prompt */
41 | .highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */
42 | .highlight .gu { color: #cb4b16 } /* Generic.Subheading */
43 | .highlight .gt { color: #93a1a1 } /* Generic.Traceback */
44 | .highlight .kc { color: #cb4b16 } /* Keyword.Constant */
45 | .highlight .kd { color: #268bd2 } /* Keyword.Declaration */
46 | .highlight .kn { color: #859900 } /* Keyword.Namespace */
47 | .highlight .kp { color: #859900 } /* Keyword.Pseudo */
48 | .highlight .kr { color: #268bd2 } /* Keyword.Reserved */
49 | .highlight .kt { color: #dc322f } /* Keyword.Type */
50 | .highlight .ld { color: #93a1a1 } /* Literal.Date */
51 | .highlight .m { color: #2aa198 } /* Literal.Number */
52 | .highlight .s { color: #2aa198 } /* Literal.String */
53 | .highlight .na { color: #93a1a1 } /* Name.Attribute */
54 | .highlight .nb { color: #B58900 } /* Name.Builtin */
55 | .highlight .nc { color: #268bd2 } /* Name.Class */
56 | .highlight .no { color: #cb4b16 } /* Name.Constant */
57 | .highlight .nd { color: #268bd2 } /* Name.Decorator */
58 | .highlight .ni { color: #cb4b16 } /* Name.Entity */
59 | .highlight .ne { color: #cb4b16 } /* Name.Exception */
60 | .highlight .nf { color: #268bd2 } /* Name.Function */
61 | .highlight .nl { color: #93a1a1 } /* Name.Label */
62 | .highlight .nn { color: #93a1a1 } /* Name.Namespace */
63 | .highlight .nx { color: #93a1a1 } /* Name.Other */
64 | .highlight .py { color: #93a1a1 } /* Name.Property */
65 | .highlight .nt { color: #268bd2 } /* Name.Tag */
66 | .highlight .nv { color: #268bd2 } /* Name.Variable */
67 | .highlight .ow { color: #859900 } /* Operator.Word */
68 | .highlight .w { color: #93a1a1 } /* Text.Whitespace */
69 | .highlight .mf { color: #2aa198 } /* Literal.Number.Float */
70 | .highlight .mh { color: #2aa198 } /* Literal.Number.Hex */
71 | .highlight .mi { color: #2aa198 } /* Literal.Number.Integer */
72 | .highlight .mo { color: #2aa198 } /* Literal.Number.Oct */
73 | .highlight .sb { color: #586e75 } /* Literal.String.Backtick */
74 | .highlight .sc { color: #2aa198 } /* Literal.String.Char */
75 | .highlight .sd { color: #93a1a1 } /* Literal.String.Doc */
76 | .highlight .s2 { color: #2aa198 } /* Literal.String.Double */
77 | .highlight .se { color: #cb4b16 } /* Literal.String.Escape */
78 | .highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */
79 | .highlight .si { color: #2aa198 } /* Literal.String.Interpol */
80 | .highlight .sx { color: #2aa198 } /* Literal.String.Other */
81 | .highlight .sr { color: #dc322f } /* Literal.String.Regex */
82 | .highlight .s1 { color: #2aa198 } /* Literal.String.Single */
83 | .highlight .ss { color: #2aa198 } /* Literal.String.Symbol */
84 | .highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
85 | .highlight .vc { color: #268bd2 } /* Name.Variable.Class */
86 | .highlight .vg { color: #268bd2 } /* Name.Variable.Global */
87 | .highlight .vi { color: #268bd2 } /* Name.Variable.Instance */
88 | .highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */
89 |
--------------------------------------------------------------------------------
/examples/angularview/static/css/style.css:
--------------------------------------------------------------------------------
1 | .reg_label {
2 | color: #b58900;
3 | font-weight: bold;
4 | }
5 |
6 | .reg_highlight {
7 | color: #dc322f;
8 | }
9 |
10 | .reg_normal {
11 | }
12 |
13 | #tile {
14 | display:inline-block;
15 | vertical-align: top;
16 | background-color: #073642;
17 | padding-left: 0.5em;
18 | padding-right: 0.5em;
19 | padding-top: 0.3em;
20 | padding-bottom: 0.3em;
21 | }
22 |
23 | pre {
24 | margin: 0;
25 | }
26 |
27 | #disasm {
28 | background-color: #073642;
29 | }
--------------------------------------------------------------------------------
/examples/angularview/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | voltron
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
registers
15 |
16 | {{reg.name}}
17 | {{reg.value}}
18 |
19 |
20 |
21 |
disassembly
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/examples/angularview/static/js/app.js:
--------------------------------------------------------------------------------
1 | angular.module('VoltronApp', [
2 | 'VoltronApp.controllers',
3 | 'VoltronApp.services'
4 | ])
5 | .config(function($sceProvider) {
6 | $sceProvider.enabled(false);
7 | });
8 |
--------------------------------------------------------------------------------
/examples/angularview/static/js/controllers.js:
--------------------------------------------------------------------------------
1 | function f_hex(number, pad)
2 | {
3 | return ("000000000000000000000000000000" + number.toString(16)).substr(-pad).toUpperCase();
4 | }
5 |
6 | angular.module('VoltronApp.controllers', []).
7 | controller('voltronController', function($scope, voltronAPIservice)
8 | {
9 | $scope.version = null;
10 | $scope.registers = [];
11 | $scope.disassembly = null;
12 | var state = null;
13 | var new_regs = null;
14 | var old_regs = [];
15 |
16 | voltronAPIservice.version().success(function (response) {
17 | $scope.version = response.data;
18 | });
19 |
20 | var format_registers = function(new_regs, old_regs, arch) {
21 | fmt = []
22 | if (arch == 'x86_64') {
23 | regs = ['rip','rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','r8','r9','r10','r11','r12','r13','r14','r15']
24 | for (i = 0; i < regs.length; i++) {
25 | fmt.push({
26 | name: (regs[i].length == 2 ? String.fromCharCode(160) + regs[i] : regs[i]),
27 | value: f_hex(new_regs[regs[i]], 16),
28 | class: (new_regs[regs[i]] != old_regs[regs[i]] ? "reg_highlight" : "reg_normal")
29 | })
30 | }
31 | }
32 | return fmt
33 | }
34 |
35 | var format_disasm = function(disasm) {
36 | lines = disasm.split();
37 |
38 | // trim lldb's "inferior`main:" so hljs works
39 | if (lines[0].indexOf("`") > -1) {
40 | lines.splice(0, 1);
41 | }
42 |
43 | return lines.join('\n');
44 | }
45 |
46 | var update = function() {
47 | // get target info
48 | voltronAPIservice.targets().success(function (response) {
49 | targets = response.data.targets;
50 |
51 | // make sure we have a target
52 | if (targets[0]['arch'] != null) {
53 | // update registers
54 | voltronAPIservice.registers().success(function (response) {
55 | // get new register values
56 | new_regs = response.data.registers
57 |
58 | // format registers
59 | $scope.registers = format_registers(new_regs, old_regs, targets[0]['arch']);
60 |
61 | // keep old registers
62 | old_regs = new_regs;
63 | });
64 |
65 | // update disassembly
66 | voltronAPIservice.disassemble(null, 32).success(function (response) {
67 | $scope.disassembly = response.data.formatted;
68 | });
69 | }
70 | });
71 | }
72 |
73 | var poll = function() {
74 | // wait for the next state change
75 | response = voltronAPIservice.wait(30).success(function (response) {
76 | if (response.status == "success") {
77 | update();
78 | }
79 |
80 | // wait for the next state change
81 | poll();
82 | });
83 | };
84 |
85 | // initial update
86 | update();
87 |
88 | // start waiting for debugger stops
89 | poll();
90 | });
--------------------------------------------------------------------------------
/examples/angularview/static/js/services.js:
--------------------------------------------------------------------------------
1 | angular.module('VoltronApp.services', []).
2 | factory('voltronAPIservice', function($http)
3 | {
4 | var voltronAPI = {};
5 |
6 | function createRequest(requestType, data) {
7 | return {type: "request", request: requestType, data: data}
8 | }
9 |
10 | voltronAPI.request = function(request) {
11 | return $http({
12 | method: 'POST',
13 | url: '/api/request',
14 | data: request
15 | });
16 | }
17 |
18 | voltronAPI.disassemble_format = function(data) {
19 | res = voltronAPI.request(createRequest('disassemble', {address: address, count: count}))
20 | return $http({
21 | method: 'POST',
22 | url: '/api/request',
23 | data: createRequest('format_disasm', {disassembly: res.data.disassembly})
24 | });
25 | }
26 |
27 | voltronAPI.disassemble = function(address, count) {
28 | return voltronAPI.request(createRequest('disassemble', {address: address, count: count}))
29 | }
30 |
31 | voltronAPI.command = function(command) {
32 | return voltronAPI.request(createRequest('command', {command: command}))
33 | }
34 |
35 | voltronAPI.targets = function() {
36 | return voltronAPI.request(createRequest('targets', {}))
37 | }
38 |
39 | voltronAPI.memory = function(address, length) {
40 | return voltronAPI.request(createRequest('memory', {address: address, length: length}))
41 | }
42 |
43 | voltronAPI.registers = function() {
44 | return voltronAPI.request(createRequest('registers', {}))
45 | }
46 |
47 | voltronAPI.stack = function(length) {
48 | return voltronAPI.request(createRequest('stack', {length: length}))
49 | }
50 |
51 | voltronAPI.state = function() {
52 | return voltronAPI.request(createRequest('state', {}))
53 | }
54 |
55 | voltronAPI.version = function() {
56 | return voltronAPI.request(createRequest('version', {}))
57 | }
58 |
59 | return voltronAPI;
60 | });
61 |
--------------------------------------------------------------------------------
/examples/client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Example Voltron client.
4 |
5 | Start your debugger as follows:
6 |
7 | $ lldb /tmp/inferior
8 | Voltron loaded.
9 | Run `voltron init` after you load a target.
10 | (lldb) target create "/tmp/inferior"
11 | Current executable set to '/tmp/inferior' (x86_64).
12 | (lldb) voltron init
13 | Registered stop-hook
14 | (lldb) b main
15 | Breakpoint 1: where = inferior`main, address = 0x0000000100000cf0
16 | (lldb) run
17 | Process 13185 launched: '/Volumes/Data/Users/snare/code/voltron/repo/tests/inferior' (x86_64)
18 | Process 13185 stopped
19 | * thread #1: tid = 0x1ee63, 0x0000000100000cf0 inferior`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
20 | frame #0: 0x0000000100000cf0 inferior`main
21 | inferior`main:
22 | -> 0x100000cf0: push rbp
23 | 0x100000cf1: mov rbp, rsp
24 | 0x100000cf4: sub rsp, 0x50
25 | 0x100000cf8: mov dword ptr [rbp - 0x4], 0x0
26 |
27 | Run this client in another terminal. Each time you `stepi` in the debugger,
28 | the client will output the current RIP:
29 |
30 | $ python client.py
31 | Instruction pointer is: 0x100000CFF
32 | Instruction pointer is: 0x100000D02
33 | Instruction pointer is: 0x100000D06
34 | Instruction pointer is: 0x100000D0D
35 | Instruction pointer is: 0x100000D15
36 | Instruction pointer is: 0x100000D1C
37 | """
38 |
39 | import voltron
40 | from voltron.core import Client
41 |
42 |
43 | def main():
44 | # Create a client and connect to the server
45 | client = Client()
46 |
47 | # Main event loop
48 | while True:
49 | # Wait for the debugger to stop again
50 | res = client.perform_request('version', block=True)
51 | if res.is_success:
52 | # If nothing went wrong, get the instruction pointer and print it
53 | res = client.perform_request('registers', registers=['rip'])
54 | if res.is_success:
55 | print("Instruction pointer is: 0x{:X}".format(res.registers['rip']))
56 | else:
57 | print("Failed to get registers: {}".format(res))
58 | else:
59 | print("Error waiting for the debugger to stop: {}".format(res))
60 | break
61 |
62 |
63 | if __name__ == "__main__":
64 | main()
65 |
--------------------------------------------------------------------------------
/examples/command.py:
--------------------------------------------------------------------------------
1 | """
2 | Example command plugin
3 |
4 | Copy this to your ~/.voltron/plugins directory. It will be loaded when
5 | Voltron is initialised, and register a debugger command that works as follows:
6 |
7 | $ lldb /tmp/inferior
8 | Voltron loaded.
9 | Run `voltron init` after you load a target.
10 | (lldb) target create "/tmp/inferior"
11 | Current executable set to '/tmp/inferior' (x86_64).
12 | (lldb) b main
13 | Breakpoint 1: where = inferior`main, address = 0x0000000100000cf0
14 | (lldb) run
15 | Process 12561 launched: '/tmp/inferior' (x86_64)
16 | Process 12561 stopped
17 | * thread #1: tid = 0x1d33f, 0x0000000100000cf0 inferior`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
18 | frame #0: 0x0000000100000cf0 inferior`main
19 | inferior`main:
20 | -> 0x100000cf0: push rbp
21 | 0x100000cf1: mov rbp, rsp
22 | 0x100000cf4: sub rsp, 0x50
23 | 0x100000cf8: mov dword ptr [rbp - 0x4], 0x0
24 | (lldb) example
25 | rax 0000000100000CF0
26 | rbx 0000000000000000
27 | rcx 00007FFF5FBFFA70
28 | rdx 00007FFF5FBFF978
29 | rbp 00007FFF5FBFF958
30 | rsp 00007FFF5FBFF948
31 | rdi 0000000000000001
32 | rsi 00007FFF5FBFF968
33 | rip 0000000100000CF0
34 | r8 0000000000000000
35 | r9 00007FFF5FBFEA08
36 | r10 0000000000000032
37 | r11 0000000000000246
38 | r12 0000000000000000
39 | r13 0000000000000000
40 | r14 0000000000000000
41 | r15 0000000000000000
42 |
43 | Provided you stick to the adaptor API that is implemented in every *DBAdaptor
44 | class, custom command plugins should work across all debugger hosts.
45 |
46 | This is a quick example that will only work on an x86_64 target.
47 | """
48 |
49 | import blessed
50 | import voltron
51 | from voltron.plugin import CommandPlugin
52 | from voltron.command import VoltronCommand
53 |
54 |
55 | class ExampleCommand(VoltronCommand):
56 | def invoke(self, *args):
57 | regs = voltron.debugger.registers()
58 | reg_list = ['rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','rip',
59 | 'r8','r9','r10','r11','r12','r13','r14','r15']
60 | for name in reg_list:
61 | print("{t.bold}{:3} {t.normal}{:0=16X}".format(name, regs[name], t=blessed.Terminal()))
62 |
63 |
64 | class ExampleCommandPlugin(CommandPlugin):
65 | name = 'example'
66 | command_class = ExampleCommand
--------------------------------------------------------------------------------
/examples/view.py:
--------------------------------------------------------------------------------
1 | """
2 | Example Voltron view.
3 |
4 | Copy this to your ~/.voltron/plugins directory. When the `voltron view` command
5 | is executed, 'example' should be visible in the list of valid view names.
6 |
7 | Start your debugger as follows:
8 |
9 | $ lldb /tmp/inferior
10 | Voltron loaded.
11 | Run `voltron init` after you load a target.
12 | (lldb) target create "/tmp/inferior"
13 | Current executable set to '/tmp/inferior' (x86_64).
14 | (lldb) voltron init
15 | Registered stop-hook
16 | (lldb) b main
17 | Breakpoint 1: where = inferior`main, address = 0x0000000100000cf0
18 | (lldb) run
19 | Process 13185 launched: '/Volumes/Data/Users/snare/code/voltron/repo/tests/inferior' (x86_64)
20 | Process 13185 stopped
21 | * thread #1: tid = 0x1ee63, 0x0000000100000cf0 inferior`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
22 | frame #0: 0x0000000100000cf0 inferior`main
23 | inferior`main:
24 | -> 0x100000cf0: push rbp
25 | 0x100000cf1: mov rbp, rsp
26 | 0x100000cf4: sub rsp, 0x50
27 | 0x100000cf8: mov dword ptr [rbp - 0x4], 0x0
28 |
29 | Run this view in another terminal (as follows). Each time you `stepi` in the
30 | debugger, the view will update and display the current register values.
31 |
32 | $ voltron view example
33 | """
34 |
35 | from voltron.view import TerminalView
36 | from voltron.plugin import ViewPlugin
37 |
38 |
39 | class ExampleView(TerminalView):
40 | def render(self, *args, **kwargs):
41 | # Perform the request
42 | res = self.client.perform_request('registers')
43 | if res.is_success:
44 | # Process the registers and set the body to the formatted list
45 | reg_list = ['rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','rip',
46 | 'r8','r9','r10','r11','r12','r13','r14','r15']
47 | lines = map(lambda x: '{:3}: {:016X}'.format(x, res.registers[x]), reg_list)
48 | self.body = '\n'.join(lines)
49 | else:
50 | self.body = "Failed to get registers: {}".format(res)
51 |
52 | # Set the title and info
53 | self.title = '[example]'
54 | self.info = 'some infoz'
55 |
56 | # Let the parent do the rendering
57 | super(ExampleView, self).render()
58 |
59 |
60 | class ExampleViewPlugin(ViewPlugin):
61 | name = 'example'
62 | view_class = ExampleView
63 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Install Voltron for whichever debuggers are detected (only GDB and LLDB so
4 | # far).
5 | #
6 |
7 | function usage {
8 | cat </dev/null 2>&1
156 | if [ $? -ne 0 ];
157 | then
158 | # If not, attempt to install it using ensurepip
159 | echo "Attempting to install pip using 'ensurepip'."
160 | ${SUDO} ${LLDB_PYTHON} -m ensurepip $USER_MODE || return $?
161 | fi
162 | # Some really old pip installations default to the old pypi.python.org, which no longer works.
163 | echo "Attempting to upgrade pip."
164 | ${SUDO} ${LLDB_PYTHON} -m pip install "pip>=$PIP_MIN_VER" $USER_MODE -U --index-url "$PYPI_URL"
165 | if [ $? != 0 ];
166 | then
167 | # We may still fail here due to TLS incompatibility
168 | # TLS 1.x got turned off 2018-04-11
169 | # https://status.python.org/incidents/hdx7w97m5hr8
170 | # Curl may be new enough to support TLS 1.2, so try to curl the pip installer from pypa.io
171 | # It's able to download and install pip without TLS errors somehow
172 | echo "Failed to upgrade pip."
173 | echo "Attempting to fall back to installation via curl."
174 | curl_get_pip || return $?
175 | fi
176 | }
177 |
178 | function get_lldb_python_exe {
179 | # Find the Python version used by LLDB
180 | local lldb_pyver=$(${LLDB} -Q -x -b --one-line 'script import platform; print(".".join(platform.python_version_tuple()[:2]))'|tail -1)
181 | local lldb_python=$(${LLDB} -Q -x -b --one-line 'script import sys; print(sys.executable)'|tail -1)
182 |
183 | lldb_python=$(${LLDB} -Q -x -b --one-line 'script import sys; print(sys.executable)'|tail -1)
184 | local lldb_python_basename=$(basename "${lldb_python}")
185 | if [ "python" = "$lldb_python_basename" ];
186 | then
187 | lldb_python="${lldb_python/%$lldb_pyver/}${lldb_pyver}"
188 | elif [ "lldb" = "$lldb_python_basename" ];
189 | then
190 | # newer lldb versions report sys.path as /path/to/lldb instead of python
191 | # sys.exec_path still appears to be the parent path of bin/python though
192 | local lldb_python_exec_prefix=$(${LLDB} -Q -x -b --one-line 'script import sys; print(sys.exec_prefix)'|tail -1)
193 | lldb_python="$lldb_python_exec_prefix/bin/python"
194 | lldb_python="${lldb_python/%$lldb_pyver/}${lldb_pyver}"
195 | fi
196 |
197 | echo "$lldb_python"
198 |
199 | }
200 |
201 | if [ "${BACKEND_GDB}" -eq 1 ]; then
202 | # Find the Python version used by GDB
203 | GDB_PYVER=$(${GDB} -batch -q --nx -ex 'pi import platform; print(".".join(platform.python_version_tuple()[:2]))')
204 | GDB_PYTHON=$(${GDB} -batch -q --nx -ex 'pi import sys; print(sys.executable)')
205 | GDB_PYTHON="${GDB_PYTHON/%$GDB_PYVER/}${GDB_PYVER}"
206 |
207 | install_packages
208 |
209 | if [ -z $USER_MODE ]; then
210 | GDB_SITE_PACKAGES=$(${GDB} -batch -q --nx -ex 'pi import site; print(site.getsitepackages()[0])')
211 | else
212 | GDB_SITE_PACKAGES=$(${GDB} -batch -q --nx -ex 'pi import site; print(site.getusersitepackages())')
213 | fi
214 |
215 | # Install Voltron and dependencies
216 | ${SUDO} ${GDB_PYTHON} -m pip install -U $USER_MODE $DEV_MODE .
217 |
218 | # Add Voltron to gdbinit
219 | GDB_INIT_FILE="${HOME}/.gdbinit"
220 | if [ -e ${GDB_INIT_FILE} ]; then
221 | sed -i.bak '/voltron/d' ${GDB_INIT_FILE}
222 | fi
223 |
224 | if [ -z $DEV_MODE ]; then
225 | GDB_ENTRY_FILE="$GDB_SITE_PACKAGES/voltron/entry.py"
226 | else
227 | GDB_ENTRY_FILE="$(pwd)/voltron/entry.py"
228 | fi
229 | echo "source $GDB_ENTRY_FILE" >> ${GDB_INIT_FILE}
230 | fi
231 |
232 | if [ "${BACKEND_LLDB}" -eq 1 ]; then
233 |
234 | LLDB_PYTHON=$(get_lldb_python_exe) || quit "Failed to locate python interpreter." 1
235 | ensure_pip || quit "Failed to install pip." 1
236 | ${LLDB_PYTHON} -m pip install --user --upgrade six || quit "Failed to install or upgrade 'six' python package." 1
237 |
238 | if [ -n "${VENV}" ]; then
239 | echo "Creating virtualenv..."
240 | ${LLDB_PYTHON} -m pip install --user virtualenv
241 | ${LLDB_PYTHON} -m virtualenv "${VENV}"
242 | LLDB_PYTHON="${VENV}/bin/python"
243 | LLDB_SITE_PACKAGES=$(find "${VENV}" -name site-packages)
244 | elif [ -z "${USER_MODE}" ]; then
245 | LLDB_SITE_PACKAGES=$(${LLDB} -Q -x -b --one-line 'script import site; print(site.getsitepackages()[0])'|tail -1) || quit "Failed to locate site-packages." 1
246 | else
247 | LLDB_SITE_PACKAGES=$(${LLDB} -Q -x -b --one-line 'script import site; print(site.getusersitepackages())'|tail -1) || quit "Failed to locate site-packages." 1
248 | fi
249 |
250 | install_packages || quit "Failed to install packages." 1
251 |
252 | if [ "$LLDB_SITE_PACKAGES" == "$GDB_SITE_PACKAGES" ]; then
253 | echo "Skipping installation for LLDB - same site-packages directory"
254 | else
255 | # Install Voltron and dependencies
256 | ${SUDO} ${LLDB_PYTHON} -m pip install -U $USER_MODE $DEV_MODE . || quit "Failed to install voltron." 1
257 | fi
258 |
259 | # Add Voltron to lldbinit
260 | LLDB_INIT_FILE="${HOME}/.lldbinit"
261 | if [ -e ${LLDB_INIT_FILE} ]; then
262 | sed -i.bak '/voltron/d' ${LLDB_INIT_FILE}
263 | fi
264 |
265 | if [ -z "${DEV_MODE}" ]; then
266 | LLDB_ENTRY_FILE="$LLDB_SITE_PACKAGES/voltron/entry.py"
267 | else
268 | LLDB_ENTRY_FILE="$(pwd)/voltron/entry.py"
269 | fi
270 |
271 | if [ -n "${VENV}" ]; then
272 | echo "script import sys;sys.path.append('${LLDB_SITE_PACKAGES}')" >> ${LLDB_INIT_FILE}
273 | fi
274 | echo "command script import $LLDB_ENTRY_FILE" >> ${LLDB_INIT_FILE}
275 | fi
276 |
277 | if [ "${BACKEND_GDB}" -ne 1 ] && [ "${BACKEND_LLDB}" -ne 1 ]; then
278 | # Find system Python
279 | PYTHON=$(command -v python)
280 | PYVER=$(${PYTHON} -c 'import platform; print(".".join(platform.python_version_tuple()[:2]))')
281 | if [ -z $USER_MODE ]; then
282 | PYTHON_SITE_PACKAGES=$(${PYTHON} -c 'import site; print(site.getsitepackages()[0])')
283 | else
284 | PYTHON_SITE_PACKAGES=$(${PYTHON} -c 'import site; print(site.getusersitepackages())')
285 | fi
286 |
287 | install_packages
288 |
289 | # Install Voltron and dependencies
290 | ${SUDO} ${PYTHON} -m pip install -U $USER_MODE $DEV_MODE .
291 | fi
292 |
293 | set +x
294 | echo "=============================================================="
295 | if [ "${BACKEND_GDB}" -eq 1 ]; then
296 | echo "Installed for GDB (${GDB}):"
297 | echo " Python: $GDB_PYTHON"
298 | echo " Packages directory: $GDB_SITE_PACKAGES"
299 | echo " Added voltron to: $GDB_INIT_FILE"
300 | echo " Entry point: $GDB_ENTRY_FILE"
301 | fi
302 | if [ "${BACKEND_LLDB}" -eq 1 ]; then
303 | echo "Installed for LLDB (${LLDB}):"
304 | echo " Python: $LLDB_PYTHON"
305 | echo " Packages directory: $LLDB_SITE_PACKAGES"
306 | echo " Added voltron to: $LLDB_INIT_FILE"
307 | echo " Entry point: $LLDB_ENTRY_FILE"
308 | fi
309 | if [ "${BACKEND_GDB}" -ne 1 ] && [ "${BACKEND_LLDB}" -ne 1 ]; then
310 | if [ -z "${GDB}" ] && [ -z "${LLDB}" ]; then
311 | echo -n "Couldn't find any debuggers. "
312 | else
313 | echo -n "No debuggers selected. "
314 | fi
315 |
316 | echo "Installed using the Python in your path:"
317 | echo " Python: $PYTHON"
318 | echo " Packages directory: $PYTHON_SITE_PACKAGES"
319 | echo " Did not add Voltron to any debugger init files."
320 | fi
321 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal=1
3 |
4 | [flake8]
5 | per-file-ignores =
6 | dbg_lldb.py:E272,E722,E241
7 | disasm.py:E722
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import sys
4 | import platform
5 | import textwrap
6 | from subprocess import check_output
7 | from setuptools import setup, find_packages
8 |
9 |
10 | def check_install():
11 | """
12 | Try to detect the two most common installation errors:
13 |
14 | 1. Installing on macOS using a Homebrew version of Python
15 | 2. Installing on Linux using Python 2 when GDB is linked with Python 3
16 | """
17 | if platform.system() == 'Darwin' and sys.executable != '/usr/bin/python':
18 | # send warning text to stderr so we can capture version string in a shell script like
19 | # version=$(python3 setup.py --version)
20 | print("*" * 79, file=sys.stderr)
21 | print(textwrap.fill(
22 | "WARNING: You are not using the version of Python included with "
23 | "macOS. If you intend to use Voltron with the LLDB included "
24 | "with Xcode, or GDB installed with Homebrew, it will not work "
25 | "unless it is installed using the system's default Python. If "
26 | "you intend to use Voltron with a debugger installed by some "
27 | "other method, it may be safe to ignore this warning. See the "
28 | "following documentation for more detailed installation "
29 | "instructions: "
30 | "https://github.com/snare/voltron/wiki/Installation", 79), file=sys.stderr)
31 | print("*" * 79, file=sys.stderr)
32 | elif platform.system() == 'Linux':
33 | try:
34 | output = check_output([
35 | "gdb", "-batch", "-q", "--nx", "-ex",
36 | "pi print(sys.version_info.major)"
37 | ]).decode("utf-8")
38 | gdb_python = int(output)
39 |
40 | if gdb_python != sys.version_info.major:
41 | print("*" * 79, file=sys.stderr)
42 | print(textwrap.fill(
43 | "WARNING: You are installing Voltron using Python {0}.x "
44 | "and GDB is linked with Python {1}.x. GDB will not be "
45 | "able to load Voltron. Please install using Python {1} "
46 | "if you intend to use Voltron with the copy of GDB that "
47 | "is installed. See the following documentation for more "
48 | "detailed installation instructions: "
49 | "https://github.com/snare/voltron/wiki/Installation"
50 | .format(sys.version_info.major, gdb_python), 79), file=sys.stderr)
51 | print("*" * 79, file=sys.stderr)
52 | except:
53 | pass
54 |
55 |
56 | check_install()
57 |
58 |
59 | requirements = [
60 | 'scruffington>=0.3.9',
61 | 'flask',
62 | 'flask_restful',
63 | 'blessed',
64 | 'pygments',
65 | 'requests',
66 | 'requests_unixsocket',
67 | 'six',
68 | 'pysigset',
69 | 'pygments',
70 | ]
71 | if sys.platform == 'win32':
72 | requirements.append('cursor')
73 |
74 |
75 | setup(
76 | name="voltron",
77 | version="0.1.8",
78 | author="snare",
79 | author_email="snare@ho.ax",
80 | description="A debugger UI",
81 | license="MIT",
82 | keywords="voltron debugger ui gdb lldb vdb "
83 | "vivisect vtrace windbg cdb pykd",
84 | url="https://github.com/snare/voltron",
85 | packages=find_packages(exclude=['tests', 'examples']),
86 | install_requires=requirements,
87 | package_data={'voltron': ['config/*']},
88 | entry_points={
89 | 'console_scripts': ['voltron=voltron:main'],
90 | 'pygments.lexers': [
91 | 'lldb_intel = voltron.lexers:LLDBIntelLexer',
92 | 'lldb_att = voltron.lexers:LLDBATTLexer',
93 | 'gdb_intel = voltron.lexers:GDBIntelLexer',
94 | 'gdb_att = voltron.lexers:GDBATTLexer',
95 | 'vdb_intel = voltron.lexers:VDBIntelLexer',
96 | 'vdb_att = voltron.lexers:VDBATTLexer',
97 | 'windbg_intel = voltron.lexers:WinDbgIntelLexer',
98 | 'windbg_att = voltron.lexers:WinDbgATTLexer',
99 | 'capstone_intel = voltron.lexers:CapstoneIntelLexer',
100 | ],
101 | 'pygments.styles': [
102 | 'volarized = voltron.styles:VolarizedStyle',
103 | ]
104 | },
105 | zip_safe=False
106 | )
107 |
--------------------------------------------------------------------------------
/tests/Vagrantfile:
--------------------------------------------------------------------------------
1 | Vagrant.configure(2) do |config|
2 | config.vm.hostname = "voltron"
3 | config.vm.box = "ubuntu/trusty64"
4 |
5 | config.vm.provider :virtualbox do |v|
6 | v.memory = 1024
7 | v.linked_clone = true
8 | end
9 |
10 | config.vm.network "forwarded_port", guest: 5555, host: 5556
11 |
12 | config.vm.synced_folder "../", "/voltron"
13 | config.vm.synced_folder "~/shared", "/shared"
14 |
15 | config.vm.provision "shell", inline: <<-SHELL
16 | apt-get update
17 | apt-get upgrade -y
18 | apt-get install -y \
19 | build-essential \
20 | gdb \
21 | zsh \
22 | python3-pip \
23 | lldb-3.8
24 | pip3 install six --upgrade
25 | su - vagrant -c "cd /voltron ; ./install.sh"
26 | mkdir -p /home/vagrant/.voltron
27 | chown vagrant:vagrant /home/vagrant/.voltron -R
28 | cat >/home/vagrant/.voltron/config < Server -> LLDBAdaptor
7 |
8 | Using an instantiated SBDebugger instance
9 | """
10 |
11 | import tempfile
12 | import sys
13 | import json
14 | import time
15 | import logging
16 | import subprocess
17 | import time
18 |
19 | from mock import Mock
20 | from nose.tools import *
21 |
22 | import voltron
23 | from voltron.core import *
24 | from voltron.api import *
25 | from voltron.plugin import *
26 |
27 | import platform
28 | if platform.system() == 'Darwin':
29 | sys.path.append("/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python")
30 |
31 | try:
32 | import lldb
33 |
34 | from .common import *
35 |
36 | log = logging.getLogger("tests")
37 |
38 | def setup():
39 | global server, client, target, pm, adaptor, methods
40 |
41 | log.info("setting up API tests")
42 |
43 | # set up voltron
44 | voltron.setup_env()
45 | pm = PluginManager()
46 | plugin = pm.debugger_plugin_for_host('lldb')
47 | adaptor = plugin.adaptor_class()
48 | voltron.debugger = adaptor
49 |
50 | # start up a voltron server
51 | server = Server()
52 | # import pdb;pdb.set_trace()
53 | server.start()
54 |
55 | time.sleep(0.1)
56 |
57 | # set up client
58 | client = Client()
59 |
60 | # compile and load the test inferior
61 | subprocess.call("cc -o tests/inferior tests/inferior.c", shell=True)
62 | target = adaptor.host.CreateTargetWithFileAndArch("tests/inferior", lldb.LLDB_ARCH_DEFAULT)
63 | main_bp = target.BreakpointCreateByName("main", target.GetExecutable().GetFilename())
64 |
65 | def teardown():
66 | server.stop()
67 | time.sleep(5)
68 |
69 | def test_state_no_target():
70 | req = api_request('state')
71 | res = client.send_request(req)
72 | assert res.is_error
73 | assert res.code == 4101
74 |
75 | def test_state_stopped():
76 | process = target.LaunchSimple(None, None, os.getcwd())
77 | req = api_request('state')
78 | res = client.send_request(req)
79 | assert res.status == 'success'
80 | assert res.state == "stopped"
81 | target.process.Destroy()
82 |
83 | def test_targets():
84 | req = api_request('targets')
85 | res = client.send_request(req)
86 | assert res.status == 'success'
87 | t = res.targets[0]
88 | assert t["id"] == 0
89 | assert t["arch"] == "x86_64"
90 | assert t["file"].endswith("inferior")
91 |
92 | def test_registers():
93 | process = target.LaunchSimple(None, None, os.getcwd())
94 | req = api_request('registers')
95 | res = client.send_request(req)
96 | assert res.status == 'success'
97 | assert len(res.registers) > 0
98 | assert res.registers['rip'] != 0
99 | target.process.Destroy()
100 |
101 | def test_memory():
102 | process = target.LaunchSimple(None, None, os.getcwd())
103 | res = client.perform_request('registers')
104 | rsp = res.registers['rsp']
105 | res = client.perform_request('memory', address=rsp, length=0x40)
106 | assert res.status == 'success'
107 | assert len(res.memory) > 0
108 | res = client.perform_request('memory', address=rsp, length=0x40, deref=True)
109 | assert res.status == 'success'
110 | assert len(res.deref) > 0
111 | target.process.Destroy()
112 |
113 | def test_stack():
114 | process = target.LaunchSimple(None, None, os.getcwd())
115 | req = api_request('stack', length=0x40)
116 | res = client.send_request(req)
117 | assert res.status == 'success'
118 | assert len(res.memory) > 0
119 | target.process.Destroy()
120 |
121 | def test_command():
122 | process = target.LaunchSimple(None, None, os.getcwd())
123 | req = api_request('command', command="reg read")
124 | res = client.send_request(req)
125 | assert res.status == 'success'
126 | assert len(res.output) > 0
127 | assert 'rax' in res.output
128 | target.process.Destroy()
129 |
130 | def test_disassemble():
131 | process = target.LaunchSimple(None, None, os.getcwd())
132 | req = api_request('disassemble', count=16)
133 | res = client.send_request(req)
134 | assert res.status == 'success'
135 | assert len(res.disassembly) > 0
136 | assert 'push' in res.disassembly
137 | req = api_request('disassemble', count=16, use_capstone=True)
138 | res = client.send_request(req)
139 | assert res.status == 'success'
140 | assert len(res.disassembly) > 0
141 | assert 'push' in res.disassembly
142 | target.process.Destroy()
143 |
144 | def test_dereference():
145 | process = target.LaunchSimple(None, None, os.getcwd())
146 | res = client.perform_request('registers')
147 | res = client.perform_request('dereference', pointer=res.registers['rsp'])
148 | assert res.status == 'success'
149 | assert res.output[0][0] == 'pointer'
150 | assert res.output[-1][1] == 'start + 0x1'
151 | target.process.Destroy()
152 |
153 | def test_breakpoints():
154 | process = target.LaunchSimple(None, None, os.getcwd())
155 | res = client.perform_request('breakpoints')
156 | assert res.status == 'success'
157 | assert len(res.breakpoints) == 1
158 | assert res.breakpoints[0]['one_shot'] == False
159 | assert res.breakpoints[0]['enabled']
160 | assert res.breakpoints[0]['id'] == 1
161 | assert res.breakpoints[0]['hit_count'] > 0
162 | assert res.breakpoints[0]['locations'][0]['name'] == "inferior`main"
163 | target.process.Destroy()
164 |
165 | def test_multi_request():
166 | process = target.LaunchSimple(None, None, os.getcwd())
167 | reg_res, dis_res = client.send_requests(api_request('registers'),
168 | api_request('disassemble', count=16))
169 | assert reg_res.status == 'success'
170 | assert len(reg_res.registers) > 0
171 | assert reg_res.registers['rip'] != 0
172 | assert dis_res.status == 'success'
173 | assert len(dis_res.disassembly) > 0
174 | assert 'push' in dis_res.disassembly
175 | target.process.Destroy()
176 |
177 | except:
178 | print("No LLDB")
179 |
--------------------------------------------------------------------------------
/tests/gdb_cli_tests.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests that test voltron in the gdb cli driver
3 |
4 | Tests:
5 | Client -> Server -> GDBAdaptor
6 |
7 | Inside a GDB instance
8 | """
9 |
10 | from __future__ import print_function
11 |
12 | import tempfile
13 | import sys
14 | import json
15 | import time
16 | import logging
17 | import pexpect
18 | import os
19 | import six
20 |
21 | from mock import Mock
22 | from nose.tools import *
23 |
24 | import voltron
25 | from voltron.core import *
26 | from voltron.api import *
27 | from voltron.plugin import PluginManager, DebuggerAdaptorPlugin
28 |
29 | from .common import *
30 |
31 | log = logging.getLogger('tests')
32 |
33 | p = None
34 | client = None
35 |
36 |
37 | def setup():
38 | global p, client, pm
39 |
40 | log.info("setting up GDB CLI tests")
41 |
42 | voltron.setup_env()
43 |
44 | # compile test inferior
45 | pexpect.run("cc -o tests/inferior tests/inferior.c")
46 |
47 | # start debugger
48 | start_debugger()
49 |
50 |
51 | def teardown():
52 | read_data()
53 | p.terminate(True)
54 |
55 |
56 | def start_debugger(do_break=True):
57 | global p, client
58 | p = pexpect.spawn('gdb')
59 | p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages')")
60 | p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python3.4.3/lib/python3.4/site-packages')")
61 | p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python3.3.6/lib/python3.3/site-packages')")
62 | p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python2.7.10/lib/python2.7/site-packages')")
63 | p.sendline("source voltron/entry.py")
64 | p.sendline("file tests/inferior")
65 | p.sendline("set disassembly-flavor intel")
66 | p.sendline("voltron init")
67 | if do_break:
68 | p.sendline("b main")
69 | p.sendline("run loop")
70 | read_data()
71 |
72 | time.sleep(5)
73 |
74 | client = Client()
75 |
76 |
77 | def stop_debugger():
78 | # p.sendline("kill")
79 | read_data()
80 | p.terminate(True)
81 |
82 |
83 | def read_data():
84 | try:
85 | while True:
86 | data = p.read_nonblocking(size=64, timeout=1)
87 | print(data.decode('UTF-8'), end='')
88 | except:
89 | pass
90 |
91 |
92 | def restart_debugger(do_break=True):
93 | stop_debugger()
94 | start_debugger(do_break)
95 |
96 |
97 | def test_bad_request():
98 | req = client.create_request('version')
99 | req.request = 'xxx'
100 | res = client.send_request(req)
101 | assert res.is_error
102 | assert res.code == 0x1002
103 |
104 |
105 | def test_version():
106 | req = client.create_request('version')
107 | res = client.send_request(req)
108 | assert res.api_version == 1.1
109 | assert 'gdb' in res.host_version
110 |
111 |
112 | def test_registers():
113 | global registers
114 | read_data()
115 | res = client.perform_request('registers')
116 | registers = res.registers
117 | assert res.status == 'success'
118 | assert len(registers) > 0
119 | assert registers['rip'] != 0
120 |
121 |
122 | def test_memory():
123 | res = client.perform_request('memory', address=registers['rip'], length=0x40)
124 | assert res.status == 'success'
125 | assert len(res.memory) > 0
126 |
127 |
128 | def test_state_stopped():
129 | res = client.perform_request('state')
130 | assert res.is_success
131 | assert res.state == "stopped"
132 |
133 |
134 | def test_targets():
135 | res = client.perform_request('targets')
136 | assert res.is_success
137 | assert res.targets[0]['state'] == "stopped"
138 | assert res.targets[0]['arch'] == "x86_64"
139 | assert res.targets[0]['id'] == 0
140 | assert res.targets[0]['file'].endswith('tests/inferior')
141 |
142 |
143 | def test_stack():
144 | res = client.perform_request('stack', length=0x40)
145 | assert res.status == 'success'
146 | assert len(res.memory) > 0
147 |
148 |
149 | def test_command():
150 | res = client.perform_request('command', command="info reg")
151 | assert res.status == 'success'
152 | assert len(res.output) > 0
153 | assert 'rax' in res.output
154 |
155 |
156 | def test_disassemble():
157 | res = client.perform_request('disassemble', count=0x20)
158 | assert res.status == 'success'
159 | assert len(res.disassembly) > 0
160 | assert 'DWORD' in res.disassembly
161 |
162 |
163 | def test_backtrace():
164 | res = client.perform_request('backtrace')
165 | print(res)
166 | assert res.is_success
167 | assert res.frames[0]['name'] == "main"
168 | assert res.frames[0]['index'] == 0
169 |
170 |
171 | # def test_write_memory():
172 | # value = six.b("AAAAAAAA")
173 | # res = client.perform_request('write_memory', address=registers['rsp'], value=value)
174 | # assert res.is_success
175 | # res = client.perform_request('memory', address=registers['rsp'], length=len(value))
176 | # assert res.memory == value
177 |
--------------------------------------------------------------------------------
/tests/http_api_tests.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests that emulate the debugger adaptor and just test the interaction between
3 | the front end and back end API classes. HTTP edition!
4 |
5 | Tests:
6 | Server (via HTTP)
7 | """
8 |
9 | import logging
10 | import sys
11 | import json
12 | import time
13 | import subprocess
14 |
15 | from nose.tools import *
16 |
17 | import voltron
18 | from voltron.core import *
19 | from voltron.api import *
20 | from voltron.plugin import *
21 |
22 | import platform
23 | if platform.system() == 'Darwin':
24 | sys.path.append("/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python")
25 |
26 | from .common import *
27 |
28 | import requests
29 |
30 |
31 | log = logging.getLogger('tests')
32 |
33 |
34 | class APIHostNotSupportedRequest(APIRequest):
35 | @server_side
36 | def dispatch(self):
37 | return APIDebuggerHostNotSupportedErrorResponse()
38 |
39 |
40 | class APIHostNotSupportedPlugin(APIPlugin):
41 | request = "host_not_supported"
42 | request_class = APIHostNotSupportedRequest
43 | response_class = APIResponse
44 |
45 |
46 | def setup():
47 | global server, client, target, pm, adaptor, methods
48 |
49 | log.info("setting up API tests")
50 |
51 | # set up voltron
52 | voltron.setup_env()
53 | voltron.config['server'] = {
54 | "listen": {
55 | "tcp": ["127.0.0.1", 5555]
56 | }
57 | }
58 | pm = PluginManager()
59 | plugin = pm.debugger_plugin_for_host('mock')
60 | adaptor = plugin.adaptor_class()
61 | voltron.debugger = adaptor
62 |
63 | # inject mock methods
64 | inject_mock(adaptor)
65 |
66 | # start up a voltron server
67 | server = Server()
68 | server.start()
69 |
70 | time.sleep(2)
71 |
72 |
73 | def teardown():
74 | server.stop()
75 | time.sleep(2)
76 |
77 |
78 | def test_disassemble():
79 | data = requests.get('http://localhost:5555/api/disassemble?count=16').text
80 | res = APIResponse(data=data)
81 | assert res.is_success
82 | assert res.disassembly == disassemble_response
83 |
84 |
85 | def test_command():
86 | data = requests.get('http://localhost:5555/api/command?command=reg%20read').text
87 | res = APIResponse(data=data)
88 | assert res.is_success
89 | assert res.output == command_response
90 |
91 |
92 | def test_targets():
93 | data = requests.get('http://localhost:5555/api/targets').text
94 | res = api_response('targets', data=data)
95 | assert res.is_success
96 | assert res.targets == targets_response
97 |
98 |
99 | def test_memory():
100 | data = requests.get('http://localhost:5555/api/registers').text
101 | res = api_response('registers', data=data)
102 | url = 'http://localhost:5555/api/memory?address={}&length=64'.format(res.registers['rip'])
103 | data = requests.get(url).text
104 | res = api_response('memory', data=data)
105 | assert res.is_success
106 | assert res.memory == memory_response
107 |
108 |
109 | def test_registers():
110 | data = requests.get('http://localhost:5555/api/registers').text
111 | res = api_response('registers', data=data)
112 | assert res.is_success
113 | assert res.registers == registers_response
114 |
115 |
116 | def test_stack_length_missing():
117 | data = requests.get('http://localhost:5555/api/stack').text
118 | res = APIErrorResponse(data=data)
119 | assert res.is_error
120 | assert res.message == 'length'
121 |
122 |
123 | def test_stack():
124 | data = requests.get('http://localhost:5555/api/stack?length=64').text
125 | res = api_response('stack', data=data)
126 | assert res.is_success
127 | assert res.memory == stack_response
128 |
129 |
130 | def test_state():
131 | data = requests.get('http://localhost:5555/api/state').text
132 | res = api_response('state', data=data)
133 | assert res.is_success
134 | assert res.state == state_response
135 |
136 |
137 | def test_version():
138 | data = requests.get('http://localhost:5555/api/version').text
139 | res = api_response('version', data=data)
140 | assert res.is_success
141 | assert res.api_version == 1.1
142 | assert res.host_version == 'lldb-something'
143 |
144 |
145 | def test_bad_json():
146 | data = requests.post('http://localhost:5555/api/request', data='xxx').text
147 | res = APIResponse(data=data)
148 | assert res.is_error
149 | assert res.code == 0x1001
150 |
151 |
152 | def test_bad_request():
153 | data = requests.post('http://localhost:5555/api/request', data='{"type":"request","request":"no_such_request"}').text
154 | res = APIResponse(data=data)
155 | assert res.is_error
156 | assert res.code == 0x1002
157 |
158 |
159 | def test_breakpoints():
160 | data = requests.get('http://localhost:5555/api/breakpoints').text
161 | res = api_response('breakpoints', data=data)
162 | assert res.is_success
163 | assert res.breakpoints == breakpoints_response
164 |
--------------------------------------------------------------------------------
/tests/inferior.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | void test_function()
6 | {
7 | printf("*** test_function()\n");
8 | }
9 |
10 | int main(int argc, char **argv)
11 | {
12 | int a = 0;
13 | int *b = NULL;
14 | int c=0,d=0,e=0;
15 |
16 | if (argc > 1 && strcmp(argv[1], "sleep") == 0)
17 | {
18 | printf("*** Sleeping for 5 seconds\n");
19 | sleep(5);
20 | }
21 | else if (argc > 1 && strcmp(argv[1], "loop") == 0)
22 | {
23 | printf("*** Looping forever()\n");
24 | while(1) {
25 | c++;
26 | d+=2;
27 | e+=3;
28 | }
29 | sleep(1);
30 | }
31 | else if (argc > 1 && strcmp(argv[1], "function") == 0)
32 | {
33 | printf("*** Calling test_function()\n");
34 | test_function();
35 | }
36 | else if (argc > 1 && strcmp(argv[1], "crash") == 0)
37 | {
38 | printf("*** Crashing\n");
39 | a = *b;
40 | }
41 | else
42 | {
43 | printf("Usage: inferior < sleep | loop | function | crash >\n");
44 | }
45 |
46 | return 0;
47 | }
48 |
--------------------------------------------------------------------------------
/tests/lldb_api_tests.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests that exercise the LLDB backend directly by loading an inferior and then
3 | poking at it with the LLDBAdaptor class.
4 |
5 | Tests:
6 | LLDBAdaptor
7 | """
8 |
9 | import tempfile
10 | import sys
11 | import json
12 | import time
13 | import logging
14 | import subprocess
15 | import threading
16 |
17 | from mock import Mock
18 | from nose.tools import *
19 |
20 | import voltron
21 | from voltron.core import *
22 | from voltron.api import *
23 | from voltron.plugin import PluginManager, DebuggerAdaptorPlugin
24 |
25 | import platform
26 | if platform.system() == 'Darwin':
27 | sys.path.append("/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python")
28 |
29 | try:
30 | import lldb
31 |
32 | from common import *
33 |
34 | voltron.setup_env()
35 |
36 | log = logging.getLogger('tests')
37 |
38 | def setup():
39 | global adaptor, dbg, target
40 |
41 | log.info("setting up LLDB API tests")
42 |
43 | # create an LLDBAdaptor
44 | pm = PluginManager()
45 | plugin = pm.debugger_plugin_for_host('lldb')
46 | adaptor = plugin.adaptor_class()
47 |
48 | # compile and load the test inferior
49 | subprocess.call("cc -o tests/inferior tests/inferior.c", shell=True)
50 | target = adaptor.host.CreateTargetWithFileAndArch("tests/inferior", lldb.LLDB_ARCH_DEFAULT)
51 | main_bp = target.BreakpointCreateByName("main", target.GetExecutable().GetFilename())
52 |
53 | def teardown():
54 | time.sleep(2)
55 |
56 | def test_version():
57 | assert 'lldb' in adaptor.version()
58 |
59 | def test_state_invalid():
60 | try:
61 | adaptor.state()
62 | exception = False
63 | except NoSuchTargetException:
64 | exception = True
65 | except:
66 | exception = False
67 | assert exception
68 |
69 | def test_targets_not_running():
70 | t = adaptor.targets()[0]
71 | assert t["state"] == "invalid"
72 | assert t["arch"] == "x86_64"
73 | assert t["id"] == 0
74 | assert len(t["file"]) > 0
75 | assert 'inferior' in t["file"]
76 |
77 | def test_targets_stopped():
78 | process = target.LaunchSimple(None, None, os.getcwd())
79 | t = adaptor.targets()[0]
80 | assert t["state"] == "stopped"
81 | process.Destroy()
82 |
83 | def test_registers():
84 | process = target.LaunchSimple(None, None, os.getcwd())
85 | regs = adaptor.registers()
86 | assert regs is not None
87 | assert len(regs) > 0
88 | assert regs['rip'] != 0
89 | process.Destroy()
90 |
91 | def test_stack_pointer():
92 | process = target.LaunchSimple(None, None, os.getcwd())
93 | sp = adaptor.stack_pointer()
94 | assert sp != 0
95 | process.Destroy()
96 |
97 | def test_program_counter():
98 | process = target.LaunchSimple(None, None, os.getcwd())
99 | pc_name, pc = adaptor.program_counter()
100 | assert pc != 0
101 | process.Destroy()
102 |
103 | def test_memory():
104 | process = target.LaunchSimple(None, None, os.getcwd())
105 | regs = adaptor.registers()
106 | mem = adaptor.memory(address=regs['rip'], length=0x40)
107 | assert len(mem) == 0x40
108 | process.Destroy()
109 |
110 | def test_stack():
111 | process = target.LaunchSimple(None, None, os.getcwd())
112 | stack = adaptor.stack(length=0x40)
113 | assert len(stack) == 0x40
114 | process.Destroy()
115 |
116 | def test_disassemble():
117 | process = target.LaunchSimple(None, None, os.getcwd())
118 | output = adaptor.disassemble(count=0x20)
119 | assert len(output) > 0
120 | process.Destroy()
121 |
122 | def test_command():
123 | process = target.LaunchSimple(None, None, os.getcwd())
124 | output = adaptor.command("reg read")
125 | assert len(output) > 0
126 | assert 'rax' in output
127 | process.Destroy()
128 |
129 | def test_dereference_main():
130 | process = target.LaunchSimple(None, None, os.getcwd())
131 | regs = adaptor.registers()
132 | output = adaptor.dereference(regs['rip'])
133 | assert ('symbol', 'main + 0x0') in output
134 | process.Destroy()
135 |
136 | def test_dereference_rsp():
137 | process = target.LaunchSimple(None, None, os.getcwd())
138 | regs = adaptor.registers()
139 | output = adaptor.dereference(regs['rsp'])
140 | assert ('symbol', 'start + 0x1') in output
141 | process.Destroy()
142 |
143 | def test_dereference_string():
144 | process = target.LaunchSimple(None, None, os.getcwd())
145 | regs = adaptor.registers()
146 | output = adaptor.dereference(regs['rsp'] + 0x20)
147 | assert 'inferior' in list(output[-1])[-1]
148 | process.Destroy()
149 |
150 | def test_breakpoints():
151 | process = target.LaunchSimple(None, None, os.getcwd())
152 | bps = adaptor.breakpoints()
153 | assert len(bps) == 1
154 | assert bps[0]['one_shot'] == False
155 | assert bps[0]['enabled']
156 | assert bps[0]['id'] == 1
157 | assert bps[0]['hit_count'] > 0
158 | assert bps[0]['locations'][0]['name'] == "inferior`main"
159 | process.Destroy()
160 |
161 | def test_capabilities():
162 | assert adaptor.capabilities() == ['async']
163 |
164 | except:
165 | print("No LLDB")
--------------------------------------------------------------------------------
/tests/lldb_cli_tests.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests that test voltron in the lldb cli driver
3 |
4 | Tests:
5 | Client -> Server -> LLDBAdaptor
6 |
7 | Inside an LLDB CLI driver instance
8 | """
9 | from __future__ import print_function
10 |
11 | import tempfile
12 | import sys
13 | import json
14 | import time
15 | import logging
16 | import pexpect
17 | import os
18 | import tempfile
19 | import six
20 |
21 | from mock import Mock
22 | from nose.tools import *
23 |
24 | import voltron
25 | from voltron.core import *
26 | from voltron.api import *
27 | from voltron.plugin import PluginManager, DebuggerAdaptorPlugin
28 |
29 | from .common import *
30 |
31 | log = logging.getLogger('tests')
32 |
33 | p = None
34 | client = None
35 |
36 |
37 | def setup():
38 | global p, client, pm
39 |
40 | log.info("setting up LLDB CLI tests")
41 |
42 | voltron.setup_env()
43 |
44 | # compile test inferior
45 | pexpect.run("cc -o tests/inferior tests/inferior.c")
46 |
47 | # start debugger
48 | start_debugger()
49 | time.sleep(10)
50 |
51 |
52 | def teardown():
53 | read_data()
54 | p.terminate(True)
55 | time.sleep(2)
56 |
57 |
58 | def start_debugger(do_break=True):
59 | global p, client
60 |
61 | if sys.platform == 'darwin':
62 | p = pexpect.spawn('lldb')
63 | else:
64 | p = pexpect.spawn('lldb-3.4')
65 |
66 | # for travis
67 | (f, tmpname) = tempfile.mkstemp('.py')
68 | os.write(f, six.b('\n'.join([
69 | "import sys",
70 | "sys.path.append('/home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages')",
71 | "sys.path.append('/home/travis/virtualenv/python3.4.3/lib/python3.4/site-packages')",
72 | "sys.path.append('/home/travis/virtualenv/python3.3.6/lib/python3.3/site-packages')",
73 | "sys.path.append('/home/travis/virtualenv/python2.7.10/lib/python2.7/site-packages')"])))
74 | p.sendline("command script import {}".format(tmpname))
75 |
76 | print("pid == {}".format(p.pid))
77 | p.sendline("settings set target.x86-disassembly-flavor intel")
78 | p.sendline("command script import voltron/entry.py")
79 | time.sleep(2)
80 | p.sendline("file tests/inferior")
81 | time.sleep(2)
82 | p.sendline("voltron init")
83 | time.sleep(1)
84 | p.sendline("process kill")
85 | p.sendline("break delete 1")
86 | if do_break:
87 | p.sendline("b main")
88 | p.sendline("run loop")
89 | read_data()
90 | client = Client()
91 |
92 |
93 | def stop_debugger():
94 | p.terminate(True)
95 |
96 |
97 | def read_data():
98 | try:
99 | while True:
100 | data = p.read_nonblocking(size=64, timeout=1)
101 | print(data.decode('UTF-8'), end='')
102 | except:
103 | pass
104 |
105 |
106 | def restart(do_break=True):
107 | # stop_debugger()
108 | # start_debugger(do_break)
109 | p.sendline("process kill")
110 | p.sendline("break delete -f")
111 | if do_break:
112 | p.sendline("b main")
113 | p.sendline("run loop")
114 |
115 |
116 | def test_bad_request():
117 | req = client.create_request('version')
118 | req.request = 'xxx'
119 | res = client.send_request(req)
120 | assert res.is_error
121 | assert res.code == 0x1002
122 | time.sleep(2)
123 |
124 |
125 | def test_version():
126 | req = client.create_request('version')
127 | res = client.send_request(req)
128 | assert res.api_version == 1.1
129 | assert 'lldb' in res.host_version
130 |
131 |
132 | def test_registers():
133 | global registers
134 | restart()
135 | time.sleep(1)
136 | read_data()
137 | res = client.perform_request('registers')
138 | registers = res.registers
139 | assert res.status == 'success'
140 | assert len(registers) > 0
141 | if 'rip' in registers:
142 | assert registers['rip'] != 0
143 | else:
144 | assert registers['eip'] != 0
145 |
146 |
147 | def test_memory():
148 | restart()
149 | time.sleep(1)
150 | res = client.perform_request('memory', address=registers['rip'], length=0x40)
151 | assert res.status == 'success'
152 | assert len(res.memory) > 0
153 |
154 |
155 | def test_state_stopped():
156 | restart()
157 | time.sleep(1)
158 | res = client.perform_request('state')
159 | assert res.is_success
160 | assert res.state == "stopped"
161 |
162 |
163 | def test_targets():
164 | restart()
165 | time.sleep(1)
166 | res = client.perform_request('targets')
167 | assert res.is_success
168 | assert res.targets[0]['state'] == "stopped"
169 | assert res.targets[0]['arch'] == "x86_64"
170 | assert res.targets[0]['id'] == 0
171 | assert res.targets[0]['file'].endswith('tests/inferior')
172 |
173 |
174 | def test_stack():
175 | restart()
176 | time.sleep(1)
177 | res = client.perform_request('stack', length=0x40)
178 | assert res.status == 'success'
179 | assert len(res.memory) > 0
180 |
181 |
182 | def test_command():
183 | restart()
184 | time.sleep(1)
185 | res = client.perform_request('command', command="reg read")
186 | assert res.status == 'success'
187 | assert len(res.output) > 0
188 | assert 'rax' in res.output
189 |
190 |
191 | def test_disassemble():
192 | restart()
193 | time.sleep(1)
194 | res = client.perform_request('disassemble', count=0x20)
195 | assert res.status == 'success'
196 | assert len(res.disassembly) > 0
197 | assert 'push' in res.disassembly
198 |
199 |
200 | def test_dereference():
201 | restart()
202 | time.sleep(1)
203 | res = client.perform_request('registers')
204 | res = client.perform_request('dereference', pointer=res.registers['rsp'])
205 | assert res.status == 'success'
206 | assert res.output[0][0] == 'pointer'
207 | assert 'start' in res.output[-1][1] or 'main' in res.output[-1][1]
208 |
209 |
210 | def test_breakpoints():
211 | restart(True)
212 | time.sleep(1)
213 | res = client.perform_request('breakpoints')
214 | assert res.status == 'success'
215 | # assert len(res.breakpoints) == 1
216 | assert res.breakpoints[0]['one_shot'] == False
217 | assert res.breakpoints[0]['enabled']
218 | # assert res.breakpoints[0]['id'] == 1
219 | assert res.breakpoints[0]['hit_count'] > 0
220 | assert res.breakpoints[0]['locations'][0]['name'] == "inferior`main"
221 |
222 |
223 | # def test_multi():
224 | # global r1, r2
225 | # restart(True)
226 | # time.sleep(1)
227 | # r1, r2 = None, None
228 |
229 | # def send_req():
230 | # global r1, r2
231 | # r1, r2 = client.send_requests(api_request('targets', block=True), api_request('registers', block=True))
232 | # print "sent requests"
233 |
234 | # t = threading.Thread(target=send_req)
235 | # t.start()
236 | # time.sleep(5)
237 | # p.sendline("stepi")
238 | # time.sleep(5)
239 | # t.join()
240 | # print r1
241 | # print r2
242 | # assert r1.is_success
243 | # assert r1.targets[0]['state'] == "stopped"
244 | # assert r1.targets[0]['arch'] == "x86_64"
245 | # assert r1.targets[0]['id'] == 0
246 | # assert r1.targets[0]['file'].endswith('tests/inferior')
247 | # assert r2.status == 'success'
248 | # assert len(r2.registers) > 0
249 | # assert r2.registers['rip'] != 0
250 |
251 |
252 | def test_capabilities():
253 | restart(True)
254 | res = client.perform_request('version')
255 | assert res.capabilities == ['async']
256 |
257 |
258 | def test_backtrace():
259 | restart(True)
260 | time.sleep(1)
261 | res = client.perform_request('backtrace')
262 | print(res)
263 | assert res.frames[0]['name'] == "inferior`main + 0"
264 | assert res.frames[0]['index'] == 0
265 |
--------------------------------------------------------------------------------
/tests/test.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int main(void)
4 | {
5 | printf("hello\n");
6 |
7 | return 0;
8 | }
--------------------------------------------------------------------------------
/tests/testinit.lldb:
--------------------------------------------------------------------------------
1 | file tests/inferior
2 | voltron init
3 | b main
4 | run loop
5 |
6 |
--------------------------------------------------------------------------------
/voltron/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 | import logging.config
4 |
5 | import voltron
6 | from .main import main
7 |
8 | from scruffy import Environment, Directory, File, ConfigFile, PluginDirectory, PackageDirectory
9 |
10 | # scruffy environment containing config, plugins, etc
11 | env = None
12 | config = None
13 |
14 | debugger = None
15 | command = None
16 | commands = None
17 | server = None
18 |
19 | loaded = False
20 |
21 |
22 | def setup_env():
23 | global env, config
24 | env = Environment(setup_logging=False,
25 | voltron_dir=Directory('~/.voltron', create=True,
26 | config=ConfigFile('config', defaults=File('config/default.cfg', parent=PackageDirectory()), apply_env=True),
27 | sock=File('{config:server.listen.domain}'),
28 | history=File('history'),
29 | user_plugins=PluginDirectory('plugins')
30 | ),
31 | pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory())
32 | )
33 | config = env.config
34 |
35 | voltron.plugin.pm.register_plugins()
36 |
37 | LOGGER_DEFAULT = {
38 | 'handlers': ['null'],
39 | 'level': 'DEBUG',
40 | 'propagate': False
41 | }
42 |
43 | LOG_CONFIG = {
44 | 'version': 1,
45 | 'formatters': {
46 | 'standard': {'format': 'voltron: [%(levelname)s] %(message)s'}
47 | },
48 | 'handlers': {
49 | 'default': {
50 | 'class': 'logging.StreamHandler',
51 | 'formatter': 'standard'
52 | },
53 | 'null': {
54 | 'class': 'logging.NullHandler'
55 | }
56 | },
57 | 'loggers': {
58 | '': LOGGER_DEFAULT,
59 | 'debugger': LOGGER_DEFAULT,
60 | 'core': LOGGER_DEFAULT,
61 | 'main': LOGGER_DEFAULT,
62 | 'api': LOGGER_DEFAULT,
63 | 'view': LOGGER_DEFAULT,
64 | 'plugin': LOGGER_DEFAULT,
65 | }
66 | }
67 |
68 |
69 | def setup_logging(logname=None):
70 | # configure logging
71 | logging.config.dictConfig(LOG_CONFIG)
72 |
73 | # enable the debug_file in all the loggers if the config says to
74 | if config and 'general' in config and config['general']['debug_logging']:
75 | if logname:
76 | filename = '{}.log'.format(logname)
77 | else:
78 | filename = 'voltron.log'
79 | for name in LOG_CONFIG['loggers']:
80 | h = logging.FileHandler(voltron.env.voltron_dir.path_to(filename), delay=True)
81 | h.setFormatter(logging.Formatter(fmt="%(asctime)s %(levelname)-7s %(filename)12s:%(lineno)-4s %(funcName)20s -- %(message)s"))
82 | logging.getLogger(name).addHandler(h)
83 |
84 | logging.info("======= VOLTRON - DEFENDER OF THE UNIVERSE [debug log] =======")
85 |
86 | return logging.getLogger(logname)
87 |
88 |
89 | # Python 3 shim
90 | if not hasattr(__builtins__, "xrange"):
91 | xrange = range
92 |
93 |
94 | # Setup the Voltron environment
95 | setup_env()
96 |
--------------------------------------------------------------------------------
/voltron/__main__.py:
--------------------------------------------------------------------------------
1 | try:
2 | # for some reason the relative import doesn't work in VSCode's
3 | # interactive debugger, but this does but I'm not sure if there's a
4 | # chance a different voltron could be somewhere in sys.path, so
5 | # let's try the relative import first
6 | from .main import main
7 | except ImportError:
8 | from voltron.main import main
9 |
10 | main()
11 |
--------------------------------------------------------------------------------
/voltron/api.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 | import socket
4 | import select
5 | import threading
6 | import logging
7 | import logging.config
8 | import json
9 | import inspect
10 | import base64
11 | import six
12 |
13 | from collections import defaultdict
14 |
15 | from scruffy.plugin import Plugin
16 |
17 | import voltron
18 | from .plugin import APIPlugin
19 |
20 | log = logging.getLogger('api')
21 |
22 | version = 1.1
23 |
24 |
25 | class InvalidRequestTypeException(Exception):
26 | """
27 | Exception raised when the client is requested to send an invalid request type.
28 | """
29 | pass
30 |
31 |
32 | class InvalidDebuggerHostException(Exception):
33 | """
34 | Exception raised when the debugger host is invalid.
35 | """
36 | pass
37 |
38 |
39 | class InvalidViewNameException(Exception):
40 | """
41 | Exception raised when an invalid view name is specified.
42 | """
43 | pass
44 |
45 |
46 | class InvalidMessageException(Exception):
47 | """
48 | Exception raised when an invalid API message is received.
49 | """
50 | pass
51 |
52 |
53 | class ServerSideOnlyException(Exception):
54 | """
55 | Exception raised when a server-side method is called on an APIMessage
56 | subclass that exists on the client-side.
57 |
58 | See @server_side decorator.
59 | """
60 | pass
61 |
62 |
63 | class ClientSideOnlyException(Exception):
64 | """
65 | Exception raised when a server-side method is called on an APIMessage
66 | subclass that exists on the client-side.
67 |
68 | See @client_side decorator.
69 | """
70 | pass
71 |
72 |
73 | class DebuggerNotPresentException(Exception):
74 | """
75 | Raised when an APIRequest is dispatched without a valid debugger present.
76 | """
77 | pass
78 |
79 |
80 | class NoSuchTargetException(Exception):
81 | """
82 | Raised when an APIRequest specifies an invalid target.
83 | """
84 | pass
85 |
86 |
87 | class TargetBusyException(Exception):
88 | """
89 | Raised when an APIRequest specifies a target that is currently busy and
90 | cannot be queried.
91 | """
92 | pass
93 |
94 |
95 | class MissingFieldError(Exception):
96 | """
97 | Raised when an APIMessage is validated and has a required field missing.
98 | """
99 | pass
100 |
101 |
102 | class NoSuchThreadException(Exception):
103 | """
104 | Raised when the specified thread ID or index does not exist.
105 | """
106 | pass
107 |
108 |
109 | class UnknownArchitectureException(Exception):
110 | """
111 | Raised when the debugger host is running in an unknown architecture.
112 | """
113 | pass
114 |
115 |
116 | class BlockingNotSupportedError(Exception):
117 | """
118 | Raised when a view that does not support blocking connects to a debugger
119 | host that does not support async mode.
120 | """
121 | pass
122 |
123 |
124 | def server_side(func):
125 | """
126 | Decorator to designate an API method applicable only to server-side
127 | instances.
128 |
129 | This allows us to use the same APIRequest and APIResponse subclasses on the
130 | client and server sides without too much confusion.
131 | """
132 | def inner(*args, **kwargs):
133 | if args and hasattr(args[0], 'is_server') and not voltron.debugger:
134 | raise ServerSideOnlyException("This method can only be called on a server-side instance")
135 | return func(*args, **kwargs)
136 | return inner
137 |
138 |
139 | def client_side(func):
140 | """
141 | Decorator to designate an API method applicable only to client-side
142 | instances.
143 |
144 | This allows us to use the same APIRequest and APIResponse subclasses on the
145 | client and server sides without too much confusion.
146 | """
147 | def inner(*args, **kwargs):
148 | if args and hasattr(args[0], 'is_server') and voltron.debugger:
149 | raise ClientSideOnlyException("This method can only be called on a client-side instance")
150 | return func(*args, **kwargs)
151 | return inner
152 |
153 |
154 | def cast_b(val):
155 | if isinstance(val, six.binary_type):
156 | return val
157 | elif isinstance(val, six.text_type):
158 | return val.encode('latin1')
159 | return six.binary_type(val)
160 |
161 |
162 | def cast_s(val):
163 | if type(val) == six.text_type:
164 | return val
165 | elif type(val) == six.binary_type:
166 | return val.decode('latin1')
167 | return six.text_type(val)
168 |
169 |
170 | class APIMessage(object):
171 | """
172 | Top-level API message class.
173 | """
174 | _top_fields = ['type']
175 | _fields = {}
176 | _encode_fields = []
177 |
178 | type = None
179 |
180 | def __init__(self, data=None, *args, **kwargs):
181 | # process any data that was passed in
182 | if data:
183 | self.from_json(data)
184 |
185 | # any other kwargs are treated as field values
186 | for field in kwargs:
187 | setattr(self, field, kwargs[field])
188 |
189 | def __str__(self):
190 | """
191 | Return a string (JSON) representation of the API message properties.
192 | """
193 | return self.to_json()
194 |
195 | def to_dict(self):
196 | """
197 | Return a transmission-safe dictionary representation of the API message properties.
198 | """
199 | d = {field: getattr(self, field) for field in self._top_fields if hasattr(self, field)}
200 |
201 | # set values of data fields
202 | d['data'] = {}
203 | for field in self._fields:
204 | if hasattr(self, field):
205 | # base64 encode the field for transmission if necessary
206 | if field in self._encode_fields:
207 | val = getattr(self, field)
208 | if val:
209 | val = cast_s(base64.b64encode(cast_b(val)))
210 | d['data'][field] = val
211 | else:
212 | d['data'][field] = getattr(self, field)
213 |
214 | return d
215 |
216 | def from_dict(self, d):
217 | """
218 | Initialise an API message from a transmission-safe dictionary.
219 | """
220 | for key in d:
221 | if key == 'data':
222 | for dkey in d['data']:
223 | if dkey in self._encode_fields:
224 | setattr(self, str(dkey), base64.b64decode(d['data'][dkey]))
225 | else:
226 | setattr(self, str(dkey), d['data'][dkey])
227 | else:
228 | setattr(self, str(key), d[key])
229 |
230 | def to_json(self):
231 | """
232 | Return a JSON representation of the API message properties.
233 | """
234 | return json.dumps(self.to_dict())
235 |
236 | def from_json(self, data):
237 | """
238 | Initialise an API message from a JSON representation.
239 | """
240 | try:
241 | d = json.loads(data)
242 | except ValueError:
243 | raise InvalidMessageException()
244 | self.from_dict(d)
245 |
246 | def __getattr__(self, name):
247 | """
248 | Attribute accessor.
249 |
250 | If a defined field is requested that doesn't have a value set,
251 | return None.
252 | """
253 | if name in self._fields:
254 | return None
255 |
256 | def validate(self):
257 | """
258 | Validate the message.
259 |
260 | Ensure all the required fields are present and not None.
261 | """
262 | required_fields = list(filter(lambda x: self._fields[x], self._fields.keys()))
263 | for field in (self._top_fields + required_fields):
264 | if not hasattr(self, field) or hasattr(self, field) and getattr(self, field) == None:
265 | raise MissingFieldError(field)
266 |
267 |
268 | class APIRequest(APIMessage):
269 | """
270 | An API request object. Contains functions and accessors common to all API
271 | request types.
272 |
273 | Subclasses of APIRequest are used on both the client and server sides. On
274 | the server side they are instantiated by Server's `handle_request()`
275 | method. On the client side they are instantiated by whatever class is doing
276 | the requesting (probably a view class).
277 | """
278 | _top_fields = ['type', 'request', 'block', 'timeout']
279 | _fields = {}
280 |
281 | type = 'request'
282 | request = None
283 | block = False
284 | timeout = 10
285 |
286 | response = None
287 | wait_event = None
288 | timed_out = False
289 |
290 | @server_side
291 | def dispatch(self):
292 | """
293 | In concrete subclasses this method will actually dispatch the request
294 | to the debugger host and return a response. In this case it raises an
295 | exception.
296 | """
297 | raise NotImplementedError("Subclass APIRequest")
298 |
299 | @server_side
300 | def wait(self):
301 | """
302 | Wait for the request to be dispatched.
303 | """
304 | self.wait_event = threading.Event()
305 | timeout = int(self.timeout) if self.timeout else None
306 | self.timed_out = not self.wait_event.wait(timeout)
307 |
308 | def signal(self):
309 | """
310 | Signal that the request has been dispatched and can return.
311 | """
312 | self.wait_event.set()
313 |
314 |
315 | class APIBlockingRequest(APIRequest):
316 | """
317 | An API request that blocks by default.
318 | """
319 | block = True
320 |
321 |
322 | class APIResponse(APIMessage):
323 | """
324 | An API response object. Contains functions and accessors common to all API
325 | response types.
326 |
327 | Subclasses of APIResponse are used on both the client and server sides. On
328 | the server side they are instantiated by the APIRequest's `dispatch` method
329 | in order to serialise and send to the client. On the client side they are
330 | instantiated by the Client class and returned by `send_request`.
331 | """
332 | _top_fields = ['type', 'status']
333 | _fields = {}
334 |
335 | type = 'response'
336 | status = None
337 |
338 | @property
339 | def is_success(self):
340 | return self.status == 'success'
341 |
342 | @property
343 | def is_error(self):
344 | return self.status == 'error'
345 |
346 | def __repr__(self):
347 | return "<%s: success = %s, error = %s, body: %s>" % (
348 | str(self.__class__),
349 | self.is_success,
350 | self.is_error,
351 | {f: getattr(self, f) for f in self._top_fields + list(self._fields.keys())}
352 | )
353 |
354 |
355 | class APISuccessResponse(APIResponse):
356 | """
357 | A generic API success response.
358 | """
359 | status = 'success'
360 |
361 |
362 | class APIErrorResponse(APIResponse):
363 | """
364 | A generic API error response.
365 | """
366 | _fields = {'code': True, 'message': True}
367 |
368 | status = 'error'
369 |
370 | @property
371 | def timed_out(self):
372 | return self.code == APITimedOutErrorResponse.code
373 |
374 |
375 | class APIGenericErrorResponse(APIErrorResponse):
376 | code = 0x1000
377 | message = "An error occurred"
378 |
379 | def __init__(self, message=None):
380 | super(APIGenericErrorResponse, self).__init__()
381 | if message:
382 | self.message = message
383 |
384 |
385 | class APIInvalidRequestErrorResponse(APIErrorResponse):
386 | code = 0x1001
387 | message = "Invalid API request"
388 |
389 |
390 | class APIPluginNotFoundErrorResponse(APIErrorResponse):
391 | code = 0x1002
392 | message = "Plugin was not found for request"
393 |
394 |
395 | class APIDebuggerHostNotSupportedErrorResponse(APIErrorResponse):
396 | code = 0x1003
397 | message = "The targeted debugger host is not supported by this plugin"
398 |
399 |
400 | class APITimedOutErrorResponse(APIErrorResponse):
401 | code = 0x1004
402 | message = "The request timed out"
403 |
404 |
405 | class APIDebuggerNotPresentErrorResponse(APIErrorResponse):
406 | code = 0x1004
407 | message = "No debugger host was found"
408 |
409 |
410 | class APINoSuchTargetErrorResponse(APIErrorResponse):
411 | code = 0x1005
412 | message = "No such target"
413 |
414 |
415 | class APITargetBusyErrorResponse(APIErrorResponse):
416 | code = 0x1006
417 | message = "Target busy"
418 |
419 |
420 | class APIMissingFieldErrorResponse(APIGenericErrorResponse):
421 | code = 0x1007
422 | message = "Missing field"
423 |
424 |
425 | class APIEmptyResponseErrorResponse(APIGenericErrorResponse):
426 | code = 0x1008
427 | message = "Empty response"
428 |
429 |
430 | class APIServerNotRunningErrorResponse(APIGenericErrorResponse):
431 | code = 0x1009
432 | message = "Server is not running"
433 |
--------------------------------------------------------------------------------
/voltron/colour.py:
--------------------------------------------------------------------------------
1 | import re
2 | ESCAPES = {
3 | # reset
4 | 'reset': 0,
5 |
6 | # colours
7 | 'grey': 30,
8 | 'red': 31,
9 | 'green': 32,
10 | 'yellow': 33,
11 | 'blue': 34,
12 | 'magenta': 35,
13 | 'cyan': 36,
14 | 'white': 37,
15 |
16 | # background
17 | 'b_grey': 40,
18 | 'b_red': 41,
19 | 'b_green': 42,
20 | 'b_yellow': 43,
21 | 'b_blue': 44,
22 | 'b_magenta': 45,
23 | 'b_cyan': 46,
24 | 'b_white': 47,
25 |
26 | # attributes
27 | 'a_bold': 1,
28 | 'a_dark': 2,
29 | 'a_underline': 4,
30 | 'a_blink': 5,
31 | 'a_reverse': 7,
32 | 'a_concealed': 8
33 | }
34 | ESC_TEMPLATE = '\033[{}m'
35 |
36 | def escapes():
37 | return ESCAPES
38 |
39 | def get_esc(name):
40 | return ESCAPES[name]
41 |
42 | def fmt_esc(name):
43 | return ESC_TEMPLATE.format(escapes()[name])
44 |
45 | FMT_ESCAPES = dict((k, fmt_esc(k)) for k in ESCAPES)
46 |
47 |
48 | def uncolour(text):
49 | """
50 | Remove ANSI color/style sequences from a string. The set of all possible
51 | ANSI sequences is large, so does not try to strip every possible one. But
52 | does strip some outliers by other ANSI colorizers in the wild. Those
53 | include `\x1b[K` (aka EL or erase to end of line) and `\x1b[m`, a terse
54 | version of the more common `\x1b[0m`.
55 |
56 | Stolen from: https://github.com/jonathaneunice/colors/blob/master/colors/colors.py
57 | """
58 | text = re.sub('\x1b\\[(K|.*?m)', '', text)
59 | return text
60 |
--------------------------------------------------------------------------------
/voltron/config/default.cfg:
--------------------------------------------------------------------------------
1 | general:
2 | debug_logging: false
3 | server:
4 | listen:
5 | domain: "~/.voltron/sock"
6 | tcp:
7 | - 127.0.0.1
8 | - 5555
9 | view:
10 | #api_url: "http+unix://~%2f.voltron%2fsock/api/request",
11 | api_url: http://localhost:5555/api/request
12 | reconnect: true
13 | all_views:
14 | clear: true
15 | update_on: stop
16 | format:
17 | pygments_style: volarized
18 | pygments_formatter: terminal256
19 | header:
20 | show: true
21 | pad: " "
22 | colour: blue
23 | bg_colour: grey
24 | attrs: []
25 | label_left:
26 | name: info
27 | colour: blue
28 | bg_colour: grey
29 | attrs: []
30 | label_right:
31 | name: title
32 | colour: white
33 | bg_colour: grey
34 | attrs:
35 | - bold
36 | footer:
37 | show: false
38 | pad: " "
39 | colour: blue
40 | bg_colour: grey
41 | attrs: []
42 | label_left:
43 | name:
44 | colour: blue
45 | bg_colour: grey
46 | attrs: []
47 | label_right:
48 | name:
49 | colour: blue
50 | bg_colour: grey
51 | attrs:
52 | - bold
53 | pad:
54 | pad_right: 0
55 | pad_bottom: 0
56 | keymap:
57 | q: exit
58 | p: page_up
59 | n: page_down
60 | KEY_PPAGE: page_up
61 | KEY_NPAGE: page_down
62 | KEY_UP: line_up
63 | KEY_DOWN: line_down
64 | KEY_ENTER: reset
65 | register_view:
66 | format:
67 | label_format: "{0}"
68 | label_func: str_upper
69 | label_colour: green
70 | label_colour_en: true
71 | value_format: "{0:0=16X}"
72 | value_func:
73 | value_colour: reset
74 | value_colour_mod: red
75 | value_colour_en: true
76 | format_name:
77 | addr_colour: blue
78 | divider_colour: green
79 | string_colour: white
80 | symbol_colour: cyan
81 | sections:
82 | - general
83 | orientation: vertical
84 | disassembly_view:
85 | header:
86 | show: true
87 | label_left:
88 | name: title
89 | colour: white
90 | bg_colour: grey
91 | attrs:
92 | - bold
93 | label_right:
94 | name:
95 | stack_view:
96 | header:
97 | show: false
98 | footer:
99 | show: true
100 | label_left:
101 | name: title
102 | colour: white
103 | bg_colour: grey
104 | attrs:
105 | - bold
106 | label_right:
107 | name: info
108 | colour: blue
109 | bg_colour: grey
110 | attrs: []
111 | format:
112 | addr_format: "{0:0=16X}"
113 | memory_view:
114 | header:
115 | show: false
116 | footer:
117 | show: true
118 | label_left:
119 | name: title
120 | colour: white
121 | bg_colour: grey
122 | attrs:
123 | - bold
124 | label_right:
125 | name: info
126 | colour: blue
127 | bg_colour: grey
128 | attrs: []
129 | format:
130 | addr_format: "{0:0=16X}"
131 | backtrace_view:
132 | header:
133 | show: false
134 | footer:
135 | show: true
136 | label_left:
137 | name: title
138 | colour: white
139 | bg_colour: grey
140 | attrs:
141 | - bold
142 | label_right:
143 | name: info
144 | colour: blue
145 | bg_colour: grey
146 | attrs: []
147 | breakpoints_view:
148 | format:
149 | row: "{disabled}{one_shot}{t.bold}{id}{t.normal} {hit}{t.blue}0x{address:0=16X}{t.normal}
150 | {t.green}h:{t.normal}{hit_count:<4} {t.cyan}{name}{t.normal}"
151 | disabled: "{t.red}"
152 | one_shot: "{t.underline}"
153 | hit: "{t.standout}"
154 | some_named_stack_view:
155 | header:
156 | show: true
157 | label_left:
158 | name: title
159 | colour: red
160 | bg_colour: grey
161 | attrs:
162 | - bold
163 | label_right:
164 | name: info
165 | colour: white
166 | bg_colour: grey
167 | attrs: []
168 | footer:
169 | show: false
170 | console:
171 | prompt:
172 | format: "{red}voltron>{reset} "
173 |
--------------------------------------------------------------------------------
/voltron/dbg.py:
--------------------------------------------------------------------------------
1 | try:
2 | import capstone
3 | except:
4 | capstone = None
5 |
6 | from voltron.api import *
7 | from voltron.plugin import *
8 |
9 |
10 | class InvalidPointerError(Exception):
11 | """
12 | Raised when attempting to dereference an invalid pointer.
13 | """
14 | pass
15 |
16 |
17 | def validate_target(func, *args, **kwargs):
18 | """
19 | A decorator that ensures that the specified target_id exists and
20 | is valid.
21 |
22 | Expects the target ID to be either the 'target_id' param in kwargs,
23 | or the first positional parameter.
24 |
25 | Raises a NoSuchTargetException if the target does not exist.
26 | """
27 | def inner(self, *args, **kwargs):
28 | # find the target param
29 | target_id = None
30 | if 'target_id' in kwargs and kwargs['target_id'] != None:
31 | target_id = kwargs['target_id']
32 | else:
33 | target_id = 0
34 |
35 | # if there was a target specified, check that it's valid
36 | if not self.target_is_valid(target_id):
37 | raise NoSuchTargetException()
38 |
39 | # call the function
40 | return func(self, *args, **kwargs)
41 | return inner
42 |
43 |
44 | def validate_busy(func, *args, **kwargs):
45 | """
46 | A decorator that raises an exception if the specified target is busy.
47 |
48 | Expects the target ID to be either the 'target_id' param in kwargs,
49 | or the first positional parameter.
50 |
51 | Raises a TargetBusyException if the target does not exist.
52 | """
53 | def inner(self, *args, **kwargs):
54 | # find the target param
55 | target_id = None
56 | if 'target_id' in kwargs and kwargs['target_id'] != None:
57 | target_id = kwargs['target_id']
58 | else:
59 | target_id = 0
60 |
61 | # if there was a target specified, ensure it's not busy
62 | if self.target_is_busy(target_id):
63 | raise TargetBusyException()
64 |
65 | # call the function
66 | return func(self, *args, **kwargs)
67 | return inner
68 |
69 |
70 | def lock_host(func, *args, **kwargs):
71 | """
72 | A decorator that acquires a lock before accessing the debugger to
73 | avoid API locking related errors with the debugger host.
74 | """
75 | def inner(self, *args, **kwargs):
76 | self.host_lock.acquire()
77 | try:
78 | res = func(self, *args, **kwargs)
79 | self.host_lock.release()
80 | except Exception as e:
81 | self.host_lock.release()
82 | raise e
83 | return res
84 | return inner
85 |
86 |
87 | class DebuggerAdaptor(object):
88 | """
89 | Base debugger adaptor class. Debugger adaptors implemented in plugins for
90 | specific debuggers inherit from this.
91 | """
92 |
93 | reg_names = {
94 | "x86": {"pc": "eip", "sp": "esp"},
95 | "x86_64": {"pc": "rip", "sp": "rsp"},
96 | "arm": {"pc": "pc", "sp": "sp"},
97 | "armv6": {"pc": "pc", "sp": "sp"},
98 | "armv7": {"pc": "pc", "sp": "sp"},
99 | "armv7s": {"pc": "pc", "sp": "sp"},
100 | "arm64": {"pc": "pc", "sp": "sp"},
101 | "powerpc": {"pc": "pc", "sp": "r1"},
102 | }
103 | cs_archs = {}
104 | if capstone:
105 | cs_archs = {
106 | "x86": (capstone.CS_ARCH_X86, capstone.CS_MODE_32),
107 | "x86_64": (capstone.CS_ARCH_X86, capstone.CS_MODE_64),
108 | "arm": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM),
109 | "armv6": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM),
110 | "armv7": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM),
111 | "armv7s": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM),
112 | "arm64": (capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM),
113 | "powerpc": (capstone.CS_ARCH_PPC, capstone.CS_MODE_32),
114 | }
115 |
116 | def __init__(self, *args, **kwargs):
117 | self.listeners = []
118 |
119 | def target_exists(self, target_id=0):
120 | """
121 | Returns True or False indicating whether or not the specified
122 | target is present and valid.
123 |
124 | `target_id` is a target ID (or None for the first target)
125 | """
126 | try:
127 | target = self.target(target_id=target_id)
128 | except Exception as e:
129 | log.error("Exception checking if target exists: {} {}".format(type(e), e))
130 | return False
131 | return target is not None
132 |
133 | def target_is_valid(self, target_id=0):
134 | """
135 | Returns True or False indicating whether or not the specified
136 | target is present and valid.
137 |
138 | `target_id` is a target ID (or None for the first target)
139 | """
140 | try:
141 | target = self.target(target_id=target_id)
142 | except:
143 | return False
144 | return target['state'] != "invalid"
145 |
146 | def target_is_busy(self, target_id=0):
147 | """
148 | Returns True or False indicating whether or not the specified
149 | target is busy.
150 |
151 | `target_id` is a target ID (or None for the first target)
152 | """
153 | try:
154 | target = self.target(target_id=target_id)
155 | except:
156 | raise NoSuchTargetException()
157 | return target['state'] == "running"
158 |
159 | def add_listener(self, callback, state_changes=["stopped"]):
160 | """
161 | Add a listener for state changes.
162 | """
163 | self.listeners.append({"callback": callback, "state_changes": state_changes})
164 |
165 | def remove_listener(self, callback):
166 | """
167 | Remove a listener.
168 | """
169 | listeners = filter(lambda x: x['callback'] == callback, self.listeners)
170 | for l in listeners:
171 | self.listeners.remove(l)
172 |
173 | def update_state(self):
174 | """
175 | Notify all the listeners (probably `wait` plugins) that the state
176 | has changed.
177 |
178 | This is called by the debugger's stop-hook.
179 | """
180 | for listener in self.listeners:
181 | listener['callback']()
182 |
183 | def register_command_plugin(self, name, cls):
184 | pass
185 |
186 | def capabilities(self):
187 | """
188 | Return a list of the debugger's capabilities.
189 |
190 | Thus far only the 'async' capability is supported. This indicates
191 | that the debugger host can be queried from a background thread,
192 | and that views can use non-blocking API requests without queueing
193 | requests to be dispatched next time the debugger stops.
194 | """
195 | return []
196 |
197 | def pc(self, target_id=0, thread_id=None):
198 | return self.program_counter(target_id, thread_id)
199 |
200 | def sp(self, target_id=0, thread_id=None):
201 | return self.stack_pointer(target_id, thread_id)
202 |
203 | def disassemble_capstone(self, target_id=0, address=None, count=None):
204 | """
205 | Disassemble with capstone.
206 | """
207 | target = self.target(target_id)
208 | if not address:
209 | pc_name, address = self.pc()
210 |
211 | mem = self.memory(address, count * 16, target_id=target_id)
212 |
213 | md = capstone.Cs(*self.cs_archs[target['arch']])
214 | output = []
215 | for idx, i in enumerate(md.disasm(mem, address)):
216 | if idx >= count:
217 | break
218 | output.append("0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str))
219 |
220 | return '\n'.join(output)
221 |
222 |
223 | class DebuggerCommand (object):
224 | """
225 | The `voltron` command in the debugger.
226 | """
227 | def __init__(self, *args, **kwargs):
228 | super(DebuggerCommand, self).__init__(*args, **kwargs)
229 | self.adaptor = voltron.debugger
230 | self.registered = False
231 |
232 | def handle_command(self, command):
233 | global log
234 | if 'debug' in command:
235 | if 'enable' in command:
236 | log.setLevel(logging.DEBUG)
237 | print("Debug logging enabled")
238 | elif 'disable' in command:
239 | log.setLevel(logging.INFO)
240 | print("Debug logging disabled")
241 | else:
242 | enabled = "enabled" if log.getEffectiveLevel() == logging.DEBUG else "disabled"
243 | print("Debug logging is currently " + enabled)
244 | elif 'init' in command:
245 | self.register_hooks()
246 | elif 'stopped' in command or 'update' in command:
247 | self.adaptor.update_state()
248 | voltron.server.dispatch_queue()
249 | else:
250 | print("Usage: voltron ")
251 |
--------------------------------------------------------------------------------
/voltron/entry.py:
--------------------------------------------------------------------------------
1 | """
2 | This is the main entry point for Voltron from the debugger host's perspective.
3 | This file is loaded into the debugger through whatever means the given host
4 | supports.
5 |
6 | LLDB:
7 |
8 | (lldb) command script import /path/to/voltron/entry.py
9 |
10 | GDB:
11 |
12 | (gdb) source /path/to/voltron/entry.py
13 |
14 | VDB:
15 |
16 | (vdb) script /path/to/voltron/entry.py
17 |
18 | WinDbg/CDB (via PyKD):
19 |
20 | > .load pykd.pyd
21 | > !py --global C:\path\to\voltron\entry.py
22 | """
23 |
24 | log = None
25 |
26 | try:
27 | # fix path if it's clobbered by brew
28 | import sys
29 | if sys.platform == 'darwin':
30 | py_base = '/System/Library/Frameworks/Python.framework/Versions/2.7/'
31 | new_path = ['lib/python27.zip', 'lib/python2.7', 'lib/python2.7/plat-darwin', 'lib/python2.7/plat-mac',
32 | 'lib/python2.7/plat-mac/lib-scriptpackages', 'Extras/lib/python', 'lib/python2.7/lib-tk',
33 | 'lib/python2.7/lib-old', 'lib/python2.7/lib-dynload']
34 | sys.path = [p for p in sys.path if 'Cellar' not in p] + [py_base + p for p in new_path]
35 | except:
36 | pass
37 |
38 | try:
39 | import logging
40 | import os
41 | import sys
42 | blessed = None
43 | import blessed
44 |
45 | # add vtrace to the path so that dbg_vdb.py can import from vdb/vtrace.
46 | if "vtrace" in locals():
47 | def parent_directory(the_path):
48 | return os.path.abspath(os.path.join(the_path, os.pardir))
49 |
50 | def add_vdb_to_path(vtrace):
51 | sys.path.append(parent_directory(parent_directory(vtrace.__file__)))
52 |
53 | add_vdb_to_path(vtrace)
54 | else:
55 | pass
56 |
57 | import voltron
58 | from voltron.plugin import pm
59 | from voltron.core import Server
60 |
61 | log = voltron.setup_logging('debugger')
62 |
63 | # figure out in which debugger host we are running
64 | args = []
65 | host = None
66 | try:
67 | import lldb
68 | host = "lldb"
69 |
70 | def invoke(*args):
71 | voltron.command._invoke(*args)
72 | except ImportError:
73 | pass
74 | try:
75 | import gdb
76 | host = "gdb"
77 | except ImportError:
78 | pass
79 | try:
80 | import pykd
81 | host = "windbg"
82 | except:
83 | pass
84 | if "vtrace" in locals():
85 | host = "vdb"
86 | args = [db]
87 | if not host:
88 | raise Exception("No debugger host is present")
89 |
90 | # register any plugins that were loaded
91 | pm.register_plugins()
92 |
93 | # get the debugger plugin for the host we're in
94 | plugin = pm.debugger_plugin_for_host(host)
95 |
96 | if not voltron.server:
97 | # set up command and adaptor instances
98 | voltron.debugger = plugin.adaptor_class(*args)
99 | voltron.command = plugin.command_class(*args)
100 |
101 | # register command plugins now that we have a debugger host loaded
102 | pm.register_command_plugins()
103 |
104 | # create and start the voltron server
105 | voltron.server = Server()
106 | voltron.server.start()
107 |
108 | print(blessed.Terminal().bold_red("Voltron loaded."))
109 | if host == 'lldb' and not voltron.command.registered:
110 | print("Run `voltron init` after you load a target.")
111 | except Exception as e:
112 | import traceback
113 | msg = ("An error occurred while loading Voltron:\n\n{}"
114 | "\nPlease ensure Voltron is installed correctly per the documentation: "
115 | "https://github.com/snare/voltron/wiki/Installation").format(traceback.format_exc())
116 | if blessed:
117 | msg = blessed.Terminal().bold_red(msg)
118 | if log:
119 | log.exception("Exception raised while loading Voltron")
120 | print(msg)
121 |
--------------------------------------------------------------------------------
/voltron/lexers.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from pygments.lexer import RegexLexer, include, bygroups, using, DelegatingLexer
4 | from pygments.lexers import get_lexer_by_name
5 | from pygments.token import *
6 |
7 |
8 | class DisassemblyLexer(RegexLexer):
9 | """
10 | For Nasm (Intel) disassembly from LLDB.
11 |
12 | Based on the NasmLexer included with Pygments
13 | """
14 | name = 'LLDB Intel syntax disassembly'
15 | aliases = ['lldb_intel']
16 | filenames = []
17 | mimetypes = []
18 |
19 | identifier = r'[]*'
20 | hexn = r'(?:0[xX][0-9a-f]+|$0[0-9a-f]*|[0-9]+[0-9a-f]*h)'
21 | octn = r'[0-7]+q'
22 | binn = r'[01]+b'
23 | decn = r'[0-9]+'
24 | floatn = decn + r'\.e?' + decn
25 | string = r'"(\\"|[^"\n])*"|' + r"'(\\'|[^'\n])*'|" + r"`(\\`|[^`\n])*`"
26 | declkw = r'(?:res|d)[bwdqt]|times'
27 | register = (r'r[0-9]+[bwd]{0,1}|'
28 | r'[a-d][lh]|[er]?[a-d]x|[er]?[sbi]p|[er]?[sd]i|[c-gs]s|st[0-7]|'
29 | r'mm[0-7]|cr[0-4]|dr[0-367]|tr[3-7]|.mm\d+')
30 | wordop = r'seg|wrt|strict'
31 | type = r'byte|[dq]?word|ptr|xmmword|opaque'
32 |
33 | flags = re.IGNORECASE | re.MULTILINE
34 | tokens = {
35 | 'root': [
36 | (identifier + '`' + identifier, Name.Function),
37 | ('->', Generic.Prompt),
38 | include('whitespace'),
39 | (r'^\s*%', Comment.Preproc, 'preproc'),
40 | (identifier + ':', Name.Label),
41 | (r'(%s)(\s+)(equ)' % identifier,
42 | bygroups(Name.Constant, Keyword.Declaration, Keyword.Declaration),
43 | 'instruction-args'),
44 | (declkw, Keyword.Declaration, 'instruction-args'),
45 | (identifier, Keyword.Declaration, 'instruction-args'),
46 | (r' *' + hexn, Name.Label),
47 | (r'[:]', Text),
48 | (r'^->', Error),
49 | (r'[\r\n]+', Text)
50 | ],
51 | 'instruction-args': [
52 | (register, Name.Builtin),
53 | (string, String),
54 | (hexn, Number.Hex),
55 | (octn, Number.Oct),
56 | (binn, Number.Bin),
57 | (floatn, Number.Float),
58 | (decn, Number.Integer),
59 | include('punctuation'),
60 | (identifier, Name.Variable),
61 | (r'[\r\n]+', Text, '#pop'),
62 | include('whitespace')
63 | ],
64 | 'preproc': [
65 | (r'[^;\n]+', Comment.Preproc),
66 | (r';.*?\n', Comment.Single, '#pop'),
67 | (r'\n', Comment.Preproc, '#pop'),
68 | ],
69 | 'whitespace': [
70 | (r'\n', Text),
71 | (r'[ \t]+', Text),
72 | (r';.*', Comment.Single),
73 | (r'#.*', Comment.Single)
74 | ],
75 | 'punctuation': [
76 | (r'[,():\[\]]+', Punctuation),
77 | (r'[&|^<>+*/%~-]+', Operator),
78 | (r'[$]+', Keyword.Constant),
79 | (wordop, Operator.Word),
80 | (type, Keyword.Type)
81 | ],
82 | }
83 |
84 |
85 | class LLDBIntelLexer(DisassemblyLexer):
86 | name = 'LLDB Intel syntax disassembly'
87 | aliases = ['lldb_intel']
88 |
89 |
90 | class LLDBATTLexer(DisassemblyLexer):
91 | name = 'LLDB AT&T syntax disassembly'
92 | aliases = ['lldb_att']
93 |
94 |
95 | class GDBATTLexer(DisassemblyLexer):
96 | name = 'GDB AT&T syntax disassembly'
97 | aliases = ['gdb_att']
98 |
99 |
100 | class GDBIntelLexer(DisassemblyLexer):
101 | name = 'GDB Intel syntax disassembly'
102 | aliases = ['gdb_intel']
103 |
104 |
105 | class VDBATTLexer(DisassemblyLexer):
106 | name = 'VDB AT&T syntax disassembly'
107 | aliases = ['vdb_att']
108 |
109 |
110 | class CapstoneIntelLexer(DisassemblyLexer):
111 | name = 'Capstone Intel syntax disassembly'
112 | aliases = ['capstone_intel']
113 |
114 |
115 | class VDBIntelLexer(RegexLexer):
116 | """
117 | For Nasm (Intel) disassembly from VDB.
118 |
119 | Based on the LLDBIntelLexer above.
120 | major difference is the raw instruction hex after the instruction address.
121 |
122 | example:
123 | rip 0x000000000056eb4f: 4885ff test rdi,rdi ;0x7f4f8740ca50,0x7f4f8740ca50
124 | 0x000000000056eb52: 740f jz 0x0056eb63
125 | """
126 | name = 'VDB Intel syntax disassembly'
127 | aliases = ['vdb_intel']
128 | filenames = []
129 | mimetypes = []
130 |
131 | space = r'[ \t]+'
132 | identifier = r'[]*'
133 | hexn = r'(?:0[xX][0-9a-f]+|$0[0-9a-f]*|[0-9]+[0-9a-f]*h)' # hex number
134 | hexr = r'(?:[0-9a-f]+)' # hex raw (no leader/trailer)
135 | octn = r'[0-7]+q'
136 | binn = r'[01]+b'
137 | decn = r'[0-9]+'
138 | floatn = decn + r'\.e?' + decn
139 | string = r'"(\\"|[^"\n])*"|' + r"'(\\'|[^'\n])*'|" + r"`(\\`|[^`\n])*`"
140 | register = (r'r[0-9]+[bwd]{0,1}|'
141 | r'[a-d][lh]|[er]?[a-d]x|[er]?[sbi]p|[er]?[sd]i|[c-gs]s|st[0-7]|'
142 | r'mm[0-7]|cr[0-4]|dr[0-367]|tr[3-7]|.mm\d*')
143 | wordop = r'seg|wrt|strict'
144 | type = r'byte|[dq]?word|ptr'
145 |
146 | flags = re.IGNORECASE | re.MULTILINE
147 | tokens = {
148 | 'root': [
149 | (r'^(%s)(%s)(%s)(: )(%s)(%s)' % (register, space, hexn, hexr, space),
150 | bygroups(Name.Builtin, Text, Name.Label, Text, Number.Hex, Text),
151 | "instruction"),
152 | (r'^(%s)(%s)(: )(%s)(%s)' % (space, hexn, hexr, space),
153 | bygroups(Text, Name.Label, Text, Number.Hex, Text),
154 | "instruction")
155 | ],
156 | 'instruction': [
157 | (space, Text),
158 | (r"(rep[a-z]*)( )", bygroups(Name.Function, Text)),
159 | (r"(%s)" % identifier, Name.Function, ("#pop", "instruction-args")),
160 | ],
161 | 'instruction-args': [
162 | (space, Text),
163 | (string, String),
164 | (hexn, Number.Hex),
165 | (octn, Number.Oct),
166 | (binn, Number.Bin),
167 | (floatn, Number.Float),
168 | (decn, Number.Integer),
169 | include('punctuation'),
170 | (register, Name.Builtin),
171 | (identifier, Name.Variable),
172 | (r'[\r\n]+', Text, '#pop'),
173 | (r';', Text, ("#pop", 'comment')),
174 | ],
175 | 'comment': [
176 | (space, Text),
177 | (string, Comment.Single),
178 | (hexn, Number.Hex),
179 | (octn, Number.Oct),
180 | (binn, Number.Bin),
181 | (floatn, Number.Float),
182 | (decn, Number.Integer),
183 | include('punctuation'),
184 | (register, Name.Builtin),
185 | (identifier, Name.Variable),
186 | (r'[\r\n]+', Text, '#pop'),
187 | ],
188 | 'punctuation': [
189 | (r'[,():\[\]]+', Punctuation),
190 | (r'[&|^<>+*/%~-]+', Operator),
191 | (r'[$]+', Keyword.Constant),
192 | (wordop, Operator.Word),
193 | (type, Keyword.Type)
194 | ],
195 | }
196 |
197 |
198 | class WinDbgIntelLexer(RegexLexer):
199 | name = 'WinDbg Intel syntax disassembly'
200 | aliases = ['windbg_intel']
201 | filenames = []
202 | mimetypes = []
203 |
204 | identifier = r'[]*'
205 | hexn = r'(0[xX])?([0-9a-f]+|$0[0-9a-f`]*|[0-9]+[0-9a-f]*h)'
206 | addr = r'(0[xX])?([0-9a-f`]+|$0[0-9a-f`]*|[0-9]+[0-9a-f`]*h)'
207 | octn = r'[0-7]+q'
208 | binn = r'[01]+b'
209 | decn = r'[0-9]+'
210 | floatn = decn + r'\.e?' + decn
211 | string = r'"(\\"|[^"\n])*"|' + r"'(\\'|[^'\n])*'|" + r"`(\\`|[^`\n])*`"
212 | declkw = r'(?:res|d)[bwdqt]|times'
213 | register = (r'r[0-9]+?[bwd]{0,1}|'
214 | r'[a-d][lh]|[er]?[a-d]x|[er]?[sbi]p|[er]?[sd]i|[c-gs]s|st[0-7]|'
215 | r'mm[0-7]|cr[0-4]|dr[0-367]|tr[3-7]|.mm\d*')
216 | wordop = r'seg|wrt|strict'
217 | type = r'byte|[dq]?word|ptr'
218 | func = r'[a-zA-Z]*\!?'
219 |
220 | flags = re.IGNORECASE | re.MULTILINE
221 | tokens = {
222 | 'root': [
223 | (addr, Number.Hex, 'instruction-line'),
224 | include('whitespace'),
225 | (identifier, Name.Class, 'label'),
226 | (r'[:]', Text),
227 | (r'[\r\n]+', Text)
228 | ],
229 | 'instruction-line': [
230 | (r' ', Text),
231 | (hexn, Text, 'instruction'),
232 | ],
233 | 'instruction': [
234 | include('whitespace'),
235 | (r'(%s)(\s+)(equ)' % identifier,
236 | bygroups(Name.Constant, Keyword.Declaration, Keyword.Declaration),
237 | 'instruction-args'),
238 | (declkw, Keyword.Declaration, 'instruction-args'),
239 | (identifier, Name.Function, 'instruction-args'),
240 | ],
241 | 'label': [
242 | (r'[!+]', Operator),
243 | (identifier, Name.Function),
244 | (hexn, Number.Hex),
245 | (r'[:]', Text, '#pop'),
246 |
247 | ],
248 | 'instruction-args': [
249 | (string, String),
250 | include('punctuation'),
251 | (register, Name.Builtin),
252 | include('label'),
253 | (identifier, Name.Variable),
254 | (r'[\r\n]+', Text, '#pop:3'),
255 | include('whitespace'),
256 | (hexn, Number.Hex),
257 | (addr, Number.Hex),
258 | (octn, Number.Oct),
259 | (binn, Number.Bin),
260 | (floatn, Number.Float),
261 | (decn, Number.Integer),
262 | ],
263 | 'preproc': [
264 | (r'[^;\n]+', Comment.Preproc),
265 | (r';.*?\n', Comment.Single, '#pop'),
266 | (r'\n', Comment.Preproc, '#pop'),
267 | ],
268 | 'whitespace': [
269 | (r'\n', Text),
270 | (r'[ \t]+', Text),
271 | (r';.*', Comment.Single),
272 | (r'#.*', Comment.Single)
273 | ],
274 | 'punctuation': [
275 | (r'[,():\[\]]+', Punctuation),
276 | (r'[&|^<>+*/%~-]+', Operator),
277 | (r'[$]+', Keyword.Constant),
278 | (wordop, Operator.Word),
279 | (type, Keyword.Type)
280 | ],
281 | }
282 |
283 |
284 | class WinDbgATTLexer(WinDbgIntelLexer):
285 | name = 'WinDbg ATT syntax disassembly'
286 | aliases = ['windbg_att']
287 |
--------------------------------------------------------------------------------
/voltron/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import argparse
3 | import logging
4 | import traceback
5 | import logging
6 | import logging.config
7 |
8 | import voltron
9 | from .view import *
10 | from .core import *
11 |
12 | log = logging.getLogger('main')
13 |
14 |
15 | def main(debugger=None):
16 | voltron.setup_logging('main')
17 |
18 | # Set up command line arg parser
19 | parser = argparse.ArgumentParser()
20 | parser.register('action', 'parsers', AliasedSubParsersAction)
21 | parser.add_argument('--debug', '-d', action='store_true', help='print debug logging')
22 | parser.add_argument('-o', action='append', help='override config variable', default=[])
23 | top_level_sp = parser.add_subparsers(title='subcommands', description='valid subcommands', dest='subcommand')
24 | top_level_sp.required = True
25 | view_parser = top_level_sp.add_parser('view', help='display a view', aliases=('v'))
26 | view_parser.register('action', 'parsers', AliasedSubParsersAction)
27 | view_sp = view_parser.add_subparsers(title='views', description='valid view types', help='additional help', dest='view')
28 | view_sp.required = True
29 |
30 | # Set up a subcommand for each view class
31 | pm = PluginManager()
32 | pm.register_plugins()
33 | for plugin in pm.view_plugins:
34 | pm.view_plugins[plugin].view_class.configure_subparser(view_sp)
35 |
36 | # Parse args
37 | args = parser.parse_args()
38 | if args.debug:
39 | voltron.config['general']['debug_logging'] = True
40 | voltron.setup_logging('main')
41 | voltron.config.update(options=dict((tuple(x.split('=')) for x in args.o)))
42 |
43 | # Instantiate and run the appropriate module
44 | inst = args.func(args, loaded_config=voltron.config)
45 | inst.pm = pm
46 | try:
47 | inst.run()
48 | except Exception as e:
49 | log.exception("Exception running module {}: {}".format(inst.__class__.__name__, traceback.format_exc()))
50 | print("Encountered an exception while running the view '{}':\n{}".format(inst.__class__.__name__, traceback.format_exc()))
51 | except KeyboardInterrupt:
52 | suppress_exit_log = True
53 | inst.cleanup()
54 |
--------------------------------------------------------------------------------
/voltron/plugin.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import inspect
3 | import os
4 | from collections import defaultdict
5 |
6 | from scruffy.plugin import Plugin
7 |
8 | import voltron
9 |
10 | log = logging.getLogger('plugin')
11 |
12 |
13 | class PluginManager(object):
14 | """
15 | Collects and validates API, debugger and view plugins. Provides methods to
16 | access and search the plugin collection.
17 |
18 | Plugin loading itself is handled by scruffy, which is configured in the
19 | environment specification in `env.py`.
20 | """
21 | def __init__(self):
22 | """
23 | Initialise a new PluginManager.
24 | """
25 | self._api_plugins = defaultdict(lambda: None)
26 | self._debugger_plugins = defaultdict(lambda: None)
27 | self._view_plugins = defaultdict(lambda: None)
28 | self._web_plugins = defaultdict(lambda: None)
29 | self._command_plugins = defaultdict(lambda: None)
30 |
31 | def register_plugins(self):
32 | for p in voltron.env.plugins:
33 | self.register_plugin(p)
34 |
35 | def register_command_plugins(self):
36 | for p in voltron.env.plugins:
37 | if issubclass(p, CommandPlugin):
38 | self.register_plugin(p)
39 |
40 | @property
41 | def api_plugins(self):
42 | return self._api_plugins
43 |
44 | @property
45 | def debugger_plugins(self):
46 | return self._debugger_plugins
47 |
48 | @property
49 | def view_plugins(self):
50 | return self._view_plugins
51 |
52 | @property
53 | def web_plugins(self):
54 | return self._web_plugins
55 |
56 | @property
57 | def command_plugins(self):
58 | return self._command_plugins
59 |
60 | def register_plugin(self, plugin):
61 | """
62 | Register a new plugin with the PluginManager.
63 |
64 | `plugin` is a subclass of scruffy's Plugin class.
65 |
66 | This is called by __init__(), but may also be called by the debugger
67 | host to load a specific plugin at runtime.
68 | """
69 | if hasattr(plugin, 'initialise'):
70 | plugin.initialise()
71 | if self.valid_api_plugin(plugin):
72 | log.debug("Registering API plugin: {}".format(plugin))
73 | self._api_plugins[plugin.request] = plugin()
74 | elif self.valid_debugger_plugin(plugin):
75 | log.debug("Registering debugger plugin: {}".format(plugin))
76 | self._debugger_plugins[plugin.host] = plugin()
77 | elif self.valid_view_plugin(plugin):
78 | log.debug("Registering view plugin: {}".format(plugin))
79 | self._view_plugins[plugin.name] = plugin()
80 | elif self.valid_web_plugin(plugin):
81 | log.debug("Registering web plugin: {}".format(plugin))
82 | self._web_plugins[plugin.name] = plugin()
83 | elif self.valid_command_plugin(plugin):
84 | log.debug("Registering command plugin: {}".format(plugin))
85 | self._command_plugins[plugin.name] = plugin()
86 | if voltron.debugger:
87 | voltron.debugger.register_command_plugin(plugin.name, plugin.command_class)
88 | else:
89 | log.debug("Ignoring invalid plugin: {}".format(plugin))
90 |
91 | def valid_api_plugin(self, plugin):
92 | """
93 | Validate an API plugin, ensuring it is an API plugin and has the
94 | necessary fields present.
95 |
96 | `plugin` is a subclass of scruffy's Plugin class.
97 | """
98 | if (issubclass(plugin, APIPlugin) and
99 | hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'api' and
100 | hasattr(plugin, 'request') and plugin.request != None and
101 | hasattr(plugin, 'request_class') and plugin.request_class != None and
102 | hasattr(plugin, 'response_class') and plugin.response_class != None):
103 | return True
104 | return False
105 |
106 | def valid_debugger_plugin(self, plugin):
107 | """
108 | Validate a debugger plugin, ensuring it is a debugger plugin and has
109 | the necessary fields present.
110 |
111 | `plugin` is a subclass of scruffy's Plugin class.
112 | """
113 | if (issubclass(plugin, DebuggerAdaptorPlugin) and
114 | hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'debugger' and
115 | hasattr(plugin, 'host') and plugin.host != None):
116 | return True
117 | return False
118 |
119 | def valid_view_plugin(self, plugin):
120 | """
121 | Validate a view plugin, ensuring it is a view plugin and has the
122 | necessary fields present.
123 |
124 | `plugin` is a subclass of scruffy's Plugin class.
125 | """
126 | if (issubclass(plugin, ViewPlugin) and
127 | hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'view' and
128 | hasattr(plugin, 'name') and plugin.name != None and
129 | hasattr(plugin, 'view_class') and plugin.view_class != None):
130 | return True
131 | return False
132 |
133 | def valid_web_plugin(self, plugin):
134 | """
135 | Validate a web plugin, ensuring it is a web plugin and has the
136 | necessary fields present.
137 |
138 | `plugin` is a subclass of scruffy's Plugin class.
139 | """
140 | if (issubclass(plugin, WebPlugin) and
141 | hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'web' and
142 | hasattr(plugin, 'name') and plugin.name != None):
143 | return True
144 | return False
145 |
146 | def valid_command_plugin(self, plugin):
147 | """
148 | Validate a command plugin, ensuring it is a command plugin and has the
149 | necessary fields present.
150 |
151 | `plugin` is a subclass of scruffy's Plugin class.
152 | """
153 | if (issubclass(plugin, CommandPlugin) and
154 | hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'command' and
155 | hasattr(plugin, 'name') and plugin.name != None):
156 | return True
157 | return False
158 |
159 | def api_plugin_for_request(self, request=None):
160 | """
161 | Find an API plugin that supports the given request type.
162 | """
163 | return self.api_plugins[request]
164 |
165 | def debugger_plugin_for_host(self, host=None):
166 | """
167 | Find a debugger plugin that supports the debugger host.
168 | """
169 | return self.debugger_plugins[host]
170 |
171 | def view_plugin_with_name(self, name=None):
172 | """
173 | Find a view plugin that for the given view name.
174 | """
175 | return self.view_plugins[name]
176 |
177 | def web_plugin_with_name(self, name=None):
178 | """
179 | Find a web plugin that for the given view name.
180 | """
181 | return self.web_plugins[name]
182 |
183 | def command_plugin_with_name(self, name=None):
184 | """
185 | Find a command plugin that for the given view name.
186 | """
187 | return self.command_plugins[name]
188 |
189 |
190 | class VoltronPlugin(Plugin):
191 | @classmethod
192 | def initialise(cls):
193 | pass
194 |
195 |
196 | class APIPlugin(VoltronPlugin):
197 | """
198 | Abstract API plugin class. API plugins subclass this.
199 |
200 | `plugin_type` is 'api'
201 | `request` is the request type (e.g. 'version')
202 | `request_class` is the APIRequest subclass (e.g. APIVersionRequest)
203 | `response_class` is the APIResponse subclass (e.g. APIVersionResponse)
204 | `supported_hosts` is an array of debugger adaptor plugins that this plugin
205 | supports (e.g. 'lldb', 'gdb'). There is also a special host type called
206 | 'core' If the plugin requires only the 'core' debugger host, it can be used
207 | with any debugger adaptor plugin that implements the full interface (ie.
208 | the included 'lldb' and 'gdb' plugins). If it requires a specifically named
209 | debugger host plugin, then it will only work with those plugins specified.
210 | This allows developers to add custom API plugins that communicate directly
211 | with their chosen debugger host API, to do things that the standard
212 | debugger adaptor plugins don't support.
213 |
214 | See the core API plugins in voltron/plugins/api/ for examples.
215 | """
216 | plugin_type = 'api'
217 | request = None
218 | request_class = None
219 | response_class = None
220 | supported_hosts = ['core']
221 |
222 | @classmethod
223 | def initialise(cls):
224 | if cls.request_class:
225 | cls.request_class._plugin = cls
226 | cls.request_class.request = cls.request
227 |
228 |
229 | class DebuggerAdaptorPlugin(VoltronPlugin):
230 | """
231 | Debugger adaptor plugin parent class.
232 |
233 | `plugin_type` is 'debugger'
234 | `host` is the name of the debugger host (e.g. 'lldb' or 'gdb')
235 | `adaptor_class` is the debugger adaptor class that can be queried
236 | See the core debugger plugins in voltron/plugins/debugger/ for examples.
237 | """
238 | plugin_type = 'debugger'
239 | host = None
240 | adaptor_class = None
241 | supported_hosts = ['core']
242 |
243 | @classmethod
244 | def initialise(cls):
245 | if cls.adaptor_class:
246 | cls.adaptor_class._plugin = cls
247 |
248 |
249 | class ViewPlugin(VoltronPlugin):
250 | """
251 | View plugin parent class.
252 |
253 | `plugin_type` is 'view'
254 | `name` is the name of the view (e.g. 'register' or 'disassembly')
255 | `view_class` is the main view class
256 |
257 | See the core view plugins in voltron/plugins/view/ for examples.
258 | """
259 | plugin_type = 'view'
260 | name = None
261 | view_class = None
262 |
263 | @classmethod
264 | def initialise(cls):
265 | if cls.view_class:
266 | cls.view_class._plugin = cls
267 | cls.view_class.view_type = cls.name
268 |
269 |
270 | class WebPlugin(VoltronPlugin):
271 | """
272 | Web plugin parent class.
273 |
274 | `plugin_type` is 'web'
275 | `name` is the name of the web plugin (e.g. 'webview')
276 | `app` is a Flask app (or whatever, optional)
277 | """
278 | _dir = None
279 |
280 | plugin_type = 'web'
281 | name = None
282 | app = None
283 |
284 | def __init__(self):
285 | self._dir = os.path.dirname(inspect.getfile(self.__class__))
286 |
287 |
288 | class CommandPlugin(VoltronPlugin):
289 | """
290 | Command plugin parent class.
291 |
292 | `plugin_type` is 'command'
293 | `name` is the name of the command plugin
294 | """
295 | plugin_type = 'command'
296 | name = None
297 |
298 |
299 | class VoltronCommand(object):
300 | pass
301 |
302 |
303 | #
304 | # Shared plugin manager and convenience methods
305 | #
306 |
307 | pm = PluginManager()
308 |
309 |
310 | def api_request(request, *args, **kwargs):
311 | """
312 | Create an API request.
313 |
314 | `request_type` is the request type (string). This is used to look up a
315 | plugin, whose request class is instantiated and passed the remaining
316 | arguments passed to this function.
317 | """
318 | plugin = pm.api_plugin_for_request(request)
319 | if plugin and plugin.request_class:
320 | req = plugin.request_class(*args, **kwargs)
321 | else:
322 | raise Exception("Invalid request type")
323 | return req
324 |
325 |
326 | def api_response(request, *args, **kwargs):
327 | plugin = pm.api_plugin_for_request(request)
328 | if plugin and plugin.response_class:
329 | req = plugin.response_class(*args, **kwargs)
330 | else:
331 | raise Exception("Invalid request type")
332 | return req
333 |
334 |
335 | def debugger_adaptor(host, *args, **kwargs):
336 | plugin = pm.debugger_plugin_for_host(host)
337 | if plugin and plugin.adaptor_class:
338 | adaptor = plugin.adaptor_class(*args, **kwargs)
339 | else:
340 | raise Exception("Invalid debugger host")
341 | return adaptor
342 |
343 |
344 | def view(name, *args, **kwargs):
345 | plugin = pm.view_plugin_with_name(name)
346 | if plugin and plugin.view_class:
347 | view = plugin.view_class(*args, **kwargs)
348 | else:
349 | raise Exception("Invalid view name")
350 | return view
351 |
352 |
353 | def command(name, *args, **kwargs):
354 | plugin = pm.command_plugin_with_name(name)
355 | if plugin and plugin.command_class:
356 | command = plugin.command_class(*args, **kwargs)
357 | else:
358 | raise Exception("Invalid command name")
359 | return command
360 |
361 |
362 | def web_plugins():
363 | return pm.web_plugins
364 |
--------------------------------------------------------------------------------
/voltron/plugins/__init__.py:
--------------------------------------------------------------------------------
1 | if not hasattr(__builtins__, "xrange"):
2 | xrange = range
3 |
--------------------------------------------------------------------------------
/voltron/plugins/api/__init__.py:
--------------------------------------------------------------------------------
1 | if not hasattr(__builtins__, "xrange"):
2 | xrange = range
3 |
--------------------------------------------------------------------------------
/voltron/plugins/api/backtrace.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | from voltron.api import *
3 |
4 |
5 | import logging
6 |
7 | import voltron
8 | from voltron.api import *
9 |
10 | from scruffy.plugin import Plugin
11 |
12 | log = logging.getLogger('api')
13 |
14 |
15 | class APIBacktraceRequest(APIRequest):
16 | """
17 | API backtrace request.
18 |
19 | {
20 | "type": "request",
21 | "request": "backtrace"
22 | }
23 | """
24 | @server_side
25 | def dispatch(self):
26 | try:
27 | bt = voltron.debugger.backtrace()
28 | res = APIBacktraceResponse(frames=bt)
29 | except NoSuchTargetException:
30 | res = APINoSuchTargetErrorResponse()
31 | except Exception as e:
32 | msg = "Exception getting backtrace: {}".format(repr(e))
33 | log.exception(msg)
34 | res = APIGenericErrorResponse(msg)
35 |
36 | return res
37 |
38 |
39 | class APIBacktraceResponse(APISuccessResponse):
40 | """
41 | API backtrace response.
42 |
43 | {
44 | "type": "response",
45 | "status": "success",
46 | "data": {
47 | "frames": [
48 | {
49 | "index": 0,
50 | "addr": 0xffff,
51 | "name": "inferior`main + 0"
52 | }
53 | ]
54 | }
55 | }
56 | """
57 | _fields = {'frames': True}
58 |
59 | frames = []
60 |
61 |
62 | class APIBacktracePlugin(APIPlugin):
63 | request = "backtrace"
64 | request_class = APIBacktraceRequest
65 | response_class = APIBacktraceResponse
66 |
--------------------------------------------------------------------------------
/voltron/plugins/api/breakpoints.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | from voltron.api import *
3 |
4 |
5 | import logging
6 |
7 | import voltron
8 | from voltron.api import *
9 |
10 | from scruffy.plugin import Plugin
11 |
12 | log = logging.getLogger('api')
13 |
14 |
15 | class APIBreakpointsRequest(APIRequest):
16 | """
17 | API breakpoints request.
18 |
19 | {
20 | "type": "request",
21 | "request": "breakpoints"
22 | }
23 | """
24 | @server_side
25 | def dispatch(self):
26 | try:
27 | bps = voltron.debugger.breakpoints()
28 | res = APIBreakpointsResponse(breakpoints=bps)
29 | except NoSuchTargetException:
30 | res = APINoSuchTargetErrorResponse()
31 | except Exception as e:
32 | msg = "Exception getting breakpoints: {}".format(repr(e))
33 | log.exception(msg)
34 | res = APIGenericErrorResponse(msg)
35 |
36 | return res
37 |
38 |
39 | class APIBreakpointsResponse(APISuccessResponse):
40 | """
41 | API breakpoints response.
42 |
43 | {
44 | "type": "response",
45 | "status": "success",
46 | "data": {
47 | "breakpoints": [
48 | {
49 | "id": 1,
50 | "enabled": True,
51 | "one_shot": False,
52 | "hit_count": 5,
53 | "locations": [
54 | {
55 | "address": 0x100000cf0,
56 | "name": 'main'
57 | }
58 | ]
59 | }
60 | ]
61 | }
62 | }
63 | """
64 | _fields = {'breakpoints': True}
65 |
66 | breakpoints = []
67 |
68 |
69 | class APIBreakpointsPlugin(APIPlugin):
70 | request = "breakpoints"
71 | request_class = APIBreakpointsRequest
72 | response_class = APIBreakpointsResponse
73 |
--------------------------------------------------------------------------------
/voltron/plugins/api/command.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import voltron
4 | from voltron.api import *
5 |
6 | from scruffy.plugin import Plugin
7 |
8 | log = logging.getLogger('api')
9 |
10 | class APICommandRequest(APIRequest):
11 | """
12 | API execute command request.
13 |
14 | {
15 | "type": "request",
16 | "request": "command"
17 | "data": {
18 | "command": "break list"
19 | }
20 | }
21 | """
22 | _fields = {'command': True}
23 |
24 | @server_side
25 | def dispatch(self):
26 | try:
27 | output = voltron.debugger.command(self.command)
28 | res = APICommandResponse()
29 | res.output = output
30 | except NoSuchTargetException:
31 | res = APINoSuchTargetErrorResponse()
32 | except Exception as e:
33 | msg = "Exception executing debugger command: {}".format(repr(e))
34 | log.exception(msg)
35 | res = APIGenericErrorResponse(msg)
36 |
37 | return res
38 |
39 |
40 | class APICommandResponse(APISuccessResponse):
41 | """
42 | API list targets response.
43 |
44 | {
45 | "type": "response",
46 | "status": "success",
47 | "data": {
48 | "output": "stuff"
49 | }
50 | }
51 | """
52 | _fields = {'output': True}
53 |
54 | output = None
55 |
56 |
57 | class APICommandPlugin(APIPlugin):
58 | request = "command"
59 | request_class = APICommandRequest
60 | response_class = APICommandResponse
61 |
--------------------------------------------------------------------------------
/voltron/plugins/api/dereference.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import voltron
4 | from voltron.api import *
5 |
6 | from scruffy.plugin import Plugin
7 |
8 | log = logging.getLogger('api')
9 |
10 | class APIDerefRequest(APIRequest):
11 | """
12 | API dereference pointer request.
13 |
14 | {
15 | "type": "request",
16 | "request": "dereference"
17 | "data": {
18 | "pointer": 0xffffff8012341234
19 | }
20 | }
21 | """
22 | _fields = {'pointer': True}
23 |
24 | @server_side
25 | def dispatch(self):
26 | try:
27 | output = voltron.debugger.dereference(self.pointer)
28 | log.debug('output: {}'.format(str(output)))
29 | res = APIDerefResponse()
30 | res.output = output
31 | except NoSuchTargetException:
32 | res = APINoSuchTargetErrorResponse()
33 | except Exception as e:
34 | msg = "Exception dereferencing pointer: {}".format(repr(e))
35 | log.exception(msg)
36 | res = APIGenericErrorResponse(msg)
37 |
38 | return res
39 |
40 |
41 | class APIDerefResponse(APISuccessResponse):
42 | """
43 | API dereference pointer response.
44 |
45 | {
46 | "type": "response",
47 | "status": "success",
48 | "data": {
49 | "output": [0xffffff8055555555, "main + 0x123"]
50 | }
51 | }
52 | """
53 | _fields = {'output': True}
54 |
55 | output = None
56 |
57 |
58 | class APIDerefPlugin(APIPlugin):
59 | request = "dereference"
60 | request_class = APIDerefRequest
61 | response_class = APIDerefResponse
62 |
--------------------------------------------------------------------------------
/voltron/plugins/api/disassemble.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import logging
3 |
4 | from voltron.api import *
5 | from voltron.plugin import *
6 |
7 | log = logging.getLogger('api')
8 |
9 | class APIDisassembleRequest(APIRequest):
10 | """
11 | API disassemble request.
12 |
13 | {
14 | "type": "request",
15 | "request": "disassemble"
16 | "data": {
17 | "target_id": 0,
18 | "address": 0x12341234,
19 | "count": 16,
20 | "use_capstone": False
21 | }
22 | }
23 |
24 | `target_id` is optional.
25 | `address` is the address at which to start disassembling. Defaults to
26 | instruction pointer if not specified.
27 | `count` is the number of instructions to disassemble.
28 | `use_capstone` a flag to indicate whether or not Capstone should be used
29 | instead of the debugger's disassembler.
30 | """
31 | _fields = {'target_id': False, 'address': False, 'count': True, 'use_capstone': False}
32 |
33 | target_id = 0
34 | address = None
35 | count = 16
36 |
37 | @server_side
38 | def dispatch(self):
39 | try:
40 | if self.address == None:
41 | pc_name, self.address = voltron.debugger.program_counter(target_id=self.target_id)
42 | if self.use_capstone:
43 | disasm = voltron.debugger.disassemble_capstone(target_id=self.target_id, address=self.address,
44 | count=self.count)
45 | else:
46 | disasm = voltron.debugger.disassemble(target_id=self.target_id, address=self.address, count=self.count)
47 | res = APIDisassembleResponse()
48 | res.disassembly = disasm
49 | try:
50 | res.flavor = voltron.debugger.disassembly_flavor()
51 | except:
52 | res.flavor = 'NA'
53 | res.host = voltron.debugger._plugin.host
54 | except NoSuchTargetException:
55 | res = APINoSuchTargetErrorResponse()
56 | except TargetBusyException:
57 | res = APITargetBusyErrorResponse()
58 | except Exception as e:
59 | msg = "Unhandled exception {} disassembling: {}".format(type(e), e)
60 | log.exception(msg)
61 | res = APIErrorResponse(code=0, message=msg)
62 |
63 | return res
64 |
65 |
66 | class APIDisassembleResponse(APISuccessResponse):
67 | """
68 | API disassemble response.
69 |
70 | {
71 | "type": "response",
72 | "status": "success",
73 | "data": {
74 | "disassembly": "mov blah blah"
75 | }
76 | }
77 | """
78 | _fields = {'disassembly': True, 'formatted': False, 'flavor': False, 'host': False}
79 |
80 | disassembly = None
81 | formatted = None
82 | flavor = None
83 | host = None
84 |
85 |
86 | class APIDisassemblePlugin(APIPlugin):
87 | request = 'disassemble'
88 | request_class = APIDisassembleRequest
89 | response_class = APIDisassembleResponse
90 |
--------------------------------------------------------------------------------
/voltron/plugins/api/memory.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import logging
3 | import six
4 | import struct
5 |
6 | from voltron.api import *
7 |
8 | log = logging.getLogger('api')
9 |
10 |
11 | class APIMemoryRequest(APIRequest):
12 | """
13 | API read memory request.
14 |
15 | {
16 | "type": "request",
17 | "request": "memory",
18 | "data": {
19 | "target_id":0,
20 | "address": 0x12341234,
21 | "length": 0x40
22 | }
23 | }
24 |
25 | `target_id` is optional. If not present, the currently selected target
26 | will be used.
27 |
28 | `address` is the address at which to start reading.
29 |
30 | `length` is the number of bytes to read.
31 |
32 | `register` is the register containing the address at which to start reading.
33 |
34 | `command` is the debugger command to execute to calculate the address at
35 | which to start reading.
36 |
37 | `deref` is a flag indicating whether or not to dereference any pointers
38 | within the memory region read.
39 |
40 | `offset` is an offset to add to the address at which to start reading.
41 | """
42 | _fields = {
43 | 'target_id': False,
44 | 'address': False,
45 | 'length': False,
46 | 'words': False,
47 | 'register': False,
48 | 'command': False,
49 | 'deref': False,
50 | 'offset': False
51 | }
52 |
53 | target_id = 0
54 |
55 | @server_side
56 | def dispatch(self):
57 | try:
58 | target = voltron.debugger.target(self.target_id)
59 |
60 | # if 'words' was specified, get the addr_size and calculate the length to read
61 | if self.words:
62 | self.length = self.words * target['addr_size']
63 |
64 | # calculate the address at which to begin reading
65 | if self.address:
66 | addr = self.address
67 | elif self.command:
68 | output = voltron.debugger.command(self.command)
69 | if output:
70 | for item in reversed(output.split()):
71 | log.debug("checking item: {}".format(item))
72 | try:
73 | addr = int(item)
74 | break
75 | except:
76 | try:
77 | addr = int(item, 16)
78 | break
79 | except:
80 | pass
81 | elif self.register:
82 | regs = voltron.debugger.registers(registers=[self.register])
83 | addr = list(regs.values())[0]
84 | if self.offset:
85 | if self.words:
86 | addr += self.offset * target['addr_size']
87 | else:
88 | addr += self.offset
89 |
90 | # read memory
91 | memory = voltron.debugger.memory(address=int(addr), length=int(self.length), target_id=int(self.target_id))
92 |
93 | # deref pointers
94 | deref = None
95 | if self.deref:
96 | fmt = ('<' if target['byte_order'] == 'little' else '>') + {2: 'H', 4: 'L', 8: 'Q'}[target['addr_size']]
97 | deref = []
98 | for chunk in zip(*[six.iterbytes(memory)] * target['addr_size']):
99 | chunk = ''.join([six.unichr(x) for x in chunk]).encode('latin1')
100 | p = list(struct.unpack(fmt, chunk))[0]
101 | if p > 0:
102 | try:
103 | deref.append(voltron.debugger.dereference(pointer=p))
104 | except:
105 | deref.append([])
106 | else:
107 | deref.append([])
108 |
109 | res = APIMemoryResponse()
110 | res.address = addr
111 | res.memory = six.u(memory)
112 | res.bytes = len(memory)
113 | res.deref = deref
114 | except TargetBusyException:
115 | res = APITargetBusyErrorResponse()
116 | except NoSuchTargetException:
117 | res = APINoSuchTargetErrorResponse()
118 | except Exception as e:
119 | msg = "Exception getting memory from debugger: {}".format(repr(e))
120 | log.exception(msg)
121 | res = APIGenericErrorResponse(msg)
122 |
123 | return res
124 |
125 |
126 | class APIMemoryResponse(APISuccessResponse):
127 | """
128 | API read memory response.
129 |
130 | {
131 | "type": "response",
132 | "status": "success",
133 | "data": {
134 | "memory": "ABCDEF" # base64 encoded memory
135 | }
136 | }
137 | """
138 | _fields = {
139 | 'address': True,
140 | 'memory': True,
141 | 'bytes': True,
142 | 'deref': False
143 | }
144 | _encode_fields = ['memory']
145 |
146 | address = None
147 | memory = None
148 | bytes = None
149 | deref = None
150 |
151 |
152 | class APIReadMemoryPlugin(APIPlugin):
153 | request = 'memory'
154 | request_class = APIMemoryRequest
155 | response_class = APIMemoryResponse
156 |
--------------------------------------------------------------------------------
/voltron/plugins/api/null.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import voltron.api
3 | from voltron.api import *
4 |
5 | from scruffy.plugin import Plugin
6 |
7 |
8 | class APINullRequest(APIRequest):
9 | """
10 | API null request.
11 |
12 | {
13 | "type": "request",
14 | "request": "null"
15 | }
16 | """
17 | @server_side
18 | def dispatch(self):
19 | return APINullResponse()
20 |
21 |
22 | class APINullResponse(APISuccessResponse):
23 | """
24 | API null response.
25 |
26 | {
27 | "type": "response",
28 | "status": "success"
29 | }
30 | """
31 |
32 |
33 | class APINullPlugin(APIPlugin):
34 | request = 'null'
35 | request_class = APINullRequest
36 | response_class = APINullResponse
37 |
--------------------------------------------------------------------------------
/voltron/plugins/api/plugins.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import voltron.api
3 | from voltron.api import *
4 |
5 | from scruffy.plugin import Plugin
6 |
7 |
8 | class APIPluginsRequest(APIRequest):
9 | """
10 | API plugins request.
11 |
12 | {
13 | "type": "request",
14 | "request": "plugins"
15 | }
16 | """
17 | @server_side
18 | def dispatch(self):
19 | res = APIPluginsResponse()
20 | return res
21 |
22 |
23 | class APIPluginsResponse(APISuccessResponse):
24 | """
25 | API plugins response.
26 |
27 | {
28 | "type": "response",
29 | "status": "success",
30 | "data": {
31 | "plugins": {
32 | "api": {
33 | "version": ["api_version", "host_version", "capabilities"]
34 | ...
35 | },
36 | "debugger": {
37 | ...
38 | },
39 | ...
40 | }
41 | }
42 | }
43 | """
44 | _fields = {
45 | 'plugins': True
46 | }
47 |
48 | def __init__(self, *args, **kwargs):
49 | super(APIPluginsResponse, self).__init__(*args, **kwargs)
50 | self.plugins = {
51 | 'api': {n: {'request': p.request_class._fields, 'response': p.response_class._fields}
52 | for (n, p) in voltron.plugin.pm.api_plugins.iteritems()},
53 | 'debugger': [n for n in voltron.plugin.pm.debugger_plugins],
54 | 'view': [n for n in voltron.plugin.pm.view_plugins],
55 | 'command': [n for n in voltron.plugin.pm.command_plugins],
56 | 'web': [n for n in voltron.plugin.pm.web_plugins],
57 | }
58 |
59 |
60 | class APIPluginsPlugin(APIPlugin):
61 | request = 'plugins'
62 | request_class = APIPluginsRequest
63 | response_class = APIPluginsResponse
64 |
--------------------------------------------------------------------------------
/voltron/plugins/api/registers.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import logging
3 |
4 | from voltron.api import *
5 |
6 | log = logging.getLogger('api')
7 |
8 |
9 | class APIRegistersRequest(APIRequest):
10 | """
11 | API state request.
12 |
13 | {
14 | "type": "request",
15 | "request": "registers",
16 | "data": {
17 | "target_id": 0,
18 | "thread_id": 123456,
19 | "registers": ['rsp']
20 | }
21 | }
22 |
23 | `target_id` and `thread_id` are optional. If not present, the currently
24 | selected target and thread will be used.
25 |
26 | `registers` is optional. If it is not included all registers will be
27 | returned.
28 | """
29 | _fields = {'target_id': False, 'thread_id': False, 'registers': False}
30 |
31 | target_id = 0
32 | thread_id = None
33 | registers = []
34 |
35 | @server_side
36 | def dispatch(self):
37 | try:
38 | regs = voltron.debugger.registers(target_id=self.target_id, thread_id=self.thread_id, registers=self.registers)
39 | res = APIRegistersResponse()
40 | res.registers = regs
41 | res.deref = {}
42 | for reg, val in regs.items():
43 | try:
44 | if val > 0:
45 | try:
46 | res.deref[reg] = voltron.debugger.dereference(pointer=val)
47 | except:
48 | res.deref[reg] = []
49 | else:
50 | res.deref[reg] = []
51 | except TypeError:
52 | res.deref[reg] = []
53 | except TargetBusyException:
54 | res = APITargetBusyErrorResponse()
55 | except NoSuchTargetException:
56 | res = APINoSuchTargetErrorResponse()
57 | except Exception as e:
58 | msg = "Exception getting registers from debugger: {}".format(repr(e))
59 | log.exception(msg)
60 | res = APIGenericErrorResponse(msg)
61 |
62 | return res
63 |
64 |
65 | class APIRegistersResponse(APISuccessResponse):
66 | """
67 | API status response.
68 |
69 | {
70 | "type": "response",
71 | "status": "success",
72 | "data": {
73 | "registers": { "rip": 0x12341234, ... },
74 | "deref": {"rip": [(pointer, 0x12341234), ...]}
75 | }
76 | }
77 | """
78 | _fields = {'registers': True, 'deref': False}
79 |
80 |
81 | class APIRegistersPlugin(APIPlugin):
82 | request = 'registers'
83 | request_class = APIRegistersRequest
84 | response_class = APIRegistersResponse
85 |
--------------------------------------------------------------------------------
/voltron/plugins/api/stack.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import logging
3 | import base64
4 |
5 | from voltron.api import *
6 |
7 | log = logging.getLogger('api')
8 |
9 | class APIStackRequest(APIRequest):
10 | """
11 | API read stack request.
12 |
13 | {
14 | "type": "request",
15 | "request": "stack"
16 | "data": {
17 | "target_id": 0,
18 | "thread_id": 123456,
19 | "length": 0x40
20 | }
21 | }
22 |
23 | `target_id` and `thread_id` are optional. If not present, the currently
24 | selected target and thread will be used.
25 |
26 | `length` is the number of bytes to read.
27 | """
28 | _fields = {'target_id': False, 'thread_id': False, 'length': True}
29 |
30 | target_id = 0
31 | thread_id = None
32 | length = None
33 |
34 | @server_side
35 | def dispatch(self):
36 | try:
37 | sp_name, sp = voltron.debugger.stack_pointer(target_id=self.target_id)
38 | memory = voltron.debugger.memory(address=sp, length=self.length, target_id=self.target_id)
39 | res = APIStackResponse()
40 | res.memory = memory
41 | res.stack_pointer = sp
42 | except NoSuchTargetException:
43 | res = APINoSuchTargetErrorResponse()
44 | except TargetBusyException:
45 | res = APITargetBusyErrorResponse()
46 | except Exception as e:
47 | msg = "Unhandled exception {} reading stack: {}".format(type(e), e)
48 | log.exception(msg)
49 | res = APIErrorResponse(code=0, message=msg)
50 |
51 | return res
52 |
53 |
54 | class APIStackResponse(APISuccessResponse):
55 | """
56 | API read stack response.
57 |
58 | {
59 | "type": "response",
60 | "status": "success",
61 | "data": {
62 | "memory": "\xff...",
63 | "stack_pointer": 0x12341234
64 | }
65 | }
66 | """
67 | _fields = {'memory': True, 'stack_pointer': True}
68 |
69 | _encode_fields = ['memory']
70 |
71 | memory = None
72 | stack_pointer = None
73 |
74 |
75 | class APIStackPlugin(APIPlugin):
76 | request = 'stack'
77 | request_class = APIStackRequest
78 | response_class = APIStackResponse
79 |
--------------------------------------------------------------------------------
/voltron/plugins/api/state.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import logging
3 |
4 | from voltron.api import *
5 |
6 | log = logging.getLogger('api')
7 |
8 | class APIStateRequest(APIRequest):
9 | """
10 | API state request.
11 |
12 | {
13 | "type": "request",
14 | "request": "state",
15 | "data": {
16 | "target_id": 0
17 | }
18 | }
19 | """
20 | _fields = {'target_id': False}
21 |
22 | target_id = 0
23 |
24 | @server_side
25 | def dispatch(self):
26 | try:
27 | state = voltron.debugger.state(target_id=self.target_id)
28 | log.debug("Got state from debugger: {}".format(state))
29 | res = APIStateResponse()
30 | res.state = state
31 | except TargetBusyException:
32 | res = APITargetBusyErrorResponse()
33 | except NoSuchTargetException:
34 | res = APINoSuchTargetErrorResponse()
35 |
36 | return res
37 |
38 |
39 | class APIStateResponse(APISuccessResponse):
40 | """
41 | API status response.
42 |
43 | {
44 | "type": "response",
45 | "data": {
46 | "state": "stopped"
47 | }
48 | }
49 | """
50 | _fields = {'state': True}
51 |
52 | state = None
53 |
54 |
55 | class APIStatePlugin(APIPlugin):
56 | request = 'state'
57 | request_class = APIStateRequest
58 | response_class = APIStateResponse
59 |
--------------------------------------------------------------------------------
/voltron/plugins/api/targets.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import voltron
4 | from voltron.api import *
5 |
6 | from scruffy.plugin import Plugin
7 |
8 | log = logging.getLogger('api')
9 |
10 | class APITargetsRequest(APIRequest):
11 | """
12 | API list targets request.
13 |
14 | {
15 | "type": "request",
16 | "request": "targets"
17 | }
18 | """
19 | _fields = {}
20 |
21 | @server_side
22 | def dispatch(self):
23 | try:
24 | res = APITargetsResponse()
25 | res.targets = voltron.debugger.targets()
26 | except NoSuchTargetException:
27 | res = APINoSuchTargetErrorResponse()
28 | except Exception as e:
29 | msg = "Exception getting targets from debugger: {}".format(repr(e))
30 | log.exception(msg)
31 | res = APIGenericErrorResponse(msg)
32 |
33 | return res
34 |
35 |
36 | class APITargetsResponse(APISuccessResponse):
37 | """
38 | API list targets response.
39 |
40 | {
41 | "type": "response",
42 | "status": "success",
43 | "data": {
44 | "targets": [{
45 | "id": 0, # ID that can be used in other funcs
46 | "file": "/bin/ls", # target's binary file
47 | "arch": "x86_64", # target's architecture
48 | "state: "stopped" # state
49 | }]
50 | }
51 | }
52 | """
53 | _fields = {'targets': True}
54 |
55 | targets = []
56 |
57 |
58 | class APITargetsPlugin(APIPlugin):
59 | request = 'targets'
60 | request_class = APITargetsRequest
61 | response_class = APITargetsResponse
62 |
63 |
--------------------------------------------------------------------------------
/voltron/plugins/api/version.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import voltron.api
3 | from voltron.api import *
4 |
5 | from scruffy.plugin import Plugin
6 |
7 |
8 | class APIVersionRequest(APIRequest):
9 | """
10 | API version request.
11 |
12 | {
13 | "type": "request",
14 | "request": "version"
15 | }
16 | """
17 | @server_side
18 | def dispatch(self):
19 | res = APIVersionResponse()
20 | res.api_version = voltron.api.version
21 | res.host_version = voltron.debugger.version()
22 | res.capabilities = voltron.debugger.capabilities()
23 | return res
24 |
25 |
26 | class APIVersionResponse(APISuccessResponse):
27 | """
28 | API version response.
29 |
30 | {
31 | "type": "response",
32 | "status": "success",
33 | "data": {
34 | "api_version": 1.0,
35 | "host_version": 'lldb-something',
36 | "capabilities": ["async"]
37 | }
38 | }
39 | """
40 | _fields = {
41 | 'api_version': True,
42 | 'host_version': True,
43 | 'capabilities': False
44 | }
45 |
46 | api_version = None
47 | host_version = None
48 | capabilities = None
49 |
50 |
51 | class APIVersionPlugin(APIPlugin):
52 | request = 'version'
53 | request_class = APIVersionRequest
54 | response_class = APIVersionResponse
55 |
56 |
--------------------------------------------------------------------------------
/voltron/plugins/api/write_memory.py:
--------------------------------------------------------------------------------
1 | import voltron
2 | import logging
3 | import six
4 | import struct
5 |
6 | from voltron.api import *
7 |
8 | log = logging.getLogger('api')
9 |
10 |
11 | class APIWriteMemoryRequest(APIRequest):
12 | """
13 | API write memory request.
14 |
15 | {
16 | "type": "request",
17 | "request": "write_memory",
18 | "data": {
19 | "target_id":0,
20 | "address": 0x12341234,
21 | "data": "\xcc"
22 | }
23 | }
24 |
25 | `target_id` is optional. If not present, the currently selected target
26 | will be used.
27 |
28 | `address` is the address at which to start writing.
29 |
30 | `data` is the data to write.
31 | """
32 | _fields = {
33 | 'target_id': False,
34 | 'address': True,
35 | 'value': True
36 | }
37 | _encode_fields = ['value']
38 |
39 | target_id = 0
40 |
41 | @server_side
42 | def dispatch(self):
43 | try:
44 | target = voltron.debugger.target(self.target_id)
45 |
46 | voltron.debugger.write_memory(address=int(self.address), data=self.value, target_id=int(self.target_id))
47 |
48 | res = APISuccessResponse()
49 | except TargetBusyException:
50 | res = APITargetBusyErrorResponse()
51 | except NoSuchTargetException:
52 | res = APINoSuchTargetErrorResponse()
53 | except Exception as e:
54 | msg = "Exception writing memory in debugger: {}".format(repr(e))
55 | log.exception(msg)
56 | res = APIGenericErrorResponse(msg)
57 |
58 | return res
59 |
60 |
61 | class APIWriteMemoryPlugin(APIPlugin):
62 | request = 'write_memory'
63 | request_class = APIWriteMemoryRequest
64 | response_class = APISuccessResponse
65 |
--------------------------------------------------------------------------------
/voltron/plugins/debugger/__init__.py:
--------------------------------------------------------------------------------
1 | if not hasattr(__builtins__, "xrange"):
2 | xrange = range
3 |
--------------------------------------------------------------------------------
/voltron/plugins/debugger/dbg_mock.py:
--------------------------------------------------------------------------------
1 | from voltron.plugin import *
2 | from voltron.dbg import *
3 |
4 |
5 | class MockAdaptor(DebuggerAdaptor):
6 | pass
7 |
8 |
9 | class MockAdaptorPlugin(DebuggerAdaptorPlugin):
10 | host = 'mock'
11 | adaptor_class = MockAdaptor
12 |
--------------------------------------------------------------------------------
/voltron/plugins/view/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snare/voltron/be9e54b109380a21e4ec7ec9b3ecc9d4a7fd6529/voltron/plugins/view/__init__.py
--------------------------------------------------------------------------------
/voltron/plugins/view/backtrace.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from voltron.view import *
4 | from voltron.plugin import *
5 | from voltron.api import *
6 |
7 | log = logging.getLogger('view')
8 |
9 |
10 | class BacktraceView (TerminalView):
11 | def build_requests(self):
12 | return [api_request('command', block=self.block, command='bt')]
13 |
14 | def render(self, results):
15 | [res] = results
16 |
17 | # Set up header and error message if applicable
18 | self.title = '[backtrace]'
19 |
20 | # don't render if it timed out, probably haven't stepped the debugger again
21 | if res.timed_out:
22 | return
23 |
24 | if res and res.is_success:
25 | # Get the command output
26 | self.body = res.output
27 | else:
28 | log.error("Error getting backtrace: {}".format(res.message))
29 | self.body = self.colour(res.message, 'red')
30 |
31 | # Call parent's render method
32 | super(BacktraceView, self).render(results)
33 |
34 |
35 | class BacktraceViewPlugin(ViewPlugin):
36 | plugin_type = 'view'
37 | name = 'backtrace'
38 | aliases = ('t', 'bt', 'back')
39 | view_class = BacktraceView
40 |
--------------------------------------------------------------------------------
/voltron/plugins/view/breakpoints.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from blessed import Terminal
4 |
5 | from voltron.view import *
6 | from voltron.plugin import *
7 | from voltron.api import *
8 |
9 | log = logging.getLogger('view')
10 |
11 |
12 | class BreakpointsView (TerminalView):
13 | def build_requests(self):
14 | return [
15 | api_request('registers', registers=['pc']),
16 | api_request('breakpoints', block=self.block)
17 | ]
18 |
19 | def render(self, results):
20 | self.title = '[breakpoints]'
21 |
22 | r_res, b_res = results
23 |
24 | # get PC first so we can highlight a breakpoint we're at
25 | if r_res and r_res.is_success and len(r_res.registers) > 0:
26 | pc = r_res.registers[list(r_res.registers.keys())[0]]
27 | else:
28 | pc = -1
29 |
30 | if b_res and b_res.is_success:
31 | fmtd = []
32 | term = Terminal()
33 | for bp in b_res.breakpoints:
34 | # prepare formatting dictionary for the breakpoint
35 | d = bp.copy()
36 | d['locations'] = None
37 | d['t'] = term
38 | d['id'] = '#{:<2}'.format(d['id'])
39 | if d['one_shot']:
40 | d['one_shot'] = self.config.format.one_shot.format(t=term)
41 | else:
42 | d['one_shot'] = ''
43 | if d['enabled']:
44 | d['disabled'] = ''
45 | else:
46 | d['disabled'] = self.config.format.disabled.format(t=term)
47 |
48 | # add a row for each location
49 | for location in bp['locations']:
50 | # add location data to formatting dict and format the row
51 | d.update(location)
52 | if pc == d['address']:
53 | d['hit'] = self.config.format.hit.format(t=term)
54 | else:
55 | d['hit'] = ''
56 | f = self.config.format.row.format(**d)
57 | fmtd.append(f)
58 | d['id'] = ' '
59 |
60 | self.body = '\n'.join(fmtd)
61 | else:
62 | log.error("Error getting breakpoints: {}".format(b_res.message))
63 | self.body = self.colour(b_res.message, 'red')
64 |
65 | super(BreakpointsView, self).render(results)
66 |
67 |
68 | class BreakpointsViewPlugin(ViewPlugin):
69 | plugin_type = 'view'
70 | name = 'breakpoints'
71 | aliases = ('b', 'bp', 'break')
72 | view_class = BreakpointsView
73 |
--------------------------------------------------------------------------------
/voltron/plugins/view/command.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import pygments
4 | from pygments.lexers import get_lexer_by_name
5 |
6 | from voltron.view import *
7 | from voltron.plugin import *
8 | from voltron.api import *
9 |
10 | log = logging.getLogger('view')
11 |
12 |
13 | class CommandView (TerminalView):
14 | @classmethod
15 | def configure_subparser(cls, subparsers):
16 | sp = subparsers.add_parser('command', aliases=('c', 'cmd'),
17 | help='run a command each time the debugger host stops')
18 | VoltronView.add_generic_arguments(sp)
19 | sp.add_argument('command', action='store', help='command to run')
20 | sp.add_argument('--lexer', '-l', action='store',
21 | help='apply a Pygments lexer to the command output (e.g. "c")',
22 | default=None)
23 | sp.set_defaults(func=CommandView)
24 |
25 | def build_requests(self):
26 | return [api_request('command', block=self.block, command=self.args.command)]
27 |
28 | def render(self, results):
29 | [res] = results
30 |
31 | # Set up header and error message if applicable
32 | self.title = '[cmd:' + self.args.command + ']'
33 |
34 | if res and res.is_success:
35 | if get_lexer_by_name and self.args.lexer:
36 | try:
37 | lexer = get_lexer_by_name(self.args.lexer, stripall=True)
38 | self.body = pygments.highlight(res.output, lexer, pygments.formatters.TerminalFormatter())
39 | except Exception as e:
40 | log.warning('Failed to highlight view contents: ' + repr(e))
41 | self.body = res.output
42 | else:
43 | self.body = res.output
44 | else:
45 | log.error("Error executing command: {}".format(res.message))
46 | self.body = self.colour(res.message, 'red')
47 |
48 | # Call parent's render method
49 | super(CommandView, self).render(results)
50 |
51 |
52 | class CommandViewPlugin(ViewPlugin):
53 | plugin_type = 'view'
54 | name = 'command'
55 | view_class = CommandView
56 |
--------------------------------------------------------------------------------
/voltron/plugins/view/disasm.py:
--------------------------------------------------------------------------------
1 | from voltron.view import TerminalView, VoltronView, log
2 | from voltron.plugin import api_request, ViewPlugin
3 | from voltron.lexers import get_lexer_by_name
4 | import pygments
5 | import pygments.formatters
6 |
7 |
8 | class DisasmView(TerminalView):
9 | @classmethod
10 | def configure_subparser(cls, subparsers):
11 | sp = subparsers.add_parser('disasm', help='disassembly view', aliases=('d', 'dis'))
12 | VoltronView.add_generic_arguments(sp)
13 | sp.set_defaults(func=DisasmView)
14 | sp.add_argument('--use-capstone', '-c', action='store_true', default=False, help='use capstone')
15 | sp.add_argument('--address', '-a', action='store', default=None,
16 | help='address (in hex or decimal) from which to start disassembly')
17 |
18 | def build_requests(self):
19 | if self.args.address:
20 | if self.args.address.startswith('0x'):
21 | addr = int(self.args.address, 16)
22 | else:
23 | try:
24 | addr = int(self.args.address, 10)
25 | except:
26 | addr = int(self.args.address, 16)
27 | else:
28 | addr = None
29 | req = api_request('disassemble', block=self.block, use_capstone=self.args.use_capstone,
30 | offset=self.scroll_offset, address=addr)
31 | req.count = self.body_height()
32 | return [req]
33 |
34 | def render(self, results):
35 | [res] = results
36 |
37 | # Set up header & error message if applicable
38 | self.title = '[disassembly]'
39 |
40 | # don't render if it timed out, probably haven't stepped the debugger again
41 | if res.timed_out:
42 | return
43 |
44 | if res and res.is_success:
45 | # Get the disasm
46 | disasm = res.disassembly
47 | disasm = '\n'.join(disasm.split('\n')[:self.body_height()])
48 |
49 | # Highlight output
50 | try:
51 | host = 'capstone' if self.args.use_capstone else res.host
52 | lexer = get_lexer_by_name('{}_{}'.format(host, res.flavor))
53 | formatter = pygments.formatters.get_formatter_by_name(
54 | self.config.format.pygments_formatter,
55 | style=self.config.format.pygments_style)
56 | disasm = pygments.highlight(disasm, lexer, formatter)
57 | except Exception as e:
58 | log.warning('Failed to highlight disasm: ' + str(e))
59 | log.info(self.config.format)
60 |
61 | # Build output
62 | self.body = disasm.rstrip()
63 | else:
64 | log.error("Error disassembling: {}".format(res.message))
65 | self.body = self.colour(res.message, 'red')
66 |
67 | # Call parent's render method
68 | super(DisasmView, self).render(results)
69 |
70 |
71 | class DisasmViewPlugin(ViewPlugin):
72 | plugin_type = 'view'
73 | name = 'disassembly'
74 | aliases = ('d', 'dis', 'disasm')
75 | view_class = DisasmView
76 |
--------------------------------------------------------------------------------
/voltron/plugins/view/memory.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import six
3 | import pygments
4 | import pygments.formatters
5 | from pygments.token import *
6 |
7 | from voltron.view import TerminalView, VoltronView
8 | from voltron.plugin import ViewPlugin, api_request
9 |
10 | log = logging.getLogger("view")
11 |
12 |
13 | class MemoryView(TerminalView):
14 | printable_filter = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
15 |
16 | asynchronous = True
17 | last_memory = None
18 | last_address = 0
19 |
20 | @classmethod
21 | def configure_subparser(cls, subparsers):
22 | sp = subparsers.add_parser('memory', help='display a chunk of memory', aliases=('m', 'mem'))
23 | VoltronView.add_generic_arguments(sp)
24 | group = sp.add_mutually_exclusive_group(required=False)
25 | group.add_argument('--deref', '-d', action='store_true',
26 | help='display the data in a column one CPU word wide and dereference any valid pointers',
27 | default=False)
28 | group.add_argument('--bytes', '-b', action='store', type=int, help='bytes per line (default 16)', default=16)
29 | group.add_argument('--words', '-w', action='store', type=int, help='machine words per line', default=0)
30 | sp.add_argument('--reverse', '-v', action='store_true', help='reverse the output', default=False)
31 | sp.add_argument('--track', '-t', action='store_true', help='track and highlight changes', default=True)
32 | sp.add_argument('--no-track', '-T', action='store_false', help='don\'t track and highlight changes')
33 |
34 | group = sp.add_mutually_exclusive_group(required=False)
35 | group.add_argument('--address', '-a', action='store',
36 | help='address (in hex or decimal) from which to start reading memory')
37 | group.add_argument('--command', '-c', action='store',
38 | help='command to execute resulting in the address from which to start reading memory. '
39 | 'voltron will do his almighty best to find an address. e.g. "print \$rip + 0x1234"',
40 | default=None)
41 | group.add_argument('--register', '-r', action='store',
42 | help='register containing the address from which to start reading memory', default=None)
43 | sp.set_defaults(func=MemoryView)
44 |
45 | def build_requests(self):
46 | height, width = self.window_size()
47 |
48 | # check args
49 | if self.args.register:
50 | args = {'register': self.args.register}
51 | elif self.args.command:
52 | args = {'command': self.args.command}
53 | elif self.args.address:
54 | if self.args.address.startswith('0x'):
55 | addr = int(self.args.address, 16)
56 | else:
57 | try:
58 | addr = int(self.args.address, 10)
59 | except:
60 | addr = int(self.args.address, 16)
61 | args = {'address': addr}
62 | else:
63 | args = {'register': 'sp'}
64 |
65 | if self.args.deref:
66 | args['words'] = height
67 | args['offset'] = self.scroll_offset if self.args.reverse else -self.scroll_offset
68 | else:
69 | args['length'] = height * self.args.bytes * 2
70 | args['offset'] = self.scroll_offset * self.args.bytes * (1 if self.args.reverse else -1)
71 |
72 | # get memory and target info
73 | return [
74 | api_request('targets'),
75 | api_request('memory', deref=self.args.deref is True, **args)
76 | ]
77 |
78 | def generate_tokens(self, results):
79 | t_res, m_res = results
80 |
81 | if t_res and t_res.is_success and len(t_res.targets) > 0:
82 | target = t_res.targets[0]
83 |
84 | if m_res and m_res.is_success:
85 | bytes_per_chunk = self.args.words*target['addr_size'] if self.args.words else self.args.bytes
86 | for c in range(0, m_res.bytes, bytes_per_chunk):
87 | chunk = m_res.memory[c:c + bytes_per_chunk]
88 | yield (Name.Label, self.format_address(m_res.address + c, size=target['addr_size'], pad=False))
89 | yield (Name.Label, ': ')
90 |
91 | # Hex bytes
92 | byte_array = []
93 | for i, x in enumerate(six.iterbytes(chunk)):
94 | n = "%02X" % x
95 | token = Text if x else Comment
96 | if self.args.track and self.last_memory and self.last_address == m_res.address:
97 | if x != six.indexbytes(self.last_memory, c + i):
98 | token = Error
99 | byte_array.append((token, n))
100 |
101 | if self.args.words:
102 | if target['byte_order'] =='little':
103 | byte_array_words = [byte_array[i:i+ target['addr_size']] for i in range(0, bytes_per_chunk, target['addr_size'])]
104 | for word in byte_array_words:
105 | word.reverse()
106 | for x in word:
107 | yield x
108 | yield (Text, ' ')
109 | else:
110 | for x in byte_array:
111 | yield x
112 | yield (Text, ' ')
113 |
114 | # ASCII representation
115 | yield (Punctuation, '| ')
116 | for i, x in enumerate(six.iterbytes(chunk)):
117 | token = String.Char if x else Comment
118 | if self.args.track and self.last_memory and self.last_address == m_res.address:
119 | if x != six.indexbytes(self.last_memory, c + i):
120 | token = Error
121 | yield (token, ((x <= 127 and self.printable_filter[x]) or '.'))
122 | yield (Punctuation, ' | ')
123 |
124 | # Deref chain
125 | if self.args.deref:
126 | chain = m_res.deref.pop(0)
127 | for i, (t, item) in enumerate(chain):
128 | if t == "pointer":
129 | yield (Number.Hex, self.format_address(item, size=target['addr_size'], pad=False))
130 | elif t == "string":
131 | for r in ['\n', '\r', '\v']:
132 | item = item.replace(r, '\\{:x}'.format(ord(r)))
133 | yield (String.Double, '"' + item + '"')
134 | elif t == "unicode":
135 | for r in ['\n', '\r', '\v']:
136 | item = item.replace(r, '\\{:x}'.format(ord(r)))
137 | yield (String.Double, 'u"' + item + '"')
138 | elif t == "symbol":
139 | yield (Name.Function, '`' + item + '`')
140 | elif t == "circular":
141 | yield (Text, '(circular)')
142 | if i < len(chain) - 1:
143 | yield (Punctuation, ' => ')
144 |
145 | yield (Text, '\n')
146 |
147 | def render(self, results):
148 | target = None
149 | self.trunc_top = self.args.reverse
150 |
151 | t_res, m_res = results
152 |
153 | if t_res and t_res.is_success and len(t_res.targets) > 0:
154 | target = t_res.targets[0]
155 |
156 | if self.args.deref or self.args.words:
157 | self.args.bytes = target['addr_size']
158 |
159 | f = pygments.formatters.get_formatter_by_name(self.config.format.pygments_formatter,
160 | style=self.config.format.pygments_style)
161 |
162 | if m_res and m_res.is_success:
163 | lines = pygments.format(self.generate_tokens(results), f).split('\n')
164 | self.body = '\n'.join(reversed(lines)).strip() if self.args.reverse else '\n'.join(lines)
165 | self.info = '[0x{0:0=4x}:'.format(len(m_res.memory)) + self.config.format.addr_format.format(m_res.address) + ']'
166 | else:
167 | log.error("Error reading memory: {}".format(m_res.message))
168 | self.body = pygments.format([(Error, m_res.message)], f)
169 | self.info = ''
170 |
171 | # Store the memory
172 | if self.args.track:
173 | self.last_address = m_res.address
174 | self.last_memory = m_res.memory
175 | else:
176 | self.body = self.colour("Failed to get targets", 'red')
177 |
178 | if not self.title:
179 | self.title = "[memory]"
180 |
181 | super(MemoryView, self).render(results)
182 |
183 | def format_address(self, address, size=8, pad=True, prefix='0x'):
184 | fmt = '{:' + ('0=' + str(size * 2) if pad else '') + 'X}'
185 | addr_str = fmt.format(address)
186 | if prefix:
187 | addr_str = prefix + addr_str
188 | return addr_str
189 |
190 |
191 | class MemoryViewPlugin(ViewPlugin):
192 | plugin_type = 'view'
193 | name = 'memory'
194 | view_class = MemoryView
195 |
196 |
197 | class StackView(MemoryView):
198 | @classmethod
199 | def configure_subparser(cls, subparsers):
200 | sp = subparsers.add_parser('stack', help='display a chunk of stack memory', aliases=('s', 'st'))
201 | VoltronView.add_generic_arguments(sp)
202 | sp.set_defaults(func=StackView)
203 | sp.add_argument('--reverse', '-v', action='store_false', help='(un)reverse the output', default=True)
204 | sp.add_argument('--track', '-t', action='store_true', help='track and highlight changes', default=True)
205 | sp.add_argument('--no-track', '-T', action='store_false', help='don\'t track and highlight changes')
206 |
207 | def build_requests(self):
208 | self.args.deref = True
209 | self.args.words = False
210 | self.args.register = 'sp'
211 | self.args.command = None
212 | self.args.address = None
213 | self.args.bytes = None
214 |
215 | return super(StackView, self).build_requests()
216 |
217 | def render(self, results):
218 | self.title = '[stack]'
219 |
220 | super(StackView, self).render(results)
221 |
222 |
223 | class StackViewPlugin(ViewPlugin):
224 | plugin_type = 'view'
225 | name = 'stack'
226 | view_class = StackView
227 |
--------------------------------------------------------------------------------
/voltron/rdb.py:
--------------------------------------------------------------------------------
1 | import pdb
2 | import socket
3 | import sys
4 |
5 | # Trying to debug a quirk in some code that gets called async by {ll,g}db?
6 | #
7 | # from .rdb import Rdb
8 | # Rdb().set_trace()
9 | #
10 | # Then: telnet localhost 4444
11 |
12 |
13 | socks = {}
14 | # Only bind the socket once
15 | def _sock(port):
16 | if port in socks:
17 | return socks[port]
18 |
19 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
20 | s.bind(("127.0.0.1", port))
21 | socks[port] = s
22 | return s
23 |
24 | class Rdb(pdb.Pdb):
25 | def __init__(self, port=4444):
26 | self.old_stdout = sys.stdout
27 | self.old_stdin = sys.stdin
28 | self.skt = _sock(port)
29 | self.skt.listen(1)
30 | (clientsocket, address) = self.skt.accept()
31 | handle = clientsocket.makefile('rw')
32 | pdb.Pdb.__init__(self, completekey='tab', stdin=handle, stdout=handle)
33 | sys.stdout = sys.stdin = handle
34 |
--------------------------------------------------------------------------------
/voltron/repl.py:
--------------------------------------------------------------------------------
1 | from .core import Client
2 |
3 |
4 | class REPLClient(Client):
5 | """
6 | A Voltron client for use in the Python REPL (e.g. Calculon).
7 | """
8 | def __getattr__(self, key):
9 | try:
10 | res = self.perform_request('registers', registers=[key])
11 | if res.is_success:
12 | return res.registers[key]
13 | else:
14 | print("Error getting register: {}".format(res.message))
15 | except Exception as e:
16 | print("Exception getting register: {}".format(repr(e)))
17 |
18 | def __getitem__(self, key):
19 | try:
20 | d = {}
21 | if isinstance(key, slice):
22 | d['address'] = key.start
23 | d['length'] = key.stop - key.start
24 | else:
25 | d['address'] = key
26 | d['length'] = 1
27 |
28 | res = self.perform_request('memory', **d)
29 |
30 | if res.is_success:
31 | return res.memory
32 | else:
33 | print("Error reading memory: {}".format(res.message))
34 | except Exception as e:
35 | print("Exception reading memory: {}".format(repr(e)))
36 |
37 | def __setitem__(self, key, value):
38 | try:
39 | d = {}
40 | if isinstance(key, slice):
41 | d['address'] = key.start
42 | d['value'] = ((key.stop - key.start) * value)[:key.stop - key.start]
43 | else:
44 | d['address'] = key
45 | d['value'] = value
46 |
47 | res = self.perform_request('write_memory', **d)
48 |
49 | if res.is_success:
50 | return None
51 | else:
52 | print("Error writing memory: {}".format(res.message))
53 | except Exception as e:
54 | print("Exception writing memory: {}".format(repr(e)))
55 |
56 | def __call__(self, command):
57 | try:
58 | res = self.perform_request('command', command=command)
59 | if res.is_success:
60 | return res.output
61 | else:
62 | print("Error executing command: {}".format(res.message))
63 | except Exception as e:
64 | print("Exception executing command: {}".format(repr(e)))
65 |
66 |
67 | V = REPLClient()
68 |
--------------------------------------------------------------------------------
/voltron/styles.py:
--------------------------------------------------------------------------------
1 | from pygments.style import Style
2 | from pygments.token import Token, Comment, Name, Keyword, Generic, Number, Operator, String, Punctuation, Error
3 |
4 | BASE03 = '#002b36'
5 | BASE02 = '#073642'
6 | BASE01 = '#586e75'
7 | BASE00 = '#657b83'
8 | BASE0 = '#839496'
9 | BASE1 = '#93a1a1'
10 | BASE2 = '#eee8d5'
11 | BASE3 = '#fdf6e3'
12 | YELLOW = '#b58900'
13 | ORANGE = '#cb4b16'
14 | RED = '#dc322f'
15 | MAGENTA = '#d33682'
16 | VIOLET = '#6c71c4'
17 | BLUE = '#268bd2'
18 | CYAN = '#2aa198'
19 | GREEN = '#859900'
20 |
21 |
22 | class VolarizedStyle(Style):
23 | background_color = BASE03
24 | styles = {
25 | Keyword: GREEN,
26 | Keyword.Constant: ORANGE,
27 | Keyword.Declaration: BASE1,
28 | Keyword.Namespace: ORANGE,
29 | # Keyword.Pseudo
30 | Keyword.Reserved: BLUE,
31 | Keyword.Type: VIOLET,
32 |
33 | Name: BASE1,
34 | Name.Attribute: BASE1,
35 | Name.Builtin: YELLOW,
36 | Name.Builtin.Pseudo: YELLOW,
37 | Name.Class: BLUE,
38 | Name.Constant: ORANGE,
39 | Name.Decorator: BLUE,
40 | Name.Entity: ORANGE,
41 | Name.Exception: YELLOW,
42 | Name.Function: BLUE,
43 | Name.Label: BASE01,
44 | # Name.Namespace
45 | # Name.Other
46 | Name.Tag: BLUE,
47 | Name.Variable: BLUE,
48 | # Name.Variable.Class
49 | # Name.Variable.Global
50 | # Name.Variable.Instance
51 |
52 | # Literal
53 | # Literal.Date
54 | String: BASE1,
55 | String.Backtick: BASE01,
56 | String.Char: BASE1,
57 | String.Doc: CYAN,
58 | # String.Double
59 | String.Escape: RED,
60 | String.Heredoc: CYAN,
61 | # String.Interpol
62 | # String.Other
63 | String.Regex: RED,
64 | # String.Single
65 | # String.Symbol
66 | Number: CYAN,
67 | # Number.Float
68 | # Number.Hex
69 | # Number.Integer
70 | # Number.Integer.Long
71 | # Number.Oct
72 |
73 | Operator: GREEN,
74 | Operator.Word: GREEN,
75 |
76 | Punctuation: BASE00,
77 |
78 | Comment: BASE00,
79 | # Comment.Multiline
80 | Comment.Preproc: GREEN,
81 | # Comment.Single
82 | Comment.Special: GREEN,
83 |
84 | # Generic
85 | Generic.Deleted: CYAN,
86 | Generic.Emph: 'italic',
87 | Generic.Error: RED,
88 | Generic.Heading: ORANGE,
89 | Generic.Inserted: GREEN,
90 | # Generic.Output
91 | Generic.Prompt: RED,
92 | Generic.Strong: 'bold',
93 | Generic.Subheading: ORANGE,
94 | # Generic.Traceback
95 |
96 | Token: BASE1,
97 | Token.Other: ORANGE,
98 |
99 | Error: RED
100 | }
101 |
--------------------------------------------------------------------------------