嵌入式 Rust 之书---第二章 入门(QEMU)

目录

2.1 QEMU

2.1.1 创建非标准的Rust程序

2.1.2 代码简介 

2.1.3 交叉编译 

2.1.4 二进制文件分析工具

2.1.5 运行 

2.1.6 调试


在本节中,我们将引导您完成编写,构建,刷新和调试嵌入式程序的过程。您将能够在没有任何特殊硬件的情况下尝试大多数示例,因为我们将向您展示使用QEMU(一种流行的开源硬件仿真器)的基础知识。唯一需要硬件的部分自然就是硬件部分,我们使用OpenOCD对自己的开发板进行编程。

2.1 QEMU

我们将开始为LM3S6965编写一个程序,这是一个Cortex-M3微控制器。我们选择这个作为我们的初始目标,因为它可以使用QEMU进行仿真,因此您不需要在本节中使用硬件,我们可以专注于工具和开发过程。

重要 我们将在本教程中使用名称“app”作为项目名称。每当您看到“app”一词时,您应该将其替换为您为项目选择的名称。或者,您也可以将项目命名为“app”并避免替换。

2.1.1 创建非标准的Rust程序

我们将从cortex-m-quickstart项目模板中生成一个新项目。 

使用cargo-generate 

首先安装cargo-generate

cargo install cargo-generate

然后生成新项目

cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart

 Project Name: app
 Creating project called `app`...
 Done! New project created /tmp/app

cd app

使用git 

克隆repository 

git clone https://github.com/rust-embedded/cortex-m-quickstart app
cd app

然后在Cargo.toml文件中填写占位符

[package]
authors = ["{{authors}}"] # "{{authors}}" -> "John Smith"
edition = "2018"
name = "{{project-name}}" # "{{project-name}}" -> "awesome-app"
version = "0.1.0"

# ..

[[bin]]
name = "{{project-name}}" # "{{project-name}}" -> "awesome-app"
test = false
bench = false

 以上两者都不用

获取cortex-m-quickstart模板的最新快照并将其解压缩。

curl -LO https://github.com/rust-embedded/cortex-m-quickstart/archive/master.zip
unzip master.zip
mv cortex-m-quickstart-master app
cd app 

或者您可以浏览到cortex-m-quickstart,单击绿色的“克隆或下载”按钮,然后单击“下载ZIP”。然后在“使用git”版本的第二部分中填写Cargo.toml文件中的占位符。 

2.1.2 代码简介 

为方便起见,这里是src / main.rs中源代码最重要的部分:

#![no_std]
#![no_main]

extern crate panic_halt;

use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    loop {
        // your code goes here
    }
}

这个程序与标准的Rust程序有点不同,所以让我们仔细看看。

  • #![no_std]表示该程序不会链接到标准包,std。相反,它将链接到其子集:core crate。
  • #![no_main]表示该程序不会使用大多数Rust程序使用的标准main接口。 使用no_main的主要(没有双关语)原因是在no_std上下文中使用main接口需要nightly。
  • extern crate panic_halt; 这个crate 提供了一个panic_handler,用于定义程序的恐慌行为(the panicking behavior)。我们将在本书的Panicking章节中更详细地介绍这一点。
  • #[entry]是由cortex-m-rt crate提供的属性,用于标记程序的入口点。由于我们没有使用标准main接口,我们需要另一种方式来指示程序的入口点,它就是#[entry]。 
  • fn main() -> !。我们的程序将是目标硬件上运行的唯一进程,因此我们不希望它结束!我们使用一个 divergent function(函数签名中的 - >!位)来确保在编译时就是这种情况。

2.1.3 交叉编译 

下一步是交叉编译Cortex-M3架构的程序。这就像运行cargo build --target $TRIPLE一样简单 - 如果您知道编译目标($triple)应该是什么。幸运的是,模板中的.cargo / config有答案:

[build]
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi"    # Cortex-M0 and Cortex-M0+
target = "thumbv7m-none-eabi"    # Cortex-M3
# target = "thumbv7em-none-eabi"   # Cortex-M4 and Cortex-M7 (no FPU)
# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)

要为Cortex-M3架构进行交叉编译,我们必须使用thumbv7m-none-eabi。此编译目标已设置为默认值,因此下面的两个命令执行相同的操作:

cargo build --target thumbv7m-none-eabi
cargo build

2.1.4 二进制文件分析工具

现在我们在target / thumbv7m-none-eabi / debug / app中有一个非本机ELF二进制文件。我们可以使用cargo-binutils进行检查。使用cargo-readobj,我们可以打印ELF头以确认这是一个ARM二进制文件。

注意:

  • --bin app,是用于在target/$triple/debug/app检查二进制文件的sugar
  • --bin app,如有必要,还将重新编译二进制文件 

嵌入式 Rust 之书---第二章 入门(QEMU)_第1张图片

cargo-size可以打印二进制文件的链接器部分的大小。

注:该输出假设rust-embedded/cortex-m-rt#111已合并。 

cargo size --bin app --release -- -A

我们使用--release检查优化版本

 嵌入式 Rust 之书---第二章 入门(QEMU)_第2张图片

关于ELF链接器部分的扩展:

  • .text    包含程序指令
  • .rodata    包含字符串等常量值
  • .data    包含初始值不为零的静态分配变量
  • .bss    包含初始值为零的静态分配变量 
  • .vector_table    是我们用来存储向量(中断)表的非标准部分
  • .ARM.attributes和.debug_*    包含元数据(metadata),并且在flashing 二进制文件时不会加载到目标上。

重要:ELF文件包含调试信息之类的元数据,因此它们在磁盘上的大小无法准确反映程序拷贝到设备时占用的空间。始终使用cargo-size来检查二进制文件的实际大小。 

cargo-objdump可用于反汇编二进制文件。

cargo objdump --bin app --release -- -disassemble -no-show-raw-insn -print-imm-hex

注意此输出可能因系统而异。新版本的rustc,LLVM和库可以生成不同的汇编语言。 我们截取了一些指令以使代码片段保持较小。

嵌入式 Rust 之书---第二章 入门(QEMU)_第3张图片

2.1.5 运行 

接下来,让我们看看如何在QEMU上运行嵌入式程序!这一次我们将使用Hello示例,它实际上可以做一些事情。为了方便起见,这里是examples/hello.rs的源代码:

//! Prints "Hello, world!" on the host console using semihosting

#![no_main]
#![no_std]

extern crate panic_halt;

use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hprintln};

#[entry]
fn main() -> ! {
    hprintln!("Hello, world!").unwrap();

    // exit QEMU
    // NOTE do not run this on hardware; it can corrupt OpenOCD state
    debug::exit(debug::EXIT_SUCCESS);

    loop {}
}

 该程序使用称为semihosting的东西将文本打印到主机控制台。当使用真正的硬件时,这需要一个调试会话,但当使用QEMU时,这才有效。让我们从编译示例开始:

cargo build --example hello

输出二进制文件将位于target/thumbv7m-none-eabi/debug/examples/hello。要在QEMU上运行此二进制文件,请运行以下命令:

qemu-system-arm \
  -cpu cortex-m3 \
  -machine lm3s6965evb \
  -nographic \
  -semihosting-config enable=on,target=native \
  -kernel target/thumbv7m-none-eabi/debug/examples/hello

 嵌入式 Rust 之书---第二章 入门(QEMU)_第4张图片

 打印文本后,该命令应成功退出(退出代码= 0)。在*nix上,您可以使用以下命令检查:

echo $?

让我们分解一下QEMU命令:

  • qemu-system-arm    这是QEMU模拟器。这些QEMU二进制文件有一些变体; 这个名称是ARM机器的完整系统仿真。
  • -cpu cortex-m3    这告诉QEMU模拟Cortex-M3 CPU。指定CPU模型可以捕获一些错误的编译错误:例如,运行为Cortex-M4F编译的程序(具有硬件FPU)将在执行期间发生QEMU错误。
  • -machine lm3s6965evb    这告诉QEMU模拟LM3S6965EVB,这是一个包含LM3S6965微控制器的评估板。
  • -nographic    这告诉QEMU不要启动它的GUI。
  • -semihosting-config (..)    这告诉QEMU启用半主机机制(semihosting)。半主机允许模拟设备使用主机stdout,stderr和stdin以及在主机上创建文件。 
  • -kernel $file    这告诉QEMU在模拟机器上加载和运行哪个二进制文件。

输入这么长的qemu命令太费事了!我们可以设置一个自定义的运行程序来简化这个过程。.cargo/config有一个引用qemu的注释掉的运行程序;让我们取消注释:

[target.thumbv7m-none-eabi]
# 取消注释,'cargo run'使程序在QEMU上执行
runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"

此运行程序仅适用于thumbv7m-none-eabi目标,这是我们的默认编译目标。 cargo run将编译程序并在qemu上运行:

cargo run --example hello --release

2.1.6 调试

调试对嵌入式开发至关重要。让我们看看是怎么做到的。

调试嵌入式设备涉及远程调试,因为我们要调试的程序不会在运行调试程序(gdb或lldb)的计算机上运行。

远程调试涉及客户机和服务器。在QEMU设置中,客户机将是一个gdb(或lldb)进程,服务器将是运行嵌入式程序的QEMU进程。在本节中,我们将使用已经编译的hello示例。第一个调试步骤是在调试模式下启动QEMU:

qemu-system-arm \
  -cpu cortex-m3 \
  -machine lm3s6965evb \
  -nographic \
  -semihosting-config enable=on,target=native \
  -gdb tcp::3333 \
  -S \
  -kernel target/thumbv7m-none-eabi/debug/examples/hello 

此命令不会将任何内容打印到控制台,并且会阻止终端。这次我们又传递了两个标志:

  • -gdb tcp::3333    这告诉QEMU等待TCP端口3333上的gdb连接。
  • -S    这告诉QEMU在启动时冻结机器。如果没有这个,在我们有机会启动调试器之前,程序就已经到了main的末尾! 

接下来,我们在另一个终端启动GDB并告诉它加载示例的调试符号: 

gdb-multiarch -q target/thumbv7m-none-eabi/debug/examples/hello 

注意:您可能需要另一个版本的gdb而不是gdb multiarch,这取决于您在安装章节中安装了哪个版本。这也可以是 arm-none-eabi-gdb或只是gdb。然后在GDB shell中我们连接到QEMU,它正在等待TCP端口3333上的连接。

target remote :3333 

嵌入式 Rust 之书---第二章 入门(QEMU)_第5张图片 

 您将看到进程停止,程序计数器指向一个名为reset的函数。这是重置处理程序:启动时Cortex-M核心执行的操作。这个重置处理程序最终将调用我们的主函数。让我们使用断点和continue命令一路跳过:

 嵌入式 Rust 之书---第二章 入门(QEMU)_第6张图片

我们现在已经接近打印“Hello, world!”的代码了。 让我们使用next命令继续前进:

嵌入式 Rust 之书---第二章 入门(QEMU)_第7张图片

到这里,你应该看到“Hello, world!”打印在运行qemu-system-arm的终端上。再次调用Next将终止QEMU进程。

嵌入式 Rust 之书---第二章 入门(QEMU)_第8张图片

现在可以退出gdb会话。

 

你可能感兴趣的:(rust)