Skip to content

Bobby-Ling/hust-riscv-pke-gdb-vscode-config

Repository files navigation

在PKE OS Lab中使用GDB调试

省流

包含: .gdbinit文件、vscode调试配置、openocd多核调试配置等

仓库里缺失的.gdbinit文件内容是:

target extended-remote localhost:3333
symbol-file obj/riscv-pke
# m_start中会timerinit()
break s_start
continue
set $mie = $mie & ~0x80

官方docker镜像apt update && apt install lsof libncurses5 libjim-dev libjim0.79 libpython2.7装完依赖后就可以使用这几个命令连接至openocd调试. 但是给的gdb版本有点老, 建议更新一下

配置步骤

gdb调试pke相当于调试一个嵌入式设备, 这方面资料很多, 总体流程是这样的:

  1. 安装工具链:
    spike, openocdpke-doc那个就行, riscv64-unknown-elf-gdb建议用官方docker镜像的那个, 不然有些实验可能有些小问题(未解决).
  2. 启动spike:
    spike --rbb-port=9824 --halted obj/riscv-pke obj/app_errorline (旧版本--halted-H)
  3. 启动openocd:
    openocd -f ./.spike.cfg
  4. 启动gdb:
    • riscv64-unknown-elf-gdb启动
    • 之后在gdb命令行中target extended-remote localhost:3333连接到openocd 使用symbol-file obj/riscv-pke载入调试符号信息
    • 正常打断点调试
      b m_start
      c

坑点

  1. spike会先从0x1000开始执行几条代码(并非elf入口点), 用来提供dtb相关信息, 这几条指令并不在elf文件中, GDB也无法感知, 因此gdbload命令会从_mentry开始执行, 此时a0 a1未初始化, 调试到init_dtb()就会炸;

  2. 在启用timer后每次gdb step都会先进入中断处理, 可通过set $mie = $mie & ~0x80关闭MIE_MTIE

  3. 工具链最好不要用发行版的, 虽然写了有riscv支持, 但是有点容易出兼容性问题, 最好去riscv官方仓库那里;

  4. pke-doc那个镜像的gdb工具链有点小小兼容性问题, 有些调试信息识别不了;

    gdb版本有点老
  5. 从21年到现在spike的命令行参数(-H -> --halted)和openocd的配置文件格式有些小改, 需要稍微注意.

踩坑过程

  1. 起初按照GDB-and-OpenOCD文档的20.2节进行调试:

    GDB-and-OpenOCD 20.2

    riscv64-unknown-elf-gdb连接上openocd后:

    连接上openocd后

    并未察觉到0x1000地址的含义, 以为只是由于gdb"尚未感知"obj/riscv-pke导致的问题, 因此参考文档执行load obj/riscv-pke, 随后$pc变成了elf入口点(_mentry), 似乎一切正常.

    load obj/riscv-pke

    然后发现执行到init_dtb()内的fdt_scan()$pc突然变成0x0:

    发现执行到`init_dtb()`内的`fdt_scan()` `$pc`即将变成0x0 `$pc`突然变成0x0
  2. 想不明白, 开始排查:

    先把GDB去了, 用openocd调试看看, $pc依然是0x1000:

    $pc依然是0x1000

    打完断点, 单步之后依然复现:

    单步之后依然复现
  3. 终于注意到了0x1000, 查看spike的指令log:

    spike的指令log

    查看spike源代码发现其加电后会在0x1000进行一些dtb之类的初始化, 然后跳转至入口点_mentry:

    spike源代码

    void m_start(uintptr_t hartid, uintptr_t dtb)的两个参数a0 a1

    alt text
  4. 原因分析:

    通过set debug remote 1查看传输日志得知, gdb中load操作会将本地的obj/riscv-pke文件通过openocd传输至spike内存, 其实是重复操作, 而GDB load之后会根据elf文件将$pc设置为其中的入口点, 从而跳过了0x1000那段代码的执行, a0 a1未能初始化, 导致init_dtb()炸掉.

    (gdb) help load
    Dynamically load FILE into the running program.
    FILE symbols are recorded for access from GDB.
    Usage: load [FILE] [OFFSET]
    
  5. 解决办法:

    通过上面的过程可知, openocd具备完备的调试功能(断点/单步/内存查等), gdb不过是作为一个友好的接口(连接源代码/反汇编), 因此gdb只需要处理调试符号相关内容:

    target extended-remote localhost:3333
    symbol-file obj/riscv-pke
    b m_start
    c
  6. 开启timer后时钟中断影响调试:

    开启时钟中断前next能够正常执行, 开启后会进入中断处理, 且无法继续:

    开启timer后时钟中断影响调试

    set $mie = $mie & ~0x80关闭MIE_MTIE中断使能.

  7. 多核调试:

    开启多核后GDB和OpenOCD识别不到两个hart, 因为只创建了riscv.cpu:

    GDB识别不到两个hart OpenOCD识别不到两个hart

    参考smp-threads-not-showing-in-gdb添加调试target后:

    set _TARGETNAME $_CHIPNAME.cpu
    target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME -coreid 0 -rtos hwthread
    target create $_TARGETNAME.1 riscv -chain-position $_TARGETNAME -coreid 1 -rtos hwthread

    OpenOCD针对每个hwthread开了一个GDB端口(因为hwthread不一定是对称的).

    alt text

    参考文档Define CPU targets working in SMP, 应该手动开启SMP:

    target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME -coreid 0 -rtos hwthread
    target create $_TARGETNAME.1 riscv -chain-position $_TARGETNAME -coreid 1 -rtos hwthread
    target smp riscv.cpu.0 riscv.cpu.1

    可以在GDB中进行多线程调试:

    GDB中进行多线程调试

相关资源

  1. 浙江大学24年秋冬操作系统实验(关于 spike 工具链的使用)
  2. gdb target和load
  3. GDB-and-OpenOCD
  4. VSCode调试时preLaunchTask为持续后台任务
  5. smp-threads-not-showing-in-gdb
  6. Config-File-Guidelines # 6.3.4 Define CPU targets working in SMP

附: 环境信息

  • 系统: Ubuntu 22.04.1 LTS

  • GDB: riscv64-unknown-elf-gdb (官方release)

  • Spike: commit 206268c

  • OpenOCD: commit fac1412

  • 比较新的GCC(2025年)编译发现有些实验会遇到Misaligned Load!, 2021年的可以正常通过, 未仔细分析, 其实是代码有BUG.

  • Spike和OpenOCD镜像中的2021版本和最新的版本都可以, 只是要小改一下参数格式.

附: VSCode配置

配置文件: 详细配置文件

效果:

VSCode配置

  • .gdbinit:

    # riscv64-unknown-elf-gdb -ex "target ext :3333"
    monitor reset halt
    symbol-file obj/riscv-pke
    # m_start中会timerinit()
    b s_start
    c
    set $mie = $mie & ~0x80
  • .vscode/launch.json:

    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "(gdb) 调试pke",
                "type": "cppdbg",
                "request": "launch",
                "args": [],
                "stopAtEntry": true,
                // "stopAtConnect": true,
                "cwd": "${workspaceFolder}",
                "environment": [],
                "externalConsole": false,
                "MIMode": "gdb",
                "program": "${workspaceFolder}/obj/riscv-pke",
                "miDebuggerPath": "riscv64-unknown-elf-gdb",
                // "miDebuggerServerAddress": "localhost:3333",
                // "miDebuggerArgs": "obj/riscv-pke",
                // "useExtendedRemote": true,
                "setupCommands": [
                    {
                        "description": "为 gdb 启用整齐打印",
                        "text": "-enable-pretty-printing",
                        "ignoreFailures": false
                    },
                    {
                        "text": "cd ${workspaceFolder}",
                        "ignoreFailures": false
                    },
                    {
                        "description": "target ext :3333, 连接至openocd",
                        "text": "target extended-remote localhost:3333",
                        "ignoreFailures": false
                    },
                    {
                        "text": "source .gdbinit",
                        "ignoreFailures": true
                    },
                ],
                "preLaunchTask": "start_kernel.sh"
            }
        ]
    }
  • .vscode/tasks.json:

    {
      "version": "2.0.0",
      "options": {
        "cwd": "${workspaceFolder}"
      },
      "tasks": [
        {
          "type": "shell",
          "label": "start_kernel.sh",
          "command": "cd ${workspaceFolder} && ./start_kernel.sh",
          "isBackground": true,
          "args": [],
          "group": {
            "kind": "build",
            "isDefault": false
          },
          "problemMatcher": [
            {
              "pattern": {
                "regexp": "^(START_KERNEL)", //问题模式或者所提供或预定义问题模式的名称。如果已指定基准,则可以省略。
                "line": 1
                // "file": 1,
                // "location": 2,
                // "message": 3
              },
              "background": { //用于跟踪在后台任务上激活的匹配程序的开始和结束的模式。
                "activeOnStart": true, //如KERNEL_STARTED果设置为 true,则任务启动时后台监视器处于活动模式。这相当于发出与 beginsPattern 匹配的行
                "beginsPattern": "^(START_KERNEL)", //如果在输出内匹配,则会发出后台任务开始的信号
                "endsPattern": "^(START_KERNEL)" //如果在输出内匹配,则会发出后台任务结束的信号
              }
            }
          ],
          "detail": "start_kernel.sh"
        }
      ]
    }
  • start_kernel.sh

    #!/bin/bash
    
    echo "START_KERNEL"
    
    echo "start at $(pwd)"
    # make gdb-clean
    kill -9 $(lsof -i:9824 -t)
    kill -9 $(lsof -i:3333 -t)
    
    # spike -l --log=spike.log obj/riscv-pke obj/app*
    # 效果是spike先后台运行, 但是依然显示spike的输出
    spike --rbb-port=9824 --halted obj/riscv-pke obj/app* 2>&1 | tee spike.log &
    # spike --rbb-port=9824 --halted -p2 obj/riscv-pke obj/app* 2>&1 | tee spike.log &
    
    sleep 0.1s
    
    # openocd -f ./.spike.cfg -c "reset halt" 2>&1 | tee openocd.log
    openocd -f ./.spike.cfg > openocd.log 2>&1
    
    # telnet localhost 4444
    # reset
    # halt
    # bp 0x0000000080001d32 4
    # rbp 0x0000000080001d32
    # step
    # resume
    
    # riscv64-unknown-elf-gdb obj/riscv-pke -ex "target ext :3333" -ex "monitor reset halt" -ex "b m_start" -ex "c" -ex "layout src" -ex "focus cmd"
    
    echo STARTED

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors