From 21ba70e1aec71dc87cf948dc5f3644c96fe437c0 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 12 Nov 2021 14:15:07 -0700 Subject: [PATCH 1/5] Fix typo in target state change log message --- avatar2/targets/target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avatar2/targets/target.py b/avatar2/targets/target.py index 72200b1732..bc1f77d940 100644 --- a/avatar2/targets/target.py +++ b/avatar2/targets/target.py @@ -447,7 +447,7 @@ def remove_breakpoint(self, bkptno): return self.protocols.execution.remove_breakpoint(bkptno) def update_state(self, state): - self.log.info("State changed to to %s" % TargetStates(state)) + self.log.info("State changed to %s", TargetStates(state)) self.state = state @watch('TargetWait') From 49893f7f53242351e016a0f9f6227572dbe1279a Mon Sep 17 00:00:00 2001 From: Grant Hernandez Date: Sun, 9 Jan 2022 16:40:45 -0500 Subject: [PATCH 2/5] Fix GDB protocol test stability across address changes --- tests/test_gdbprotocol.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/test_gdbprotocol.py b/tests/test_gdbprotocol.py index 14d5762910..0b6c30b128 100644 --- a/tests/test_gdbprotocol.py +++ b/tests/test_gdbprotocol.py @@ -9,7 +9,7 @@ SLEEP_TIME = 1 -MEM_ADDR = 0x555555554000 +MEM_BASE = 0 port = 4444 p = None g = None @@ -20,7 +20,7 @@ def setup_helloworld(): - global p, g, port + global p, g, port, MEM_BASE binary = '%s/tests/binaries/hello_world' % os.getcwd() @@ -35,6 +35,8 @@ def setup_helloworld(): g = GDBProtocol(arch=avatar2.archs.X86_64) g.remote_connect(port=port) + # Base addresses can change across kernel versions due to PIE binaries + MEM_BASE = g.get_symbol("main")[1] & ~0xfff def setup_inf_loop(): global p, g, port @@ -84,13 +86,13 @@ def test_break_run_and_read_write_mem(): time.sleep(SLEEP_TIME) - ret = g.read_memory(MEM_ADDR, 4) + ret = g.read_memory(MEM_BASE, 4) assert_equal(ret, 0x464c457f) - ret = g.write_memory(MEM_ADDR, 4, 0x41414141) + ret = g.write_memory(MEM_BASE, 4, 0x41414141) assert_equal(ret, True) - ret = g.read_memory(MEM_ADDR, 4) + ret = g.read_memory(MEM_BASE, 4) assert_equal(ret, 0x41414141) @with_setup(setup_inf_loop, teardown_func) @@ -112,7 +114,8 @@ def test_continue_stopping_stepping(): @with_setup(setup_helloworld, teardown_func) def test_watchpoint(): - ret = g.set_watchpoint(0x555555554754, read=True, + # .rodata string + ret = g.set_watchpoint(MEM_BASE + 0x754, read=True, write=False) assert_equal(ret, True) @@ -122,7 +125,7 @@ def test_watchpoint(): time.sleep(SLEEP_TIME) - ret = g.read_memory(MEM_ADDR, 4) + ret = g.read_memory(MEM_BASE, 4) assert_equal(ret, 0x464c457f) if __name__ == '__main__': From f13ac1d02ed91b04adcf637523d7c93a8c9e2145 Mon Sep 17 00:00:00 2001 From: Grant Hernandez Date: Sun, 9 Jan 2022 16:46:20 -0500 Subject: [PATCH 3/5] Move GDB remote connect logic to common function --- avatar2/protocols/gdb.py | 84 +++++++++++++++------------------------- 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/avatar2/protocols/gdb.py b/avatar2/protocols/gdb.py index 77501fc523..27160ce751 100644 --- a/avatar2/protocols/gdb.py +++ b/avatar2/protocols/gdb.py @@ -336,15 +336,7 @@ def set_abi(self, abi): return ret - def remote_connect(self, ip='127.0.0.1', port=3333): - """ - connect to a remote gdb server - - :param ip: ip of the remote gdb-server (default: localhost) - :param port: port of the remote gdb-server (default: port) - :returns: True on successful connection - """ - + def _remote_connect_common(self, remote_string, transport_setup=None): req = ['-gdb-set', 'target-async', 'on'] ret, resp = self._sync_request(req, GDB_PROT_DONE) if not ret: @@ -355,8 +347,6 @@ def remote_connect(self, ip='127.0.0.1', port=3333): req = ['-gdb-set', 'architecture', self._arch.gdb_name] ret, resp = self._sync_request(req, GDB_PROT_DONE) - - if not ret: self.log.critical( "Unable to set architecture, received response: %s" % @@ -378,7 +368,11 @@ def remote_connect(self, ip='127.0.0.1', port=3333): resp) raise Exception("GDBProtocol was unable to set endianness") - req = ['-target-select', 'remote', '%s:%d' % (ip, int(port))] + # transport unique setup, if applicable + if transport_setup: + transport_setup() + + req = ['-target-select', 'remote', remote_string] ret, resp = self._sync_request(req, GDB_PROT_CONN) self.log.debug( @@ -392,6 +386,17 @@ def remote_connect(self, ip='127.0.0.1', port=3333): return ret + def remote_connect(self, ip='127.0.0.1', port=3333): + """ + connect to a remote gdb server via TCP + + :param ip: ip of the remote gdb-server (default: localhost) + :param port: port of the remote gdb-server (default: port) + :returns: True on successful connection + """ + + return self._remote_connect_common('%s:%d' % (ip, int(port))) + def remote_connect_serial(self, device='/dev/ttyACM0', baud_rate=38400, parity='none'): """ @@ -403,49 +408,24 @@ def remote_connect_serial(self, device='/dev/ttyACM0', baud_rate=38400, :returns: True on successful connection """ - req = ['-gdb-set', 'architecture', self._arch.gdb_name] - ret, resp = self._sync_request(req, GDB_PROT_DONE) - if not ret: - self.log.critical( - "Unable to set architecture, received response: %s" % - resp) - raise Exception(("GDBProtocol was unable to set the architecture\n" - "Did you select the right gdb_executable?")) - - if parity not in ['none', 'even', 'odd']: - self.log.critical("Parity must be none, even or odd") - raise Exception("Cannot set parity to %s" % parity) - - req = ['-gdb-set', 'mi-async', 'on'] - ret, resp = self._sync_request(req, GDB_PROT_DONE) - if not ret: - self.log.critical( - "Unable to set GDB/MI to async, received response: %s" % - resp) - raise Exception("GDBProtocol was unable to connect") - - req = ['-gdb-set', 'serial', 'parity', '%s' % parity] - ret, resp = self._sync_request(req, GDB_PROT_DONE) - if not ret: - self.log.critical("Unable to set parity") - raise Exception("GDBProtocol was unable to set parity") - - req = ['-gdb-set', 'serial', 'baud', '%i' % baud_rate] - ret, resp = self._sync_request(req, GDB_PROT_DONE) - if not ret: - self.log.critical("Unable to set baud rate") - raise Exception("GDBProtocol was unable to set Baudrate") - - req = ['-target-select', 'remote', '%s' % device] - ret, resp = self._sync_request(req, GDB_PROT_CONN) + def serial_setup(): + if parity not in ['none', 'even', 'odd']: + self.log.critical("Parity must be none, even or odd") + raise Exception("Cannot set parity to %s" % parity) - self.log.debug( - "Attempted to connect to target. Received response: %s" % - resp) + req = ['-gdb-set', 'serial', 'parity', '%s' % parity] + ret, resp = self._sync_request(req, GDB_PROT_DONE) + if not ret: + self.log.critical("Unable to set parity") + raise Exception("GDBProtocol was unable to set parity") - self.update_target_regs() + req = ['-gdb-set', 'serial', 'baud', '%i' % baud_rate] + ret, resp = self._sync_request(req, GDB_PROT_DONE) + if not ret: + self.log.critical("Unable to set baud rate") + raise Exception("GDBProtocol was unable to set Baudrate") - return ret + return self._remote_connect_common(device, transport_setup=serial_setup) def remote_disconnect(self): """ From 08c119d7c52859d33205c5149c6ad5b0916f7fff Mon Sep 17 00:00:00 2001 From: Grant Hernandez Date: Fri, 14 Jan 2022 17:01:11 -0500 Subject: [PATCH 4/5] Support remote UNIX sockets for GDBProtocol --- avatar2/protocols/gdb.py | 9 +++++++ tests/test_gdbprotocol.py | 29 +++++++++++++++++++++++ tests/test_utils.py | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 tests/test_utils.py diff --git a/avatar2/protocols/gdb.py b/avatar2/protocols/gdb.py index 27160ce751..f8ef915375 100644 --- a/avatar2/protocols/gdb.py +++ b/avatar2/protocols/gdb.py @@ -397,6 +397,15 @@ def remote_connect(self, ip='127.0.0.1', port=3333): return self._remote_connect_common('%s:%d' % (ip, int(port))) + def remote_connect_unix(self, unix_path): + """ + connect to a remote gdb server via a UNIX domain socket path + + :param unix_path: path of the UNIX domain socket + :returns: True on successful connection + """ + return self._remote_connect_common(unix_path) + def remote_connect_serial(self, device='/dev/ttyACM0', baud_rate=38400, parity='none'): """ diff --git a/tests/test_gdbprotocol.py b/tests/test_gdbprotocol.py index 0b6c30b128..e22fa7b4b1 100644 --- a/tests/test_gdbprotocol.py +++ b/tests/test_gdbprotocol.py @@ -5,6 +5,7 @@ import os import time +from test_utils import unix2tcp from nose.tools import * SLEEP_TIME = 1 @@ -38,6 +39,28 @@ def setup_helloworld(): # Base addresses can change across kernel versions due to PIE binaries MEM_BASE = g.get_symbol("main")[1] & ~0xfff +def setup_helloworld_unix(): + global p, g, port, MEM_BASE + + socket_path ='/tmp/test_socket' + unix2tcp(socket_path, "127.0.0.1", port) + + binary = '%s/tests/binaries/hello_world' % os.getcwd() + p = subprocess.Popen(['gdbserver', '--once', '127.0.0.1:%d' % port, binary], + stderr=subprocess.PIPE) + + out = str(p.stderr.readline()) + assert_equal(binary in out, True) + out = str(p.stderr.readline()) + assert_equal(str(port) in out, True) + + g = GDBProtocol(arch=avatar2.archs.X86_64) + g.remote_connect_unix(socket_path) + + # Base addresses can change across kernel versions due to PIE binaries + MEM_BASE = g.get_symbol("main")[1] & ~0xfff + + def setup_inf_loop(): global p, g, port @@ -73,6 +96,12 @@ def test_register_read_and_write(): ret = g.read_register('rax') assert_equal(ret, 1678) +@with_setup(setup_helloworld_unix, teardown_func) +def test_register_read_and_write_unix(): + ret = g.write_register('rax', 1678) + assert_equal(ret, True) + ret = g.read_register('rax') + assert_equal(ret, 1678) @with_setup(setup_helloworld, teardown_func) def test_break_run_and_read_write_mem(): diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000000..4d1ac3f123 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,50 @@ +import socket +import threading +import os +import time + +# oneshot UNIX to TCP socket proxy. Stops after first connection +def unix2tcp(unix_socket_path, tcp_host, tcp_port): + try: + os.unlink(unix_socket_path) + except OSError: + pass + + usock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + tsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + usock.bind(unix_socket_path) + usock.listen(1) + + def proxy_loop(): + uconn, addr = usock.accept() + tsock.connect((tcp_host, tcp_port)) + + uconn.setblocking(False) + tsock.setblocking(False) + + while True: + data = None + + try: + data = uconn.recv(1000) + if len(data) == 0: + break + tsock.sendall(data) + except BlockingIOError: + pass + + try: + data = tsock.recv(1000) + if len(data) == 0: + break + uconn.sendall(data) + except BlockingIOError: + pass + + usock.close() + uconn.close() + tsock.close() + + threading.Thread(target=proxy_loop, daemon=True).start() + From f4ad6fc6e845ce990e3f4a4c81515e5e56b3f32c Mon Sep 17 00:00:00 2001 From: Grant Hernandez Date: Fri, 14 Jan 2022 18:17:29 -0500 Subject: [PATCH 5/5] QEMUTarget: add support for GDB UNIX sockets --- avatar2/targets/gdb_target.py | 9 ++++++++- avatar2/targets/qemu_target.py | 19 +++++++++++++++++-- tests/test_qemutarget.py | 16 +++++++++++++--- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/avatar2/targets/gdb_target.py b/avatar2/targets/gdb_target.py index 3b8887909e..75d99256eb 100644 --- a/avatar2/targets/gdb_target.py +++ b/avatar2/targets/gdb_target.py @@ -8,6 +8,7 @@ class GDBTarget(Target): def __init__(self, avatar, gdb_executable=None, gdb_additional_args=None, gdb_ip='127.0.0.1', gdb_port=3333, + gdb_unix_socket_path=None, gdb_serial_device='/dev/ttyACM0', gdb_serial_baud_rate=38400, gdb_serial_parity='none', @@ -26,6 +27,7 @@ def __init__(self, avatar, self.gdb_additional_args = gdb_additional_args if gdb_additional_args else [] self.gdb_ip = gdb_ip self.gdb_port = gdb_port + self.gdb_unix_socket_path = gdb_unix_socket_path self.gdb_serial_device = gdb_serial_device self.gdb_serial_baud_rate = gdb_serial_baud_rate self.gdb_serial_parity = gdb_serial_parity @@ -49,7 +51,12 @@ def init(self): # If we are debugging a program locally, # we do not need to establish any connections if not self._local_binary: - if not self._serial: + if self.gdb_unix_socket_path is not None: + if gdb.remote_connect_unix(self.gdb_unix_socket_path): + self.log.info("Connected to Target") + else: + self.log.warning("Connecting failed") + elif not self._serial: if gdb.remote_connect(ip=self.gdb_ip, port=self.gdb_port): self.log.info("Connected to Target") else: diff --git a/avatar2/targets/qemu_target.py b/avatar2/targets/qemu_target.py index 2be84c10c9..6257d44ec6 100644 --- a/avatar2/targets/qemu_target.py +++ b/avatar2/targets/qemu_target.py @@ -25,6 +25,7 @@ def __init__( firmware=None, gdb_executable=None, gdb_port=3333, + gdb_unix_socket_path=None, additional_args=None, gdb_additional_args=None, gdb_verbose=False, @@ -58,6 +59,7 @@ def __init__( ) self.gdb_port = gdb_port + self.gdb_unix_socket_path = gdb_unix_socket_path self.gdb_additional_args = gdb_additional_args if gdb_additional_args else [] self.gdb_verbose = gdb_verbose @@ -90,7 +92,10 @@ def assemble_cmd_line(self): machine = ["-machine", "configurable"] kernel = ["-kernel", self.qemu_config_file] - gdb_option = ["-gdb", "tcp::" + str(self.gdb_port)] + if self.gdb_unix_socket_path: + gdb_option = ["-gdb", "unix:%s,server,nowait" % str(self.gdb_unix_socket_path)] + else: + gdb_option = ["-gdb", "tcp::" + str(self.gdb_port)] stop_on_startup = ["-S"] nographic = ["-nographic"] # , "-monitor", "/dev/null"] qmp = ["-qmp", "tcp:127.0.0.1:%d,server,nowait" % self.qmp_port] @@ -301,10 +306,20 @@ def _connect_protocols(self): self.protocols.monitor = qmp self.protocols.remote_memory = rmp - if gdb.remote_connect(port=self.gdb_port) and qmp.connect(): + connect_success = True + + if self.gdb_unix_socket_path: + connect_success = connect_success and gdb.remote_connect_unix(self.gdb_unix_socket_path) + else: + connect_success = connect_success and gdb.remote_connect(port=self.gdb_port) + + connect_success = connect_success and qmp.connect() + + if connect_success: self.log.info("Connected to remote target") else: self.log.warning("Connection to remote target failed") + if rmp: rmp.connect() self.wait() diff --git a/tests/test_qemutarget.py b/tests/test_qemutarget.py index d366e4ea21..6683380cf2 100644 --- a/tests/test_qemutarget.py +++ b/tests/test_qemutarget.py @@ -73,17 +73,18 @@ def write_memory(self, addr, size, val, *args, **kwargs): return True -def setup(): +def setup(gdb_unix_socket_path=None): global qemu global avatar global fake_target arch = setup_ARCH() - avatar = Avatar(arch=arch, output_directory=test_dir) + avatar = Avatar(arch=arch, output_directory=test_dir, configure_logging=False) qemu = QemuTarget(avatar, name='qemu_test', #firmware="./tests/binaries/qemu_arm_test", firmware='%s/firmware' % test_dir, + gdb_unix_socket_path=gdb_unix_socket_path, ) fake_target = FakeTarget() @@ -127,13 +128,22 @@ def teardown(): @with_setup(setup, teardown) -def test_initilization(): +def test_initialization(): global qemu qemu.init() qemu.wait() assert_equal(qemu.state, TargetStates.STOPPED) +@with_setup(lambda: setup(gdb_unix_socket_path="/tmp/test_sock"), teardown) +def test_initialization_unix(): + global qemu + + qemu.init() + qemu.wait() + + assert_equal(qemu.state, TargetStates.STOPPED) + qemu.shutdown() @with_setup(setup, teardown) def test_step():