Skip to content

Commit

Permalink
Dev/pypanda target (#73)
Browse files Browse the repository at this point in the history
* Fix cpu at 100% spinning bug

Pygdbmis new API with multilevel timeout allows us to not spin at 100%
when waiting for responses, while not experience a performance degration
at the same time.

* move qemu_cmd_line init into __init__

* allow sublcassing for pandatarget

* refactor init to mulitple steps

* initial pypanda implementation

* use modern pypanda api

* add init watch

* add callback wrappers

* resolve qemutarget executable only if it is qemutarget

* Advancements in PyPandaTarget

* saner init of PandaTarget (for resolving executable)
* disable/enable callbacks
* high-performant memory read/write
* CI testcase

* Fix threading/signal issue

* Improved signal handling
  • Loading branch information
mariusmue authored Feb 10, 2021
1 parent c392248 commit c41e6e5
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
language: python
python:
python:
- "3.8"

services:
Expand Down Expand Up @@ -34,6 +34,7 @@ script:
- docker exec avatar2 bash -c 'cd avatar2/ && AVATAR2_GDB_EXECUTABLE=gdb-multiarch AVATAR2_ARCH=MIPS AVATAR2_QEMU_EXECUTABLE=panda-system-mips nosetests-3.4 ./tests/test_qemutarget.py'
- docker exec avatar2 bash -c 'cd avatar2/ && AVATAR2_GDB_EXECUTABLE=gdb-multiarch AVATAR2_QEMU_EXECUTABLE=panda-system-arm nosetests-3.4 ./tests/pyperipheral/test_pyperipheral.py'
- docker exec avatar2 bash -c 'cd avatar2/ && AVATAR2_GDB_EXECUTABLE=gdb-multiarch AVATAR2_QEMU_EXECUTABLE=panda-system-arm AVATAR2_PANDA_EXECUTABLE=panda-system-arm nosetests-3.4 ./tests/smoke/panda_thumb.py'
- docker exec avatar2 bash -c 'cd avatar2/ && AVATAR2_GDB_EXECUTABLE=gdb-multiarch AVATAR2_QEMU_EXECUTABLE=panda-system-arm AVATAR2_PANDA_EXECUTABLE=panda-system-arm nosetests-3.4 ./tests/test_pypandatarget.py'

- docker exec avatar2 bash -c 'cd avatar2/ && python3 ./tests/hello_world.py'
- docker exec avatar2 bash -c 'cd avatar2/ && python3 ./tests/gdb_memory_map_loader.py'
Expand Down
1 change: 1 addition & 0 deletions avatar2/targets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
from .jlink_target import *
from .qemu_target import *
from .panda_target import *
from .pypanda_target import *
from .unicorn_target import *
9 changes: 5 additions & 4 deletions avatar2/targets/panda_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

class PandaTarget(QemuTarget):
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
super(PandaTarget, self).__init__(*args, **kwargs)

executable = kwargs.get('executable')
self.executable = (executable if executable is not None
else self._arch.get_panda_executable())
Expand All @@ -17,7 +18,7 @@ def begin_record(self, record_name):
"""
Starts recording the execution in PANDA
:param record_name: The name of the record file
:param record_name: The name of the record file
"""
filename = "%s/%s" % (self.avatar.output_directory, record_name)
return self.protocols.monitor.execute_command('begin_record',
Expand Down Expand Up @@ -59,9 +60,9 @@ def load_plugin(self, plugin_name, plugin_args=None, file_name=None):
Loads a PANDA plugin
:param plugin_name: The name of the plugin to be loaded
:param plugin_args: Arguments to be passed to the plugin,
:param plugin_args: Arguments to be passed to the plugin,
aseperated by commas
:param file_name: Absolute path to the plugin shared object file,
:param file_name: Absolute path to the plugin shared object file,
in case that the default one should not be used
"""

Expand Down
122 changes: 122 additions & 0 deletions avatar2/targets/pypanda_target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from threading import Thread
from time import sleep
from avatar2.targets import PandaTarget

from ..watchmen import watch
from .target import action_valid_decorator_factory, TargetStates


class PyPandaTarget(PandaTarget):
'''
The pypanda target is a PANDA target, but uses pypanda to run the framework.
'''
def __init__(self, *args, **kwargs):
try:
import pandare
except ImportError:
raise RuntimeError(("PyPanda could not be found! for installation, "
"please follow the steps at https://github.com/"
"panda-re/panda/blob/master/panda/pypanda/docs/USAGE.md"))

super(PyPandaTarget, self).__init__(*args, **kwargs)

self.cb_ctx = 0
self.pypanda = None
self._thread = None

def shutdown(self):


if self._thread.is_alive():
self.protocols.execution.remote_disconnect()
self.pypanda.end_analysis()

# Wait for shutdown
while self._thread.is_alive():
sleep(.01)


@watch('TargetInit')
def init(self, **kwargs):
from pandare import Panda

arch = self.avatar.arch.gdb_name # for now, gdbname and panda-name match
args = self.assemble_cmd_line()[1:]



self.avatar.save_config(file_name=self.qemu_config_file,
config=self.generate_qemu_config())


self.pypanda = Panda(arch=arch, extra_args=args, **kwargs)


# adjust panda's signal handler to avatar2-standard
def SigHandler(SIG,a,b):
if self.state == TargetStates.RUNNING:
self.stop()
self.wait()

self.avatar.sigint_handler()



self.pypanda.setup_internal_signal_handler(signal_handler=SigHandler)

self._thread = Thread(target=self.pypanda.run, daemon=True)
self._thread.start()

self._connect_protocols()



def register_callback(self, callback, function, name=None, enabled=True,
procname=None):
pp = self.pypanda

if hasattr(pp.callback, callback) is False:
raise Exception("Callback %s not found!" % callback)
cb = getattr(pp.callback, callback)

if name == None:
name = 'avatar_cb_%d' % self.cb_ctx
self.cb_ctx += 1

pp.register_callback(cb, cb(function), name, enabled=enabled,
procname=procname)

return name

def disable_callback(self, name):
pp = self.pypanda
pp.disable_callback(name)

def enable_callback(self, name):
pp = self.pypanda
pp.enable_callback(name)


@watch('TargetReadMemory')
@action_valid_decorator_factory(TargetStates.STOPPED, 'memory')
def read_memory(self, address, size, num_words=1, raw=False):
if raw == False:
return self.protocols.memory.read_memory(address, size, num_words)
else:
return self.pypanda.physical_memory_read(address,size*num_words)


@watch('TargetWriteMemory')
@action_valid_decorator_factory(TargetStates.STOPPED, 'memory')
def write_memory(self, address, size, value, num_words=1, raw=False):
if raw == False:
return self.protocols.memory.write_memory(address, size, num_words)
else:
return self.pypanda.physical_memory_write(address, value)



def delete_callback(self, name):
return self.pypanda.delete_callback(name)

20 changes: 13 additions & 7 deletions avatar2/targets/qemu_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self, avatar,

# Qemu parameters
self.system_clock_scale = system_clock_scale
if hasattr(self, 'executable') is False: # May be initialized by subclass
if hasattr(self, 'executable') is False and self.__class__ == QemuTarget:
self.executable = (executable if executable is not None
else self._arch.get_qemu_executable())
self.fw = firmware
Expand All @@ -58,10 +58,12 @@ def __init__(self, avatar,
self._rmem_rx_queue_name = '/{:s}_rx_queue'.format(self.name)
self._rmem_tx_queue_name = '/{:s}_tx_queue'.format(self.name)


self.log_items = log_items
self.log_file = log_file

self.qemu_config_file = ("%s/%s_conf.json" %
(self.avatar.output_directory, self.name) )


def assemble_cmd_line(self):
if isfile(self.executable + self._arch.qemu_name):
Expand All @@ -82,7 +84,7 @@ def assemble_cmd_line(self):

cmd_line = executable_name + machine + kernel + gdb_option \
+ stop_on_startup + self.additional_args + nographic + qmp

if self.log_items is not None:
if isinstance(self.log_items, str):
log_items = ['-d', self.log_items]
Expand All @@ -97,7 +99,7 @@ def assemble_cmd_line(self):
log_file = ['-D', '%s/%s' % (self.avatar.output_directory,
self.log_file)]
else:
log_file = ['-D', '%s/%s_log.txt' %
log_file = ['-D', '%s/%s_log.txt' %
(self.avatar.output_directory, self.name)]

cmd_line += log_items + log_file
Expand Down Expand Up @@ -166,8 +168,6 @@ def init(self, cmd_line=None):
else:
self.log.warning('No cpu_model specified - are you sure?')

self.qemu_config_file = ("%s/%s_conf.json" %
(self.avatar.output_directory, self.name) )
if cmd_line is None:
cmd_line = self.assemble_cmd_line()

Expand All @@ -181,14 +181,20 @@ def init(self, cmd_line=None):
self._process = Popen(cmd_line, stdout=out, stderr=err)
self.log.debug("QEMU command line: %s" % ' '.join(cmd_line))
self.log.info("QEMU process running")
self._connect_protocols()

def _connect_protocols(self):
"""
Internal routine to connect the various protocols to a running qemu
"""

gdb = GDBProtocol(gdb_executable=self.gdb_executable,
arch=self.avatar.arch,
verbose=self.gdb_verbose,
additional_args=self.gdb_additional_args,
avatar=self.avatar, origin=self,
)
qmp = QMPProtocol(self.qmp_port, origin=self)
qmp = QMPProtocol(self.qmp_port, origin=self)

if 'avatar-rmemory' in [i[2].qemu_name for i in
self._memory_mapping.iter() if
Expand Down
Loading

0 comments on commit c41e6e5

Please sign in to comment.