/ tools / scripts / debug / debug.py
debug.py
  1  #!/usr/bin/env python3
  2  
  3  from dotenv import load_dotenv, find_dotenv
  4  import argparse
  5  import git
  6  import platform
  7  import os
  8  import signal
  9  import socket
 10  import subprocess
 11  import sys
 12  import tempfile
 13  import time
 14  
 15  
 16  def get_project_root():
 17      git_repo = git.Repo(search_parent_directories=True)
 18      return os.path.realpath(git_repo.git.rev_parse("--show-toplevel"))
 19  
 20  
 21  # Don't close python script with Ctrl+C since GDB uses it
 22  def sig_handler(signum, frame):
 23      pass
 24  
 25  # Try ports in range until one is open
 26  # Used in case we launch multiple openocd/gdb instances at once
 27  def get_free_port(port=3333, max_port=4000):
 28      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 29      while port <= max_port:
 30          try:
 31              sock.bind(("", port))
 32              sock.close()
 33              return port
 34          except OSError:
 35              port += 1
 36      raise IOError("no free ports")
 37  
 38  
 39  # Register alternate SIGINT handler above to ignore Ctrl+C
 40  signal.signal(signal.SIGINT, sig_handler)
 41  
 42  debug_scripts_path = "{}/tools/scripts/debug".format(get_project_root())
 43  
 44  
 45  #
 46  # Create gdb script with memfault coredump information (gathered from .env)
 47  # Since it needs to have user info to run
 48  #
 49  def create_coredump_gdb_script():
 50      memfault_gdb_path = os.path.join(
 51          get_project_root(),
 52          "src/third_party/memfault-firmware-sdk/scripts/memfault_gdb.py",
 53      )
 54  
 55      coredump_script = """
 56  define coredump
 57    source {script_path}
 58    memfault login {username} {password} -o sofar-ocean -p {project}
 59    memfault coredump --region (uint32_t)&_ram_start (uint32_t)&_ram_size
 60  end
 61  """.format(
 62          script_path=memfault_gdb_path,
 63          username=os.getenv("MEMFAULT_USERNAME"),
 64          password=os.getenv("MEMFAULT_PASSWORD"),
 65          project=os.getenv("MEMFAULT_PROJECT"),
 66      )
 67  
 68      fd, path = tempfile.mkstemp(suffix=".gdb_cmds")
 69      os.write(fd, coredump_script.encode())
 70      os.close(fd)
 71  
 72      return path
 73  
 74  
 75  def run_openocd(args, port=3333):
 76      # OpenOCD config overrides
 77      part_cfg = "st_nucleo_u5.cfg"
 78      if args.rtos:
 79          part_cfg = "st_nucleo_u5_rtos.cfg"
 80  
 81      openocd_cmd = [
 82          "openocd",
 83          "-f",
 84          f"{debug_scripts_path}/stlink-mod.cfg",
 85          "-f",
 86          f"{debug_scripts_path}/{part_cfg}",
 87          "-f",
 88          f"{debug_scripts_path}/utils.cfg",
 89          "-c",
 90          "tcl_port disabled",
 91          "-c",
 92          "telnet_port disabled",
 93          "-c",
 94          f"gdb_port {port}",
 95          "-c",
 96          "init",
 97      ]
 98  
 99      if platform.system() == "Windows" and platform.architecture()[0] == "64bit":
100          # Create a STARTUPINFO structure to specify that the subprocess should be run in a hidden window
101          startupinfo = subprocess.STARTUPINFO()
102          startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
103          startupinfo.wShowWindow = subprocess.SW_HIDE
104  
105          return subprocess.Popen(
106              openocd_cmd,
107              # Comment out the following two lines to print openocd output to console
108              # stdout=subprocess.DEVNULL,
109              # stderr=subprocess.STDOUT,
110              startupinfo=startupinfo
111          )
112      elif os.name == "posix":
113          # https://stackoverflow.com/questions/5045771/python-how-to-prevent-subprocesses-from-receiving-ctrl-c-control-c-sigint
114          return subprocess.Popen(
115              openocd_cmd,
116              # Comment out the following two lines to print openocd output to console
117              # stdout=subprocess.DEVNULL,
118              # stderr=subprocess.STDOUT,
119              preexec_fn=os.setpgrp,
120          )
121      else:
122          print("Unsupported platform")
123  
124  
125  #
126  # Run GDB with all the required scripts
127  #
128  def run_gdb(args, port=3333):
129      if platform.system() == "Windows" and platform.architecture()[0] == "64bit":
130          gdb_cmd = ["gdb-multiarch"]
131      else:
132          gdb_cmd = ["gdb"]
133  
134      for source_dir in args.dirs:
135          gdb_cmd.append(f"--directory={source_dir}")
136  
137      # Create `coredump` command script
138      coredump_script_path = create_coredump_gdb_script()
139  
140      # Include debug gdb commands (reset, rh, etc.)
141      gdb_cmd += [
142          "-ex",
143          f"target remote localhost:{port}",
144          "-x",
145          f"{debug_scripts_path}/debug.gdb_cmds",
146          "-x",
147          coredump_script_path,
148      ]
149  
150      # Add executable
151      gdb_cmd.append(f"{args.elf}")
152  
153      if args.tui:
154          gdb_cmd.append("-tui")
155  
156      subprocess.run(gdb_cmd)
157  
158      # Remove temporary coredump script file
159      if coredump_script_path:
160          os.remove(coredump_script_path)
161  
162  
163  parser = argparse.ArgumentParser()
164  parser.add_argument("elf", help="Firmware elf file")
165  parser.add_argument("dirs", help="Source search directories", nargs="+")
166  
167  parser.add_argument("--tui", action="store_true", help="Use gdb-tui")
168  parser.add_argument(
169      "--rtos", action="store_true", help="Enable RTOS support in openocd"
170  )
171  
172  args, unknownargs = parser.parse_known_args()
173  
174  load_dotenv(find_dotenv())
175  
176  # Get port to use for openocd/gdb
177  port = get_free_port()
178  
179  # Run openocd in the background
180  openocd = run_openocd(args, port)
181  
182  # Wait for openocd to start up before trying to connect with gdb
183  time.sleep(1)
184  
185  # Make sure openocd actually ran
186  openocd_rval = openocd.poll()
187  if openocd_rval is not None:
188      print(f"Error starting openocd! ({openocd_rval})")
189      sys.exit(openocd_rval)
190  
191  # Run GDB to do all the things!
192  run_gdb(args, port)
193  
194  # Make sure we don't leave openocd running
195  openocd.terminate()