@@ -163,6 +163,18 @@ class DebuggerCrossConfig:
"""Called after construction to generate any required config files."""
pass
+ def resolve_host_port(self, mode=None):
+ """Return the host port this debug server's target port is reachable on."""
+ target_port = self.port(mode)
+ if not is_loopback_target(self.debugger_cross.target_device.target):
+ return target_port
+ return self.image_recipe.slirp_port_mappings.get(('tcp', target_port), target_port)
+
+ def _resolve_host_port_shell_lines(self, var_name, mode=None):
+ """Shell line assigning $<var_name> to resolve_host_port(), for use in
+ a generated ide=none wrapper script."""
+ return ['%s=%d' % (var_name, self.resolve_host_port(mode))]
+
# Abstract — subclasses must implement
def _target_start_cmd(self, mode):
raise NotImplementedError
@@ -423,7 +423,7 @@ class IdeVSCode(IdeBase):
"MIMode": "gdb",
"preLaunchTask": cross_debug_config.id_pretty_mode(server_mode),
"miDebuggerPath": modified_recipe.debugger_cross.gdb,
- "miDebuggerServerAddress": "%s:%d" % (modified_recipe.debugger_cross.host, cross_debug_config.port(server_mode))
+ "miDebuggerServerAddress": "%s:%d" % (modified_recipe.debugger_cross.host, cross_debug_config.resolve_host_port(server_mode))
}
if is_attach:
@@ -530,7 +530,7 @@ class IdeVSCode(IdeBase):
init_commands = [
"platform select remote-linux",
- "platform connect connect://%s:%d" % (debugger_cross.host, lldb_config.port(server_mode)),
+ "platform connect connect://%s:%d" % (debugger_cross.host, lldb_config.resolve_host_port(server_mode)),
# Clear the default step-avoid-regexp so std:: and other library
# namespaces are not silently skipped on step-in. (default is "std::" in LLDB 15+)
"settings set target.process.thread.step-avoid-regexp \"\"",
@@ -60,17 +60,14 @@ class GdbCrossConfigNone(GdbCrossConfig):
gdbinit_lines = ['# This file is generated by devtool ide-sdk']
if server_mode == DebuggerServerModes.MULTI:
target_help = '# gdbserver --multi :%d' % self.debug_server_port
- remote_cmd = 'target extended-remote'
else:
target_help = '# gdbserver :%d %s' % (
self.debug_server_port, self.binary)
- remote_cmd = 'target remote'
gdbinit_lines.append('# On the remote target:')
gdbinit_lines.append(target_help)
gdbinit_lines.append('# On the build machine:')
gdbinit_lines.append('# cd ' + self.modified_recipe.real_srctree)
- gdbinit_lines.append(
- '# ' + self.debugger_cross.gdb + ' -ix ' + self.gdbinit)
+ gdbinit_lines.append('# ' + self.gdb_script)
gdbinit_lines.append('set sysroot ' + self.modified_recipe.d)
if self.image_recipe.rootfs_dbg:
@@ -114,18 +111,25 @@ class GdbCrossConfigNone(GdbCrossConfig):
gdbinit_lines.append("end" + os.linesep)
gdbinit_lines.append(
- '%s %s:%d' % (remote_cmd, self.debugger_cross.host, self.debug_server_port))
- gdbinit_lines.append('set remote exec-file ' + self.binary.binary_path)
- gdbinit_lines.append('run ' + self.binary.binary_path)
+ 'set remote exec-file ' + self.binary.binary_path)
GdbCrossConfigNone.write_file(self.gdbinit, gdbinit_lines)
- def _gen_gdb_start_script(self):
+ def _gen_gdb_start_script(self, server_mode=None):
"""Generate a script starting GDB with the corresponding gdbinit configuration."""
+ if server_mode is None:
+ server_mode = self.default_mode
+ remote_cmd = ('target extended-remote'
+ if server_mode == DebuggerServerModes.MULTI
+ else 'target remote')
cmd_lines = ['#!/bin/sh']
cmd_lines.append('cd ' + self.modified_recipe.real_srctree)
- cmd_lines.append(self.debugger_cross.gdb + ' -ix ' +
- self.gdbinit + ' "$@"')
+ cmd_lines += self._resolve_host_port_shell_lines('GDB_PORT', server_mode)
+ gdb_cmd = self.debugger_cross.gdb + ' -ix ' + self.gdbinit
+ gdb_cmd += " -ex '%s %s:'\"$GDB_PORT\"" % (remote_cmd, self.debugger_cross.host)
+ gdb_cmd += " -ex 'run %s'" % self.binary.binary_path
+ gdb_cmd += ' "$@"'
+ cmd_lines.append(gdb_cmd)
GdbCrossConfigNone.write_file(self.gdb_script, cmd_lines, True)
def initialize(self):
@@ -210,10 +214,8 @@ class LldbServerConfigNone(LldbServerConfig):
self.port(server_mode), self.gdbserver_ports[server_mode]))
lines.append('# On the build machine:')
lines.append('# cd ' + self.modified_recipe.real_srctree)
- lines.append('# ' + self.debugger_cross.lldb + ' -s ' + self.lldbinit)
+ lines.append('# ' + self.lldb_script)
lines.append('platform select remote-linux')
- lines.append('platform connect connect://%s:%d' % (
- self.debugger_cross.host, self.port(server_mode)))
lines.append('settings set target.process.thread.step-avoid-regexp ""')
if self.image_recipe.rootfs_dbg:
@@ -248,16 +250,21 @@ class LldbServerConfigNone(LldbServerConfig):
'Cannot setup debug symbols configuration for LLDB. '
'IMAGE_GEN_DEBUGFS is not enabled.')
- lines.append('target create --remote-file %s %s' % (
- self.binary.binary_path, self.binary.binary_host_path))
-
LldbServerConfigNone.write_file(self.lldbinit, lines)
- def _gen_lldb_start_script(self):
+ def _gen_lldb_start_script(self, server_mode=None):
"""Generate a script starting lldb with the lldbinit configuration."""
+ if server_mode is None:
+ server_mode = self.default_mode
cmd_lines = ['#!/bin/sh']
cmd_lines.append('cd ' + self.modified_recipe.real_srctree)
- cmd_lines.append(self.debugger_cross.lldb + ' -s ' + self.lldbinit + ' "$@"')
+ cmd_lines += self._resolve_host_port_shell_lines('LLDB_PORT', server_mode)
+ lldb_cmd = self.debugger_cross.lldb + ' -s ' + self.lldbinit
+ lldb_cmd += " -o 'platform connect connect://%s:'\"$LLDB_PORT\"" % self.debugger_cross.host
+ lldb_cmd += " -o 'target create --remote-file %s %s'" % (
+ self.binary.binary_path, self.binary.binary_host_path)
+ lldb_cmd += ' "$@"'
+ cmd_lines.append(lldb_cmd)
LldbServerConfigNone.write_file(self.lldb_script, cmd_lines, True)
def initialize(self):
@@ -306,6 +306,24 @@ class RecipeImage:
def debug_support(self):
return bool(self.rootfs_dbg)
+ @property
+ def slirp_port_mappings(self):
+ """QB_SLIRP_OPT's hostfwd rules as {(protocol, target_port): host_port}.
+
+ QB_SLIRP_OPT is only a wishlist: runqemu moves a forward to another
+ host port whenever qemu cannot bind the wished-for one, so host
+ port == target port cannot be assumed. A caller that knows the
+ effective mapping of a running qemu (see QemuRunner.slirp_port_mappings)
+ can write it back into the bbappend and re-run devtool ide-sdk
+ --skip-bitbake to have the debugger configs regenerated against the
+ ports that really work.
+ """
+ mappings = {}
+ for content in (self.qb_slirp_opt, self._orig_bbappend_content or ''):
+ for m in re.finditer(r'hostfwd=([a-z]+):[^:]*:(\d+)-[^:,]*:(\d+)', content):
+ mappings[(m.group(1), int(m.group(3)))] = int(m.group(2))
+ return mappings
+
@property
def rootfs(self):
"""Prefer the live NFS-exported rootfs (if --nfs=rootfs is used) over the
@@ -548,28 +566,39 @@ class RecipeImage:
if not ports:
return False
- # Determine which ports are already in the base value
- already = {int(m.group(1))
- for m in re.finditer(r':(\d+)-:\d+', self.qb_slirp_opt)}
- missing_ports = [p for p in ports if p not in already]
+ # Determine which ports are already in the base value. Match the
+ # target port, not the host port: once the effective mapping is
+ # written back the two differ, and a forward already exists for a
+ # target port regardless of which host port it ended up on.
+ already = {(m.group(1), int(m.group(2)))
+ for m in re.finditer(r'hostfwd=([a-z]+):[^:]*:\d+-[^:,]*:(\d+)', self.qb_slirp_opt)}
+ missing_ports = [p for p in ports if ('tcp', p) not in already]
if not missing_ports:
logger.info("QB_SLIRP_OPT already contains all needed port forwards")
return False
+ # Keep any already known effective mapping instead of wishing for
+ # host port == target port again, so re-running does not discard a
+ # mapping written back from a running qemu (see slirp_port_mappings).
+ mappings = self.slirp_port_mappings
+
+ def hostfwd(target_port):
+ return ',hostfwd=tcp:127.0.0.1:%d-:%d' % (
+ mappings.get(('tcp', target_port), target_port), target_port)
+
if self.qb_slirp_opt:
# Base value exists: :append only the missing port forwards
- extra = ''.join(
- ',hostfwd=tcp:127.0.0.1:%d-:%d' % (p, p) for p in missing_ports)
+ extra = ''.join(hostfwd(p) for p in missing_ports)
new_line = 'QB_SLIRP_OPT:append = "%s"' % extra
else:
# No base value: mirror runqemu's built-in default (SSH 2222, telnet
# 2323, tftp) and add the debugger ports.
all_hostfwds = (
- 'hostfwd=tcp:127.0.0.1:2222-:22,'
- 'hostfwd=tcp:127.0.0.1:2323-:23'
+ 'hostfwd=tcp:127.0.0.1:%d-:22,'
+ 'hostfwd=tcp:127.0.0.1:%d-:23'
+ % (mappings.get(('tcp', 22), 2222), mappings.get(('tcp', 23), 2323))
)
- all_hostfwds += ''.join(
- ',hostfwd=tcp:127.0.0.1:%d-:%d' % (p, p) for p in missing_ports)
+ all_hostfwds += ''.join(hostfwd(p) for p in missing_ports)
new_line = 'QB_SLIRP_OPT = "-netdev user,id=net0,%s,tftp=${DEPLOY_DIR_IMAGE}"' % all_hostfwds
if os.path.exists(self._bbappend):