#!/usr/bin/env python3 import os import re import shutil import shlex import socket import subprocess from struct import pack, unpack from . import rsp from . import utils from . import gdblike from . import DebugAdapter class DebugAdapterGdb(gdblike.DebugAdapterGdbLike): def __init__(self, **kwargs): gdblike.DebugAdapterGdbLike.__init__(self, **kwargs) self.rsp = None self.module_cache = {} #-------------------------------------------------------------------------- # API #-------------------------------------------------------------------------- def exec(self, path, args=[], **kwargs): if not os.access(path, os.X_OK): raise DebugAdapter.NotExecutableError(path) # resolve path to gdbserver path_gdbserver = shutil.which('gdbserver') if not os.path.exists(path_gdbserver): raise DebugAdapter.NotInstalledError('gdbserver') # get available port port = gdblike.get_available_port() if port == None: raise Exception('no available ports') # invoke gdbserver try: if kwargs.get('terminal', False): dbg_args = [path_gdbserver, '--once', '--no-startup-with-shell', 'localhost:%d'%port, shlex.quote(path)] dbg_args.extend([shlex.quote(arg) for arg in args]) DebugAdapter.new_terminal(' '.join(dbg_args)) else: dbg_args = [path_gdbserver, '--once', '--no-startup-with-shell', 'localhost:%d'%port, path] dbg_args.extend(args) subprocess.Popen(dbg_args, stdin=None, stdout=None, stderr=None, preexec_fn=gdblike.preexec) except Exception: raise Exception('invoking gdbserver (used path: %s)' % path_gdbserver) # connect to gdbserver self.connect('localhost', port) def connect_continued(self, sock, rsp_connect): self.sock = sock self.rspConn = rsp_connect self.reg_info_load() # acquire pid as first tid reply = self.rspConn.tx_rx('?') tdict = rsp.packet_T_to_dict(reply) self.tid = tdict.get('thread', None) self.target_pid_ = self.tid def connect(self, address, port): # connect to gdbserver sock = gdblike.connect(address, port) rspConn = rsp.RspConnection(sock) # initial commands rspConn.tx_rx('Hg0') # if 'multiprocess+' in list here, thread reply is like 'pX.Y' where X is core id, Y is thread id # negotiate server capabilities # TODO: replace these with something sensible, not something copied from a packet dump capabilities = 'swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386' rspConn.negotiate(capabilities) self.connect_continued(sock, rspConn) def mem_modules(self, cache_ok=True): if cache_ok and self.module_cache: return self.module_cache self.module_cache = {} fpath = '/proc/%d/maps' % self.target_pid_ # TODO: prefer local open() if debuggee is on same filesystem as debugger #with open(fpath, 'r') as fp: # lines = fp.readlines() data = self.get_remote_file(fpath) data = data.decode('utf-8') lines = data.split('\n') for line in lines: line = line.strip() m = re.match(r'^([0-9a-f]+)-[0-9a-f]+ [rwxp-]{4} .* (/.*)$', line) if not m: continue (addr, module) = m.group(1,2) if module in self.module_cache: continue self.module_cache[module] = int(addr, 16) return self.module_cache #-------------------------------------------------------------------------- # NON-DEBUGADAPTER API #-------------------------------------------------------------------------- def thread_stop_pkt_to_reason(self, tdict): linux_signal_to_name = { # ISO C99 2: 'SIGINT', 4: 'SIGILL', 6: 'SIGABRT', 8: 'SIGFPE', 11: 'SIGSEGV', 15: 'SIGTERM', # historical POSIX 1: 'SIGHUP', 3: 'SIGQUIT', 5: 'SIGTRAP', 9: 'SIGKILL', 10: 'SIGUSR1', # differs from macos 12: 'SIGUSR2', # differs from macos 13: 'SIGPIPE', 14: 'SIGALRM', # newer POSIX 16: 'SIGSTKFLT', 17: 'SIGCHLD', # differs from macos 18: 'SIGCONT', # differs from macos 19: 'SIGSTOP', # differs 20: 'SIGTSTP', # differs 21: 'SIGTTIN', 22: 'SIGTTOU', 23: 'SIGURG', # differs from macos 24: 'SIGXCPU', 25: 'SIGXFSZ', 26: 'SIGVTALRM', 27: 'SIGPROF', 30: 'SIGUSR1', 31: 'SIGUSR2', # nonstandard POSIX 28: 'SIGWINCH', # unallocated posix 7: 'SIGBUX', # differs from macos 29: 'SIGPOLL', 30: 'SIGSTKFLT', 31: 'SIGSYS' } # TODO: detect OS and adjust away from hardcoded Ubuntu 4.15.0-51-generic lookup = { 1: DebugAdapter.STOP_REASON.SIGNAL_HUP, 2: DebugAdapter.STOP_REASON.SIGNAL_INT, 4: DebugAdapter.STOP_REASON.ILLEGAL_INSTRUCTION, 6: DebugAdapter.STOP_REASON.SIGNAL_ABRT, 8: DebugAdapter.STOP_REASON.CALCULATION, 11: DebugAdapter.STOP_REASON.ACCESS_VIOLATION, 15: DebugAdapter.STOP_REASON.SIGNAL_TERM, 3: DebugAdapter.STOP_REASON.SIGNAL_QUIT, 5: DebugAdapter.STOP_REASON.SINGLE_STEP, 9: DebugAdapter.STOP_REASON.SIGNAL_KILL, 10: DebugAdapter.STOP_REASON.SIGNAL_USR1, 12: DebugAdapter.STOP_REASON.SIGNAL_USR2, 13: DebugAdapter.STOP_REASON.SIGNAL_PIPE, 14: DebugAdapter.STOP_REASON.SIGNAL_ALRM, 16: DebugAdapter.STOP_REASON.SIGNAL_STKFLT, 17: DebugAdapter.STOP_REASON.SIGNAL_CHLD, 18: DebugAdapter.STOP_REASON.SIGNAL_CONT, 19: DebugAdapter.STOP_REASON.SIGNAL_STOP, 20: DebugAdapter.STOP_REASON.SIGNAL_TSTP, 21: DebugAdapter.STOP_REASON.SIGNAL_TTIN, 22: DebugAdapter.STOP_REASON.SIGNAL_TTOU, 23: DebugAdapter.STOP_REASON.SIGNAL_URG, 24: DebugAdapter.STOP_REASON.SIGNAL_XCPU, 25: DebugAdapter.STOP_REASON.SIGNAL_XFSZ, 26: DebugAdapter.STOP_REASON.SIGNAL_VTALRM, 27: DebugAdapter.STOP_REASON.SIGNAL_PROF, 30: DebugAdapter.STOP_REASON.SIGNAL_USR1, 31: DebugAdapter.STOP_REASON.SIGNAL_USR2, 28: DebugAdapter.STOP_REASON.SIGNAL_WINCH, 7: DebugAdapter.STOP_REASON.SIGNAL_BUX, 29: DebugAdapter.STOP_REASON.SIGNAL_POLL, 30: DebugAdapter.STOP_REASON.SIGNAL_STKFLT, 31: DebugAdapter.STOP_REASON.SIGNAL_SYS } dreason = DebugAdapter.STOP_REASON.UNKNOWN result = (dreason, None) if 'signal' in tdict: signal = tdict['signal'] # breakpoint and trap flag exception are both reported as SIGTRAP # use presence of 'swbreak' to differentiate, if possible if linux_signal_to_name[signal] == 'SIGTRAP' and 'swbreak' in tdict: result = (DebugAdapter.STOP_REASON.BREAKPOINT, 0) else: if signal in lookup: result = (lookup[signal], None) else: result = (dreason, signal) #print('returning: ', result) return result