解決辦法在最下面,可以直接跳到最後

整體 debug 日誌

Openocd 报错信息分析

在进行调试的时候打开 openocd,但是在复现之前已经在 rust-raspberry-os-tutorials 中完成过的 load 操作的时候出现报错信息。

根据简单的筛选,其中 openocd 主要的报错信息如下:

Error: 514581 53396 aarch64.c:2425 aarch64_read_cpu_memory(): abort occurred - dscr = 0x03047253
Error: 515189 53457 aarch64.c:2425 aarch64_read_cpu_memory(): abort occurred - dscr = 0x03047253
Error: 516909 53629 armv8_dpm.c:260 dpmv8_exec_opcode(): Opcode 0xd53e4020, DSCR.ERR=1, DSCR.EL=2
Error: 517039 53642 armv8_dpm.c:686 dpmv8_read_reg(): Failed to read ELR_EL3 register
Error: 517151 53653 armv8_dpm.c:260 dpmv8_exec_opcode(): Opcode 0xd53e5200, DSCR.ERR=1, DSCR.EL=2
Error: 517281 53665 armv8_dpm.c:686 dpmv8_read_reg(): Failed to read ESR_EL3 register
Error: 517393 53677 armv8_dpm.c:260 dpmv8_exec_opcode(): Opcode 0xd53e4000, DSCR.ERR=1, DSCR.EL=2
Error: 517523 53689 armv8_dpm.c:686 dpmv8_read_reg(): Failed to read SPSR_EL3 register
Error: 633354 66124 aarch64.c:2206 aarch64_write_cpu_memory(): abort occurred - dscr = 0x03047253
Error: 634038 66526 aarch64.c:2206 aarch64_write_cpu_memory(): abort occurred - dscr = 0x03047253
  1. Opcode 0xd53e4000 鉴于某个其他教程中出现了类似情况,他没有管,暂且认为这个问题不是特别重要: https://blog.lambdaconcept.com/post/2019-10/iphone-bootrom-debug/
  2. Opcode 0xd53e4020 Unknown
  3. Opcode 0xd53e5200 Unknown

Gdb 报错信息分析

  1. (gdb) load 的直接报错
  aarch64_read_cpu_memory(): abort occurred - dscr = 0x03047253
  Loading section .text, size 0xa000 lma 0xffff000000080000
  Error: abort occurred - dscr = 0x03047253   

根据ARM Architecture Reference Manual Debug supplement

以及报错信息中所呈现的 DSCR 寄存器值:

--------------------HEX-DEC-MAPPING-TABLE--------------------
31      30      29      28      27      26      25      24
0       0       0       0       0       0       (1)     (1)
-------------------------------------------------------------
23      22      21      20      19      18      17      16
0       0       0       0       0       (1)     0       0
-------------------------------------------------------------
15      14      13      12      11      10      9       8
0       (1)     (1)     (1)     0       0       (1)     0
-------------------------------------------------------------
7       6       5       4       3       2       1       0
0       (1)     0       (1)     0       0       (1)     (1)
-------------------------------------------------------------

得到如下指示:(斜体表示暂时没看懂)

  • 当前内核已经尝试进入 debug status,但是经由未知原因退出(1),目前还正处于 debug status(0)。
  • Imprecise Watchpoint Debug event occurred. (2-5)【发生不精确的监视点错误事件。】
  • a precise Data Abort exception has occurred since the last time this bit was cleared to 0.(6) 【自上次将该位清0后,发生了精确的数据中止异常。】
  • This bit has the same behavior as the No Power-down bit in the ARMv7 Device Power-down and Reset Control Register (PRCR)(9)
  • User Mode access to Comms Channel disabled. (12) If this bit is 1 and a User Mode process tries to access the DIDR, INT-DSCR, INT-DTRRX, or INT-DTRTX through CP14 operations, the Undefined Instruction exception is taken.
  • the mechanism for forcing the core to execute instructions in Debug state via the external debug interface is enabled.(13) 【启用用于经由外部调试接口迫使核心执行处于调试状态的指令的机制。】
  • Halting Debug-mode enabled. (14) 【已启用 debug 模式。】
  • the processor is not in the Secure world (SCR[0]=1 and the processor is not in Monitor Mode). (18) 【处理器不在安全世界中】
  • InstrCompl is set to 1 on entry to Debug state. (OR) cleared to 0 following issue of an instruction through ITR, and InstrCompl becomes 1 once the instruction completes (24) 【进入调试模式或者完成了一条ITR发出的指令】
  • This bit is set to 1 every time the processor pipeline retires one instruction. It is cleared to 0 by a write to DRCR[3]. The purpose is to allow the debugger to detect that the processor is idle. In some situations this might mean that the processor is deadlocked.(25) 【每当处理器流水线引退一条指令时,此位设置为1。写入DRCR[3]后,该值被清零,请参见“运行控制寄存器(DRCR)”。目的是允许调试器检测处理器是否空闲。在某些情况下,这可能意味着处理器死锁。】
  1. 是否可能由于在当前情况下未按照对应规范要求启用对应端口的 ALT4 使 JTAG 接口打开?

否,按照说明文档,在 config.txt 中设置 enable_jtag_gpio 会自动通过固件完成对应寄存器的启用操作。

Setting enable_jtag_gpio=1 selects Alt4 mode for GPIO pins 22-27, and sets up some internal SoC connections, enabling the JTAG interface for the Arm CPU. It works on all models of Raspberry Pi.

  1. 是否可能与页表有关?

否,rust-raspberry-os-tutorials 中使用的是裸机应用程序,并且设置了内存映射,但没有实现页表,在对于 app/board/raspi-usb 进行分析的时候,发现其中依赖了 axfeat/usb-host 这个 features 间接依赖了 paging。 但是奇怪的是,使用 apps/helloworld 进行调试并没有缓解这种问题。在 (gdb) load 的时候仍然出现报错信息 Loading section .text, size 0xf000 lma 0xffff000000080000, 而非类似 rust-raspberry-os-tutorials 中出现的 Loading section .text, size 0x2d2c lma 0x80000

此处所说的 lma(load memory address) 由 ELF 文件提供,根据 nm 显示的结果与 linked.ld 文件结合进行评判,发现确实符合上述文件所说的 load .text in 0xffff000000080000,该错误不属于异常报错

$ aarch64-linux-gnu-nm helloworld_aarch64-raspi4.elf | rg .text                                                                                                                                                                
ffff000000087000 T _etext                                                                                                                                                                                                                                 ││ffff000000080000 T _stext
ffff000000080000 T _stext                                                                                                                                                                                                                                 ││ffff000000080000 T _stext

那么,这个全局文本符号是如何定义的呢?

全局搜索 _stext 等符号,可以发现这些符号都出现在 module/axhal 中,在该目录下找到了 build.rs 其中对于编译行为进行了定义,其中生成了 module/axhal/linker_aarch64-raspi4.lds 文件。 在该文件中可以明显看到 BASE_ADDRESS = 0xffff000000080000; 原因或许与这个的产生有关。 但是根据 osdev wiki about raspberry Pi4,这个地址远远超过了可用地址范围,那么这个地址是如何生成出来的呢? 按图索骥发现,这个 BASE_ADDRESS 来源于 KERNEL_BASE_VADDR,这个值由 axconfig 从环境中导入,找到 target/aarch64-unknown-none-softfloat/release/build/axconfig-47609bc9af56dbab/out/config.rs 为其来源。 这个文件由 axconfig/build.rs 生成,最后找到最根源的位置 platform/aarch64-raspi4.toml 中的 kernel-base-vaddr = "0x8_0000" 设置。 针对于 apps/helloworld 这个应用程序而言,只需要简单的将这个修改成为 0x80000 就可以正常进行调试。:-( 但是就不能正常初始化程序了 我怀疑可能跟 arceos 在启动的时候打开的临时页表有关。

根据 Makefile 文件说明,我们最后通过 chainboot 传入到树莓派中的二进制文件实际上是剔除了调试信息和符号表的,在其中的各个函数被转换成为了对应的虚拟地址。 尝试通过另外一种方式 load ./helloworld_aarch64-raspi4.elf -0xffff000000080000 绕过 load 的时候的报错 load .text lma 0x ... ,但是这样会导致其他符号的地址解析不正确。

尝试过的几种方法:

  1. 怀疑是由 openocd 无法正确解析 boot_page_table 的页表,尝试通过修改 openocd 配置文件实现对应的效果, 似乎并没有直接提到 boot_page_table 有什么特殊的操作方式,目前看下来在配置文件中可能完成的操作只有设定 working area,并且根据 target configure -work-area-phys/-work-area-virt 尝试能否有效果。config file guidelines

  2. 然后尝试能否通过 objcopy 修改对应的段位置,因为看上去段位置被解析在 0xffff000000080000 或者 0x80000 决定了 gdb load 能否成功。 --change-sections-lma "+OFFSET",--change-section-address,最后没有解决不支持的情况。 不过发现,可以直接修改 module/axhal 使用物理地址作为 BASE_ADDRESS,这样修改生成的链接文件,也能临时达到修改 BASE_ADDRESS 的效果, 只不过这项操作不能与原有代码并存,需要进一步考量合理性(至少能加载并简单调试.jpg)。但是,这样操作所带来的后果就是没有办法加载 CPU 状态。

  3. 经由 gdb manual 以及 gdb 源代码分析发现,输出报错信息的 Loading section ... 是通过下面的代码实现的输出效果。

    # sim/common/sim-load.c
    xprintf (callback, "Loading section %s, size 0x%lx %s ",
    	 bfd_section_name (s),
    	 (unsigned long) size,
    	 (lma_p ? "lma" : "vma"));
    xprintf_bfd_vma (callback, lma);
    

    而 xprintf_bfd_vma 实际引用了由 bfd 库提供的 bfd_section_lma/vma 方法。 外加上 [arceos]:platform/aarch64-raspi4 中的 phys-virt-offset 设定。 可以发现,gdb 在这里将 0xffff000000080000 认定成为 lma (Load Memory Address)。

很明显,按照现在的分析,可以做出一个简单的结论,即 gdb 将 0xffff000000080000 这个明显的虚拟地址作为了程序载入地址。
同时,由于 gdb 没有启用页表功能,导致这个明显的虚拟地址被作为实质上的程序载入地址进行载入,同时又因为 Raspberry_Pi_4 这块板子的内存范围限制,导致加载出错。 

	根据 [gdb]:sim/common/sim-load.c 所言 lma_p 决定了具体由 vma 还是 lma 作为程序载入地址,
	在 sim/common/hload.c 文件中可以看到传入参数叫做 STATE_LOAD_AT_LMA_P,这来自于 [gdb]:sim/common/sim-options.c 中的 OPTION_LOAD_VMA 枚举。

```c
# bfd/bfd-in2.h
  /*  The virtual memory address of the section - where it will be
at run time.  The symbols are relocated against this.  The
user_set_vma flag is maintained by bfd; if it's not set, the
backend can assign addresses (for example, in <<a.out>>, where
the default address for <<.data>> is dependent on the specific
target and various flags).  */
bfd_vma vma;

/*  The load address of the section - where it would be in a
    rom image; really only used for writing section header
    information.  */
bfd_vma lma;
```

根据上述内容以及 user_set_vma 函数未出现在 gdb manual 等手册中,只在代码中出现,暂且认定为该设定为内部方法。
但是,尚不明确所言 specific target and various flags 具体在哪里体现,在有关文档中没有明确指出对应内容。
  1. 根据 [gdb]:/bfd/elf.c _bfd_elf_make_section_from_shdr 函数说明,这个函数实现了基于 ELF header 生成 BFD section 的功能。 (将对应的指针存储到 BFD section 中)
if (!bfd_set_section_vma (newsect, hdr->sh_addr / opb)
    || !bfd_set_section_size (newsect, hdr->sh_size)
    || !bfd_set_section_alignment (newsect, bfd_log2 (hdr->sh_a:ddralign)))
  return FALSE;

其中 bfd_set_section_vma 是其中唯一牵涉到对应值的设置的函数,他会将 sec->lma 和 sec->vma 都设置成为 hdr_sh_addr/opb 的值, 按照目前的理解来说,他会将 headers 里面设置的数据直接拷贝到其中,因此,前文所研究的可能存在的 LOAD_VMA 应该不存在,因为调用这个函数似乎是默认行为。 (至少从名字上来说)

  1. 通過 Openocd 調試的時候,發現 poll 顯示 MMU 未啟動,懷疑配置不正確導致 MMU 無法啟動,進而導致無法正確檢出虛擬地址

復現: 首先按照 [rust-raspberry-os-tutorials] 教程,在其之上打開 gdb 調試,依次輸入

  (gdb) target ext<tab> :3333
  (gdb) poll 

此時顯示 MMU 並未啟用,依次輸入

  (gdb) load
  (gdb) b kernel::kernel_main
  (gdb) cont

之後,再執行 poll 指令之後可以發現 MMU 被啟用了

  • 檢索 cfg 文件之後發現當前選擇的核心 aarch64 實際上具有MMU。 src
  • 搜索之後發現可能 Openocd 目前大概率並沒有能直接在 Openocd 上操作,能啟用 MMU 調試的指令。rejected commit message
  • 下一步考慮從 arceos 中有關於啟用 MMU 的部分逐行進行審查,爭取發現具體於 [rust-raspberry-os-tutorials] 不同的地方。

最終解決與判定思路:

判斷邏輯說明:

  1. 首先,在通過 objdump 與 gdb disas 的反匯編結果進行對比可知二者內容並不一致。
$ aarch64-linux-gnu-objdump -d apps/helloworld/helloworld_aarch64-raspi4.elf                                    || (gdb) disas 0x80000,+20 │Disassembly of section .text:                                                                                   ││End of assembler dump.                                                                      ││                                                                                                                ││>>> disas 0x80000,+60                                                                       
│0000000000080000 <_start>:                                                                                      ││Dump of assembler code from 0x80000 to 0x8003c:
│   80000:       d53800b3        mrs     x19, mpidr_el1                                                          ││   0x0000000000080000 <_start+0>:       mrs     x1, mpidr_el1
│   80004:       92405e73        and     x19, x19, #0xffffff                                                     ││   0x0000000000080004 <_start+4>:       and     x1, x1, #0x3
│   80008:       aa0003f4        mov     x20, x0                                                                 ││   0x0000000000080008 <_start+8>:       ldr     x2, 0x80068 <_stext+104>
│   8000c:       d0000048        adrp    x8, 8a000 <_ZN5axhal8platform14aarch64_common4boot10BOOT_STACK17hf02bfc3││   0x000000000008000c <_start+12>:      cmp     x1, x2
│e3efd5628E>                                                                                                     ││   0x0000000000080010 <_start+16>:      b.ne    0x8005c <_stext+92>  // b.any
│   80010:       91410108        add     x8, x8, #0x40, lsl #12                                                  ││   0x0000000000080014 <_start+20>:      nop
│   80014:       9100011f        mov     sp, x8                                                                  ││   0x0000000000080018 <_start+24>:      adr     x0, 0x824a0 <_ZN53_$LT$core..fmt..Error$u20$
│   80018:       940005d7        bl      81774 <_ZN5axhal8platform14aarch64_common4boot13switch_to_el117h1141147f││as$u20$core..fmt..Debug$GT$3fmt17hb2b0fd692ce73eb8E+8>
│e991f5beE>                                                                                                      ││   0x000000000008001c <_start+28>:      nop                                                 
│   8001c:       940005f6        bl      817f4 <_ZN5axhal8platform14aarch64_common4boot20init_boot_page_table17h7││   0x0000000000080020 <_start+32>:      adr     x1, 0x82530 <_ZN4core3fmt5Write10write_char1││d6a84480395ee4bE>                                                                                               
│7h688c1f7ddb451b19E+124>                                                                    
│   80020:       94000609        bl      81844 <_ZN5axhal8platform14aarch64_common4boot8init_mmu17h97db47fd2ded1b││   0x0000000000080024 <_start+36>:      cmp     x0, x1
│bcE>                                                                                                            ││   0x0000000000080028 <_start+40>:      b.eq    0x80034 <_start+52>  // b.none
│   80024:       9400061d        bl      81898 <_ZN5axhal8platform14aarch64_common4boot9enable_fp17h67c7a58399482││   0x000000000008002c <_start+44>:      stp     xzr, xzr, [x0], #16
│2ebE>                                                                                                           ││   0x0000000000080030 <_start+48>:      b       0x80024 <_start+36>
│   80028:       d2ffffe8        mov     x8, #0xffff000000000000      // #-281474976710656                       ││   0x0000000000080034 <_start+52>:      nop
│   8002c:       8b2863ff        add     sp, sp, x8                                                              ││   0x0000000000080038 <_start+56>:      adr     x0, 0x80000 <_start>
│   80030:       aa1303e0        mov     x0, x19                                                                 ││End of assembler dump.                                                                      ││                                                                                              304,1          6% ││>>>
  1. 然後,我初步懷疑可能是在進行鏈接的時候出現了問題, 因為印象中記得之前有手動操作過(cat kernel8.img helloworld_aarch64-raspi4.bin > kernel8.img) 還以為是因為這個導致 gdb 所識別的程序頭是 chainboot loader 的程序頭,因此程序無法如同預期運行。 但是,根據挨個構建複查,發現實際上我們並沒有在 makefile 腳本中進行這個操作, minipush 非常幹淨,就只起到了發送數據和打開串口顯示的效果。

  2. 具體的問題出在 JTAG_BOOT_IMAGE 的不正確設置 最初始的版本中將 X1_JTAG_BOOT 認為是某個幫助腳本的內容, 並且在後續調用中一直將其鏡像作為 make jtagboot 的 minipush 傳入文件。 即我們在板子上跑的是 rust-raspberry-os-tutorials 的 cpu::wait_forever 腳本,但是 gdb 上對應的是 helloworld_aarch64-raspi4.elf 文件。

解決方法:

  1. 首先撤回前文所提到的對於 load failure 的可能性改善方案(KERNEL_BASE_VADDR->KERNEL_BASE_PADDR) 同時修改 script/make/raspi4.mk 中 JTAG_BOOT_IMAGE := $(OUT_BIN) (其實無所謂了,後面都不用這個了)
  2. 由於我們在進行 JTAG 調試的時候,需要預先將 CPU 設定成為 halt 模式,以方便我們後續打開 openocd 和 gdb 進行調試, 預先打開 openocd 和 gdb,使用 monitor 或者 telnet 的方式 halt 內核再加載內核的方式不可行(該說不說,確實似乎理論上可行)
  3. 因此,基於最原始的 rust-raspberry-os-tutorials 源代碼 06_uart_chainloader 文件夾,做出如下修改:(需要在這裡手動 halt 等待 openocd 後續介入)
diff --git a/06_uart_chainloader/src/main.rs b/06_uart_chainloader/src/main.rs
index dd82ec3f..b20c9e03 100644
--- a/06_uart_chainloader/src/main.rs
+++ b/06_uart_chainloader/src/main.rs
@@ -188,9 +188,19 @@ fn kernel_main() -> ! {
   println!("[ML] Loaded! Executing the payload now\n");
   console().flush();

-    // Use black magic to create a function pointer.
-    let kernel: fn() -> ! = unsafe { core::mem::transmute(kernel_addr) };
-
-    // Jump to loaded kernel!
-    kernel()
+    println!("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+    println!("@ You're using a JTAG debug mirror. @");
+    println!("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+    println!("@ 1. open openocd, gdb              @");
+    println!("@ 2. target extended-remote :3333;  @");
+    println!("@ 3. set $pc=0x80000                @");
+    println!("@ 4. break rust_entry/others        @");
+    println!("@ 5. break $previous_addr           @");
+    println!("@ 6. delete 1                       @");
+    println!("@ 7. load                           @");
+    println!("@ 8. continue                       @");
+    println!("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+
+    // wait for gdb connect
+    cpu::wait_forever()
}
  1. 通過 BSP=rpi4 make 生成 kernel8.img 文件,將文件傳入 SD 卡。 A=apps/helloworld PLATFORM=aarch64-raspi4為默認抬頭,不再進行說明
  2. 使用最簡單的 apps/helloworld 進行測試,發現運行 chainboot 和 jtagboot 都可以得到相同的結果,即 Loaded 之後無反應了即裝載正確
  3. make openocd, make gdb
  4. gdb shell 按照下面說明進行操作:
(gdb) target extended-remote :3333
(gdb) set $pc=0x80000               // 從死循環中逃逸出來
(gdb) b rust_entry                  // 如果只是想單步調試 app 可以不用繼續了
(gdb) mon poll                      // 此時 MMU:disable 
(gdb) c                             // 完成 module/axhal 中的初始化步驟
(gdb) mon poll                      // 此時 MMU:enable
(gdb) load                          // 此時加載就可以成功了

调试记录

debug log 003 (info registers, info mem, load)

正常 load 的输出结果(rust-raspberry-os-tutorials 测试结果)

Loading section .text, size 0x2d2c lma 0x80000
Loading section .rodata, size 0xce0 lma 0x82d30 
Loading section .data, size 0x30 lma 0x83a10 
Start address 0x0000000000080000, load size 14908
Transfer rate: 37 KB/sec, 4969 bytes/write.