From 14f3192af3c61d2d10b7953bccf1c18b27928b5b Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 11:49:43 +0100 Subject: [PATCH 01/11] Made client restartable, for starting, stopping and starting again --- voltron/core.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/voltron/core.py b/voltron/core.py index f22f375c..da799a23 100644 --- a/voltron/core.py +++ b/voltron/core.py @@ -412,7 +412,7 @@ def __init__(self, host='127.0.0.1', port=5555, sockfile=None, url=None, self.server_version = None self.block = False self.supports_blocking = supports_blocking - + self.sw = None def send_request(self, request): """ Send a request to the server. @@ -570,17 +570,28 @@ def start(self, build_requests=None, callback=None): """ Run the client using a background thread. """ + if not self.done and self.sw != None: + self.callback(error='Error: Client is already running') + return + + if self.done and self.sw != None: + if self.sw.is_alive(): + self.sw.join() + self.sw = None + if callback: self.callback = callback if build_requests: self.build_requests = build_requests # spin off requester thread + self.done = False self.sw = threading.Thread(target=self.run) self.sw.start() - def stop(self): """ Stop the background thread. """ self.done = True + self.sw.join() + self.sw = None From a926ce0e1f124e63996920c62673fe37b6235a56 Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 11:51:20 +0100 Subject: [PATCH 02/11] GDB: Support looking up line number from address --- voltron/plugins/debugger/dbg_gdb.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/voltron/plugins/debugger/dbg_gdb.py b/voltron/plugins/debugger/dbg_gdb.py index 5e9c5367..cc0e1405 100644 --- a/voltron/plugins/debugger/dbg_gdb.py +++ b/voltron/plugins/debugger/dbg_gdb.py @@ -306,6 +306,23 @@ def disassemble(self, target_id=0, address=None, count=16): return output + @post_event + def source_location(self, target_id=0, address=None): + """ + Get the source file name and line number of the given address. + This requires symbols to be loaded for the target. + + `address` is the code address to resolve filename and + line number from. If None, the current program counter is used + """ + if address == None: + pc_name, address = self._program_counter(target_id=target_id) + m = re.match('Line\\s+(\\d+)\\s+of\\s+"([^"]+)"', gdb.execute('info line *0x{:x}'.format(address), to_string=True)) + if m != None: + file, line = m.group(2,1) + return (file, int(line)) + return None + @validate_busy @validate_target @post_event From a575e4840a9c88cadd3eaa475bb1bde1156e58f4 Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 11:51:43 +0100 Subject: [PATCH 03/11] LLDB: Support looking up line number from address --- voltron/plugins/debugger/dbg_lldb.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/voltron/plugins/debugger/dbg_lldb.py b/voltron/plugins/debugger/dbg_lldb.py index 4dd8ec8e..bb19fbfc 100644 --- a/voltron/plugins/debugger/dbg_lldb.py +++ b/voltron/plugins/debugger/dbg_lldb.py @@ -330,6 +330,22 @@ def disassemble(self, target_id=0, address=None, count=None): return output + @validate_busy + @validate_target + @lock_host + def source_location(self, target_id=0, address=None): + if address == None: + pc_name, address = self.program_counter(target_id=target_id) + t = self.host.GetTargetAtIndex(target_id or 0) + sbaddr = lldb.SBAddress(address, t) + ctx = t.ResolveSymbolContextForAddress(sbaddr, lldb.eSymbolContextEverything) + if ctx.IsValid() and ctx.GetSymbol().IsValid(): + line_entry = ctx.GetLineEntry() + if line_entry.IsValid(): + file_spec = line_entry.GetFileSpec() + return (file_spec.__get_fullpath__(), line_entry.GetLine()) + return None + @validate_busy @validate_target @lock_host From c3e87619798f77a338741bf45ee1378738fb418f Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 11:52:11 +0100 Subject: [PATCH 04/11] API: Added source_location API request --- voltron/plugins/api/source_location.py | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 voltron/plugins/api/source_location.py diff --git a/voltron/plugins/api/source_location.py b/voltron/plugins/api/source_location.py new file mode 100644 index 00000000..f8daee7d --- /dev/null +++ b/voltron/plugins/api/source_location.py @@ -0,0 +1,66 @@ +import logging + +import voltron +from voltron.api import * + +from scruffy.plugin import Plugin + +log = logging.getLogger('api') + +class APISourceLocationRequest(APIRequest): + """ + API source location from address request. + + { + "type": "request", + "request": "source_location" + "data": { + "target_id": 0, + "address": 0xffffff8012341234 + } + } + `target_id` is optional. + `address` is the address to lookup source information for. Defaults to + instruction pointer if not specified. + """ + _fields = {'target_id': False, 'address': False} + + @server_side + def dispatch(self): + try: + output = voltron.debugger.source_location(target_id=self.target_id, address=self.address) + log.debug('output: {}'.format(str(output))) + res = APISourceLocationResponse() + res.output = output + except NoSuchTargetException: + res = APINoSuchTargetErrorResponse() + except Exception as e: + msg = "Exception finding source location from address: {}".format(repr(e)) + log.exception(msg) + res = APIGenericErrorResponse(msg) + + return res + + +class APISourceLocationResponse(APISuccessResponse): + """ + API source location from address response. Output may be None if no + sorce information is available for the address + + { + "type": "response", + "status": "success", + "data": { + "output": ["main.c", 8] + } + } + """ + _fields = {'output': True} + + output = None + + +class APISourceLocationPlugin(APIPlugin): + request = "source_location" + request_class = APISourceLocationRequest + response_class = APISourceLocationResponse From b04c9c6a712ca0d8dec231fc94b3adc2ff168601 Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 12:57:19 +0100 Subject: [PATCH 05/11] WinDBG: Support looking up line number from address --- voltron/plugins/debugger/dbg_windbg.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/voltron/plugins/debugger/dbg_windbg.py b/voltron/plugins/debugger/dbg_windbg.py index fa91d24e..c795bc0f 100644 --- a/voltron/plugins/debugger/dbg_windbg.py +++ b/voltron/plugins/debugger/dbg_windbg.py @@ -259,6 +259,18 @@ def disassemble(self, target_id=0, address=None, count=16): return output + @validate_busy + @validate_target + @lock_host + def source_location(self, target_id=0, address=None): + if address is None: + pc_name, address = self.program_counter(target_id=target_id) + try: + file, line, displacement = pykd.getSourceLine(address) + return (file, line) + except: + return None + @validate_busy @validate_target @lock_host From 07dff3fcbf7abe9e94ce1625d3f0a5fc8de5d68c Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 14:11:01 +0100 Subject: [PATCH 06/11] VDB: Add source_location method. Always returns None --- voltron/plugins/debugger/dbg_vdb.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/voltron/plugins/debugger/dbg_vdb.py b/voltron/plugins/debugger/dbg_vdb.py index 1567954a..5732daec 100644 --- a/voltron/plugins/debugger/dbg_vdb.py +++ b/voltron/plugins/debugger/dbg_vdb.py @@ -245,6 +245,12 @@ def _get_n_opcodes_length(self, address, count): length += op.size return length + @validate_busy + @validate_target + @lock_host + def source_location(self, target_id=0, address=None): + return None + @validate_busy @validate_target @lock_host From b9edd752723cfbcff06a73371d1ad200e585cca2 Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 14:35:00 +0100 Subject: [PATCH 07/11] GDB: Added test case for source_location method --- tests/gdb_cli_tests.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/gdb_cli_tests.py b/tests/gdb_cli_tests.py index c10fed9f..ce244ad7 100644 --- a/tests/gdb_cli_tests.py +++ b/tests/gdb_cli_tests.py @@ -42,7 +42,7 @@ def setup(): voltron.setup_env() # compile test inferior - pexpect.run("cc -o tests/inferior tests/inferior.c") + pexpect.run("cc -g -o tests/inferior tests/inferior.c") # start debugger start_debugger() @@ -124,6 +124,13 @@ def test_memory(): assert res.status == 'success' assert len(res.memory) > 0 +def test_source_location(): + res = client.perform_request('source_location', address=registers['rip']) + assert res.status == 'success' + assert not res.output is None + file, line = res.output + assert os.path.basename(file) == 'inferior.c' + assert line == 12 def test_state_stopped(): res = client.perform_request('state') From 7e7814ddf1da0cd50a088c0277e1f5d9a9deb837 Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 15:24:27 +0100 Subject: [PATCH 08/11] LLDB: `source_information` should fallback to first target if none is provided --- voltron/plugins/debugger/dbg_lldb.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/voltron/plugins/debugger/dbg_lldb.py b/voltron/plugins/debugger/dbg_lldb.py index bb19fbfc..a4d6d16c 100644 --- a/voltron/plugins/debugger/dbg_lldb.py +++ b/voltron/plugins/debugger/dbg_lldb.py @@ -334,6 +334,8 @@ def disassemble(self, target_id=0, address=None, count=None): @validate_target @lock_host def source_location(self, target_id=0, address=None): + if target_id is None: + target_id=0 if address == None: pc_name, address = self.program_counter(target_id=target_id) t = self.host.GetTargetAtIndex(target_id or 0) From 76538ae150c408ddced5a76e7eb4f4f2de298dbf Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Wed, 14 Mar 2018 15:26:49 +0100 Subject: [PATCH 09/11] LLDB: Added test cases for source_location, for targets with and without debugging symbols --- tests/lldb_cli_tests.py | 6 ++ tests/lldb_cli_tests_with_symbols.py | 140 +++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 tests/lldb_cli_tests_with_symbols.py diff --git a/tests/lldb_cli_tests.py b/tests/lldb_cli_tests.py index a198fc19..bdf422b2 100644 --- a/tests/lldb_cli_tests.py +++ b/tests/lldb_cli_tests.py @@ -196,6 +196,12 @@ def test_disassemble(): assert len(res.disassembly) > 0 assert 'push' in res.disassembly +def test_source_location(): + restart() + time.sleep(1) + res = client.perform_request('source_location', address=registers['rip']) + assert res.status == 'success' + assert res.output is None def test_dereference(): restart() diff --git a/tests/lldb_cli_tests_with_symbols.py b/tests/lldb_cli_tests_with_symbols.py new file mode 100644 index 00000000..a3410f40 --- /dev/null +++ b/tests/lldb_cli_tests_with_symbols.py @@ -0,0 +1,140 @@ +""" +Tests that test voltron in the lldb cli driver + +Tests: +Client -> Server -> LLDBAdaptor + +Inside an LLDB CLI driver instance +""" +from __future__ import print_function + +import tempfile +import sys +import json +import time +import logging +import pexpect +import os +import tempfile +import six + +from mock import Mock +from nose.tools import * + +import voltron +from voltron.core import * +from voltron.api import * +from voltron.plugin import PluginManager, DebuggerAdaptorPlugin + +from .common import * + +log = logging.getLogger('tests') + +p = None +client = None + + +def setup(): + global p, client, pm + + log.info("setting up LLDB CLI tests") + + voltron.setup_env() + + # compile test inferior + pexpect.run("cc -g -o tests/inferior tests/inferior.c") + + # start debugger + start_debugger() + time.sleep(10) + + +def teardown(): + read_data() + p.terminate(True) + time.sleep(2) + + +def start_debugger(do_break=True): + global p, client + + if sys.platform == 'darwin': + p = pexpect.spawn('lldb') + else: + p = pexpect.spawn('lldb-3.4') + + # for travis + (f, tmpname) = tempfile.mkstemp('.py') + os.write(f, six.b('\n'.join([ + "import sys", + "sys.path.append('/home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages')", + "sys.path.append('/home/travis/virtualenv/python3.4.3/lib/python3.4/site-packages')", + "sys.path.append('/home/travis/virtualenv/python3.3.6/lib/python3.3/site-packages')", + "sys.path.append('/home/travis/virtualenv/python2.7.10/lib/python2.7/site-packages')"]))) + p.sendline("command script import {}".format(tmpname)) + + print("pid == {}".format(p.pid)) + p.sendline("settings set target.x86-disassembly-flavor intel") + p.sendline("command script import voltron/entry.py") + time.sleep(2) + p.sendline("file tests/inferior") + time.sleep(2) + p.sendline("voltron init") + time.sleep(1) + p.sendline("process kill") + p.sendline("break delete 1") + if do_break: + p.sendline("b main") + p.sendline("run loop") + read_data() + client = Client() + + +def stop_debugger(): + p.terminate(True) + + +def read_data(): + try: + while True: + data = p.read_nonblocking(size=64, timeout=1) + print(data.decode('UTF-8'), end='') + except: + pass + + +def restart(do_break=True): + # stop_debugger() + # start_debugger(do_break) + p.sendline("process kill") + p.sendline("break delete -f") + if do_break: + p.sendline("b main") + p.sendline("run loop") + + +def test_registers(): + global registers + restart() + time.sleep(1) + read_data() + res = client.perform_request('registers') + registers = res.registers + assert res.status == 'success' + assert len(registers) > 0 + if 'rip' in registers: + assert registers['rip'] != 0 + else: + assert registers['eip'] != 0 + + +def test_source_location(): + restart() + time.sleep(1) + res = client.perform_request('source_location', address=registers['rip']) + assert res.status == 'success' + assert not res.output is None + file, line = res.output + assert os.path.basename(file) == 'inferior.c' + assert line == 12 + From 61ee80e7451a800951ad971f9923556d1e12b6d7 Mon Sep 17 00:00:00 2001 From: "Fredrik A. Krisiansen" Date: Wed, 14 Mar 2018 16:55:37 +0100 Subject: [PATCH 10/11] Fixed indentation error --- voltron/plugins/debugger/dbg_lldb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voltron/plugins/debugger/dbg_lldb.py b/voltron/plugins/debugger/dbg_lldb.py index a4d6d16c..364e7107 100644 --- a/voltron/plugins/debugger/dbg_lldb.py +++ b/voltron/plugins/debugger/dbg_lldb.py @@ -334,8 +334,8 @@ def disassemble(self, target_id=0, address=None, count=None): @validate_target @lock_host def source_location(self, target_id=0, address=None): - if target_id is None: - target_id=0 + if target_id is None: + target_id=0 if address == None: pc_name, address = self.program_counter(target_id=target_id) t = self.host.GetTargetAtIndex(target_id or 0) From e6d635ee28a78ef5396a6ff84846c54237b917fe Mon Sep 17 00:00:00 2001 From: "Fredrik A. Kristiansen" Date: Sat, 17 Mar 2018 21:01:15 +0100 Subject: [PATCH 11/11] GDB: Rust code with symbols messes up register output. Broke everything when debugging Rust code --- voltron/plugins/debugger/dbg_gdb.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/voltron/plugins/debugger/dbg_gdb.py b/voltron/plugins/debugger/dbg_gdb.py index cc0e1405..6bbaec3c 100644 --- a/voltron/plugins/debugger/dbg_gdb.py +++ b/voltron/plugins/debugger/dbg_gdb.py @@ -561,7 +561,11 @@ def get_registers_x86_64(self): return vals def get_register_x86_64(self, reg): - return int(gdb.parse_and_eval('(long long)$'+reg)) & 0xFFFFFFFFFFFFFFFF + # Encountered some errors debugging Rust code with symbols + # Output was 0xdeadbeef <_some_func> + # With this hack, everything works, and nothing breaks + val = re.match('(^(?:0x[0-9a-fA-F]+)|(?:-?[0-9]+))', str(gdb.parse_and_eval('$'+reg))).group(1) + return int(val, 0) & 0xFFFFFFFFFFFFFFFF def get_registers_x86(self): # Get regular registers