Zephyr调试器配置及原理

Creative Commons
本作品采用知识共享署名

本文在ubuntu下以pyocd为例说明Zephyr如何配置调试器,及其实现的原理。

调试器简介

为了方便理解,先画一个完整的调试环境如下图:
debug
硬件上:PC通过USB连接调试器(Debug Probe), 调试器通过调试电缆连接被调试的目标板(Target Board)
软件上:PC上的调试工具(Debug tool)通过USB HID控制调试器,调试器通过调试协议(JTAG/SWD)调试目标板。PC上的前端工具(GUI/CLI)通过gdb或者其它协议控制debug tool调试和查看数据。debug tool相当于是调试器的一个软件代理。下面列举各个部分常见的一些工具
GUI: VS Code,Eclipse,CodeBlock,ddd
CLI: GDB, tui
Debug Tool: pyocd, openocd
Debug Probe : Daplink, J-link, st-link, OpenJtag
protocol: JTAG, SWD
一些工具会将GUI/CLI会和Debug Tool集成在一起,例如keil, ozoen

对于Zephyr来说是面向Debug Tool的设置,在Zephyr中用的Debug tool名字是和系统的可执行档名有关

配置

使用说明

zephyr中默认debug tool和board是绑定的,因此对于debug tool的配置是放在board下面的board.cmake

1
2
3
4
board_set_debugger_ifnset(pyocd)                                                //调试使用pyocd
board_set_flasher_ifnset(pyocd) //刷flash使用pyocd
board_runner_args(pyocd "--target=mimxrt1050_hyperflash") // 设置pyocd的运行参数
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake) //调试器默认使用pyocd

通过上面配置可以指定对应的board调试使用pyocd,连接的DAPLink目标是mimxrt1050_hyperflash

配置宏/函数

在zephyr/cmake/extensions.cmake中提供了下面的函数和宏供外部使用,作用如下

1
2
3
4
5
6
7
8
function(board_set_runner type runner)                    //设置调试器/烧写器
macro(board_set_runner_ifnset type runner) //如果之前没有设置调试器/烧写器,则设置调试器/烧写器
macro(board_set_flasher runner) //设置烧写器
macro(board_set_debugger runner) //设置调试器
macro(board_set_flasher_ifnset runner) //如果之前没有设置调试器,则设置调试器
macro(board_set_debugger_ifnset runner) //如果之前没有设置烧写器,则设置烧写器
function(board_runner_args runner) //设置调试器/烧写器运行参数
function(board_finalize_runner_args runner) //设置调试器/烧写器运行默认参数,该参数在没有board runner和args时使用

原理

配置宏/函数的实现

关于调试器配置总共3个函数(红色)和5个宏,关系和作用如下图
cmake
可以看到这组被board.cmake调用的宏和函数,最后是生成了2个变量:
BOARD_FLASH_RUNNER=pyocd
BOARD_DEBUG_RUNNER=pyocd
2个属性
BOARD_RUNNER_ARGS_PYOCD=”–target=mimxrt1050_hyperflash”
ZEPHYR_RUNNERS=pyocd

Zephyr cmake公共变量

在zephyr/cmake/flash/CMakeLists.txt中以上2个变量和2个属性被变为zephyr公共的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#转zephyr公共变量
if(BOARD_FLASH_RUNNER)
set(ZEPHYR_BOARD_FLASH_RUNNER ${BOARD_FLASH_RUNNER} CACHE STRING
"Default runner for flashing binaries" FORCE)
endif()
if(BOARD_DEBUG_RUNNER)
set(ZEPHYR_BOARD_DEBUG_RUNNER ${BOARD_DEBUG_RUNNER} CACHE STRING
"Default runner for debugging" FORCE)
endif()

#属性转为zephyr公共变量
get_property(RUNNERS GLOBAL PROPERTY ZEPHYR_RUNNERS)
if(RUNNERS)
set(ZEPHYR_RUNNERS ${RUNNERS} CACHE INTERNAL "Available runners")
foreach(runner ${RUNNERS})
string(MAKE_C_IDENTIFIER ${runner} runner_id)
# E.g. args = BOARD_RUNNER_ARGS_openocd, BOARD_RUNNER_ARGS_dfu_util, etc.
get_property(runner_args GLOBAL PROPERTY "BOARD_RUNNER_ARGS_${runner_id}")
set(ZEPHYR_RUNNER_ARGS_${runner_id} ${runner_args} CACHE STRING
"Runner-specific arguments for ${runner}" FORCE)
endforeach()

最后得到4个变量
ZEPHYR_BOARD_FLASH_RUNNER=pyocd
ZEPHYR_BOARD_DEBUG_RUNNER=pyocd
ZEPHYR_RUNNER_ARGS_PYOCD=”–target=mimxrt1050_hyperflash”
ZEPHYR_RUNNERS=pyocd

west调用

有了配置好Zephyr公共变量,我们再来看west如何调用。west工具读取zephyr/scripts/west-commands.yml,可以获取flash和debug命令,在执行这west flash和west debug(deuggerserver和attach类似,不再展开)两个命令的时候会分别调用flash.py和debug.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- file: scripts/west_commands/flash.py
commands:
- name: flash
class: Flash
help: flash and run a binary on a board
- file: scripts/west_commands/debug.py
commands:
- name: debug
class: Debug
help: flash and interactively debug a Zephyr application
- name: debugserver
class: DebugServer
help: connect to board and launch a debug server
- name: attach
class: Attach
help: interactively debug a board

west flash呼叫zephyr/scripts/west_commands/flash.py Flash.do_run 读取ZEPHYR_BOARD_FLASH_RUNNER=pyocd

1
2
3
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_FLASH_RUNNER') //

west debug呼叫zephyr/scripts/west_commands/debug.py Debug.do_run读取ZEPHYR_BOARD_DEBUG_RUNNER=pyocd

1
2
3
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_DEBUG_RUNNER')

两者都是呼叫到zephyr/scripts/west_commands/run_common.py的do_run_common, 在do_run_common中读取ZEPHYR_RUNNER_ARGS_PYOCD获取board.cmake传入的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cached_runner_args = cache.get_list(
'ZEPHYR_RUNNER_ARGS_{}'.format(cmake.make_c_identifier(runner))) //board.cmake board_runner_args传入的参数
runner_args = [arg for arg in runner_args if arg != '--'] //west命令行传入的参数
final_runner_args = cached_runner_args + runner_args

//解析参数
parser = argparse.ArgumentParser(prog=runner)
runner_cls.add_parser(parser)
parsed_args, unknown = parser.parse_known_args(args=final_runner_args)

//创建调试器
runner = runner_cls.create(cfg, parsed_args)

//执行调试命令
runner.run(command_name)

前面三个重要的函数:解析参数,创建调试器,执行调试命令 都来自于zephyr/scripts/west_commands/runners/core.py

1
2
3
add_parser->cls.do_add_parser(parser) -> do_add_parser抽象方法
create 抽象方法
run->self.do_run(command, **kwargs) -> do_run 抽象方法

这些抽象方法在pyocd的情况下被zephyr/scripts/west_commands/runners/pyocd.py实现:
do_add_parser->parser.add_argument, 可在这里添加要解析的参数
create->PyOcdBinaryRunner,构造时使用do_add_parser内添加的参数
pyocd.py会根据不同的参数构造不同的cmd
do_run->self.flash->self.check_call(cmd) –> 这里就会执行pyocd flash ….

1
2
3
4
5
6
7
8
9
10
cmd = ([self.pyocd] +
['flash'] +
['-e', 'sector'] +
self.flash_addr_args +
self.daparg_args +
self.target_args +
self.board_args +
self.frequency_args +
self.flash_extra +
[fname])

do_run-> self.debug_debugserver->self.check_call(cmd) –>这里就会执行pyocd gdbserver …

1
2
3
4
5
6
7
server_cmd = ([self.pyocd] +
['gdbserver'] +
self.daparg_args +
self.port_args() +
self.target_args +
self.board_args +
self.frequency_args)

实例

分析了这么多,其实就是为了在zephyr内pyocd加上尚未支援的参数,这里我是想的pyocd flash时指定–script参数,方便在添加不同的板子时在不修改pyocd的情况下通过脚本适配板子上的flash。从上面的分析看增加一个参数,其实就是:
在do_add_parser中增加parser的参数
在PyOcdBinaryRunner构造是添加增加的参数
在do_run执行命令时添加该参数
详细修改见:
https://github.com/lgl88911/zephyr/commit/dab736910903bb81ea15571673a7b973652c30a2