debug_ext.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import json
  4. import os
  5. import re
  6. import shlex
  7. import shutil
  8. import subprocess
  9. import sys
  10. import threading
  11. import time
  12. from base64 import b64decode
  13. from textwrap import indent
  14. from threading import Thread
  15. from typing import Any, Dict, List, Optional
  16. from click import INT
  17. from click.core import Context
  18. from esp_coredump import CoreDump
  19. from idf_py_actions.constants import OPENOCD_TAGET_CONFIG, OPENOCD_TAGET_CONFIG_DEFAULT
  20. from idf_py_actions.errors import FatalError
  21. from idf_py_actions.serial_ext import BAUD_RATE, PORT
  22. from idf_py_actions.tools import PropertyDict, ensure_build_directory, get_default_serial_port, get_sdkconfig_value
  23. PYTHON = sys.executable
  24. ESP_ROM_INFO_FILE = 'roms.json'
  25. GDBINIT_PYTHON_TEMPLATE = '''
  26. # Add Python GDB extensions
  27. python
  28. import sys
  29. sys.path = {sys_path}
  30. import freertos_gdb
  31. end
  32. '''
  33. GDBINIT_PYTHON_NOT_SUPPORTED = '''
  34. # Python scripting is not supported in this copy of GDB.
  35. # Please make sure that your Python distribution contains Python shared library.
  36. '''
  37. GDBINIT_BOOTLOADER_ADD_SYMBOLS = '''
  38. # Load bootloader symbols
  39. set confirm off
  40. add-symbol-file {boot_elf}
  41. set confirm on
  42. '''
  43. GDBINIT_BOOTLOADER_NOT_FOUND = '''
  44. # Bootloader elf was not found
  45. '''
  46. GDBINIT_APP_ADD_SYMBOLS = '''
  47. # Load application file
  48. file {app_elf}
  49. '''
  50. GDBINIT_CONNECT = '''
  51. # Connect to the default openocd-esp port and break on app_main()
  52. target remote :3333
  53. monitor reset halt
  54. flushregs
  55. thbreak app_main
  56. continue
  57. '''
  58. GDBINIT_MAIN = '''
  59. source {py_extensions}
  60. source {symbols}
  61. source {connect}
  62. '''
  63. def get_openocd_arguments(target: str) -> str:
  64. default_args = OPENOCD_TAGET_CONFIG_DEFAULT.format(target=target)
  65. return str(OPENOCD_TAGET_CONFIG.get(target, default_args))
  66. def action_extensions(base_actions: Dict, project_path: str) -> Dict:
  67. OPENOCD_OUT_FILE = 'openocd_out.txt'
  68. GDBGUI_OUT_FILE = 'gdbgui_out.txt'
  69. # Internal dictionary of currently active processes, threads and their output files
  70. processes: Dict = {'threads_to_join': [], 'openocd_issues': None}
  71. def _check_for_common_openocd_issues(file_name: str, print_all: bool=True) -> Any:
  72. if processes['openocd_issues'] is not None:
  73. return processes['openocd_issues']
  74. try:
  75. message = 'Please check JTAG connection!'
  76. with open(file_name, 'r') as f:
  77. content = f.read()
  78. if print_all:
  79. print(content)
  80. if re.search(r'Address already in use', content):
  81. message = ('Please check if another process uses the mentioned ports. OpenOCD already running, perhaps in the background?\n'
  82. 'Please list all processes to check if OpenOCD is already running; if so, terminate it before starting OpenOCD from idf.py')
  83. finally:
  84. processes['openocd_issues'] = message
  85. return message
  86. def _check_openocd_errors(fail_if_openocd_failed: Dict, target: str, ctx: Context) -> None:
  87. if fail_if_openocd_failed:
  88. if 'openocd' in processes and processes['openocd'] is not None:
  89. p = processes['openocd']
  90. name = processes['openocd_outfile_name']
  91. # watch OpenOCD (for 5x500ms) to check if it hasn't terminated or outputs an error
  92. for _ in range(5):
  93. if p.poll() is not None:
  94. print('OpenOCD exited with {}'.format(p.poll()))
  95. break
  96. with open(name, 'r') as f:
  97. content = f.read()
  98. if re.search(r'no device found', content):
  99. break
  100. if re.search(r'Listening on port \d+ for gdb connections', content):
  101. # expect OpenOCD has started successfully - stop watching
  102. return
  103. time.sleep(0.5)
  104. else:
  105. return
  106. # OpenOCD exited or error message detected -> print possible output and terminate
  107. raise FatalError('Action "{}" failed due to errors in OpenOCD:\n{}'.format(target, _check_for_common_openocd_issues(name)), ctx)
  108. def _terminate_async_target(target: str) -> None:
  109. if target in processes and processes[target] is not None:
  110. try:
  111. if target + '_outfile' in processes:
  112. processes[target + '_outfile'].close()
  113. p = processes[target]
  114. if p.poll() is None:
  115. p.terminate()
  116. # waiting 10x100ms for the process to terminate gracefully
  117. for _ in range(10):
  118. if p.poll() is not None:
  119. break
  120. time.sleep(0.1)
  121. else:
  122. p.kill()
  123. if target + '_outfile_name' in processes and target == 'openocd':
  124. print(_check_for_common_openocd_issues(processes[target + '_outfile_name'], print_all=False))
  125. except Exception as e:
  126. print(e)
  127. print('Failed to close/kill {}'.format(target))
  128. processes[target] = None # to indicate this has ended
  129. def _get_espcoredump_instance(ctx: Context,
  130. args: PropertyDict,
  131. gdb_timeout_sec: int = None,
  132. core: str = None,
  133. save_core: str = None) -> CoreDump:
  134. ensure_build_directory(args, ctx.info_name)
  135. project_desc = get_project_desc(args, ctx)
  136. coredump_to_flash_config = get_sdkconfig_value(project_desc['config_file'],
  137. 'CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH')
  138. coredump_to_flash = coredump_to_flash_config.rstrip().endswith('y') if coredump_to_flash_config else False
  139. prog = os.path.join(project_desc['build_dir'], project_desc['app_elf'])
  140. esp_port = args.port or get_default_serial_port()
  141. espcoredump_kwargs = dict()
  142. espcoredump_kwargs['port'] = esp_port
  143. espcoredump_kwargs['baud'] = args.baud
  144. espcoredump_kwargs['gdb_timeout_sec'] = gdb_timeout_sec
  145. # for reproducible builds
  146. extra_gdbinit_file = project_desc.get('debug_prefix_map_gdbinit', None)
  147. if extra_gdbinit_file:
  148. espcoredump_kwargs['extra_gdbinit_file'] = extra_gdbinit_file
  149. core_format = None
  150. if core:
  151. espcoredump_kwargs['core'] = core
  152. espcoredump_kwargs['chip'] = get_sdkconfig_value(project_desc['config_file'], 'CONFIG_IDF_TARGET')
  153. core_format = get_core_file_format(core)
  154. elif coredump_to_flash:
  155. # If the core dump is read from flash, we don't need to specify the --core-format argument at all.
  156. # The format will be determined automatically
  157. pass
  158. else:
  159. print('Path to core dump file is not provided. '
  160. "Core dump can't be read from flash since this option is not enabled in menuconfig")
  161. sys.exit(1)
  162. if core_format:
  163. espcoredump_kwargs['core_format'] = core_format
  164. if save_core:
  165. espcoredump_kwargs['save_core'] = save_core
  166. espcoredump_kwargs['prog'] = prog
  167. return CoreDump(**espcoredump_kwargs)
  168. def get_core_file_format(core_file: str) -> str:
  169. bin_v1 = 1
  170. bin_v2 = 2
  171. elf_crc32 = 256
  172. elf_sha256 = 257
  173. with open(core_file, 'rb') as f:
  174. coredump_bytes = f.read(16)
  175. if coredump_bytes.startswith(b'\x7fELF'):
  176. return 'elf'
  177. core_version = int.from_bytes(coredump_bytes[4:7], 'little')
  178. if core_version in [bin_v1, bin_v2, elf_crc32, elf_sha256]:
  179. # esp-coredump will determine automatically the core format (ELF or BIN)
  180. return 'raw'
  181. with open(core_file) as c:
  182. coredump_str = c.read()
  183. try:
  184. b64decode(coredump_str)
  185. except Exception:
  186. print('The format of the provided core-file is not recognized. '
  187. 'Please ensure that the core-format matches one of the following: ELF (“elf”), '
  188. 'raw (raw) or base64-encoded (b64) binary')
  189. sys.exit(1)
  190. else:
  191. return 'b64'
  192. def is_gdb_with_python(gdb: str) -> bool:
  193. # execute simple python command to check is it supported
  194. return subprocess.run([gdb, '--batch-silent', '--ex', 'python import os'],
  195. stderr=subprocess.DEVNULL).returncode == 0
  196. def get_normalized_path(path: str) -> str:
  197. if os.name == 'nt':
  198. return os.path.normpath(path).replace('\\', '\\\\')
  199. return path
  200. def get_rom_if_condition_str(date_addr: int, date_str: str) -> str:
  201. r = []
  202. for i in range(0, len(date_str), 4):
  203. value = hex(int.from_bytes(bytes(date_str[i:i + 4], 'utf-8'), 'little'))
  204. r.append(f'(*(int*) {hex(date_addr + i)}) == {value}')
  205. return 'if ' + ' && '.join(r)
  206. def generate_gdbinit_rom_add_symbols(target: str) -> str:
  207. base_ident = ' '
  208. rom_elfs_dir = os.getenv('ESP_ROM_ELF_DIR')
  209. if not rom_elfs_dir:
  210. raise FatalError('ESP_ROM_ELF_DIR environment variable is not defined. Please try to run IDF "install" and "export" scripts.')
  211. with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), ESP_ROM_INFO_FILE), 'r') as f:
  212. roms = json.load(f)
  213. if target not in roms:
  214. msg_body = f'Target "{target}" was not found in "{ESP_ROM_INFO_FILE}". Please check IDF integrity.'
  215. if os.getenv('ESP_IDF_GDB_TESTING'):
  216. raise FatalError(msg_body)
  217. print(f'Warning: {msg_body}')
  218. return f'# {msg_body}'
  219. r = ['', f'# Load {target} ROM ELF symbols']
  220. is_one_revision = len(roms[target]) == 1
  221. if not is_one_revision:
  222. r.append('define target hookpost-remote')
  223. r.append('set confirm off')
  224. # Workaround for reading ROM data on xtensa chips
  225. # This should be deleted after the new openocd-esp release (newer than v0.11.0-esp32-20220706)
  226. xtensa_chips = ['esp32', 'esp32s2', 'esp32s3']
  227. if target in xtensa_chips:
  228. r.append('monitor xtensa set_permissive 1')
  229. # Since GDB does not have 'else if' statement than we use nested 'if..else' instead.
  230. for i, k in enumerate(roms[target], 1):
  231. indent_str = base_ident * i
  232. rom_file = get_normalized_path(os.path.join(rom_elfs_dir, f'{target}_rev{k["rev"]}_rom.elf'))
  233. build_date_addr = int(k['build_date_str_addr'], base=16)
  234. r.append(indent(f'# if $_streq((char *) {hex(build_date_addr)}, "{k["build_date_str"]}")', indent_str))
  235. r.append(indent(get_rom_if_condition_str(build_date_addr, k['build_date_str']), indent_str))
  236. r.append(indent(f'add-symbol-file {rom_file}', indent_str + base_ident))
  237. r.append(indent('else', indent_str))
  238. if i == len(roms[target]):
  239. # In case no one known ROM ELF fits - print error and exit with error code 1
  240. indent_str += base_ident
  241. msg_body = f'unknown {target} ROM revision.'
  242. if os.getenv('ESP_IDF_GDB_TESTING'):
  243. r.append(indent(f'echo Error: {msg_body}\\n', indent_str))
  244. r.append(indent('quit 1', indent_str))
  245. else:
  246. r.append(indent(f'echo Warning: {msg_body}\\n', indent_str))
  247. # Close 'else' operators
  248. for i in range(len(roms[target]), 0, -1):
  249. r.append(indent('end', base_ident * i))
  250. if target in xtensa_chips:
  251. r.append('monitor xtensa set_permissive 0')
  252. r.append('set confirm on')
  253. if not is_one_revision:
  254. r.append('end')
  255. r.append('')
  256. return os.linesep.join(r)
  257. raise FatalError(f'{ESP_ROM_INFO_FILE} file not found. Please check IDF integrity.')
  258. def generate_gdbinit_files(gdb: str, gdbinit: Optional[str], project_desc: Dict[str, Any]) -> None:
  259. app_elf = get_normalized_path(os.path.join(project_desc['build_dir'], project_desc['app_elf']))
  260. if not os.path.exists(app_elf):
  261. raise FatalError('ELF file not found. You need to build & flash the project before running debug targets')
  262. # Recreate empty 'gdbinit' directory
  263. gdbinit_dir = os.path.join(project_desc['build_dir'], 'gdbinit')
  264. if os.path.isfile(gdbinit_dir):
  265. os.remove(gdbinit_dir)
  266. elif os.path.isdir(gdbinit_dir):
  267. shutil.rmtree(gdbinit_dir)
  268. os.mkdir(gdbinit_dir)
  269. # Prepare gdbinit for Python GDB extensions import
  270. py_extensions = os.path.join(gdbinit_dir, 'py_extensions')
  271. with open(py_extensions, 'w') as f:
  272. if is_gdb_with_python(gdb):
  273. f.write(GDBINIT_PYTHON_TEMPLATE.format(sys_path=sys.path))
  274. else:
  275. f.write(GDBINIT_PYTHON_NOT_SUPPORTED)
  276. # Prepare gdbinit for related ELFs symbols load
  277. symbols = os.path.join(gdbinit_dir, 'symbols')
  278. with open(symbols, 'w') as f:
  279. boot_elf = get_normalized_path(project_desc['bootloader_elf']) if 'bootloader_elf' in project_desc else None
  280. if boot_elf and os.path.exists(boot_elf):
  281. f.write(GDBINIT_BOOTLOADER_ADD_SYMBOLS.format(boot_elf=boot_elf))
  282. else:
  283. f.write(GDBINIT_BOOTLOADER_NOT_FOUND)
  284. f.write(generate_gdbinit_rom_add_symbols(project_desc['target']))
  285. f.write(GDBINIT_APP_ADD_SYMBOLS.format(app_elf=app_elf))
  286. # Generate the gdbinit for target connect if no custom gdbinit is present
  287. if not gdbinit:
  288. gdbinit = os.path.join(gdbinit_dir, 'connect')
  289. with open(gdbinit, 'w') as f:
  290. f.write(GDBINIT_CONNECT)
  291. with open(os.path.join(gdbinit_dir, 'gdbinit'), 'w') as f:
  292. f.write(GDBINIT_MAIN.format(py_extensions=py_extensions, symbols=symbols, connect=gdbinit))
  293. def debug_cleanup() -> None:
  294. print('cleaning up debug targets')
  295. for t in processes['threads_to_join']:
  296. if threading.currentThread() != t:
  297. t.join()
  298. _terminate_async_target('openocd')
  299. _terminate_async_target('gdbgui')
  300. _terminate_async_target('gdb')
  301. def post_debug(action: str, ctx: Context, args: PropertyDict, **kwargs: str) -> None:
  302. """ Deal with asynchronous targets, such as openocd running in background """
  303. if kwargs['block'] == 1:
  304. for target in ['openocd', 'gdbgui']:
  305. if target in processes and processes[target] is not None:
  306. break
  307. else:
  308. return
  309. try:
  310. p = processes[target]
  311. name = processes[target + '_outfile_name']
  312. pos = 0
  313. while True:
  314. with open(name, 'r') as f:
  315. f.seek(pos)
  316. for line in f:
  317. print(line.rstrip())
  318. pos = f.tell()
  319. if p.poll() is not None:
  320. print('"{}" exited with {}'.format(target, p.poll()))
  321. break
  322. time.sleep(0.5)
  323. except KeyboardInterrupt:
  324. print('Terminated -> exiting debug utility targets')
  325. _terminate_async_target('openocd')
  326. _terminate_async_target('gdbgui')
  327. def get_project_desc(args: PropertyDict, ctx: Context) -> Any:
  328. desc_path = os.path.join(args.build_dir, 'project_description.json')
  329. if not os.path.exists(desc_path):
  330. ensure_build_directory(args, ctx.info_name)
  331. with open(desc_path, 'r') as f:
  332. project_desc = json.load(f)
  333. return project_desc
  334. def openocd(action: str, ctx: Context, args: PropertyDict, openocd_scripts: Optional[str],
  335. openocd_commands: str) -> None:
  336. """
  337. Execute openocd as external tool
  338. """
  339. if os.getenv('OPENOCD_SCRIPTS') is None:
  340. raise FatalError('OPENOCD_SCRIPTS not found in the environment: Please run export.sh/export.bat', ctx)
  341. openocd_arguments = os.getenv('OPENOCD_COMMANDS') if openocd_commands is None else openocd_commands
  342. project_desc = get_project_desc(args, ctx)
  343. if openocd_arguments is None:
  344. # use default value if commands not defined in the environment nor command line
  345. target = project_desc['target']
  346. openocd_arguments = get_openocd_arguments(target)
  347. print('Note: OpenOCD cfg not found (via env variable OPENOCD_COMMANDS nor as a --openocd-commands argument)\n'
  348. 'OpenOCD arguments default to: "{}"'.format(openocd_arguments))
  349. # script directory is taken from the environment by OpenOCD, update only if command line arguments to override
  350. if openocd_scripts is not None:
  351. openocd_arguments += ' -s {}'.format(openocd_scripts)
  352. local_dir = project_desc['build_dir']
  353. args = ['openocd'] + shlex.split(openocd_arguments)
  354. openocd_out_name = os.path.join(local_dir, OPENOCD_OUT_FILE)
  355. openocd_out = open(openocd_out_name, 'w')
  356. try:
  357. process = subprocess.Popen(args, stdout=openocd_out, stderr=subprocess.STDOUT, bufsize=1)
  358. except Exception as e:
  359. print(e)
  360. raise FatalError(
  361. 'Error starting openocd. Please make sure it is installed and is present in executable paths', ctx)
  362. processes['openocd'] = process
  363. processes['openocd_outfile'] = openocd_out
  364. processes['openocd_outfile_name'] = openocd_out_name
  365. print('OpenOCD started as a background task {}'.format(process.pid))
  366. def get_gdb_args(project_desc: Dict[str, Any]) -> List:
  367. gdbinit = os.path.join(project_desc['build_dir'], 'gdbinit', 'gdbinit')
  368. args = ['-x={}'.format(gdbinit)]
  369. debug_prefix_gdbinit = project_desc.get('debug_prefix_map_gdbinit')
  370. if debug_prefix_gdbinit:
  371. args.append('-ix={}'.format(debug_prefix_gdbinit))
  372. return args
  373. def gdbui(action: str, ctx: Context, args: PropertyDict, gdbgui_port: Optional[str], gdbinit: Optional[str],
  374. require_openocd: bool) -> None:
  375. """
  376. Asynchronous GDB-UI target
  377. """
  378. project_desc = get_project_desc(args, ctx)
  379. local_dir = project_desc['build_dir']
  380. gdb = project_desc['monitor_toolprefix'] + 'gdb'
  381. generate_gdbinit_files(gdb, gdbinit, project_desc)
  382. # this is a workaround for gdbgui
  383. # gdbgui is using shlex.split for the --gdb-args option. When the input is:
  384. # - '"-x=foo -x=bar"', would return ['foo bar']
  385. # - '-x=foo', would return ['-x', 'foo'] and mess up the former option '--gdb-args'
  386. # so for one item, use extra double quotes. for more items, use no extra double quotes.
  387. gdb_args_list = get_gdb_args(project_desc)
  388. gdb_args = '"{}"'.format(' '.join(gdb_args_list)) if len(gdb_args_list) == 1 else ' '.join(gdb_args_list)
  389. args = ['gdbgui', '-g', gdb, '--gdb-args', gdb_args]
  390. print(args)
  391. if gdbgui_port is not None:
  392. args += ['--port', gdbgui_port]
  393. gdbgui_out_name = os.path.join(local_dir, GDBGUI_OUT_FILE)
  394. gdbgui_out = open(gdbgui_out_name, 'w')
  395. env = os.environ.copy()
  396. # The only known solution for https://github.com/cs01/gdbgui/issues/359 is to set the following environment
  397. # variable. The greenlet package cannot be downgraded for compatibility with other requirements (gdbgui,
  398. # pygdbmi).
  399. env['PURE_PYTHON'] = '1'
  400. try:
  401. process = subprocess.Popen(args, stdout=gdbgui_out, stderr=subprocess.STDOUT, bufsize=1, env=env)
  402. except (OSError, subprocess.CalledProcessError) as e:
  403. print(e)
  404. if sys.version_info[:2] >= (3, 11):
  405. raise SystemExit('Unfortunately, gdbgui is supported only with Python 3.10 or older. '
  406. 'See: https://github.com/espressif/esp-idf/issues/10116. '
  407. 'Please use "idf.py gdb" or debug in Eclipse/Vscode instead.')
  408. raise FatalError('Error starting gdbgui. Please make sure gdbgui has been installed with '
  409. '"install.{sh,bat,ps1,fish} --enable-gdbgui" and can be started.', ctx)
  410. processes['gdbgui'] = process
  411. processes['gdbgui_outfile'] = gdbgui_out
  412. processes['gdbgui_outfile_name'] = gdbgui_out_name
  413. print('gdbgui started as a background task {}'.format(process.pid))
  414. _check_openocd_errors(fail_if_openocd_failed, action, ctx)
  415. def global_callback(ctx: Context, global_args: PropertyDict, tasks: List) -> None:
  416. def move_to_front(task_name: str) -> None:
  417. for index, task in enumerate(tasks):
  418. if task.name == task_name:
  419. tasks.insert(0, tasks.pop(index))
  420. break
  421. debug_targets = any([task.name in ('openocd', 'gdbgui') for task in tasks])
  422. if debug_targets:
  423. # Register the meta cleanup callback -> called on FatalError
  424. ctx.meta['cleanup'] = debug_cleanup
  425. move_to_front('gdbgui') # possibly 2nd
  426. move_to_front('openocd') # always 1st
  427. # followed by "monitor", "gdb" or "gdbtui" in any order
  428. post_action = ctx.invoke(ctx.command.get_command(ctx, 'post_debug'))
  429. if any([task.name in ('monitor', 'gdb', 'gdbtui') for task in tasks]):
  430. post_action.action_args['block'] = 0
  431. else:
  432. post_action.action_args['block'] = 1
  433. tasks.append(post_action) # always last
  434. if any([task.name == 'openocd' for task in tasks]):
  435. for task in tasks:
  436. if task.name in ('gdb', 'gdbgui', 'gdbtui'):
  437. task.action_args['require_openocd'] = True
  438. def run_gdb(gdb_args: List) -> int:
  439. p = subprocess.Popen(gdb_args)
  440. processes['gdb'] = p
  441. return p.wait()
  442. def gdbtui(action: str, ctx: Context, args: PropertyDict, gdbinit: str, require_openocd: bool) -> None:
  443. """
  444. Synchronous GDB target with text ui mode
  445. """
  446. gdb(action, ctx, args, False, 1, gdbinit, require_openocd)
  447. def gdb(action: str, ctx: Context, args: PropertyDict, batch: bool, gdb_tui: Optional[int], gdbinit: Optional[str], require_openocd: bool) -> None:
  448. """
  449. Synchronous GDB target
  450. """
  451. watch_openocd = Thread(target=_check_openocd_errors, args=(fail_if_openocd_failed, action, ctx, ))
  452. watch_openocd.start()
  453. processes['threads_to_join'].append(watch_openocd)
  454. project_desc = get_project_desc(args, ctx)
  455. gdb = project_desc['monitor_toolprefix'] + 'gdb'
  456. generate_gdbinit_files(gdb, gdbinit, project_desc)
  457. args = [gdb, *get_gdb_args(project_desc)]
  458. if gdb_tui is not None:
  459. args += ['-tui']
  460. if batch:
  461. args += ['--batch']
  462. t = Thread(target=run_gdb, args=(args,))
  463. t.start()
  464. while True:
  465. try:
  466. t.join()
  467. break
  468. except KeyboardInterrupt:
  469. # Catching Keyboard interrupt, as this is used for breaking running program in gdb
  470. continue
  471. finally:
  472. watch_openocd.join()
  473. try:
  474. processes['threads_to_join'].remove(watch_openocd)
  475. except ValueError:
  476. # Valid scenario: watch_openocd task won't be in the list if openocd not started from idf.py
  477. pass
  478. def coredump_info(action: str,
  479. ctx: Context,
  480. args: PropertyDict,
  481. gdb_timeout_sec: int,
  482. core: str = None,
  483. save_core: str = None) -> None:
  484. espcoredump = _get_espcoredump_instance(ctx=ctx, args=args, gdb_timeout_sec=gdb_timeout_sec, core=core,
  485. save_core=save_core)
  486. espcoredump.info_corefile()
  487. def coredump_debug(action: str,
  488. ctx: Context,
  489. args: PropertyDict,
  490. core: str = None,
  491. save_core: str = None) -> None:
  492. espcoredump = _get_espcoredump_instance(ctx=ctx, args=args, core=core, save_core=save_core)
  493. espcoredump.dbg_corefile()
  494. coredump_base = [
  495. {
  496. 'names': ['--core', '-c'],
  497. 'help': 'Path to core dump file (if skipped core dump will be read from flash)',
  498. },
  499. {
  500. 'names': ['--save-core', '-s'],
  501. 'help': 'Save core to file. Otherwise temporary core file will be deleted.',
  502. },
  503. ]
  504. gdb_timeout_sec_opt = {
  505. 'names': ['--gdb-timeout-sec'],
  506. 'type': INT,
  507. 'default': 1,
  508. 'help': 'Overwrite the default internal delay for gdb responses',
  509. }
  510. fail_if_openocd_failed = {
  511. 'names': ['--require-openocd', '--require_openocd'],
  512. 'help': 'Fail this target if openocd (this targets dependency) failed.\n',
  513. 'is_flag': True,
  514. 'default': False,
  515. }
  516. gdbinit = {
  517. 'names': ['--gdbinit'],
  518. 'help': 'Specify the name of gdbinit file to use\n',
  519. 'default': None,
  520. }
  521. debug_actions = {
  522. 'global_action_callbacks': [global_callback],
  523. 'actions': {
  524. 'openocd': {
  525. 'callback': openocd,
  526. 'help': 'Run openocd from current path',
  527. 'options': [
  528. {
  529. 'names': ['--openocd-scripts', '--openocd_scripts'],
  530. 'help':
  531. ('Script directory for openocd cfg files.\n'),
  532. 'default':
  533. None,
  534. },
  535. {
  536. 'names': ['--openocd-commands', '--openocd_commands'],
  537. 'help':
  538. ('Command line arguments for openocd.\n'),
  539. 'default': None,
  540. }
  541. ],
  542. 'order_dependencies': ['all', 'flash'],
  543. },
  544. 'gdb': {
  545. 'callback': gdb,
  546. 'help': 'Run the GDB.',
  547. 'options': [
  548. {
  549. 'names': ['--batch'],
  550. 'help': ('exit after processing gdbinit.\n'),
  551. 'hidden': True,
  552. 'is_flag': True,
  553. 'default': False,
  554. },
  555. {
  556. 'names': ['--gdb-tui', '--gdb_tui'],
  557. 'help': ('run gdb in TUI mode\n'),
  558. 'default': None,
  559. }, gdbinit, fail_if_openocd_failed
  560. ],
  561. 'order_dependencies': ['all', 'flash'],
  562. },
  563. 'gdbgui': {
  564. 'callback': gdbui,
  565. 'help': 'GDB UI in default browser.',
  566. 'options': [
  567. {
  568. 'names': ['--gdbgui-port', '--gdbgui_port'],
  569. 'help':
  570. ('The port on which gdbgui will be hosted. Default: 5000\n'),
  571. 'default':
  572. None,
  573. }, gdbinit, fail_if_openocd_failed
  574. ],
  575. 'order_dependencies': ['all', 'flash'],
  576. },
  577. 'gdbtui': {
  578. 'callback': gdbtui,
  579. 'help': 'GDB TUI mode.',
  580. 'options': [gdbinit, fail_if_openocd_failed],
  581. 'order_dependencies': ['all', 'flash'],
  582. },
  583. 'coredump-info': {
  584. 'callback': coredump_info,
  585. 'help': 'Print crashed task’s registers, callstack, list of available tasks in the system, '
  586. 'memory regions and contents of memory stored in core dump (TCBs and stacks)',
  587. 'options': coredump_base + [PORT, BAUD_RATE, gdb_timeout_sec_opt], # type: ignore
  588. 'order_dependencies': ['all', 'flash'],
  589. },
  590. 'coredump-debug': {
  591. 'callback': coredump_debug,
  592. 'help': 'Create core dump ELF file and run GDB debug session with this file.',
  593. 'options': coredump_base + [PORT, BAUD_RATE], # type: ignore
  594. 'order_dependencies': ['all', 'flash'],
  595. },
  596. 'post-debug': {
  597. 'callback': post_debug,
  598. 'help': 'Utility target to read the output of async debug action and stop them.',
  599. 'options': [
  600. {
  601. 'names': ['--block', '--block'],
  602. 'help':
  603. ('Set to 1 for blocking the console on the outputs of async debug actions\n'),
  604. 'default': 0,
  605. },
  606. ],
  607. 'order_dependencies': [],
  608. },
  609. 'post_debug': {
  610. 'callback': post_debug,
  611. 'deprecated': {
  612. 'since': 'v4.4',
  613. 'removed': 'v5.0',
  614. 'exit_with_error': True,
  615. 'message': 'Have you wanted to run "post-debug" instead?',
  616. },
  617. 'hidden': True,
  618. 'help': 'Utility target to read the output of async debug action and stop them.',
  619. 'options': [
  620. {
  621. 'names': ['--block', '--block'],
  622. 'help':
  623. ('Set to 1 for blocking the console on the outputs of async debug actions\n'),
  624. 'default': 0,
  625. },
  626. ],
  627. 'order_dependencies': [],
  628. },
  629. },
  630. }
  631. return debug_actions