QEMU专栏 - 使用 QEMU 调试 FreeRTOS示例

写在最前

这几天一直在研究QEMU中多核ARM加载不同镜像的问题, 一直不得其解, 这部分后续可以分几个不分拆解下,看看为什么会出现这种问题. 今天先来看看如何使用QEMU来调试FreeRTOS的示例代码.

编译并运行 FreeRTOS 示例代码 (基础版本)

首先是下载代码, 这种只需要看最新代码情况下,我通常会使用--depth=1来减少下载的代码量; 并且需要下载子模块, 所以使用--recurse-submodules来下载子模块.

git clone https://github.com/FreeRTOS/FreeRTOS.git --recurse-submodules --depth=1

然后进入到 FreeRTOS/Demo/CORTEX_MPS2_QEMU_IAR_GCC中, 在这个目录下,使用vscode打开. 我比较喜欢使用MSYS2 MinGW 64-bit 控制台, 但是vscodebash (MSYS2)打开后,默认不是在当前工作区的目录, 所以可以通过修改Preferences: Open User Settings (JSON)的方式打开用户设置settings.json, 然后找到"terminal.integrated.profiles.windows"这个配置, 然后添加如下配置:

  "bash mingw64": {
      "path": "C:\\msys64\\msys2_shell.cmd",
      "args": [
          "-defterm",
          "-here",
          "-no-start",
          "-mingw64"    
      ]
  },

由于这个目录下已经配置好了launch.jsontasks.json, 但是这里需要简单的调整一下, 让其在我们编译出的QEMU中运行, 打开tasks.json文件:

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
          "label": "Build QEMU",
          "type": "shell",
          "command": "make --directory=${workspaceFolder}/build/gcc",
          "problemMatcher": {
            "base": "$gcc",
            "fileLocation": ["relative", "${workspaceFolder}/build/gcc"]
          },
          "group": {
              "kind": "build",
              "isDefault": true
          }
        },
        {
          "label": "Run QEMU",
          "type": "shell",
          "command": "echo 'QEMU RTOSdemo started'; qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -kernel ${workspaceFolder}/build/gcc/output/RTOSDemo.out -monitor none -nographic -serial stdio -s -S",
          "dependsOn": ["Build QEMU"],
          "isBackground": true,
          "problemMatcher": [
            {
              "pattern": [
                {
                  "regexp": ".",
                  "file": 1,
                  "location": 2,
                  "message": 3
                }
              ],
              "background": {
                "activeOnStart": true,
                "beginsPattern": ".",
                "endsPattern": "QEMU RTOSdemo started",
              }
            }
          ]
        }
    ]
}

我们需要将qemu-system-arm调整为我们编译出的QEMU的路径:

"command": "echo 'QEMU RTOSdemo started'; <Path>/build/qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -kernel ${workspaceFolder}/build/gcc/output/RTOSDemo.out -monitor none -nographic -serial stdio -s -S"

到这里, 我们就可以通过F5来运行我们的FreeRTOS示例代码了, 由于launch.json中配置了preLaunchTaskRun QEMU, 而Run QEMU又依赖于Build QEMU, 所以在运行时会先编译, 然后运行QEMU, 这里的参数更加复杂:

 <Path>/build/qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -kernel ${workspaceFolder}/build/gcc/output/RTOSDemo.out -monitor none -nographic -serial stdio -s -S

基础版中,我们只关注运行, 但是也需要简单的调试, 所以设置 -monitor none , -nographic , -serial stdio , -s , -S 这几个参数, 这里简单的解释一下:

  • -monitor none : 不显示监视器
  • -nographic : 不显示图形界面
  • -serial stdio : 将串口输出重定向到标准输出
  • -s : 启动GDB服务器
  • -S : 启动时暂停

我一般不喜欢贴图, 主要是因为贴图不方便修改, 但是这里还是需要简单的贴一下:
Debug

其实我在代码中,并未加入任何的断点, 但是启动后, 仍然停在了Reset_Handler中, 这是因为在launch.json中将stopAtEntry设置为了true, 这个参数的意思是在程序启动时, 是否停在入口处, 这里我们设置为true, 所以会停在Reset_Handler中, 如果设置为false, 则会直接运行程序, 这里大家可以自行测试.

关于断点相关的调试, 这里就不多赘述了, 至于GDB的内容, 后续需要的时候,再来详细的介绍.

进阶调试版本 (初次接触Monitor)

在这一个篇章中,我们可以初步了解下QEMU Monitor , QEMU Monitor是一个用于与QEMU交互的命令行工具, 现阶段我们主要通过QEMU Monitor查看QEMU的状态.

为此, 我们需要调整之前在tasks.jsonRun QEMU的命令, 这里有几种选择:

  1. 使用独立的QEMU Monitor窗口

为此, 首先,我们需要将-nographic去掉, 这样QEMU就会显示图形界面, 然后其次,我们要将-monitor none去掉, 改成-monitor vc , 改动后command如下:

"command": "echo 'QEMU RTOSdemo started'; <Path>/build/qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -kernel ${workspaceFolder}/build/gcc/output/RTOSDemo.out -monitor vc -serial stdio -s -S"

当然,这种效果和直接去掉-nographic以及-monitor none是一样的, 但是为了明确我们使用了QEMU Monitor, 我们这里使用了-monitor vc.

按照这种方式, 启动的QEMU Monitor窗口,不要关闭, 因为如果关闭, QEMU也会关闭, 那么调试就结束了.

  1. 使用QEMU Monitor命令行
    由于之前我们使用串口占用了标准输出, 那如果我们希望使用QEMU Monitor命令行, 那么我们需要将串口输出重定向到文件, 这里我们可以使用-serial file:output.txt来将串口输出重定向到文件, 这里我们可以将command改为:
"command": "echo 'QEMU RTOSdemo started'; <Path>/build/qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -kernel ${workspaceFolder}/build/gcc/output/RTOSDemo.out -monitor stdio -serial file:output.txt -s -S"

这两种配置方式各有利弊, 我们目前阶段都还好了.

接下来就是使用QEMU Monitor来查看代码中寄存器的值了, 运行F5后,开始调试,单步调试到main.c中的prvUARTInit中:

static void prvUARTInit( void )
{
    UART0_BAUDDIV = 16;
    UART0_CTRL = 1;
}

这个时候, 先不要着急进行下一步调试, 在这里先暂停下, 查看下UART0_BAUDDIV的定义:

 * required UART registers. */
#define UART0_ADDRESS                         ( 0x40004000UL )
#define UART0_DATA                            ( *( ( ( volatile uint32_t * ) ( UART0_ADDRESS + 0UL ) ) ) )
#define UART0_STATE                           ( *( ( ( volatile uint32_t * ) ( UART0_ADDRESS + 4UL ) ) ) )
#define UART0_CTRL                            ( *( ( ( volatile uint32_t * ) ( UART0_ADDRESS + 8UL ) ) ) )
#define UART0_BAUDDIV                         ( *( ( ( volatile uint32_t * ) ( UART0_ADDRESS + 16UL ) ) ) )

通过这个宏定义, 我们可以得知UART0_BAUDDIV的地址为0x40004010, 并且数据类型为uint32_t, 那么我们可以通过QEMU Monitor来查看这个地址的值, 在QEMU Monitor中输入xp /1xw 0x40004010 得到如下输出:

(qemu) xp /1xw 0x40004010
0000000040004010: 0x00000000

这里的xp表示eXamine Physical memory, /1xw表示/fmt, 其中:

  • w 代表了查看的数据size为word, word的大小为4个字节, 32bits.
  • x 代表了查看的数据的格式为hex, 即16进制.
  • /1 代表了查看的数据的个数为1, 即查看一个word的数据.

这里我们可以看到, UART0_BAUDDIV的值为0x00000000, 这是因为我们停在了prvUARTInit中, 还没有对UART0_BAUDDIV进行赋值, 所以这里的值为0x00000000, 这里我们可以继续单步调试, 然后再次查看UART0_BAUDDIV的值.

(qemu) xp /1xw 0x40004010
0000000040004010: 0x00000010

这里我们可以看到, UART0_BAUDDIV的值为0x00000010, 这是因为我们在prvUARTInit中对UART0_BAUDDIV进行了赋值, 这里的值为0x00000010, 后续的UART0_CTRL也可以通过这种方式来查看, 大家可以自行尝试.

在这里, 再稍微延伸一点, 为什么是0x40004010呢? 经常弄单片机的小伙伴肯定理解这是UART0的基地址, 而16 则是BAUDDIVUART0中的偏移量, 这个值是通过手册查找得到的, 这里就不多赘述了.

最后,我们通过一个简单的info mtree来查看内存的布局:

(qemu) info mtree
(qemu) info mtree
address-space: cpu-memory-0
  0000000000000000-ffffffffffffffff (prio 0, i/o): armv7m-container
...
      0000000040004000-0000000040004fff (prio 0, i/o): uart
...

address-space: bitband-source
address-space: bitband-source
address-space: memory
  0000000000000000-ffffffffffffffff (prio -1, i/o): system
...
    0000000040004000-0000000040004fff (prio 0, i/o): uart
...

从内存布局中, 我们也能看到UART的地址, 这里就不多赘述了.

写在最后/更新频次说明

这次我们简单介绍了一下FreeRTOS 中的一个示例, 并由此衍生出了一些小配置及monitor中的一些简单命令, 这里只是一个简单的入门, 后续还有很多内容需要去深入了解, 也希望大家能够多多尝试, 多多探索.

后续的更新频次会保证在一周最少一篇, 尽可能的保证质量, 但是由于个人能力有限, 也希望大家能够多多包涵, 如果有什么问题, 也欢迎大家提出, 我会尽力解答.

最后, 感谢大家的阅读, 也希望大家能够多多支持, 谢谢!

下期内容暂时未定, 但是应该和我最近遇到的问题有关, 也希望大家能够多多关注, 谢谢!

参考资料

  • 35
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
如果你在编译后运行`qemu --version`命令时,无法识别`qemu`命令,可能是因为`qemu`的可执行文件没有正确地添加到系统的可执行路径中。你可以尝试以下几个步骤来解决这个问题: 1. 确认编译成功:首先,请确保你已经成功地编译了`qemu`。编译过程可能会有一些依赖项和配置选项,你需要按照官方文档或编译说明进行操作。 2. 检查可执行文件路径:确认编译后的`qemu`可执行文件所在的路径。你可以使用`ls`命令或者文件管理器来查找该文件。通常情况下,编译后的可执行文件位于`build`目录或者指定的安装目录中。 3. 添加到系统路径:将`qemu`的可执行文件所在的路径添加到系统的可执行路径中。这样系统就能够正确地找到并执行`qemu`命令。具体的步骤可能因操作系统而异,以下是一些常见操作系统的示例: - Linux/macOS:可以将`qemu`的可执行文件所在的路径添加到`PATH`环境变量中。可以通过编辑`.bashrc`或`.bash_profile`文件,并在其中添加类似于以下内容的行: ``` export PATH=/path/to/qemu:$PATH ``` 然后保存文件并重新启动终端,或者执行`source .bashrc`或`source .bash_profile`命令使环境变量生效。 - Windows:可以将`qemu`的可执行文件所在的路径添加到系统的环境变量中。可以通过以下步骤进行操作: - 在桌面上,右键点击"此电脑"(或"我的电脑"),选择"属性"。 - 在弹出的窗口中,点击"高级系统设置"。 - 在"系统属性"窗口中,点击"环境变量"按钮。 - 在"环境变量"窗口中,找到"Path"变量,并点击"编辑"。 - 在弹出的窗口中,点击"新建",然后输入`qemu`的可执行文件所在的路径。 - 点击"确定"保存修改,并关闭所有窗口。 - 重新启动命令提示符或PowerShell窗口,然后尝试运行`qemu --version`命令。 如果你按照上述步骤操作后仍然无法解决问题,请提供更多详细信息,例如你的操作系统、编译过程中的任何错误信息等,以便我能够更好地帮助你。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值