👹
Carlos's Tech Blog
  • 🧔ECUs
    • ZYNQ_Documents
      • [ZYNQ] 构建ZYNQ的BSP工程
      • [ZYNQ] 启动流程
      • [ZYNQ] Secure Boot Flow
      • [ZYNQ] Provisioning Guideline
      • [ZYNQ] Decrypting Partition by the Decrypt Agent Using PUF key
      • [ZYNQ] enabling the cryptsetup on ramdisk
      • [ZYNQ] Encrypt external files based on file system using PUF key
      • [ZYNQ] Loading an Encrypted Linux kernel at U-Boot with a KUP Key
      • [ZYNQ] cross-compile the cryptsetup on Xilinx ZYNQ aarch64 platform
      • [ZYNQ] Linux Linaro系统镜像制作SD卡启动
    • S32G_Documents
      • [S32G] Going through the s32g hard/soft platform
      • [S32G] S32g247's Secure Boot using HSE firmware
        • S32g2 HSE key config
        • How S32g verify secure boot image
        • S32g secure boot signature generation
        • How to download and build S32g Secure boot image
        • [S32G] OTA with Secure Boot
    • RT117x_Documents
      • [RT-117x]IMX RT1170 Provisioning Guideline
      • [RT-117x] Going through the MX-RT1170 hard/soft platform
      • [RT-117x] i.MX-RT1170's Secure Boot
        • [RT-117x]Signing image with the HSM (SignServer)
    • LS104x_Documents
      • [LS104x] bsp project
      • [LS104x] boot flow
      • [LS104x] secure boot
      • [LS104x] Application Note, Using the PKCS#11 in TCU platform
      • [LS104x] 使用ostree更新rootfs
      • [LS104x] ostree的移植
      • [LS104x] Starting with Yocto
      • [LS104x] 使用FIT的kernel格式和initramfs
    • IMX6/8_Documents
      • [IMX6] Defining A U-Boot Command
      • NXP IMX6 嵌入式板子一些笔记
      • NXP-imx6 initialization
    • Vehicle_Apps
      • [SecOC] Tree
        • [SecOC] SecOC Freshness and MAC Truncation
  • 😾TECH
    • Rust Arm OS
      • ARMv7m_Using_The_RUST_Cross_Compiler
    • ARM
      • ARM-v7-M
        • 01_ARMv7-M_处理器架构技术综述
        • 02_ARMv7-M_编程模型与模式
        • 03_ARMv7-M_存储系统结构
        • 04_ARMv7-M_异常处理及中断处理
      • ARM-v8-A
        • 02_ARMv8_基本概念
        • 03_ARMv8_指令集介绍_加载指令集和存储指令集
        • 04_ARMv8_指令集_运算指令集
        • 05_ARMv8_指令集_跳转_比较与返回指令
        • 06_ARMv8_指令集_一些重要的指令
        • 0X_ARMv8_指令集_基于汇编的UART驱动
        • 07_ARMv8_汇编器Using as
        • 08_ARMv8_链接器和链接脚本
        • 09_ARMv8_内嵌汇编(内联汇编)Inline assembly
        • 10_ARMv8_异常处理(一) - 入口与返回、栈选择、异常向量表
        • 11_ARMv8_异常处理(二)- Legacy 中断处理
        • 12_ARMv8_异常处理(三)- GICv1/v2中断处理
        • 13_ARMv8_内存管理(一)-内存管理要素
        • 14_ARMv8_内存管理(二)-ARM的MMU设计
        • 15_ARMv8_内存管理(三)-MMU恒等映射及Linux实现
        • 16_ARMv8_高速缓存(一)cache要素
        • 17_ARMv8_高速缓存(二)ARM cache设计
        • 18_ARMv8_高速缓存(三)多核与一致性要素
        • 19_ARMv8_TLB管理(Translation Lookaside buffer)
        • 20_ARMv8_barrier(一)流水线和一致性模型
        • 21_ARMv8_barrier(二)内存屏障案例
      • ARM Boot Flow
        • 01_Embedded_ARMv7/v8 non-secure Boot Flow
        • 02_Embedded_ARMv8 ATF Secure Boot Flow (BL1/BL2/BL31)
        • 03_Embedded_ARMv8 BL33 Uboot Booting Flow
      • ARM Compiler
        • Compiler optimization and the volatile keyword
      • ARM Development
        • 在MACBOOK上搭建ARMv8架构的ARM开发环境
        • Starting with JLink debugger or QEMU
    • Linux
      • Kernel
        • 0x01_LinuxKernel_内核的启动(一)之启动前准备
        • 0x02_LinuxKernel_内核的启动(二)SMP多核处理器启动过程分析
        • 0x21_LinuxKernel_内核活动(一)之系统调用
        • 0x22_LinuxKernel_内核活动(二)中断体系结构(中断上文)
        • 0x23_LinuxKernel_内核活动(三)中断体系结构(中断下文)
        • 0x24_LinuxKernel_进程(一)进程的管理(生命周期、进程表示)
        • 0x25_LinuxKernel_进程(二)进程的调度器的实现
        • 0x26_LinuxKernel_设备驱动(一)综述与文件系统关联
        • 0x27_LinuxKernel_设备驱动(二)字符设备操作
        • 0x28_LinuxKernel_设备驱动(三)块设备操作
        • 0x29_LinuxKernel_设备驱动(四)资源与总线系统
        • 0x30_LinuxKernel_设备驱动(五)模块
        • 0x31_LinuxKernel_内存管理(一)物理页面、伙伴系统和slab分配器
        • 0x32_LinuxKernel_内存管理(二)虚拟内存管理、缺页与调试工具
        • 0x33_LinuxKernel_同步管理_原子操作_内存屏障_锁机制等
        • 01_LinuxDebug_调试理论和基础综述
      • Userspace
        • Linux-用户空间-多线程与同步
        • Linux进程之间的通信-管道(上)
        • Linux进程之间的通信-管道(下)
        • Linux进程之间的通信-信号量(System V)
        • Linux进程之间的通信-内存共享(System V)
        • Linux进程之间的通信-消息队列(System V)
        • Linux应用调试(一)方法、技巧和工具 - 综述
        • Linux应用调试(二)工具之coredump
        • Linux应用调试(三)工具之Valgrind
        • Linux机制之内存池
        • Linux机制之对象管理和引用计数(kobject/ktype/kset)
        • Linux机制copy_{to, from}_user
        • Linux设备树 - DTS语法、节点、设备树解析等
        • Linux System : Managing Linux Services - inittab & init.d
        • Linux System : Managing Linux Services - initramfs
      • Kernel Examples
        • Linux Driver - GPIO键盘驱动开发记录_OMAPL138
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl
        • 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(二)之cdev与read、write
        • 基于OMAPL138的字符驱动_GPIO驱动AD9833(三)之中断申请IRQ
        • Linux内核调用SPI驱动_实现OLED显示功能
        • Linux内核调用I2C驱动_驱动嵌套驱动方法MPU6050
    • OPTEE
      • 01_OPTEE-OS_基础之(一)功能综述、简要介绍
      • 02_OPTEE-OS_基础之(二)TrustZone和ATF功能综述、简要介绍
      • 03_OPTEE-OS_系统集成之(一)编译、实例、在QEMU上执行
      • 05_OPTEE-OS_系统集成之(三)ATF启动过程
      • 06_OPTEE-OS_系统集成之(四)OPTEE镜像启动过程
      • 07_OPTEE-OS_系统集成之(五)REE侧上层软件
      • 08_OPTEE-OS_系统集成之(六)TEE的驱动
      • 09_OPTEE-OS_内核之(一)ARM核安全态和非安全态的切换
      • 10_OPTEE-OS_内核之(二)对安全监控模式的调用的处理
      • 11_OPTEE-OS_内核之(三)中断与异常的处理
      • 12_OPTEE-OS_内核之(四)对TA请求的处理
      • 13_OPTEE-OS_内核之(五)内存和cache管理
      • 14_OPTEE-OS_内核之(六)线程管理与并发
      • 15_OPTEE-OS_内核之(七)系统调用及IPC机制
      • 16_OPTEE-OS_应用之(一)TA镜像的签名和加载
      • 17_OPTEE-OS_应用之(二)密码学算法和安全存储
      • 18_OPTEE-OS_应用之(三)可信应用的开发
      • 19_OPTEE-OS_应用之(四)安全驱动开发
      • 20_OPTEE-OS_应用之(五)终端密钥在线下发系统
    • Binary
      • 01_ELF文件_目标文件格式
      • 02_ELF文件结构_浅析内部文件结构
      • 03_ELF文件_静态链接
      • 04_ELF文件_加载进程虚拟地址空间
      • 05_ELF文件_动态链接
      • 06_Linux的动态共享库
      • 07_ELF文件_堆和栈调用惯例以ARMv8为例
      • 08_ELF文件_运行库(入口、库、多线程)
      • 09_ELF文件_基于ARMv7的Linux系统调用原理
      • 10_ELF文件_ARM的镜像文件(.bin/.hex/.s19)
    • Build
      • 01_Script_makefile_summary
    • Rust
      • 02_SYS_RUST_文件IO
    • Security
      • Crypto
        • 1.0_Security_计算机安全概述及安全需求
        • 2.0_Security_随机数(伪随机数)
        • 3.0_Security_对称密钥算法加解密
        • 3.1_Security_对称密钥算法之AES
        • 3.2_Security_对称密钥算法之MAC(CMAC/HMAC)
        • 3.3_Security_对称密钥算法之AEAD
        • 8.0_Security_pkcs7(CMS)_embedded
        • 9.0_Security_pkcs11(HSM)_embedded
      • Tools
        • Openssl EVP to implement RSA and SM2 en/dec sign/verify
        • 基于Mac Silicon M1 的OpenSSL 编译
        • How to compile mbedtls library on Linux/Mac/Windows
    • Embedded
      • eMMC启动介质
  • 😃Design
    • Secure Boot
      • JY Secure Boot Desgin
    • FOTA
      • [FOTA] Module of ECUs' FOTA unit design
        • [FOTA] Tech key point: OSTree Deployment
        • [FOTA] Tech key point: repositories role for onboard
        • [FOTA] Tech key point: metadata management
        • [FOTA] Tech key point: ECU verifying and Decrpting
        • [FOTA] Tech key point: time server
      • [FOTA] Local-OTA for Embedded Linux System
    • Provisioning
      • [X-Shield] Module of the Embedded Boards initialization
    • Report
由 GitBook 提供支持
在本页
  • 01_LinuxDebug_调试理论和基础综述
  • 1. ARM64的实验平台
  • 1.1 实验平台
  • REF
  1. TECH
  2. Linux
  3. Kernel

01_LinuxDebug_调试理论和基础综述

01_LinuxDebug_调试理论和基础综述

我们在Linux应用层空间引入了几个调试工具:

  • Linux应用调试(一)方法、技巧和工具 - 综述

  • Linux应用调试(二)工具之coredump

  • Linux应用调试(三)工具之Valgrind

本节主要想论述Linux Kernel等级的调试理论和基础,本文包括:

  • 对于Linux内核环境调试的搭建(以ARMv8为主,ARMv7为辅)

  • 调试相关基础知识说明

  • 不断的追加一些调试方法论

1. ARM64的实验平台

关于交叉表编译环境和一些平台的要求,可以参考:在MACBOOK上搭建ARMv8架构的ARM开发环境 。 MAC系统只能编译一些baremental相关的applications,并不能编译内核和linux的应用。对应对于QEMU环境参考:Starting with JLink debugger or QEMU

1.1 实验平台

1.1.1 初始化板级环境

我们的实验平台如图,选择树莓派的4B,是以BCM2711(ARMv8 Cortex-A72)为主的实验平台。

树莓派默认的启动对于一个完整的ARMv8的环境,应该包含:

  • AFT👇🏻

  • uboot👇🏻

  • Kernel

但由于树莓派本身的bootrom限制,只能按照以下的方式启动:

对于device的实验系统按照以下步骤进行:

  • 使用树莓派标准的镜像写入树莓派的firmware(bootloader, kernel8.img, rootfs)

  • 自定义AFT:https://github.com/carloscn/raspi-aft

  • 自定义uboot:https://github.com/carloscn/raspi-uboot

  • 自定义kernel:https://github.com/carloscn/raspi-linux

我已经在每一个仓库的readme里面指引如何编译和配置了,这里不再赘述了。最终到这里完成ATF->uboot->kernel的完整流程。

1.1.2 host调试环境

这里分为两个环境,一个是baremental环境,一个是kernel环境。两个环节比较类似,但是还有不同,因为baremental环境工具比较少,不像是linux kernel可以使用大量的工具。但是底层逻辑都是一样。

调试ARM板子需要和ARM板子建立联系的方式,对于baremental和linux内核,都需要辅助的硬件工具,比如DS-5、JLINK仿真器;而对于Linux userspace的应用程序,由于Linux内核强大的任务支持,直接就可以使用gdbserver在device板子上就可以了。

baremental及linux内核的调试,本质也是使用仿真器开启一个gdbserver监听,host端使用gdb接入加载elf符号(kernel是vmlinux)一步步调试。

本节讲述使用jlink仿真器来调试ARMv8(穷,买不起DStream,我也只是在公司使用过)。在HOST端使用vscode作为调试界面(gdb client),Device使用JLink的openocd环境(gdb server)。

电路连接

JTAG接口
树莓派IO定义
JTAG接口
树莓派管脚号
树莓派排管脚名称

VTref

01

3.3v

TRST

15

GPIO22

TDI

37

GPIO26

TMS

13

GPIO27

TCK

22

GPIO25

RTCK

16

GPIO23

TDO

18

GPIO24

GND

39

GND

config.txt

调试还需要在config.txt上做一些手脚:

[pi4]
kernel=loop.bin

[all]
arm_64bit=1 
enable_uart=1 
uart_2ndstage=1 

enable_jtag_gpio=1
gpio=22-27=a4
init_uart_clock=48000000
init_uart_baud=115200

loop.bin位置,可以换成uboot也可以换成裸机环境。 这里面有如何启动OPENOCD进入调试环境及JLINK的配置文件:Starting with JLink debugger or QEMU

host config

调试机使用vscode,主要要配置一个launch.json文件,告诉如何连接gdb server:

{
    "version": "0.2.0",
    "configurations": [
        {
            // 自定义,名字,看起来有意义就行,用来给你选的;
            "name": "armv7-debug",
            // 调试的是 go 程序
            "type": "cppdbg",
            // attach 进程的方式
            "request": "launch", // launch
            // auto、debug、remote
            "mode": "debug",
            // 调试的程序,当运行单个文件时{workspaceFolder}可改为{file}
            "program": "${workspaceFolder}/linux/test_pid/test_pid.elf", // ${workspaceFolder}/filename or ${file} or ${workspaceFolder}
            "cwd": "${workspaceFolder}/linux/test_pid/",
            "miDebuggerPath": "/opt/cross-compile/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gdb",
            "miDebuggerServerAddress": "localhost:3333",
            "env": {
                // "REDIS_ADDR": "localhost:1270",
                // "REDIS_PWD": ""
            },
            "args": [
                "-f", "server2.json"
            ]
        },
    ]
}

里面需要改一些参数,比如elf加载地址之类的。

编译关闭优化与开启符号

如果我们使用调试版的编译数据,一定要注意:

  • 使用-g,让elf文件带符号,这样人容易读;

  • 关闭优化:

    • -O0:表示关闭所有的编译器优化(调试时候需要选的,否则调试时候光标乱跳)

    • -O1:表示基本的优化

    • -O2:表示比基本的优化进行更高层级的优化(Linux内核默认编译选项)

如果是Linux内核可以在Makefile中的O2改为O0。

这个就是调试uboot界面的程序:

可以看到每一步的变量和寄存器的值。

uboot重定位

这里不得不提一下在调试的时候需要十分注意的uboot重定位的问题。由于我们的计算机是分层存储结构的,编译的程序从host编译存储在[host硬盘上]->[设备端外存] -> [DDR] -> [Cache(SRAM)] -> [CPU],这种多级的分层的存储结构就导致每经过一次存储介质的转存,就要对程序重定位一次,否则CPU就没有办法找到这部分程序。

  • HOST硬盘:使用交叉编译器对程序进行编译

    • 汇编器生成的目标文件.o 地址仅仅是符号和符号间相对应的逻辑地址,没有任何的意义

    • 链接器链接多个目标文件生成的elf地址,此时和多个.o文件的符号建立联系,使用objdump可以看到的是链接地址。

  • 设备外存:我们使用各种方法把elf二进制文件转移到设备外存上,例如EMMC,SD卡,FLASH等等。此时设备存储在这些外存的文件系统中依旧是保持编译后的链接地址。

  • DDR阶段:大多数程序是为了提高执行效率是把外存上的二进制文件加载到DDR中的,那么DDR加载位置的地址就是加载地址。由此引出了CPU运行的时候的运行地址,运行地址就是PC指针的位置,PC到哪里运行地址就是哪里。因此运行地址和加载地址可以一致也可以不一致:

    • 如果一致:使用位置无关指令/位置有关指令都可以运行;

    • 如果不一致:只有使用位置无关指令才可以运行。

  • SRAM阶段:

    • SRAM阶段其实地址是0,如果加载程序,必须要加载到0的位置,如果此时DDR的加载地址比如是0x400000,需要保证DDR复制到SRAM的程序需要使用位置无关指令。

    • DDR数据加载到SRAM这部分工作是需要BOOTROM程序来完成的。

问题:为什么要刻意的设置加载地址、运行地址、链接地址这些不一致的地址?

因为考虑到SRAM的容量非常小,也想更高效的利用比外存速度更快的DDR。所以需要分批进行加载和处理。uboot实际上就是这样做的,这也是经常说的uboot重定位。

过程是:

  • bootrom在复位阶段把外存上的前4KB的代码加载到SRAM中,这部分可以理解为uboot的stage1;

  • bootrom初始化ddr内存,把uboot真身解析出链接地址(比如0x40000000),并把程序放置到0x400000000位置;

  • 此时CPU运行状态是,执行地址是0x00,链接地址是0x400000000。

  • bootrom执行完毕之后,使用LDR直接跳转到0x400000000的地址开始执行uboot真身。此时链接地址和运行地址完全一致。

这就是uboot重定位的过程,我们调试的时候要有意识的知道,当我们加载程序调试的时候,要十分小心链接地址和PC执行的地址。我们上面使用telnet加载uboot的u-boot.bin的时候实际上已经过了BOOTROM阶段,只是把链接地址和PC都指向了0x80000,属于重定位后的结果。

MMU陷阱

uboot重定位是一个我们需要小心的问题。而启动内核的时候还需要注意MMU的陷阱,因为内核启动过程需要启动MMU,一旦启动MMU之后,在baremental按照物理地址寻址的方式变成了按照虚拟地址寻址。对于这部分处理过程,我们必须门清。

最直观的调试感受是,在调试.stext指定的内核head.S入口的时候,mmu开启之前的代码根本无法设定断点。这个根本原因就是无论是vmlinux的linker文件,还是 system.map符号记录表,都是使用虚拟地址管理的。当我们加载内核的符号的时候,也都是虚拟地址表示,gdb也只认指定的符号地址,而在开启MMU之前的汇编代码只能使用物理地址才能访问到。因此,在开启MMU之前,我们必须推算出head.S的入口函数的物理地址是多少,然后调试的时候强行指定物理地址。

当跳转到Linux内核的时候,uboot需要把Linux内核image加载到DDR中,这个无论是通过nfs还是fatload方式实际上都是加载到kernel_addr_r的DDR上面。然后uboot跳转到内核stext函数入口地址。

内核启动的时候也会有个一个重定位的过程。这个过程在__primary_switch汇编函数中。

SYM_FUNC_START_LOCAL(__primary_switch)
#ifdef CONFIG_RANDOMIZE_BASE
	mov	x19, x0				// preserve new SCTLR_EL1 value
	mrs	x20, sctlr_el1			// preserve old SCTLR_EL1 value
#endif

	adrp	x1, init_pg_dir
	bl	__enable_mmu
#ifdef CONFIG_RELOCATABLE
#ifdef CONFIG_RELR
	mov	x24, #0				// no RELR displacement yet
#endif
	bl	__relocate_kernel
#ifdef CONFIG_RANDOMIZE_BASE
	ldr	x8, =__primary_switched
	adrp	x0, __PHYS_OFFSET
	blr	x8

	/*
	 * If we return here, we have a KASLR displacement in x23 which we need
	 * to take into account by discarding the current kernel mapping and
	 * creating a new one.
	 */
	pre_disable_mmu_workaround
	msr	sctlr_el1, x20			// disable the MMU
	isb
	bl	__create_page_tables		// recreate kernel mapping

	tlbi	vmalle1				// Remove any stale TLB entries
	dsb	nsh
	isb

	set_sctlr_el1	x19			// re-enable the MMU

	bl	__relocate_kernel
#endif
#endif
	ldr	x8, =__primary_switched
	adrp	x0, __PHYS_OFFSET
	br	x8
SYM_FUNC_END(__primary_switch)

这个函数就是开启mmu之后对内核进行重定位的函数。通过br指令跳转到这个函数中。

我们可以通过一些小手段来推导出vmlinux重定位之前的物理地址,然后在gdb启动调试的时候强制指定stext链接地址为物理地址,调试到primary_switch的时候,linux内核会自动重定位到虚拟内存地址。

找到链接文件:vmlinux.lds.S

可知readelf读出的内核映像.head.text段的起始虚拟地址是0xffff ffc0 0800 0000,可以推出KIMAGE_VADDR的虚拟地址是0xffff ffc0 0000 0000,所以head.text的偏移量是0x0800 0000,加上DDR内存的其实地址0x4000 0000得到这个的物理地址为0x4800 0000;

相应地我们也可以推出其他的ADDR:

  • .head.text :0x4800 0000

  • .text : 0x4810 0000

  • .rodata:....

  • .init.text :....

要注意一下,入口函数primary_entry实际被映射到__INIT位置

在init.h中找到__INIT字段被映射到了init.text段,我们从init.text段就可以确定入口函数的地址。

我们在使用GDB的时候,可以用GDB的add-symbol-file的功能加载和读取vmlinux的符号表:

(gdb) add-symbol-file vmlinux 0x48100000 -s .head.text 0x48100000 -s .text ..... 把PC强制移动到入口函数地址,也就是.init.text段的位置(假设.init.text的地址是0x41a6000000):

(gdb) set $pc=0x41a6000000

接下来就可以调试入口函数的汇编代码了。

REF

  1. ARM Trusted Firmware on Raspberry Pi 4

上一页0x33_LinuxKernel_同步管理_原子操作_内存屏障_锁机制等下一页Userspace

最后更新于1年前

所有的符号地址都是虚拟地址
primary_switched
😾