rust做嵌入式开发_Rust 嵌入式开发环境搭建指南 (二):日常除虫

本文是Rust嵌入式开发系列的第二部分,介绍了如何简化Rust项目的编译和调试流程。通过配置.cargo/config文件,使用Cargo编译指令,并指定GCC为链接器以保留调试信息。同时,文章讲解了如何创建openocd.cfg文件以简化OpenOCD启动,并通过.gdbinit文件优化GDB的启动和快捷指令设置。最后,讨论了使用GDB进行单步调试、设置断点、查看堆栈等基础操作,以及在VS Code中利用Native Debug插件进行调试的方法。
摘要由CSDN通过智能技术生成

上一篇文章我们成功跑起了第一个由 Rust 驱动的 Blinky,可以说已经一只脚踏入嵌入式开发的大门了。但是读者如果跟着步骤实践会发现,从编译到烧录运行,整个流程的命令行存在大段的参数,而且GDB 的启动指令重复枯燥。因此,指南第二章将介绍一些技巧来简化整个流程,然后实践一些在嵌入式系统中的调试方法。

Cargo

首先我们来回顾下编译 Rust 源码的命令:

> cargo build --target thumbv7m-none-eabi

这里的编译目标可以换用 .cargo/config 来指定。在项目目录新建文件夹 .cargo 并新建文件 config, 写入:

[build]

target = "thumbv7m-none-eabi"

之前我们使用 rustup 添加的几个目标平台其实是对应了几个不同的 Cortex 指令集,它们的对应关系是:

Target | Architecture

------------------------------------------------------------

thumbv6m-none-eabi | Cortex-M0 and Cortex-M0+

thumbv7m-none-eabi | Cortex-M3

thumbv7em-none-eabi | Cortex-M4 and Cortex-M7 (no FPU)

thumbv7em-none-eabihf | Cortex-M4F and Cortex-M7F (with FPU)

STM32F103 的架构为 Cortex-M3,所以这里我们指定的是 thumbv7m-none-eabi。

另外,如果不指定链接器,rustc 会使用默认的 LLD 进行链接,然而 LLD 并不能完全兼容嵌入式指令集,因此编译的可执行文件会丢失调试符号。为了之后能够使用 GDB 进行调试,我们这里将链接器指定为 gcc,修改 .cargo/config :

[build]

target = "thumbv7m-none-eabi"

[target.thumbv7m-none-eabi]

rustflags = [

"-C", "linker=arm-none-eabi-gcc",

"-C", "link-arg=-Wl,-Tlink.x",

"-C", "link-arg=-nostartfiles",

]

现在就可以直接使用 cargo build 指令了:

> cargo build

Compiling blinky v0.1.0

Finished dev [unoptimized + debuginfo]

Openocd

上一章启动 openocd 的命令:

> openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg

为了简化,我们在项目目录里新建文件 openocd.cfg,写入:

source [find interface/stlink-v2.cfg]

source [find target/stm32f1x.cfg]

之后要在这个项目目录里启动 openocd,只需要简单地:

> openocd

64-bits Open On-Chip Debugger 0.10.0-dev-00289-g5eb5e34 (2016-09-03-09:40)

Licensed under GNU GPL v2

...

Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

GDB

GDB 在启动时会读取并执行项目目录里的 .gdbinit 文件,文件里的每一行对应一条 GDB 指令。新建文件 .gdbinit,写入:

file ./target/thumbv7m-none-eabi/debug/blinky

target remote :3333

monitor reset halt

load

我们试下启动 GDB:

> arm-none-eabi-gdb

GNU gdb (GNU Tools for ARM Embedded Processors 6-2017-q1-update) 7.12.1.20170215-git

...

warning: File "C:\Users\Andy\Documents\Code\Rust\blinky2\.gdbinit" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".

To enable execution of this file add

add-auto-load-safe-path C:\Users\Andy\Documents\Code\Rust\blinky2\.gdbinit

line to your configuration file "C:\Users\Andy/.gdbinit".

To completely disable this security protection add

set auto-load safe-path /

line to your configuration file "C:\Users\Andy/.gdbinit".

For more information about this security protection see the

"Auto-loading safe path" section in the GDB manual. E.g., run from the shell:

info "(gdb)Auto-loading safe path"

(gdb)

与预期不同,项目目录里的 .gdbinit 文件并没有被使用,这是因为安全设置把它过滤屏蔽了。因此我们还需要设置 GDB 的全局安全配置。

进入用户根目录(Windows 系统下位于 C:\Users\UserName),新建文件 .gdbinit,写入:

set auto-load safe-path /

然后我们回到项目目录,再次启动 GDB。

> arm-none-eabi-gdb

GNU gdb (GNU Tools for ARM Embedded Processors 6-2017-q1-update) 7.12.1.20170215-git

...

0x20000004 in ?? ()

stm32f1x.cpu: target state: halted

target halted due to debug-request, current mode: Thread

xPSR: 0x01000000 pc: 0x0800016c msp: 0x20000260

Start address 0x0, load size 0

Transfer rate: 0 bits in <1 sec.

(gdb)

可以看到 .gdbinit 里的初始化指令已经成功执行了。

事实上我们还可以设置一些快捷 GDB 指令,比如说使用 monitor reset halt 重置单片机是一个非常高频的操作,然而每次都输入这么长的指令是很麻烦的。

我们可以打开位于用户根目录的全局 .gdbinit 文件,在文件后面追加:

set auto-load safe-path /

define reset

monitor reset halt

end

这样我们在 GDB 中就可以直接使用 reset 指令来进行重置了:

(gdb) reset

stm32f1x.cpu: target state: halted

target halted due to debug-request, current mode: Thread

xPSR: 0x01000000 pc: 0x0800016c msp: 0x20000260

调试

嵌入式系统中的调试往往比 PC 困难的多,因为嵌入式程序直接跑在硬件上,没有系统帮助线程隔离,也没有标准输入输出 (Standard IO)。有时候嵌入式系统程序还是根本无法调试的,因为调试行为本身可能就会影响到单片机的实时时间顺序。虽然在嵌入式上调试很困难,但是还是有不少工具手段可以帮助我们:串口输入输出

Hard Fault 中断

逻辑分析仪

GDB 单步调试

Hard Fault 中断

Hard Fault 中断一般产生于段错误 (Segment Falut,访问非法内存),或者是整数除以 0 等特殊情况。一旦发生这类型错误,MCU 会终止程序,并且产生一个 Hard Fault 中断信号以供处理,你可以选择从错误中恢复,也可以不作处理让 MCU 陷入默认的死循环函数。

逻辑分析仪

逻辑分析仪

逻辑分析软件

逻辑分析仪用于分析引脚通讯时序信号。与示波器相比,示波器是测量模拟信号的,而逻辑分析仪测量分析数字信号。测量数字信号时,示波器通常可以用来观察有没有信号或者是信号的质量如何,逻辑分析仪主要用来分析信号高低电平时序时间,以及通信的是什么数据。逻辑分析仪还具备强大的数据解析能力,对于一些复杂的协议,示波器显示的是波形,而逻辑分析仪可以直接把十六进制数据解析出来。现在很多逻辑分析仪都具备几十种协议解析器。

使用 GDB 调试

使用 GDB 可以在线对 MCU 进行单步调试,下断点,条件断点,读写局部变量,查看堆栈等操作,是嵌入式调试非常重要的工具。下面我会通过一个斐波那契数列的例子简单示范 GDB 的基础操作。

我们将 src/main.rs 改为斐波那契数列计算程序:

#![no_std]

#![no_main]

extern crate panic_halt;

extern crate stm32f103xx_hal as hal;

use cortex_m_rt::entry;

#[entry]

fn main() -> ! {

let mut n = 0;

loop {

let fib = fib(n);

n += 1;

}

}

fn fib(n: usize) -> usize {

if n < 2 {

1

} else {

fib(n - 1) + fib(n - 2)

}

}

编译然后启动 GDB:

> arm-none-eabi-gdb

...

Loading section .vector_table, size 0x130 lma 0x8000000

Loading section .text, size 0x3ce lma 0x8000130

Loading section .rodata, size 0x194 lma 0x8000500

Start address 0x80001ee, load size 1682

Transfer rate: 6 KB/sec, 560 bytes/write.

(gdb)

给 main 函数加上断点:

(gdb) break main

Breakpoint 1 at 0x80001be: file src\main.rs, line 11.

我们让程序运行到断点处:

(gdb) continue

Continuing.

Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 1, main () at src\main.rs:11

11 let mut n = 0;

程序成功停在了 main 函数的入口处,这里我们可以使用 list 指令查看光标附近的代码:

(gdb) list

6

7 use cortex_m_rt::entry;

8

9 #[entry]

10 fn main() -> ! {

11 let mut n = 0;

12

13 loop {

14 let fib = fib(n);

15 n += 1;

使用 next 指令单步执行:

(gdb) next

13 loop {

直接回车会执行上一条指令 (也就是 next):

(gdb)

14 let fib = fib(n);

使用 step 指令可以跳入函数:

(gdb) step

blinky::fib::h7d40020be56f8bb6 (n=1) at src\main.rs:20

20 if n < 2 {

使用 backtrace 查看调用堆栈:

(gdb) backtrace

#0 blinky::fib::h7d40020be56f8bb6 (n=1) at src\main.rs:21

#1 0x080001c8 in main () at src\main.rs:14

这里还可以使用 up 和 down 指令在调用堆栈上上下移动。

使用 finish 指令继续执行直到函数返回,并打印返回值:

(gdb) finish

Run till exit from #0 blinky::fib::h7d40020be56f8bb6 (n=1) at src\main.rs:21

0x080001c8 in main () at src\main.rs:14

14 let fib = fib(n);

Value returned is $1 = 1

注意到这里为止只是 fib 函数返回了值,但还没赋值给 fib 变量,我们可以查看本地变量来验证一下:

(gdb) info locals

n = 1

单步运行让程序进行赋值,再查看来验证一下:

(gdb) next

15 n += 1;

(gdb) info locals

fib = 1

n = 1

可以使用 set 指令修改变量:

(gdb) set fib=500

(gdb) info locals

fib = 500

n = 1

最后我们可以通过 info breakpoints 查看已设置的断点:

(gdb) info breakpoints

Num Type Disp Enb Address What

1 breakpoint keep y 0x080001be in main at src\main.rs:11

breakpoint already hit 1 time

使用 delete 指令删除对应断点:

(gdb) delete 1

(gdb) info breakpoints

No breakpoints or watchpoints.

事实上,在日常使用中我们往往会使用简短版的 GDB 指令,比如说 list -> l, break -> b, info locals -> i lo, next -> n 等等,简写只要不与其他指令产生歧义, GDB 都能识别。另外还有一个指令 tbreak 可以用来设置一次性断点,顾名思义,这个断点会在触发中断后自动删除。

软断点

通过 break 指令设置的断点被称为硬件断点 (hardward breakpoint),从 openocd 的提示可以看出,这款单片机拥有最高 6 个硬件断点,也就是说我们设置的断点数量是有限制的。

Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

还有另一种设置断点的方法,就是使用 arm 指令集特有的 bkpt 指令,实现软件断点,这种断点方式没有数量限制。由于高级语言里不包含这个指令,所以我们要通过内嵌汇编来实现这个功能,所幸的是,cortex_m 库已经为我们封装好了这个汇编代码。修改源代码:

#![no_std]

#![no_main]

extern crate panic_halt;

extern crate stm32f103xx_hal as hal;

use cortex_m::asm;

use cortex_m_rt::entry;

#[entry]

fn main() -> ! {

loop {

asm::bkpt();

}

}

编译并运行:

(gdb) continue

Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.

0x0800036c in __bkpt ()

可以看到程序停在了软断点处,使用 up 指令沿着调用堆栈可以回溯到调用库函数的代码处:

(gdb) up

#1 0x08000136 in cortex_m::asm::bkpt::h0732619f5c574313 ()

at C:\Users\Andy\.cargo\registry\src\github.com-1ecc6299db9ec823\cortex-m-0.5.8\src/asm.rs:19

19 __bkpt();

(gdb) up

#2 main () at src\main.rs:13

13 asm::bkpt();

注意:在非调试模式下(即没有连接调试器)的情况下触发 bkpt 会导致 Hard Fault 中断。

使用 VS Code 调试

直接使用 GDB 进行调试有时不够直观,也较为繁琐,适用于临时或简单的调试。对于大型项目,我们一般喜欢使用 IDE 来辅助调试。

打开 VS Code,安装 Native Debug 插件,转到调试面板添加配置,选择 C++ (GDB/LLDB),修改 .vscode/launch.json:

{

"version": "0.2.0",

"configurations": [

{

"name": "Debug GDB",

"type": "gdb",

"request": "attach",

"executable": "./target/thumbv7m-none-eabi/debug/blinky",

"target": "localhost:3333",

"cwd": "${workspaceRoot}",

"gdbpath": "arm-none-eabi-gdb",

"remote": true,

"autorun": [

"monitor reset halt",

"load"

]

}

]

}

点击开始调试,即可开始享受 VS Code 的现代化调试体验加成:

VS Code Debugger

如果你有幸看到这,那就帮忙点个赞,让更多人看到吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值