万字连载(上):如何 Bringup SoC 芯片

2022-05-09 13:42:31 浏览数 (1)

我一直认为战略上蔑视技术,战术上重视技术是很有必要的学习态度。这是一篇 Bringup SoC 芯片的指导手册,更是一篇了解整个系统流程的地图。不会深入了解每个模块的细节,但提供了整个系统的宏观描述,让你站在上帝视角俯视每个知识点,为了对读者更负责,我打算以付费的方式和大家见面,对技术细节有需求的小伙伴欢迎加我微信(rrjike)交流,保证超有所值。

系统在启动的时候,无论是 ROM 加载 Uboot(SPL Bootloader),还是 Uboot 加载 Kernel,都是把相关的镜像放到对应的内存不同地址,然后运行启动。这个过程看起来很简单,但中间涉及到多个知识点,了解各个子过程对理解启动的本质有很大的帮助作用,对 bring up 芯片更是有必要。我们先从镜像的结构开始,后面逐一介绍 ROM->SPL->UBoot->Kernel 的全过程。

ELF 文件

在介绍加载镜像之前,需要先了解下 ELF 文件的格式定义。

从图中可以看出,一个 ELF 文件是由 4 部分组成,分别是:

ELF 头部,程序头表,节,节头表。

有 3 种类型的 ELF 文件:

目标文件:被链接器读取,用来产生一个可执行文件或者共享库文件; 可执行文件:被操作系统中的加载器从硬盘上读取,载入到内存中去执行; 共享库文件:在动态链接的时候,由 ld-linux.so 来读取;

ELF 头部

描述 ELF header 的结构体:

代码语言:javascript复制
typedef struct elfhdr{
 unsigned char e_ident[EI_NIDENT]; /* ELF Identification */
 Elf32_Half e_type;  /* object file type */
 Elf32_Half e_machine; /* machine */
 Elf32_Word e_version; /* object file version */
 Elf32_Addr e_entry; /* virtual entry point */
 Elf32_Off e_phoff; /* program header table offset */
 Elf32_Off e_shoff; /* section header table offset */
 Elf32_Word e_flags; /* processor-specific flags */
 Elf32_Half e_ehsize; /* ELF header size */
 Elf32_Half e_phentsize; /* program header entry size */
 Elf32_Half e_phnum; /* number of program header entries */
 Elf32_Half e_shentsize; /* section header entry size */
 Elf32_Half e_shnum; /* number of section header entries */
 Elf32_Half e_shstrndx; /* section header table's "section
        header string table" entry offset */
} Elf32_Ehdr;

看下 ELF 头部的信息:

代码语言:javascript复制
$ readelf -h vmlinux
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           AArch64
  Version:                           0x1
  Entry point address:               0xffff200008080000
  Start of program headers:          64 (bytes into file)
  Start of section headers:          29609944 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         4
  Size of section headers:           64 (bytes)
  Number of section headers:         35
  Section header string table index: 32

这个内容与结构体 Elf32_Ehdr 中的成员变量是一一对应的。

程序头表

描述 Program header table 的结构体:

代码语言:javascript复制
typedef struct {
 Elf32_Word p_type;  /* segment type */
 Elf32_Off p_offset; /* segment offset */
 Elf32_Addr p_vaddr; /* virtual address of segment */
 Elf32_Addr p_paddr; /* physical address - ignored? */
 Elf32_Word p_filesz; /* number of bytes in file for seg. */
 Elf32_Word p_memsz; /* number of bytes in mem. for seg. */
 Elf32_Word p_flags; /* flags */
 Elf32_Word p_align; /* memory alignment */
} Elf32_Phdr;

看下程序头表的信息:

代码语言:javascript复制
$ readelf -l vmlinux

Elf file type is DYN (Shared object file)
Entry point 0xffff200008080000
There are 4 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000010000 0xffff200008080000 0xffff200008080000 0x0000000000f534c8 0x0000000000f534c8  R E    10000
  LOAD           0x0000000000f70000 0xffff200008fe0000 0xffff200008fe0000 0x00000000007dda00 0x000000000106af20  RWE    10000
  NOTE           0x000000000133a028 0xffff2000093aa028 0xffff2000093aa028 0x0000000000000024 0x0000000000000024  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000  RW     10

 Section to Segment mapping:
  Segment Sections...
   00     .head.text .text
   01     .rodata .init___kcrctab sort .init__ksymtab_strings .pci_fixup __ksymtab __ksymtab_gpl __kcrctab __kcrctab_gpl __ksymtab_strings __param __modver __ex_table .notes .init.text .exit.text .altinstructions .altinstr_replacement .init.data .data..percpu .rela.dyn .data .got.plt .init___ksymtab sort __bug_table .mmuoff.data.write .mmuoff.data.read .pecoff_edata_padding .bss
   02     .notes
   03

可以看出该 vmlinux 的入口地址是 0xffff200008080000。一共有 4个段(segment),前两个需要 load 到内存的段,它们分别是代码段数据段,属性是读、执行(RE)和读、写、执行(RWE)。

代码段包含了两个 section。数据段包含了很多 section。

  • text 段

代码段,通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定。

  • data 段

数据段,通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

  • bss 段

通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态内存分配。

  • init 段

linux定义的一种初始化过程中才会用到的段,一旦初始化完成,那么这些段所占用的内存会被释放掉,后续会继续说明。

节头表

描述 Section header table 的结构体:

代码语言:javascript复制
typedef struct {
 Elf32_Word sh_name; /* name - index into section header
        string table section */
 Elf32_Word sh_type; /* type */
 Elf32_Word sh_flags; /* flags */
 Elf32_Addr sh_addr; /* address */
 Elf32_Off sh_offset; /* file offset */
 Elf32_Word sh_size; /* section size */
 Elf32_Word sh_link; /* section header table index link */
 Elf32_Word sh_info; /* extra information */
 Elf32_Word sh_addralign; /* address alignment */
 Elf32_Word sh_entsize; /* section entry size */
} Elf32_Shdr;

看下节头表的信息:

代码语言:javascript复制
$ readelf -S vmlinux
There are 35 section headers, starting at offset 0x1c3cfd8:

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         ffff200008080000  00010000   0000000000001000  0000000000000000  AX       0     0     4096
  [ 2] .text             PROGBITS         ffff200008081000  00011000   0000000000f524c8  0000000000000008  AX       0     0     2048
  [ 3] .rodata           PROGBITS         ffff200008fe0000  00f70000         0000000000358380  0000000000000000  WA       0     0     4096
  [ 4] .init___kcrctab s PROGBITS         ffff200009338380  012c8380     0000000000000004  0000000000000000   A       0     0     1
  [ 5] .init__ksymtab_st PROGBITS         ffff200009338384  012c8384   0000000000000005  0000000000000000   A       0     0     1
  [ 6] .pci_fixup        PROGBITS         ffff200009338390  012c8390   0000000000000018  0000000000000000   A       0     0     8
  [ 7] __ksymtab         PROGBITS         ffff2000093383a8  012c83a8   00000000000155c0  0000000000000000   A       0     0     8
  [ 8] __ksymtab_gpl     PROGBITS         ffff20000934d968  012dd968   0000000000014fe0  0000000000000000   A       0     0     8
  [ 9] __kcrctab         PROGBITS         ffff200009362948  012f2948   0000000000005570  0000000000000000   A       0     0     1
  [10] __kcrctab_gpl     PROGBITS         ffff200009367eb8  012f7eb8   00000000000053f8  0000000000000000   A       0     0     1      
  [11] __ksymtab_strings PROGBITS         ffff20000936d2b0  012fd2b0   00000000000358d8  0000000000000000   A       0     0     1
  [12] __param           PROGBITS         ffff2000093a2b88  01332b88   0000000000003548  0000000000000000   A       0     0     8
  [13] __modver          PROGBITS         ffff2000093a60d0  013360d0   0000000000000f30  0000000000000000   A       0     0     8
  [14] __ex_table        PROGBITS         ffff2000093a7000  01337000   0000000000003028  0000000000000000   A       0     0     8
  [15] .notes            NOTE             ffff2000093aa028  0133a028   0000000000000024  0000000000000000   A       0     0     4
  [16] .init.text        PROGBITS         ffff2000093b0000  01340000   0000000000080af8  0000000000000000  AX       0     0     8
  [17] .exit.text        PROGBITS         ffff200009430af8  013c0af8   000000000000421c  0000000000000000  AX       0     0     4
  [18] .altinstructions  PROGBITS         ffff200009434d14  013c4d14   000000000001e3b4  0000000000000000   A       0     0     1
  [19] .altinstr_replace PROGBITS         ffff2000094530c8  013e30c8   000000000000a36c  0000000000000000  AX       0     0     4
  [20] .init.data        PROGBITS         ffff20000945e000  013ee000   000000000001f580  0000000000000000  WA       0     0     4096
  [21] .data..percpu     PROGBITS         ffff20000947e000  0140e000   0000000000014018  0000000000000000  WA       0     0     128
  [22] .rela.dyn         RELA             ffff200009492018  01422018   00000000001d9c10  0000000000000018   A       0     0     8
  [23] .data             PROGBITS         ffff200009670000  01600000   0000000000137868  0000000000000000  WA       0     0     4096
  [24] .got.plt          PROGBITS         ffff2000097a7868  01737868   0000000000000018  0000000000000008  WA       0     0     8      
  [25] .init___ksymtab s PROGBITS         ffff2000097a7880  01737880   0000000000000010  0000000000000000  WA       0     0     8     
  [26] __bug_table       PROGBITS         ffff2000097a7890  01737890   00000000000150b4  0000000000000000  WA       0     0     4
  [27] .mmuoff.data.writ PROGBITS         ffff2000097bd000  0174d000   000000000000000c  0000000000000000  WA       0     0     2048
  [28] .mmuoff.data.read PROGBITS         ffff2000097bd800  0174d800   0000000000000008  0000000000000000  WA       0     0     8
  [29] .pecoff_edata_pad PROGBITS         ffff2000097bd808  0174d808   00000000000001f8  0000000000000000  WA       0     0     1
  [30] .bss              NOBITS           ffff2000097be000  0174da00   000000000088cf20  0000000000000000  WA       0     0     4096
  [31] .comment          PROGBITS         0000000000000000  0174da00   0000000000000027  0000000000000001  MS       0     0     1
  [32] .shstrtab         STRTAB           0000000000000000  01c3ce4a   000000000000018b  0000000000000000           0     0     1
  [33] .symtab           SYMTAB           0000000000000000  0174da28   00000000002ea610  0000000000000018          34   92369   8
  [34] .strtab           STRTAB           0000000000000000  01a38038   0000000000204e12  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

最后用一幅图总结 ELF 文件 vmlinux 的分布:

镜像的生成

ELF 文件格式是 Linux 环境下的可执行文件格式,在 Linux 环境下,加载器根据 ELF 文件里的地址信息,就可以把它加载到内存指定的地址运行,但是系统启动过程中并没有 ELF 文件的执行环境,需要将 ELF 文件转换为二进制纯指令文件,即 Image。

0 人点赞