查看原文
其他

代码导读之如何使用qemu来单步调试head.S

笨叔 奔跑吧Linux社区 2019-04-24

我们在《奔跑吧linux内核》第六章里介绍了如何使用Qemu+GBD来单步调试内核,我们通常是在start_kernel函数里设置一个断点来演示的,那有的小伙伴会问Qemu如何单步调试启动汇编代码head.S呢?


这是一个非常好的问题。我们以一个实验来验证奇迹吧。


我们在head.S里面从stext开始在通往start_kernel的路途上设置几个断点,分别在stext,__lookup_processor_type, __create_page_tables, __enable_mmu, __turn_mmu_on, __mmap_switched, start_kernel这个几个函数里设置断点。我们来看看它究竟可以停留在那个断点上?


下面是见证奇迹的时候:


我们惊奇的发现,它停在了__mmap_switched这个断点上,我们从System.map中查看stext和__mmap_switched发现它们都是在0xc0000000地址空间中。另外我们知道__mmap_switched这个地方是MMU已经开启了。

为什么只停留在这里?为什么不能停留在stext断点呢?

真是百思不得其姐!



刨根问底



要弄明白上面的疑问,我们首先要知道下面几个重要概念:


  1. 加载地址:指代码存储所在的物理地址。比如ARM处理器上电复位启动是从0x0地址开始取第一条指令的,所以通常这个地方存放了代码的最开始的部分,比如异常向量表的处理。

  2. 运行地址:是指程序运行时的地址。

  3. 链接地址:指在编译链接时指定的地址,是编程人员设想的将来程序要运行的地址。程序中所有标号的地址在链接后便确定了,不管程序在哪运行,都不会改变。使用arm-linux-objdump反汇编查看的就是链接地址。


因此链接地址和运行地址可以相同,也可以不同。


那什么时候运行地址和连接地址是不相同,什么时候是相同的呢?


我们以一块ARM板子为例,芯片内部有SRAM,地址是0x0,DDR的地址是0x60000000。


芯片复位上电之后,从SRAM中取指令,通常代码是存储在Nor Flash或者Nand Flash中,芯片内部的BOOT ROM会把最开始的一小部分代码装载到SRAM中运行,所以像uboot这里程序开始就会从SRAM中开始运行。由于uboot的镜像太大了,不可能在SRAM中放得下,所以必须要放在 DDR 中运行。所以通常Uboot的编译的时候链接地址都设置到DDR中,也就是0x60000000里。那这时候运行地址和链接地址就不一样了。运行地址是在0x0,链接地址变成了0x60000000,那程序为什么还能运行呢?


这里面就涉及到汇编编程的一个重要问题,就是位置无关和位置有关的汇编。


位置无关代码(PIC):从字面意思看就是该指令的执行是与内存地址无关的;无论运行地址和链接地址相等或者不相等,此种指令都能正常运行。在汇编语言中,像bl、b、mov指令属于位置无关指令,不管程序装载在哪个位置上,bl、b、adr指令都能正确的运行,其原因是bl、b、adr指令的地址域是基于PC值的相对偏移寻址,相当于[pc+offset]。


位置有关的汇编:从字面意思看就是该指令的执行是与内存地址有关的,和当前PC的值无关。ARM汇编里面通过绝对跳转修改PC的值为当前链接地址的值:

         ldr pc, =on_sdram                   @ 跳到SDRAM中继续执行


所以当通过ldr跳转到链接地址处执行时,这时候运行地址就等于链接地址了。这个过程叫做 “重定位”。在重定位之前,程序只能执行和位置无关的一些汇编代码。


可能还有小伙伴依然懵圈,为啥要刻意设置加载地址,运行地址以及链接地址不一样呢?


如果所有代码都在ROM(可以是Nor flash)中执行,则链接地址可以设为加载地址(0x0);而在实际项目应用中,往往想要把程序加载到DDR中快速运行,但是碍于加载地址的影响,不可能直接达到这一步,所以思路就是:让程序加载地址等于rom起始地址,而链接地址等于DDR中某一处的起始地址(暂且称为ram_start)。所以程序先从ROM中启动,然后让代码中最前面内容实现代码的复制(把ROM总的代码拷贝到DDR中),并通过ldr指令来实现跳转到DDR中,也就是链接地址里运行。(b指令当然就没法用来跳转了)


如果用一张图来描述上述过程,非它莫属:



怎么调试呢?



我们在回过头来看刚才的疑问。

我们首先通过查看System.map文件可以看到stext的链接地址是0xc0008000,这是一个虚拟地址,对吧。其实System.map看到的所有的符号表都是虚拟地址的。

这一点我们从内核的链接文件arch/arm/kernel/vmlinux.lds.S中可以得到验证:

PAGE_OFFSET=0xc0000000, TEXT_OFFSET=0x8000


我们知道这段代码其实装载的地址是在DDR中,也就是0x60008000地址里的。如图所示:



那究竟怎么去调试head.s中没有启动mmu部分的汇编代码呢?我们在gdb中设置类似“b stext”的断点,其实我们是设置到了stext断点的链接地址上的,但是其实程序在那个时刻是跑在DDR上的也就是0x60008000上,那怎么解决这个问题呢?


想知道怎么解决这个问题,请关注笨叔第一季旗舰篇高清视频,笨叔叔手把手教你!

淘宝店地址:https://shop115683645.taobao.com/

请咨询淘宝客服沈叔叔索取200元优惠券,先到先得!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存