예제 #1
0
    def __init__(self, project_dir, args, debug_options, env_options):
        self.project_dir = project_dir
        self.args = list(args)
        self.debug_options = debug_options
        self.env_options = env_options

        self._debug_server = DebugServer(debug_options, env_options)
        self._session_id = None

        if not isdir(get_project_cache_dir()):
            os.makedirs(get_project_cache_dir())
        self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(),
                                   prefix=".piodebug-")

        self._target_is_run = False
        self._last_server_activity = 0
        self._auto_continue_timer = None
예제 #2
0
    def __init__(self, project_dir, args, debug_options, env_options):
        super(GDBClient, self).__init__()
        self.project_dir = project_dir
        self.args = list(args)
        self.debug_options = debug_options
        self.env_options = env_options

        self._debug_server = DebugServer(debug_options, env_options)
        self._session_id = None

        if not isdir(get_project_cache_dir()):
            os.makedirs(get_project_cache_dir())
        self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(), prefix=".piodebug-")

        self._target_is_run = False
        self._auto_continue_timer = None
        self._errors_buffer = b""
예제 #3
0
class GDBClient(BaseProcess):  # pylint: disable=too-many-instance-attributes

    PIO_SRC_NAME = ".pioinit"
    INIT_COMPLETED_BANNER = "PlatformIO: Initialization completed"

    def __init__(self, project_dir, args, debug_options, env_options):
        self.project_dir = project_dir
        self.args = list(args)
        self.debug_options = debug_options
        self.env_options = env_options

        self._debug_server = DebugServer(debug_options, env_options)
        self._session_id = None

        if not isdir(get_project_cache_dir()):
            os.makedirs(get_project_cache_dir())
        self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(),
                                   prefix=".piodebug-")

        self._target_is_run = False
        self._last_server_activity = 0
        self._auto_continue_timer = None

    def spawn(self, gdb_path, prog_path):
        session_hash = gdb_path + prog_path
        self._session_id = sha1(hashlib_encode_data(session_hash)).hexdigest()
        self._kill_previous_session()

        patterns = {
            "PROJECT_DIR": self.project_dir,
            "PROG_PATH": prog_path,
            "PROG_DIR": dirname(prog_path),
            "PROG_NAME": basename(splitext(prog_path)[0]),
            "DEBUG_PORT": self.debug_options['port'],
            "UPLOAD_PROTOCOL": self.debug_options['upload_protocol'],
            "INIT_BREAK": self.debug_options['init_break'] or "",
            "LOAD_CMDS": "\n".join(self.debug_options['load_cmds'] or []),
        }

        self._debug_server.spawn(patterns)

        if not patterns['DEBUG_PORT']:
            patterns['DEBUG_PORT'] = self._debug_server.get_debug_port()
        self.generate_pioinit(self._gdbsrc_dir, patterns)

        # start GDB client
        args = [
            "piogdb",
            "-q",
            "--directory", self._gdbsrc_dir,
            "--directory", self.project_dir,
            "-l", "10"
        ]  # yapf: disable
        args.extend(self.args)
        if not gdb_path:
            raise exception.DebugInvalidOptions("GDB client is not configured")
        gdb_data_dir = self._get_data_dir(gdb_path)
        if gdb_data_dir:
            args.extend(["--data-directory", gdb_data_dir])
        args.append(patterns['PROG_PATH'])

        return reactor.spawnProcess(self,
                                    gdb_path,
                                    args,
                                    path=self.project_dir,
                                    env=os.environ)

    @staticmethod
    def _get_data_dir(gdb_path):
        if "msp430" in gdb_path:
            return None
        gdb_data_dir = abspath(join(dirname(gdb_path), "..", "share", "gdb"))
        return gdb_data_dir if isdir(gdb_data_dir) else None

    def generate_pioinit(self, dst_dir, patterns):
        server_exe = (self.debug_options.get("server")
                      or {}).get("executable", "").lower()
        if "jlink" in server_exe:
            cfg = initcfgs.GDB_JLINK_INIT_CONFIG
        elif "st-util" in server_exe:
            cfg = initcfgs.GDB_STUTIL_INIT_CONFIG
        elif "mspdebug" in server_exe:
            cfg = initcfgs.GDB_MSPDEBUG_INIT_CONFIG
        elif "qemu" in server_exe:
            cfg = initcfgs.GDB_QEMU_INIT_CONFIG
        elif self.debug_options['require_debug_port']:
            cfg = initcfgs.GDB_BLACKMAGIC_INIT_CONFIG
        else:
            cfg = initcfgs.GDB_DEFAULT_INIT_CONFIG
        commands = cfg.split("\n")

        if self.debug_options['init_cmds']:
            commands = self.debug_options['init_cmds']
        commands.extend(self.debug_options['extra_cmds'])

        if not any("define pio_reset_target" in cmd for cmd in commands):
            commands = [
                "define pio_reset_target",
                "   echo Warning! Undefined pio_reset_target command\\n",
                "   mon reset",
                "end"
            ] + commands  # yapf: disable
        if not any("define pio_reset_halt_target" in cmd for cmd in commands):
            commands = [
                "define pio_reset_halt_target",
                "   echo Warning! Undefined pio_reset_halt_target command\\n",
                "   mon reset halt",
                "end"
            ] + commands  # yapf: disable
        if not any("define pio_restart_target" in cmd for cmd in commands):
            commands += [
                "define pio_restart_target",
                "   pio_reset_halt_target",
                "   $INIT_BREAK",
                "   %s" % ("continue" if patterns['INIT_BREAK'] else "next"),
                "end"
            ]  # yapf: disable

        banner = [
            "echo PlatformIO Unified Debugger -> http://bit.ly/pio-debug\\n",
            "echo PlatformIO: debug_tool = %s\\n" % self.debug_options['tool'],
            "echo PlatformIO: Initializing remote target...\\n"
        ]
        footer = ["echo %s\\n" % self.INIT_COMPLETED_BANNER]
        commands = banner + commands + footer

        with open(join(dst_dir, self.PIO_SRC_NAME), "w") as fp:
            fp.write("\n".join(self.apply_patterns(commands, patterns)))

    def connectionMade(self):
        self._lock_session(self.transport.pid)

        p = protocol.Protocol()
        p.dataReceived = self.onStdInData
        stdio.StandardIO(p)

    def onStdInData(self, data):
        if LOG_FILE:
            with open(LOG_FILE, "ab") as fp:
                fp.write(data)

        self._last_server_activity = time.time()

        if b"-exec-run" in data:
            if self._target_is_run:
                token, _ = data.split(b"-", 1)
                self.outReceived(token + b"^running\n")
                return
            data = data.replace(b"-exec-run", b"-exec-continue")

        if b"-exec-continue" in data:
            self._target_is_run = True
        if b"-gdb-exit" in data or data.strip() in (b"q", b"quit"):
            # Allow terminating via SIGINT/CTRL+C
            signal.signal(signal.SIGINT, signal.default_int_handler)
            self.transport.write(b"pio_reset_target\n")
        self.transport.write(data)

    def processEnded(self, reason):  # pylint: disable=unused-argument
        self._unlock_session()
        if self._gdbsrc_dir and isdir(self._gdbsrc_dir):
            fs.rmtree(self._gdbsrc_dir)
        if self._debug_server:
            self._debug_server.terminate()

        reactor.stop()

    def outReceived(self, data):
        if LOG_FILE:
            with open(LOG_FILE, "ab") as fp:
                fp.write(data)

        self._last_server_activity = time.time()
        super(GDBClient, self).outReceived(data)
        self._handle_error(data)
        # go to init break automatically
        if self.INIT_COMPLETED_BANNER.encode() in data:
            self._auto_continue_timer = task.LoopingCall(
                self._auto_exec_continue)
            self._auto_continue_timer.start(0.1)

    def errReceived(self, data):
        super(GDBClient, self).errReceived(data)
        self._handle_error(data)

    def console_log(self, msg):
        if helpers.is_mi_mode(self.args):
            self.outReceived(('~"%s\\n"\n' % msg).encode())
        else:
            self.outReceived(("%s\n" % msg).encode())

    def _auto_exec_continue(self):
        auto_exec_delay = 0.5  # in seconds
        if self._last_server_activity > (time.time() - auto_exec_delay):
            return
        if self._auto_continue_timer:
            self._auto_continue_timer.stop()
        self._auto_continue_timer = None

        if not self.debug_options['init_break'] or self._target_is_run:
            return
        self.console_log(
            "PlatformIO: Resume the execution to `debug_init_break = %s`" %
            self.debug_options['init_break'])
        self.console_log("PlatformIO: More configuration options -> "
                         "http://bit.ly/pio-debug")
        self.transport.write(b"0-exec-continue\n" if helpers.
                             is_mi_mode(self.args) else b"continue\n")
        self._target_is_run = True

    def _handle_error(self, data):
        if (self.PIO_SRC_NAME.encode() not in data
                or b"Error in sourced" not in data):
            return
        configuration = {"debug": self.debug_options, "env": self.env_options}
        exd = re.sub(r'\\(?!")', "/", json.dumps(configuration))
        exd = re.sub(r'"(?:[a-z]\:)?((/[^"/]+)+)"',
                     lambda m: '"%s"' % join(*m.group(1).split("/")[-2:]), exd,
                     re.I | re.M)
        mp = MeasurementProtocol()
        mp['exd'] = "DebugGDBPioInitError: %s" % exd
        mp['exf'] = 1
        mp.send("exception")
        self.transport.loseConnection()

    def _kill_previous_session(self):
        assert self._session_id
        pid = None
        with app.ContentCache() as cc:
            pid = cc.get(self._session_id)
            cc.delete(self._session_id)
        if not pid:
            return
        if "windows" in util.get_systype():
            kill = ["Taskkill", "/PID", pid, "/F"]
        else:
            kill = ["kill", pid]
        try:
            proc.exec_command(kill)
        except:  # pylint: disable=bare-except
            pass

    def _lock_session(self, pid):
        if not self._session_id:
            return
        with app.ContentCache() as cc:
            cc.set(self._session_id, str(pid), "1h")

    def _unlock_session(self):
        if not self._session_id:
            return
        with app.ContentCache() as cc:
            cc.delete(self._session_id)
예제 #4
0
class GDBClient(BaseProcess):  # pylint: disable=too-many-instance-attributes

    PIO_SRC_NAME = ".pioinit"
    INIT_COMPLETED_BANNER = "PlatformIO: Initialization completed"

    def __init__(self, project_dir, args, debug_options, env_options):
        super(GDBClient, self).__init__()
        self.project_dir = project_dir
        self.args = list(args)
        self.debug_options = debug_options
        self.env_options = env_options

        self._debug_server = DebugServer(debug_options, env_options)
        self._session_id = None

        if not isdir(get_project_cache_dir()):
            os.makedirs(get_project_cache_dir())
        self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(), prefix=".piodebug-")

        self._target_is_run = False
        self._auto_continue_timer = None
        self._errors_buffer = b""

    @defer.inlineCallbacks
    def spawn(self, gdb_path, prog_path):
        session_hash = gdb_path + prog_path
        self._session_id = sha1(hashlib_encode_data(session_hash)).hexdigest()
        self._kill_previous_session()

        patterns = {
            "PROJECT_DIR": self.project_dir,
            "PROG_PATH": prog_path,
            "PROG_DIR": dirname(prog_path),
            "PROG_NAME": basename(splitext(prog_path)[0]),
            "DEBUG_PORT": self.debug_options["port"],
            "UPLOAD_PROTOCOL": self.debug_options["upload_protocol"],
            "INIT_BREAK": self.debug_options["init_break"] or "",
            "LOAD_CMDS": "\n".join(self.debug_options["load_cmds"] or []),
        }

        yield self._debug_server.spawn(patterns)
        if not patterns["DEBUG_PORT"]:
            patterns["DEBUG_PORT"] = self._debug_server.get_debug_port()

        self.generate_pioinit(self._gdbsrc_dir, patterns)

        # start GDB client
        args = [
            "piogdb",
            "-q",
            "--directory",
            self._gdbsrc_dir,
            "--directory",
            self.project_dir,
            "-l",
            "10",
        ]
        args.extend(self.args)
        if not gdb_path:
            raise DebugInvalidOptionsError("GDB client is not configured")
        gdb_data_dir = self._get_data_dir(gdb_path)
        if gdb_data_dir:
            args.extend(["--data-directory", gdb_data_dir])
        args.append(patterns["PROG_PATH"])

        transport = reactor.spawnProcess(
            self, gdb_path, args, path=self.project_dir, env=os.environ
        )
        defer.returnValue(transport)

    @staticmethod
    def _get_data_dir(gdb_path):
        if "msp430" in gdb_path:
            return None
        gdb_data_dir = realpath(join(dirname(gdb_path), "..", "share", "gdb"))
        return gdb_data_dir if isdir(gdb_data_dir) else None

    def generate_pioinit(self, dst_dir, patterns):
        # default GDB init commands depending on debug tool
        commands = get_gdb_init_config(self.debug_options).split("\n")

        if self.debug_options["init_cmds"]:
            commands = self.debug_options["init_cmds"]
        commands.extend(self.debug_options["extra_cmds"])

        if not any("define pio_reset_run_target" in cmd for cmd in commands):
            commands = [
                "define pio_reset_run_target",
                "   echo Warning! Undefined pio_reset_run_target command\\n",
                "   monitor reset",
                "end",
            ] + commands
        if not any("define pio_reset_halt_target" in cmd for cmd in commands):
            commands = [
                "define pio_reset_halt_target",
                "   echo Warning! Undefined pio_reset_halt_target command\\n",
                "   monitor reset halt",
                "end",
            ] + commands
        if not any("define pio_restart_target" in cmd for cmd in commands):
            commands += [
                "define pio_restart_target",
                "   pio_reset_halt_target",
                "   $INIT_BREAK",
                "   %s" % ("continue" if patterns["INIT_BREAK"] else "next"),
                "end",
            ]

        banner = [
            "echo PlatformIO Unified Debugger -> http://bit.ly/pio-debug\\n",
            "echo PlatformIO: debug_tool = %s\\n" % self.debug_options["tool"],
            "echo PlatformIO: Initializing remote target...\\n",
        ]
        footer = ["echo %s\\n" % self.INIT_COMPLETED_BANNER]
        commands = banner + commands + footer

        with open(join(dst_dir, self.PIO_SRC_NAME), "w") as fp:
            fp.write("\n".join(self.apply_patterns(commands, patterns)))

    def connectionMade(self):
        self._lock_session(self.transport.pid)

        p = protocol.Protocol()
        p.dataReceived = self.onStdInData
        stdio.StandardIO(p)

    def onStdInData(self, data):
        super(GDBClient, self).onStdInData(data)
        if b"-exec-run" in data:
            if self._target_is_run:
                token, _ = data.split(b"-", 1)
                self.outReceived(token + b"^running\n")
                return
            data = data.replace(b"-exec-run", b"-exec-continue")

        if b"-exec-continue" in data:
            self._target_is_run = True
        if b"-gdb-exit" in data or data.strip() in (b"q", b"quit"):
            # Allow terminating via SIGINT/CTRL+C
            signal.signal(signal.SIGINT, signal.default_int_handler)
            self.transport.write(b"pio_reset_run_target\n")
        self.transport.write(data)

    def processEnded(self, reason):  # pylint: disable=unused-argument
        self._unlock_session()
        if self._gdbsrc_dir and isdir(self._gdbsrc_dir):
            fs.rmtree(self._gdbsrc_dir)
        if self._debug_server:
            self._debug_server.terminate()

        reactor.stop()

    def outReceived(self, data):
        super(GDBClient, self).outReceived(data)
        self._handle_error(data)
        # go to init break automatically
        if self.INIT_COMPLETED_BANNER.encode() in data:
            telemetry.send_event(
                "Debug", "Started", telemetry.encode_run_environment(self.env_options)
            )
            self._auto_continue_timer = task.LoopingCall(self._auto_exec_continue)
            self._auto_continue_timer.start(0.1)

    def errReceived(self, data):
        super(GDBClient, self).errReceived(data)
        self._handle_error(data)

    def console_log(self, msg):
        if helpers.is_gdbmi_mode():
            msg = helpers.escape_gdbmi_stream("~", msg)
        self.outReceived(msg if is_bytes(msg) else msg.encode())

    def _auto_exec_continue(self):
        auto_exec_delay = 0.5  # in seconds
        if self._last_activity > (time.time() - auto_exec_delay):
            return
        if self._auto_continue_timer:
            self._auto_continue_timer.stop()
        self._auto_continue_timer = None

        if not self.debug_options["init_break"] or self._target_is_run:
            return
        self.console_log(
            "PlatformIO: Resume the execution to `debug_init_break = %s`\n"
            % self.debug_options["init_break"]
        )
        self.console_log(
            "PlatformIO: More configuration options -> http://bit.ly/pio-debug\n"
        )
        self.transport.write(
            b"0-exec-continue\n" if helpers.is_gdbmi_mode() else b"continue\n"
        )
        self._target_is_run = True

    def _handle_error(self, data):
        self._errors_buffer += data
        if self.PIO_SRC_NAME.encode() not in data or b"Error in sourced" not in data:
            return

        last_erros = self._errors_buffer.decode()
        last_erros = " ".join(reversed(last_erros.split("\n")))
        last_erros = re.sub(r'((~|&)"|\\n\"|\\t)', " ", last_erros, flags=re.M)

        err = "%s -> %s" % (
            telemetry.encode_run_environment(self.env_options),
            last_erros,
        )
        telemetry.send_exception("DebugInitError: %s" % err)
        self.transport.loseConnection()

    def _kill_previous_session(self):
        assert self._session_id
        pid = None
        with app.ContentCache() as cc:
            pid = cc.get(self._session_id)
            cc.delete(self._session_id)
        if not pid:
            return
        if "windows" in util.get_systype():
            kill = ["Taskkill", "/PID", pid, "/F"]
        else:
            kill = ["kill", pid]
        try:
            proc.exec_command(kill)
        except:  # pylint: disable=bare-except
            pass

    def _lock_session(self, pid):
        if not self._session_id:
            return
        with app.ContentCache() as cc:
            cc.set(self._session_id, str(pid), "1h")

    def _unlock_session(self):
        if not self._session_id:
            return
        with app.ContentCache() as cc:
            cc.delete(self._session_id)