萬字連載(上):如何 Bringup SoC 晶片

佈道師peter 發佈 2022-05-09T17:31:09.403786+00:00

我一直認為戰略上蔑視技術,戰術上重視技術是很有必要的學習態度。這是一篇 Bringup SoC 晶片的指導手冊,更是一篇了解整個系統流程的地圖。

我一直認為戰略上蔑視技術,戰術上重視技術是很有必要的學習態度。這是一篇 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 的結構體:

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 頭部的信息:

$ 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 的結構體:

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;

看下程序頭表的信息:

$ 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 的結構體:

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;

看下節頭表的信息:

$ 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] 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。

關鍵字: