您當前的位置:首頁 > 書法

Zircon - Fuchsia 核心分析 - 啟動(平臺初始化)

作者:由 看雪 發表于 書法時間:2019-02-25

簡介

Zircon 是 Google 新

作業系統

Fuchsia 的核心,基於 LK - Little Kernel 演變而來。而 Little Kernel 前面一直作為 Android 系統的 Bootloader 的核心而存在。Zircon 在此基礎上增加了 MMU,System Call 等功能。

Zircon 目前支援 X86/X64 和 ARM 兩種 CPU 平臺,下面我將以 ARM64 為例,一行行分析 Zircon 核心的早期啟動過程,看一下 Zircon 和 ARM64 是如何完成平臺初始化的,這部分由彙編實現。

需要事先宣告的是,本人平時從事的是 Android 開發,對於 ARM 瞭解有限,此次原始碼閱讀也會參考一些其他資料,其中難免會有一些錯誤,望廣大讀者諒解。

帶註釋的 Zircon 核心原始碼(未完成):

https://

github。com/ganyao114/zi

rcon/tree/doc

ARM64

首先需要簡單過一下涉及到的 ARM64 背景,以前雖有簡單接觸過嵌入式的 ARM,但相比之下 ARM64 確實要複雜很多很多。

特權模式/異常等級

在 ARM32 中,我們使用 SVC 等 7 種特權模式來區分 CPU 的工作模式,作業系統等底層程式會執行在高特權模式,而普通

使用者程式

則執行在低特權的使用者模式。

而在 ARM64 中,其實也類似,不過在 ARM64 中統一成了 4 個異常等級 EL-0/1/2/3。

EL 架構:

Zircon - Fuchsia 核心分析 - 啟動(平臺初始化)

特權等級

EL3 > EL2 > EL1 > EL0 , EL0 為非特權執行等級;

EL2 無 Secure State,有 None-Secure State。EL3 只有 Secure-State,並且控制 EL0和EL1 在兩種模式間切換;

EL0 和 EL1 必須實現,EL2 和 EL3 是可選的。

關於 4 個特權等級在系統軟體中的實際使用:

| EL | 用途 |

|————|————|

| EL0 | 執行使用者程式 |

| EL1 |

作業系統核心

|

| EL2 | Hypervisor (可以理解為上面跑多個虛擬核心) |

| EL3 | Secure Monitor(ARM Trusted Firmware) |

Secure State 的影響:

Zircon - Fuchsia 核心分析 - 啟動(平臺初始化)

多核心

在多核心處理器下,ID = 0 的 CPU 核心為 prime 核心,或者被稱為 BSP 引導處理器 - bootstrap processor,其他處理器則為 non - prime 核心,或者 AP 核心 - Application Processor,開機和核心初始化由 prime 核心完成,AP 核心只要完成自身的配置就可以了。

ARM MP 架構圖:

Zircon - Fuchsia 核心分析 - 啟動(平臺初始化)

CPU 核心間透過核間中斷 - IPI 彼此通訊。

每個 CPU 核心都能看到同樣的

記憶體匯流排

和資料,一般 L1 快取每個核心獨享,L2/L3 為所有核心共享。

所有 CPU 核心共享同一個 I/O 周邊與中斷控制器,中斷控制器會根據配置將中斷分發到合適的 CPU 核心。

暫存器

僅說明本文所涉及的

Zircon - Fuchsia 核心分析 - 啟動(平臺初始化)

核心程式碼

中的通用範例

有了上文對 ARM64 的簡單介紹,我們就可以看懂程式碼中的一些程式碼了

以下是比較通用的程式碼。

判斷是否是 prime CPU 核心:

mrs cpuid, mpidr_el1

ubfx cpuid, cpuid, #0, #15 /* mask Aff0 and Aff1 fields */ //aff0 記錄 cpu ID,aff1 記錄是否支援超執行緒

cbnz cpuid, 。Lno_save_bootinfo //如果不是 prim 核心(0 號核心),則不需要啟動核心,也就不需要準備核心啟動引數,直接執行核心初始化工作。

前兩行取出 mpidr_el1 的 AFF01 放入 cpuid。

第三行如果 cpuid = 0 則代表是 prime cpu 核心,並且也是第一個執行緒,雖然現在超執行緒沒有實現就是了。

取標籤/資料地址

這裡需要解釋一下,因為 kernel 在連結的時候是根據虛擬地址來的。而在核心引導的早期階段,也就是本文所介紹的這個過程中,MMU 是處於關閉狀態的,這段時間核心實際是跑在物理地址上的。

那麼,這段程式碼就必須是 PIC 位置無關程式碼,除了儘量使用暫存器,在不得不訪問記憶體時,這段程式碼還不能依賴連結器所給的地址,那麼如果在這段程式碼中需要取到記憶體中的地址只能使用指令計算資料/Label的實際地址。

Zircon 將這一操作簡化成了一個宏:

。macro adr_global reg, symbol

//得到包含 symbol 4K 記憶體頁的基地址

adrp \reg, \symbol

//第一個全域性變數的地址

add \reg, \reg, #:lo12:\symbol

。endm

第一行得到得到包含 symbol 4K 記憶體頁的基地址。

第二行在基地址上加上偏移就是 symbol 的實際地址。

判斷當前所在的 EL

mrs x9, CurrentEL

cmp x9, #(0b01 << 2)

//不等於 0 時,說明不是在異常級別 1,跳轉到 notEL1 程式碼

bne 。notEL1

迴圈遍歷

str xzr, [page_table1, tmp, lsl #3]

add tmp, tmp, #1

cmp tmp, #MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP

bne 。Lclear_top_page_table_loop

等價於

for(int tmp = 0;tmp < MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP;tmp++) {

page_table1[tmp] = 0;

}

啟動過程概述

啟動早期,即核心在進入 C++ 世界之前,主要分為以下幾步:

初始化各個 EL1 - EL3 下的異常配置

建立啟動階段頁表

為開啟 MMU 做準備

開啟 MMU

配置棧準備進入 C 世界

啟動時序與程式碼

多核處理器架構

中,很多初始化程式碼僅需要由 prime 處理器完成,其他處理器完成各自的配置即可。

prime 核心其他核心
儲存核心啟動引數跳過
初始化 EL1 - EL3 的異常配置
初始化快取
修復 kernel base 地址 跳過
檢查並等待 。bss 段資料清除跳過
建立啟動階段的頁表自旋等待頁表建立完成
開啟 MMU 之前的準備工作
開啟 MMU (以上程式碼執行在物理地址,以下程式碼執行在虛擬地址)
重新配置核心棧指標配置其他 CPU 的棧指標
跳轉到 C 世界繼續初始化休眠等待喚醒

儲存核心啟動引數

mrs cpuid, mpidr_el1 //aff0 記錄 cpu ID,aff1 記錄是否支援超執行緒

cbnz cpuid, 。Lno_save_bootinfo //如果不是 prim 核心(0 號核心),則不需要啟動核心,也就不需要準備核心啟動引數,直接執行核心初始化工作

/* save x0 in zbi_paddr */

//prim 核心走這裡,準備並儲存核心啟動引數

//計算 zbi_paddr 段中資料的地址,儲存在 x0 中,下同

adrp tmp, zbi_paddr

str x0, [tmp, #:lo12:zbi_paddr]

/* save entry point physical address in kernel_entry_paddr */

adrp tmp, kernel_entry_paddr

adr tmp2, _start

str tmp2, [tmp, #:lo12:kernel_entry_paddr]

adrp tmp2, arch_boot_el

mrs x2, CurrentEL

str x2, [tmp2, #:lo12:arch_boot_el]

//總之,x0 - x4 現在儲存了核心初始化需要的引數,為跳轉到 C 世界作準備。

初始化 EL1 - EL3

asm。S - arm64_elX_to_el1

對各個 EL 的配置,需要 CPU 在對應的 EL 狀態下才能配置

EL1

EL1 不需要配置直接返回

//讀取現在的異常級別

mrs x9, CurrentEL

cmp x9, #(0b01 << 2)

//不等於 0 時,說明不是在異常級別 1,跳轉到 notEL1 程式碼

bne 。notEL1

/* Already in EL1 */

//EL1 直接返回

ret

EL2

EL2 狀態下主要配置了:

配置 EL2 的異常向量表

配置時鐘

清除 EL2 的轉換表暫存器

配置 SPSR 和 ELR 暫存器,這兩個看上面的暫存器介紹

實際上 EL2 在 Zircon 中還沒有具體用處,所以此處初始化基本上就是設一些空值

/* Setup the init vector table for EL2。 */

//計算EL2的異常向量表的基地址

adr_global x9, arm64_el2_init_table

//設定EL2的異常向量表的基地址

msr vbar_el2, x9

/* Ensure EL1 timers are properly configured, disable EL2 trapping of

EL1 access to timer control registers。 Also clear virtual offset。

*/

//檢查並配置時鐘

mrs x9, cnthctl_el2

orr x9, x9, #3

msr cnthctl_el2, x9

msr cntvoff_el2, xzr

/* clear out stage 2 translations */

//清除 vttbr_el2 暫存器,vttbr_el2 儲存了轉換表的基地址,負責在 EL2 下進行 EL0 -> EL1 的非安全儲存器訪問的轉換

msr vttbr_el2, xzr //http://infocenter。arm。com/help/index。jsp?topic=/com。arm。doc。100403_0200_00_en/lau1457340777806。html

//當系統發生了異常並進入EL2,SPSR_EL2,Saved Program Status Register (EL2)會儲存處理器狀態,ELR_EL2,Exception Link Register (EL2)會儲存返回發生exception的現場的返回地址。

//這裡是設定SPSR_EL2和ELR_EL2的初始值。

adr x9, 。Ltarget

msr elr_el2, x9

//ELR 定義看上面

mov x9, #((0b1111 << 6) | (0b0101)) /* EL1h runlevel */

msr spsr_el2, x9

EL3

EL3 狀態的主要任務就是配置 EL0/EL1 的 Secure State/HVC/執行指令集,其他的也是上面 EL2 一樣付空值。

設定 EL0/EL1 為 non-Secure State

開啟 HVC 指令

使用 AARCH64 指令

cmp x9, #(0b10 << 2)

//當前為異常級別 2,跳轉到 inEL2

beq 。inEL2

//當不在 EL2 狀態時,則為 EL3

/* set EL2 to 64bit and enable HVC instruction */

//scr_el3 控制EL0/EL1/EL2的異常路由 邏輯1允許

//http://infocenter。arm。com/help/index。jsp?topic=/com。arm。doc。100403_0200_00_en/lau1457340777806。html

//若SCR_EL3。RW == 1,則決定 EL2/EL1 是使用AArch64,否則AArch32

mrs x9, scr_el3

//開啟 EL0/EL1 的非安全狀態,EL0/EL1 無法訪問安全記憶體

orr x9, x9, #SCR_EL3_NS

//開啟 HVC 指令

//關於 HVC,看 http://www。wowotech。net/armv8a_arch/238。html

orr x9, x9, #SCR_EL3_HCE

//設定 SCR_EL3。RW == 1,EL2/EL1 是使用AArch64

orr x9, x9, #SCR_EL3_RW

msr scr_el3, x9

//ELR 暫存器 Exception Link Register,用於儲存異常進入ELX的異常地址,在返回異常現場的時候,可以使用 ELR_ELX(x = 1/2/3) 來恢復PC值, 異常遷移到哪一個exception level就使用哪一個ELR

//同樣的,由於不會有異常把系統狀態遷移到EL0,因此也就不存在ELR_EL0了。

adr x9, 。Ltarget

//這裡異常進入地址為 Ltarget

msr elr_el3, x9

//設定 spsr_el3

mov x9, #((0b1111 << 6) | (0b0101)) /* EL1h runlevel */

msr spsr_el3, x9

//配置 EL1 並準備進入 EL1 *

b 。confEL1

從 EL3 返回 EL1

/* disable EL2

coprocessor traps

*/

mov x9, #0x33ff

msr cptr_el2, x9

/* set EL1 to 64bit */

//設定 EL1 的異常處理為 AARCH64 指令,同上

mov x9, #HCR_EL2_RW

msr hcr_el2, x9

/* disable EL1 FPU traps */

mov x9, #(0b11<<20)

msr cpacr_el1, x9

/* set up the EL1 bounce interrupt */

//配置 EL1 棧指標

mov x9, sp

msr sp_el1, x9

isb

//模擬異常返回,執行該指令會使得CPU返回EL1狀態

eret

初始化快取

//使快取失效 *

bl arch_invalidate_cache_all

/* enable caches so atomics and spinlocks work */

//啟用快取,使原子操作和自旋鎖生效

mrs tmp, sctlr_el1

//開啟指令快取

orr tmp, tmp, #(1<<12) /* Enable icache */

//開啟資料快取

orr tmp, tmp, #(1<<2)/* Enable dcache/ucache */

msr sctlr_el1, tmp

修復重定向的 Kernel Base 地址

此工作由 prime cpu 完成,其他 cpu 開始進入自旋等待:

//載入 kernel_relocated_base 段地址

//核心重定向的基地址,即核心開始的虛擬地址

adr_global tmp, kernel_relocated_base

//負值給 kernel_vaddr

ldr kernel_vaddr, [tmp]

// Load the base of the translation tables。

//貌似 Zircon 中 1GB

物理記憶體

由一個 translation_table 維護,所以這裡 tt_trampoline 相當於一級頁表?

adr_global page_table0, tt_trampoline

//虛擬地址記憶體頁地址轉換表

adr_global page_table1, arm64_kernel_translation_table

// Send secondary cpus over to a waiting spot for the primary to finish。

//如果不是 prim CPU 核心,則跳轉到 Lmmu_enable_secondary 後等待 prim 核心執行完下面程式碼

cbnz cpuid, 。Lmmu_enable_secondary

//下面的程式碼只有 prim CPU 核心執行

// The fixup code appears right after the kernel image (at __data_end in

// our view)。 Note this code overlaps with the kernel‘s bss! It

// expects x0 to contain the actual runtime address of __code_start。

//將核心程式碼開始的虛擬地址儲存到 x0 中

mov x0, kernel_vaddr

//跳轉到 __data_end *

//__data_end 指向 image。S - apply_fixups 方法

bl __data_end

FUNCTION(apply_fixups)

// This is the constant address the kernel was linked for。

movlit x9, KERNEL_BASE

sub x0, x0, x9

// The generated kernel-fixups。inc invokes this macro for each run of fixups。

。macro fixup addr, n, stride

adr x9, FIXUP_LOCATION(\addr)

。if \n >= 4 && \stride == 8

// Do a loop handling

adjacent pairs

mov x16, #(\n / 2)

0: fixup_pair

subs x16, x16, #1

b。ne 0b

。if \n % 2

// Handle the odd remainder after those pairs。

fixup_single 8

。endif

。elseif \n >= 2 && \stride == 8

// Do a single

adjacent pair

fixup_pair

。if \n == 3

// Do the third adjacent one。

fixup_single 8

。endif

。elseif \n > 1

// Do a strided loop。

mov x16, #\n

0: fixup_single \stride

subs x16, x16, #1

b。ne 0b

。else

// Do a singleton。

fixup_single 8

。endif

。endm

。macro fixup_pair

ldp x10, x11, [x9]

add x10, x10, x0

add x11, x11, x0

stp x10, x11, [x9], #16

。endm

。macro fixup_single stride

ldr x10, [x9]

add x10, x10, x0

str x10, [x9], #\stride

。endm

#include “kernel-fixups。inc”

ret

DATA(apply_fixups_end)

END_FUNCTION(apply_fixups)

檢查等待清除 。bss 段

//檢查核心 bss 段是否被清除,猜測是因為前面 bss 段所在記憶體已經被操作過

。Ldo_bss:

//見 kernel。ld

//計算儲存核心 。bss 段開始地址

adr_global tmp, __bss_start

//計算儲存核心 。bss 段結束地址

adr_global tmp2, _end

//計算 。bss 段大小

sub tmp2, tmp2, tmp

//。bss 段大小為 0 則跳轉 Lbss_loop_done

cbz tmp2, 。Lbss_loop_done

//不為 0 則迴圈等待

。Lbss_loop:

sub tmp2, tmp2, #16

stp xzr, xzr, [tmp], #16

cbnz tmp2, 。Lbss_loop

。Lbss_loop_done:

建立啟動階段的頁表

首先要把也表中的記憶體清除:

//清除核心虛地址轉換表

。Lclear_top_page_table_loop:

//遍歷轉換表中的所有條目並設定 0

/**

等價於

for(int tmp = 0;tmp < MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP;tmp++) {

page_table1[tmp] = 0;

}

關於 xzr 暫存器 https://community。arm。com/processors/f/discussions/3185/wzr-xzr-register-s-purpose

**/

str xzr, [page_table1, tmp, lsl #3]

add tmp, tmp, #1

cmp tmp, #MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP

bne 。Lclear_top_page_table_loop

在初始化階段,需要對映三段地址:

第一段是identity mapping,其實就是把物理地址mapping到物理地址上去,在開啟MMU的時候需要這樣的mapping(ARM ARCH強烈推薦這麼做的)

第二段是kernel image mapping,核心程式碼歡快的執行當然需要將kernel running需要的地址(kernel txt、dernel rodata、data、bss等等)進行映射了

第三段是blob memory對應的mapping

因為頁表對映呼叫到了 C 方法,所以需要提前為 CPU 配置 SP 指標:

。Lbss_loop_done:

/* set up a functional stack pointer */

//設定核心棧地址,準備呼叫 C 程式碼

adr_global tmp, boot_cpu_kstack_end

mov sp, tmp

/* make sure the boot allocator is given a chance to figure out where

* we are loaded in

physical memory

。 */

bl boot_alloc_init

/* save the physical address the kernel is loaded at */

//儲存核心開始地址到 kernel_base_phys 全域性變數

adr_global x0, __code_start

adr_global x1, kernel_base_phys

str x0, [x1]

/* set up the mmu according to mmu_initial_mappings */

/* clear out the kernel translation table */

mov tmp, #0

對映物理記憶體:

//準備呼叫 C 函式 arm64_boot_map

//1。該函式任務是幫核心對映物理記憶體

//先準備 5 個引數 x0-x4 暫存器儲存函式引數

/* void arm64_boot_map(pte_t* kernel_table0, vaddr_t vaddr, paddr_t paddr, size_t len, pte_t flags); */

/* map a large run of physical memory at the base of the kernel’s address space */

mov x0, page_table1

mov x1, KERNEL_ASPACE_BASE

mov x2, 0

mov x3, ARCH_PHYSMAP_SIZE

movlit x4, MMU_PTE_KERNEL_DATA_FLAGS

//呼叫 arm64_boot_map *

bl arm64_boot_map

對映核心執行記憶體:

//2。對映核心的地址

/* map the kernel to a fixed address */

/* note: mapping the kernel here with full rwx, this will get locked down later in vm initialization; */

mov x0, page_table1

mov x1, kernel_vaddr

adr_global x2, __code_start

adr_global x3, _end

sub x3, x3, x2

mov x4, MMU_PTE_KERNEL_RWX_FLAGS

bl arm64_boot_map

通知頁表配置完畢:

//標記頁表已經設定完畢,通知其他 CPU 核心可以繼續往下跑了

adr_global tmp, page_tables_not_ready

str wzr, [tmp]

//prime CPU 核心跳入 Lpage_tables_ready

b 。Lpage_tables_ready

準備開啟 MMU

開啟 MMU 之前需要做一些配置。

清理垃圾資料

需要重置一下 MMU 和 Cache 的狀態以清除裡面的殘餘資料,在進入 Kernel 程式碼之前,Bootloader 可能使用過 MMU 和 Cache,所以 ICache 和 TLB 中可能還有前面留下來的殘餘垃圾資料。

//使 TLB 失效以清除資料

/* Invalidate TLB */

tlbi vmalle1is

初始化 Memory attributes 配置

Memory attributes 簡單來說就是將 Memory 加上了幾種屬性,每種屬性都會影響 Memory 的讀寫策略。

因為 Memory 讀寫策略是非常複雜的,比如一段記憶體區域指向的是一個 FIFO 裝置,對記憶體的讀寫有嚴格的時序要求,則需要配置 Memory attributes 來禁止 CPU 讀寫重排,Cache 等等最佳化,因為這些對於這段 Memory 沒有意義,還會影響資料的讀寫的正確性。

movlit tmp, MMU_MAIR_VAL

msr mair_el1, tmp

/* Initialize TCR_EL1 */

/* set cacheable attributes on translation walk */

/* (SMP extensions) non-shareable, inner write-back write-allocate */

movlit tmp, MMU_TCR_FLAGS_IDENT

msr tcr_el1, tmp

看一下 Zircon 的預設 Memory Attribute 配置:

/* Default configuration for main kernel page table:

* - do cached translation walks

*/

/* Device-nGnRnE memory */

#define MMU_MAIR_ATTR0 MMU_MAIR_ATTR(0, 0x00)

#define MMU_PTE_ATTR_STRONGLY_ORDERED MMU_PTE_ATTR_ATTR_INDEX(0)

/* Device-nGnRE memory */

#define MMU_MAIR_ATTR1 MMU_MAIR_ATTR(1, 0x04)

#define MMU_PTE_ATTR_DEVICE MMU_PTE_ATTR_ATTR_INDEX(1)

/* Normal Memory, Outer Write-back non-transient Read/Write allocate,

* Inner Write-back non-transient Read/Write allocate

*/

#define MMU_MAIR_ATTR2 MMU_MAIR_ATTR(2, 0xff)

#define MMU_PTE_ATTR_NORMAL_MEMORY MMU_PTE_ATTR_ATTR_INDEX(2)

/* Normal Memory, Inner/Outer uncached, Write Combined */

#define MMU_MAIR_ATTR3 MMU_MAIR_ATTR(3, 0x44)

#define MMU_PTE_ATTR_NORMAL_UNCACHED MMU_PTE_ATTR_ATTR_INDEX(3)

#define MMU_MAIR_ATTR4 (0)

#define MMU_MAIR_ATTR5 (0)

#define MMU_MAIR_ATTR6 (0)

#define MMU_MAIR_ATTR7 (0)

#define MMU_MAIR_VAL (MMU_MAIR_ATTR0 | MMU_MAIR_ATTR1 | \

MMU_MAIR_ATTR2 | MMU_MAIR_ATTR3 | \

MMU_MAIR_ATTR4 | MMU_MAIR_ATTR5 | \

MMU_MAIR_ATTR6 | MMU_MAIR_ATTR7 )

#define MMU_TCR_IPS_DEFAULT MMU_TCR_IPS(2) /* TODO: read at runtime, or configure per platform */

/* Enable cached page table walks:

* inner/outer (IRGN/ORGN): write-back +

write-allocate

*/

#define MMU_TCR_FLAGS1 (MMU_TCR_TG1(MMU_TG1(MMU_KERNEL_PAGE_SIZE_SHIFT)) | \

MMU_TCR_SH1(MMU_SH_INNER_SHAREABLE) | \

MMU_TCR_ORGN1(MMU_RGN_WRITE_BACK_ALLOCATE) | \

MMU_TCR_IRGN1(MMU_RGN_WRITE_BACK_ALLOCATE) | \

MMU_TCR_T1SZ(64 - MMU_KERNEL_SIZE_SHIFT))

#define MMU_TCR_FLAGS0 (MMU_TCR_TG0(MMU_TG0(MMU_USER_PAGE_SIZE_SHIFT)) | \

MMU_TCR_SH0(MMU_SH_INNER_SHAREABLE) | \

MMU_TCR_ORGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \

MMU_TCR_IRGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \

MMU_TCR_T0SZ(64 - MMU_USER_SIZE_SHIFT))

#define MMU_TCR_FLAGS0_IDENT \

(MMU_TCR_TG0(MMU_TG0(MMU_IDENT_PAGE_SIZE_SHIFT)) | \

MMU_TCR_SH0(MMU_SH_INNER_SHAREABLE) | \

MMU_TCR_ORGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \

MMU_TCR_IRGN0(MMU_RGN_WRITE_BACK_ALLOCATE) | \

MMU_TCR_T0SZ(64 - MMU_IDENT_SIZE_SHIFT))

#define MMU_TCR_FLAGS_IDENT (MMU_TCR_IPS_DEFAULT | MMU_TCR_FLAGS1 | MMU_TCR_FLAGS0_IDENT)

開啟 MMU

這裡開啟 MMU 非常簡單,開啟之前,以上程式碼都在物理地址下執行,開啟之後則在虛擬地址下運行了。

//

記憶體柵欄

isb

//儲存 EL1 狀態的異常向量表

/* Read SCTLR */

mrs tmp, sctlr_el1

//開啟 MMU

/* Turn on the MMU */

orr tmp, tmp, #0x1

//恢復 EL1 狀態的異常向量表

/* Write back SCTLR */

msr sctlr_el1, tmp

這裡注意備份還原異常向量表。

記憶體柵欄防止開啟 MMU 前後程式碼亂序執行,造成邏輯錯誤。

準備跳入 C 世界

重新設定棧指標

在此之前,重新設定棧指標,因為此時已經變成虛擬地址:

//重新設定 prime CPU 的核心棧指標,因為現在 MMU 已經開啟,需要使用虛擬地址

// set up the boot stack for real

adr_global tmp, boot_cpu_kstack_end

mov sp, tmp

設定棧溢位異常

配置 Stack Guard,其實就是在棧末尾設定一個頁中斷,如果程式讀寫到這裡,代表棧溢位,觸發異常。

防止編譯期間沒有啟用棧保護:

//配置 Stack Guard,其實就是在棧末尾設定一個頁中斷,如果程式讀寫到這裡,代表棧溢位,觸發異常

adr_global tmp, boot_cpu_fake_thread_pointer_location

msr tpidr_el1, tmp

// set the per cpu pointer for cpu 0

adr_global x18, arm64_percpu_array

// Choose a good (ideally random) stack-guard value as early as possible。

bl choose_stack_guard

mrs tmp, tpidr_el1

str x0, [tmp, #ZX_TLS_STACK_GUARD_OFFSET]

// Don‘t leak the value to other code。

mov x0, xzr

其他 CPU 設定棧,初始化, 並且進入休眠:

。Lsecondary_boot:

//配置其他 CPU 核心的棧指標

bl arm64_get_secondary_sp

cbz x0, 。Lunsupported_cpu_trap

mov sp, x0

msr tpidr_el1, x1

bl arm64_secondary_entry

。Lunsupported_cpu_trap:

//其他 CPU 核心初始化完畢

wfe

b 。Lunsupported_cpu_trap

prime 核心進入 C 世界:

//跳轉到核心 C 程式碼入口

bl lk_main

b

關於核心初始化後期

這部分大部分在 C/C++ 中完成,下一篇分析。

原文作者:坑大

原文連結:

https://

bbs。pediy。com/thread-24

9600。htm

轉載請註明:轉自看雪學院

更多閱讀:

四。Android ELF系列:實現一些小功能(模組操作,dump so)

i春秋 ISC2016訓練賽——phrackCTF Reverse Classical CrackMe writeup

三。 Android ELF系列:手寫一個so檔案(包含兩個匯出函式)

Zircon - Fuchsia 核心分析 - 啟動(核心初始化)

標簽: MMU  tmp  核心  x9  EL1