Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debugger host and API support for resolving source locations from address #231

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion tests/gdb_cli_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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')
Expand Down
6 changes: 6 additions & 0 deletions tests/lldb_cli_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
140 changes: 140 additions & 0 deletions tests/lldb_cli_tests_with_symbols.py
Original file line number Diff line number Diff line change
@@ -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

15 changes: 13 additions & 2 deletions voltron/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
66 changes: 66 additions & 0 deletions voltron/plugins/api/source_location.py
Original file line number Diff line number Diff line change
@@ -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
23 changes: 22 additions & 1 deletion voltron/plugins/debugger/dbg_gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -544,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
Expand Down
18 changes: 18 additions & 0 deletions voltron/plugins/debugger/dbg_lldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,24 @@ 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 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)
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
Expand Down
6 changes: 6 additions & 0 deletions voltron/plugins/debugger/dbg_vdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions voltron/plugins/debugger/dbg_windbg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down