OpenOCD 代码学习(1)注册命令

11 篇文章 0 订阅
5 篇文章 1 订阅

概述

  • 1)OpenOCD 与调试器、MCU 的关系:

    • (1)OpenOCD 运行在 PC 机上,用户可以通过 GDB 调试、Telnet 连接 Socket 以及 CMD 命令行的方式执行命令

    • (2)OpenOCD 将命令发送到 MCU 端需要双方约定协议。目前常用的协议为 SWD 和 JTAG,实现这些协议的工具有 DAPLink、ST-Link、JLink 等

    • (3)在 MCU 端内置了 DAP(Debug Access Port)模块,它接收到命令,通过 AHB 总线控制 CPU 内核。(因此,通过 DAP 我们可以访问挂载在 AHB 总线上所有外设,尤其是 FLASH 外设,它是实现芯片烧录程序的关键。)

  • 2)在 《Windows 下编译 OpenOCD》一节,我们已经可以通过 JetBrains CLion 打开 OpenOCD 源码(也可以使用 VSCode 打开)。一般程序都是从 main() 函数开始,那么让我们打开 /openocd/src 目录,从 main.c 文件开始:

1 main()

  • 从 main() 函数到 openocd_main() 函数,最后再到 setup_command_handler() 函数,OpenOCD 进行命令注册操作。

  • 那么接下来,我们重点看一下 setup_command_handler() 函数。

2 setup_command_handler()

  • 1)setup_command_handler() 函数主要进行了以下两个操作,我们一个一个来看:

    • command_init()
    • (*command_registrants[i])(cmd_ctx)
  • 2)对于 command_init() 函数,我们略去 Jim 框架的代码,可以看到以下内容:

    register_commands(context, NULL, command_builtin_handlers);
    
    if (Jim_Eval_Named(interp, startup_tcl, "embedded:startup.tcl", 1) == JIM_ERR) {
    
    • 第一行代码注册一些 OpenOCD 的内置命令,如 ocd_find、capture、sleep 等等
    • 第二行则是解析了 startup.tcl 文件。

    这里要注意,.tcl 文件里有一些调用过程,当你无法在源码中找到某些命令的实现时,可以搜索一下 .tcl 文件。如 CLion 通过 program 命令烧录,而该命令实际上是一个定义在 src/flash/startup.tcl 中的一个 proc,并在其内部转换成 flash write_image erase 组合命令来进行烧录。

  • 3)对于 (*command_registrants[i])(cmd_ctx) 函数调用,我们结合上下文来看:

/* 1 定义名为 command_registrant_t,参数类型为 struct command_context 的函数指针 */
typedef int (*command_registrant_t)(struct command_context *cmd_ctx_value);
/* 2 声明一个 command_registrant_t 类型的函数指针数组 */
static const command_registrant_t command_registrants[] = {
    &workaround_for_jimtcl_expr,
    &openocd_register_commands,
    &server_register_commands,
    &gdb_register_commands,
    &log_register_commands,
    &rtt_server_register_commands,
    &transport_register_commands,
    &adapter_register_commands,
    &target_register_commands,
    &flash_register_commands,
    &nand_register_commands,
    &pld_register_commands,
    &cti_register_commands,
    &dap_register_commands,
    &arm_tpiu_swo_register_commands,
    NULL
};
/* 3 遍历并执行声明的函数指针 */
for (unsigned i = 0; command_registrants[i]; i++) {
    int retval = (*command_registrants[i])(cmd_ctx);
    if (retval != ERROR_OK) {
        command_done(cmd_ctx);
        return NULL;
    }
}
  • 也就是说,(*command_registrants[i])(cmd_ctx) 其实是调用那些注册命令的函数。下面我们来看一下命令注册的实现逻辑。

3 register_commands()

3.1 server_register_commands()

  • 1)这里我们以 server_register_commands() 函数为例,如下图:

    • telnet_register_commands():注册 telnet 相关的命令,如 exit、telnet_port 等,且在这里指定 telnet 端口号为 4444

    • tcl_register_commands() 和 jsp_register_commands() 分别指定了 6666 和 7777 两个端口号(暂时不知道这个两个是干吗的,不过不影响使用)

    • register_commands():注册 server 相关的命令,如 shutdown 等。

  • 2)然后我们看一下命令的具体声明内容:

    static const struct command_registration telnet_command_handlers[] = {
    	{
    		.name = "exit",
    		.handler = handle_exit_command,
    		.mode = COMMAND_EXEC,
    		.usage = "",
    		.help = "exit telnet session",
    	},
    	{
    		.name = "telnet_port",
    		.handler = handle_telnet_port_command,
    		.mode = COMMAND_CONFIG,
    		.help = "Specify port on which to listen "
    			"for incoming telnet connections.  "
    			"Read help on 'gdb_port'.",
    		.usage = "[port_num]",
    	},
    	COMMAND_REGISTRATION_DONE
    };
    
    • (1)其中的 .name 是处理器名称,Jim 模块可根据该名称查找到对应的 handler
    • (2)mode 表示该 handler 的模式(我愿称之为生命周期):
        1. COMMAND_EXEC,表示该 handler 在命令行输入时才会触发
        1. COMMAND_CONFIG,表示该 handler 在 OpenOCD 启动时解析配置文件时触发
        1. COMMAND_ANY,表示该 handler 以上两种情况均会触发

3.2 register command

  • 1)以下为注册命令的实现逻辑:

  • 2)每个函数的作用已经在流程图中注明,需要注意有两点:

    • 将 jim_command_dispatch() 函数赋值给 cmdProc 指针的 u.native.cmdProc 属性
    • 将 command_new() 函数构造的 command 实例,赋值给 cmdProc 指针的 u.native.privData 属性。
    Jim_Cmd *cmdPtr = Jim_Alloc(sizeof(*cmdPtr));
    
    /* Store the new details for this command */
    memset(cmdPtr, 0, sizeof(*cmdPtr));
    cmdPtr->inUse = 1;
    cmdPtr->u.native.delProc = delProc;
    cmdPtr->u.native.cmdProc = cmdProc;     // jim_command_dispatch
    cmdPtr->u.native.privData = privData;   // command 实例
    
  • 3)最终所有命令会缓存到 Jim 框架的 Hash 数组结构中(查找时间复杂度 O(1))。

  • 4)通过对各个 xx__register_commands() 函数的遍历,所有的命令都将注册到此。注册完命令后要执行命令,下一篇文章我们来看一下执行命令的逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值