gdb 调试 qemu virt 板 rv64 linux Image boot 过程

linux-5.17
echo "CONFIG_DEBUG_INFO=y" >> ./arch/riscv/configs/defconfig
make ARCH=riscv CROSS_COMPILE=${CROSS_COMPILE} defconfig
qemu-system-riscv64 -M virt -m 512M -kernel arch/riscv/boot/Image -nographic -S -s

riscv64-unknown-linux-gnu-gdb -x gdb_init -tui

set logging file log_gdb.txt
set logging on
set architecture riscv:rv64
target remote localhost:1234
启动过程中第一段 为 opensbi , 所以我们想单步调试,肯定少不了 opensbi
但是我不想调试opensbi
但是我又不知道 Image 被加载到了哪里.所以采取这种方式
改代码来确定 Linux 的加载地址
在 arch/riscv/kernel/head.S 40 行之后加入如下代码
 47     loop_test:                                                                 
 48     mv x1,x1                                                                   
 49     mv x2,x2                                                                   
 50     j loop_test 
通过c ,ctrl-c 来 确定 pc 的范围, 最小的地址就是 arch/riscv/kernel/head.S __HEAD _start 的地址
通过实践,有三个pc , 0x80200000 , 0x80200002 0x80200004
然后计算符号偏差,然后加载符号,然后修改pc

计算偏差
ffffffff80000000 => 0x80200000 


$ riscv64-unknown-linux-gnu-readelf  -S vmlinux 
There are 39 section headers, starting at offset 0x9f3f900:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .head.text        PROGBITS         ffffffff80000000  00200000
       0000000000001e86  0000000000000000  AX       0     0     4096
  [ 2] .text             PROGBITS         ffffffff80002000  00202000
       00000000005fd46c  0000000000000000  AX       0     0     4
  [ 3] .init.text        PROGBITS         ffffffff80600000  00800000
       0000000000031eaa  0000000000000000  AX       0     0     2097152
  [ 4] .exit.text        PROGBITS         ffffffff80631eb0  00831eb0
       0000000000001926  0000000000000000  AX       0     0     2
  [ 5] .init.data        PROGBITS         ffffffff80800000  00834000
       0000000000012c58  0000000000000000  WA       0     0     4096
  [ 6] .data..percpu     PROGBITS         ffffffff80813000  00847000
       0000000000007868  0000000000000000  WA       0     0     64
  [ 7] .alternative      PROGBITS         ffffffff8081a868  0084e868
       0000000000000240  0000000000000000   A       0     0     1
  [ 8] .rodata           PROGBITS         ffffffff80a00000  0084f000
       0000000000183278  0000000000000000  WA       0     0     64

add-symbol-file vmlinux -s .text 0x80202000   -s .head.text 0x80200000   -s .rodata 0x80c00000   -s .init.text 0x80800000  -s .init.data 0x80a00000


print $pc=0x80200006


单步到 call relocate , relocate 里面 最后一部分代码 就 出错了,注意: 不是失去符号信息


但是 我用 add-symbol-file vmlinux 最早可以断在 call relocate 的下一句
call setup_trap_vector , 也可以断在 start_kernel
rv64-linux boot 过程,如果要用gdb调试,需要换多少次符号信息?
1. Image 过程中
	1. gdb attach 上去 , 需要加载一次 symbol
		// 加载命令 : add-symbol-file vmlinux -s .text 0x80202000   -s .head.text 0x80200000   -s .rodata 0x80c00000   -s .init.text 0x80800000  -s .init.data 0x80a00000
		// 更新时间 : target remote localhost:1234 
		// 更新后第一个符号 : arch/riscv/kernel/head.S 中的 __HEAD
		// 注意 : opensbi 的执行pc不会经过 0x80200000 , 所以halt on reset 后 就可以 b *0x80200000
		// 注意 : 然后 c, 则会执行完 opensbi, 断在 arch/riscv/kernel/head.S 中的 __HEAD
	2. 由于开mmu , 需要更新一次
		// 更新命令 : symbol 和 add-symbol-file vmlinux
		// 更新时间 : arch/riscv/kernel/head.S relocate 的 标号1
		// 更新后第一个符号 : arch/riscv/kernel/head.S 中的 338     call setup_trap_vector
		// 其他 : 所以如果你直接加载 vmlinux 的符号,第一个可以停止的位置 : 338     call setup_trap_vector
		// 目前gdb有些问题,导致 vmlinux 符号 不能停在 "arch/riscv/kernel/head.S relocate 的 标号1" , 最早只能停在 
		// 2022-5-30 16:16:42 "call relocate" 的 下一句 "call setup_trap_vector"

setup_vm 和 relocate 的运行过程
https://crab2313.github.io/post/riscv-build-and-boot/
https://twd2.me/archives/15568
relocate
	函数的参数
		个数:1个
		含义:early_pg_dir的物理地址
		值  :0x80a06000 
	1.根据_start的虚拟地址(通过kernel_map算出)和物理地址(通过_start),算出 ra寄存器中的物理地址 对应的虚拟地址,写入ra
	2.计算 标号1的虚拟地址(0xffffffff 80001044),写入 CSR_TVEC
	3.计算 early_pg_dir&48bit 对应的 SATP寄存器值,先放入a2寄存器
	4.计算 trampoline_pg_dir&48bit 对应的 SATP寄存器值,写入CSR_SATP寄存器
	// trampoline_pg_dir 做了 kernel_map.virt_addr->kernel_map.phys_addr 的映射,大小为2M
	5.标号1的物理地址是0x80201044,标号1的虚拟地址是CSR_TVEC的值
	6.以该物理地址来取指"标号1的指令",异常
	7.PC跳转到"CSR_TVEC的值",以标号1的虚拟地址来取指"标号1的指令",正常,往下执行
	//此时已经在利用trampoline_pg_dir 的映射,用虚拟地址来取指
	//注意:trampoline_pg_dir 不是恒等映射,所以开MMU后,用异常来做了个过渡.(从物理地址->虚拟地址)
	//如果 trampoline_pg_dir 是恒等映射,开MMU后,不需要任何过渡.(从物理地址->虚拟地址),因为物理地址=虚拟地址
	
	// 8之前,切换已经完成,此时用的都是虚拟地址,用的映射关系为trampoline_pg_dir&48bit
	8..Lsecondary_park 地址(虚拟地址)写入 CSR_TVEC
	9.将 early_pg_dir&48bit 写入 CSR_SATP
		//因为 early_pg_dir&48bit的映射关系 包括 "trampoline_pg_dir&48bit的映射关系"
		//所以不需要什么显示的切换过程.
		
	// 此时已经切换到 early_pg_dir&48bit,此时用的还是虚拟地址,用的映射关系是early_pg_dir&48bit
	10. ret 指令, 会返回到 ra ,之前 ra 已经是 虚拟地址的值了(参考过程1),所以会跳到 "call relocate" 下一句
	

setup_vm 做了什么
	1.计算 kernel_map 成员,memory_limit,riscv_pfn_base
	2.检测SATP_MODE_48是否支持
	3.设置 页表alloc函数 // 申请出来的为 early_pud/early_pmd
	4.固定页表early_pg_dir->fixmap_pud->fixmap_pmd 映射 FIXADDR_START -> fixmap_pte
	5.固定页表trampoline_pg_dir->trampoline_pud->trampoline_pmd 映射 kernel_map.virt_addr -> kernel_map.phys_addr
	6.非固定页表early_pg_dir->early_pud->early_pmd 映射 kernel_map.virt_addr->kernel_map.phys_addr // kernel_page
	7.固定页表early_pg_dir->early_dtb_pud->early_dtb_pmd 映射 DTB_EARLY_BASE_VA->dtb_pa // dtb
	8.重新设置 页表alloc函数

	过程1的结果
	kernel_map = {
		page_offset = 0xffffaf8000000000, 
		virt_addr = 0xffffffff80000000, 
		phys_addr = 0x80200000, 
		size = 0xf2d000, 
		va_pa_offset = 0xffffaf7f7fe00000, 
		va_kernel_pa_offset = 0xfffffffeffe00000, // 该地址
		va_kernel_xip_pa_offset = 0x0
		}

	0x90000000000810e2 => trampoline page tables => 48bit | 810e2000 => trampoline_pg_dir =>0xffffffff80ee2000
	0x9000000000080a06 => kernel 	 page tables => 48bit | 80a06000 => early_pg_dir      =>0xffffffff80806000
思考
  • trampoline_pg_dir 有没有必要存在
https://patchwork.kernel.org/project/linux-riscv/patch/20211124060521.614015-1-guoren@kernel.org/

The trampoline_pg_dir is to handle the case when RAM is
large enough such that RAM physical address range overlaps
kernel virtual address range (i.e. VA >= PAGE_OFFSET). This
is overlap of virtual address range and physical address range
can be problematic for low-level code which is trying to enable
MMU (such as the relocate() function).


trampoline_pg_dir 用于处理RAM足够大的情况,以便RAM物理地址范围与内核虚拟地址范围重叠(即VA>=页偏移量)。
虚拟地址范围和物理地址范围的重叠,对于试图启用MMU的低级代码(如relocate()函数)可能会有问题。

Here's a old kernel thread which tries to summarize this:
https://lore.kernel.org/lkml/CAAhSdy3URWHVY_GPNb2yRBuctRELRtTTWPM2OpwUVSRFAyXyiA@mail.gmail.com/

  • 临时页表应该叫什么名字
在其他架构中 , 都是叫 swapper_pg_dir .
而在riscv 临时内核页表中,swapper_pg_dir 在临时页表中存在过,后来改名了early_pg_dir
5.2.21 		trampoline_pg_dir + swapper_pg_dir
5.3-rc1 	trampoline_pg_dir + early_pg_dir
  • 启动过程中开了几次MMU
两次
	1. set_satp_mode 中检测 SATP_MODE_48 是否支持 开关了一次
	2. relocate 中 以 trampoline_pg_dir&48bit 开了一次,就没关过
	---------
	3. relocate 中 以 early_pg_dir&48bit 无缝切换了一次.
		注意:以后都是无缝切换pgd
	4. 之后还会切换到 swapper_pg_dir&48bit
调试过程中的问题
  • bug
现在用gdb + qemu 调试 relocate 代码 , 会根据 不同厂家释放的gdb ,有不同的效果
总而言之是 gdb的bug
2022-5-30 07:12:01
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值