主頁 >  其他 > linux arm32 mmu 啟動代碼分析(匯編部分)

linux arm32 mmu 啟動代碼分析(匯編部分)

2020-10-14 06:57:24 其他

linux arm32啟動代碼分析

首先將 linux kernel 代碼編譯好以后,在目錄 arch/arm/kernel 下生成鏈接腳本檔案 vmlinux.lds (vmlinux.lds由vmlinux.lds.S編譯而來),首先分析此腳本來熟悉 linux kernel 二進制代碼分布結構,

vmlinux.lds.S

ENTRY(stext)

指明了linux內核入口,入口為stext,符號stext定義在 arch/arm/kernel/head.S 檔案中:

	.arm

	__HEAD
ENTRY(stext)
 ARM_BE8(setend	be )			@ ensure we are in BE8 mode

 THUMB(	adr	r9, BSYM(1f)	)	@ Kernel is always entered in ARM.
 THUMB(	bx	r9		)	@ If this is a Thumb-2 kernel,
 THUMB(	.thumb			)	@ switch to Thumb now.
 THUMB(1:			)

ENTRY在 include/linux/linkage.h 中定義

#ifndef ENTRY
#define ENTRY(name) \
	.globl name ASM_NL \
	ALIGN ASM_NL \
	name:
#endif

通過代碼可以看到 ENTRY 宏只是對全域符號的匯出起到包裝作用,
下面分析ENTRY陳述句下的五條指令

1、ARM_BE8(setend be )
ARM_BE8在 arch/arm/include/asm/assembler.h 中定義

/* Select code for any configuration running in BE8 mode */
#ifdef CONFIG_CPU_ENDIAN_BE8
#define ARM_BE8(code...) code
#else
#define ARM_BE8(code...)
#endif

如果開啟 CONFIG_CPU_ENDIAN_BE8 選項,code(setend be)則會被原封不動編譯到內核中,如果沒有開啟此選項,code(setend be)就不會被編譯到內核中,

(extension) #define后的省略號的意義如下(以PDEBUG為例):

#define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args)
// example
PDEBUG("a=%d, b=%d", a, b);
// 展開后
printk( KERN_DEBUG "scull: " "a=%d, b=%d", a, b);

(1)arm BE8 mode 是什么
根據博客 https://blog.richliu.com/2010/04/08/907/arm11-be8-and-be32 所述:
BE8 和 BE32 是位元組序相關的概念,舉例:
假設需要將 0x11223344 存入記憶體中:
little endian:

memory address0123
data0x440x330x220x11

big endian:

memory address0123
data0x110x220x330x44

現假設對0x11223344記憶體存盤分布如下:

memory address0123
data0x110x220x330x44

在BE32 mode下:

LDR r0, [0]
# r0 = 0x44332211
LDRB r0, [0]
# r0 = 0x00000044
LDRB r0, [3]
# r0 = 0x00000011

在BE8 mode下:

LDR r0, [0]
# r0 = 0x11223344
LDRB r0, [0]
# r0 = 0x00000011
LDRB r0, [3]
# r0 = 0x00000044

關于 byte invariant endianness 的概念可以參照博客 https://blog.csdn.net/moreaction/article/details/5280067/
(2)setend be 指令的含義
setend 指令選擇資料訪問的位元組序,在百度文庫 https://wenku.baidu.com/view/6f83c4c2951ea76e58fafab069dc5022aaea46e5.html 解釋了這個指令,在linux內核代碼中,如果開啟了 CONFIG_CPU_ENDIAN_BE8 選項,則 setend be 指令會被編譯到內核中并執行,此指令的執行代表選擇資料訪問的位元組序為 BE8 模式,BE8 模式的資料訪問效果如上文所述,

2、THUMB( adr r9, BSYM(1f) )
3、THUMB( bx r9 )
4、THUMB( .thumb )
5、THUMB(1: )
2\3\4\5 中,THUMB、BSYM都定義在 arch/arm/include/asm/unified.h 中:

#ifdef CONFIG_THUMB2_KERNEL

#if __GNUC__ < 4
#error Thumb-2 kernel requires gcc >= 4
#endif

/* The CPSR bit describing the instruction set (Thumb) */
#define PSR_ISETSTATE	PSR_T_BIT

#define ARM(x...)
#define THUMB(x...)	x
#ifdef __ASSEMBLY__
#define W(instr)	instr.w
#define BSYM(sym)	sym + 1
#else
#define WASM(instr)	#instr ".w"
#endif

#else	/* !CONFIG_THUMB2_KERNEL */

/* The CPSR bit describing the instruction set (ARM) */
#define PSR_ISETSTATE	0

#define ARM(x...)	x
#define THUMB(x...)
#ifdef __ASSEMBLY__
#define W(instr)	instr
#define BSYM(sym)	sym
#else
#define WASM(instr)	#instr
#endif

#endif	/* CONFIG_THUMB2_KERNEL */

若沒有開啟 CONFIG_THUMB2_KERNEL 選項,則這四條陳述句都不會被編譯進內核中,且開啟此選項需要 gcc 版本高于 4

#if __GNUC__ < 4
#error Thumb-2 kernel requires gcc >= 4
#endif

如果 CONFIG_THUMB2_KERNEL 選項開啟且定義了 __ASSEMBLY__,則這四條陳述句會被預編譯為:

adr	r9, 1f + 1
bx	r9
	.thumb
1:	

至于為什么需要 1f+1 原因在于分支指令 bx:
bx 為帶狀態切換的跳轉指令,其語法格式為 bx 其中 RM 只能是暫存器,并且當 RM 的bit[0]為1時,切換到 Thumb 指令,為0時切換到 ARM 指令,
以下是猜測:由于指令對齊,所以帶狀態分支跳轉地址一定為偶數,所以CPU在收到條件分支指令時不會將bit[0]當作地址的一部分,而是用來做bx的狀態選擇,并且由于地址為偶數的原因, adr r9, 1f 指令執行后,r9的bit[0]一定為0,所以需要加上1來進行狀態選擇,以達到跳轉后開始執行Thumb指令集,并且,.thumb 也告知編譯器,其下的匯編代碼需要編譯為Thumb指令

至此分析完這五條指令,


next,如果開啟了 CONFIG_ARM_VIRT_EXT 開關,則會跳轉到 __hyp_stub_install 繼續執行,具體代碼如下:

#ifdef CONFIG_ARM_VIRT_EXT
	bl	__hyp_stub_install
#endif

__hyp_stub_install 定義在 arch/arm/kernel/hyp-stub.S 中,根據源代碼中的注釋解釋:

/*
 * Hypervisor stub installation functions.
 *
 * These must be called with the MMU and D-cache off.
 * They are not ABI compliant and are only intended to be called from the kernel
 * entry points in head.S.
 */

目前只知道此代碼與arm虛擬化功能有關


next, linux 內核將會開始檢測所運行的硬體是否支持此內核映像,由于 arm 內核種類多,并且互相之間有無法兼容的區別,所以當開發者對內核進行開發或者使用者編譯內核時,都會在原始碼級別上給出對硬體的某些定義,就如下文所提到的 __proc_info 結構,內核在編譯期即確定了自己所支持的硬體種類,例如在 s3c2440 平臺下即會去指定支持 arm920t 架構,由于會有一些與硬體互動的操作無法互相兼容,所以內核會通過下文的一些操作來檢查硬體的屬性是否與編譯到內核里對硬體的定義資訊相互匹配,如果發現不匹配則會立刻讓內核陷入死回圈并告知啟動失敗,
檢查 processor id

safe_svcmode_maskall r9

mrc	p15, 0, r9, c0, c0		@ get processor id
bl	__lookup_processor_type		@ r5=procinfo r9=cpuid
movs	r10, r5				@ invalid processor (r5=0)?
 THUMB( it	eq )		@ force fixup-able long branch encoding
beq	__error_p			@ yes, error 'p'

safe_svcmode_maskall r9 用來確保 CPU 在SVC模式下,并且所有中斷都被屏蔽,其中 safe_svcmode_maskall 是一個宏,他定義在 arch/arm/include/asm/assembler.h 中,原始碼中的注釋對其解釋為:

/*
 * Helper macro to enter SVC mode cleanly and mask interrupts. reg is
 * a scratch register for the macro to overwrite.
 *
 * This macro is intended for forcing the CPU into SVC mode at boot time.
 * you cannot return to the original mode.
 */

mrc 指令將協處理器的暫存器中數值傳送到ARM處理器的暫存器中,其中 mrc p15, 0, r9, c0, c0 用來獲取處理器id(processor id)并將此值傳遞給r9
在指令 bl __lookup_processor_type 中,__lookup_processor_type 符號定義在 arch/arm/kernel/head-common.S 中:

__lookup_processor_type:
	adr	r3, __lookup_processor_type_data
	ldmia	r3, {r4 - r6}
	sub	r3, r3, r4			@ get offset between virt&phys
	add	r5, r5, r3			@ convert virt addresses to
	add	r6, r6, r3			@ physical address space
1:	ldmia	r5, {r3, r4}			@ value, mask
	and	r4, r4, r9			@ mask wanted bits
	teq	r3, r4
	beq	2f
	add	r5, r5, #PROC_INFO_SZ		@ sizeof(proc_info_list)
	cmp	r5, r6
	blo	1b
	mov	r5, #0				@ unknown processor
2:	ret	lr
ENDPROC(__lookup_processor_type)

在這段代碼中涉及到 __lookup_processor_type_data 定義為:

/*
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
	.align	2
	.type	__lookup_processor_type_data, %object
__lookup_processor_type_data:
	.long	.
	.long	__proc_info_begin
	.long	__proc_info_end
	.size	__lookup_processor_type_data, . - __lookup_processor_type_data

可以理解為在 __proc_info_begin 表示的記憶體地址與 __proc_info_end 表示的記憶體地址之間包含了一個或多個 __proc_info 結構體,這個結構體用C語言的定義如下:

struct proc_info_list {
	unsigned int		cpu_val;
	unsigned int		cpu_mask;
	unsigned long		__cpu_mm_mmu_flags;	/* used by head.S */
	unsigned long		__cpu_io_mmu_flags;	/* used by head.S */
	unsigned long		__cpu_flush;		/* used by head.S */
	const char		*arch_name;
	const char		*elf_name;
	unsigned int		elf_hwcap;
	const char		*cpu_name;
	struct processor	*proc;
	struct cpu_tlb_fns	*tlb;
	struct cpu_user_fns	*user;
	struct cpu_cache_fns	*cache;
};

這段代碼會遍歷結構體陣列,并將結構內的 cpu 資訊與通過 mrc 指令獲取的處理器id進行對比,并通過 r5 暫存器回傳結構的地址,或者回傳 0 代表匹配失敗,add r5, r5, #PROC_INFO_SZ 指令就是將指標指向下一個結構體,ldmia r5, {r3, r4} 指令就是將 cpu_valcpu_mask 的值分別裝載到 r3 r4 中,cmp r5, r6 指令來判斷是否已經遍歷到了陣列的末尾,即已經沒有未檢查過的 __proc_info 結構體了,如果r5 != r6 則 blo 1b,若相等則給 r5 賦值為0并回傳,指令 and r4, r4, r9 與 teq r3, r4 用于檢查processor_id,如果通過檢查則 beq 2f 并 通過指令 ret lr 回傳,回傳時暫存器 r5 存盤的值為此相匹配的 __proc_info 結構的地址,

movs r10, r5r5 的值轉存到 r10 中,并且由于使用 movs 指令,會影響 CPSR 暫存器的值,若 r5 為0則會使得 Z 位置1,而 beq 指令通過判斷 Z 位是否為1來決定是否跳轉,最后,如果 r5 為0,即沒有匹配的 __proc_info 則會導致 beq __error_p 執行,若執行此陳述句,最終便會跳轉到 __error 代碼段 最后系統會 halt up,其中 __error_p__error 皆定義在 arch/arm/kernel/head-common.S 中,

通過查看鏈接腳本,我們可以清晰的看到 __proc_info 結構的存盤位置:

#define PROC_INFO							\
	. = ALIGN(4);							\
	VMLINUX_SYMBOL(__proc_info_begin) = .;				\
	*(.proc.info.init)						\
	VMLINUX_SYMBOL(__proc_info_end) = .;

.text : {			/* Real text segment		*/
		_stext = .;		/* Text and read-only data	*/
			IDMAP_TEXT
			__exception_text_start = .;
			*(.exception.text)
			__exception_text_end = .;
			IRQENTRY_TEXT
			TEXT_TEXT
			SCHED_TEXT
			LOCK_TEXT
			KPROBES_TEXT
			*(.gnu.warning)
			*(.glue_7)
			*(.glue_7t)
		. = ALIGN(4);
		*(.got)			/* Global offset table		*/
			ARM_CPU_KEEP(PROC_INFO)
	}

可以看到在 __proc_info_begin__proc_info_end 之間的是 .proc.info.init 段,這樣就可以通過檢索 .proc.info.init 段的定義位置來找到這些 __proc_info 結構的定義位置了,通過檢索發現 .proc.info.init 段的定義位置在 arch/arm/mm/ 目錄下的多個 proc-x.S 中,下邊舉例:
在 arch/arm/mm/proc-arm9tdmi.S 中

		.section ".proc.info.init", #alloc

.macro arm9tdmi_proc_info name:req, cpu_val:req, cpu_mask:req, cpu_name:req
		.type	__\name\()_proc_info, #object
__\name\()_proc_info:
		.long	\cpu_val
		.long	\cpu_mask
		.long	0
		.long	0
		initfn	__arm9tdmi_setup, __\name\()_proc_info
		.long	cpu_arch_name
		.long	cpu_elf_name
		.long	HWCAP_SWP | HWCAP_THUMB | HWCAP_26BIT
		.long	\cpu_name
		.long	arm9tdmi_processor_functions
		.long	0
		.long	0
		.long	v4_cache_fns
		.size	__\name\()_proc_info, . - __\name\()_proc_info
.endm

	arm9tdmi_proc_info arm9tdmi, 0x41009900, 0xfff8ff00, cpu_arm9tdmi_name
	arm9tdmi_proc_info p2001, 0x41029000, 0xffffffff, cpu_p2001_name

在 arch/arm/mm/proc-arm920.S 中

	.section ".proc.info.init", #alloc

	.type	__arm920_proc_info,#object
__arm920_proc_info:
	.long	0x41009200
	.long	0xff00fff0
	.long   PMD_TYPE_SECT | \
		PMD_SECT_BUFFERABLE | \
		PMD_SECT_CACHEABLE | \
		PMD_BIT4 | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ
	.long   PMD_TYPE_SECT | \
		PMD_BIT4 | \
		PMD_SECT_AP_WRITE | \
		PMD_SECT_AP_READ
	initfn	__arm920_setup, __arm920_proc_info
	.long	cpu_arch_name
	.long	cpu_elf_name
	.long	HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
	.long	cpu_arm920_name
	.long	arm920_processor_functions
	.long	v4wbi_tlb_fns
	.long	v4wb_user_fns
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
	.long	arm920_cache_fns
#else
	.long	v4wt_cache_fns
#endif
	.size	__arm920_proc_info, . - __arm920_proc_info

可以看到這些結構都是通過匯編語言進行定義的,雖然在 linux 源代碼中有對 __proc_info 結構的C語言定義(如上文),但是 linux 內核代碼并沒有使用這個C語言定義的結構,由于此時系統仍然處于底層初始化階段,并沒有進行C語言執行環境初始化作業(例如初始堆疊空間等),所以無法執行C代碼,也就無法參考C語言定義的結構體,只能通過匯編語言直接定義特定記憶體位置的值來還原結構體的資料分布,

至此,linux 內核對 processor id 的檢查流程分析完畢


下邊將分析 linux 內核在內核管理方面的初始化作業,首先解釋 linux 內核對記憶體地址相關定義的幾個值:
1、PAGE_OFFSET
2、TEXT_OFFSET
3、KERNAL_RAM_VADDR
: 在分析頁表建立之前,必須先搞清楚 linux 內核對地址的這幾個特殊定義才行
這些值出現在 arch/arm/kernel/head.S 中:

#define KERNEL_RAM_VADDR	(PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

其中 TEXT_OFFSETPAGE_OFFSET 在鏈接腳本中已經被用來定義當前虛擬地址

#ifdef CONFIG_XIP_KERNEL
	. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
	. = PAGE_OFFSET + TEXT_OFFSET;
#endif
	.head.text : {
		_text = .;
		HEAD_TEXT
	}

查看 arch/arm/kernel/head.S 所包含的頭檔案,發現了與記憶體管理相關的頭檔案 asm/memory.h ,進而在此頭檔案中找到了 PAGE_OFFSET 的定義:

/* PAGE_OFFSET - the virtual address of the start of the kernel image */
#define PAGE_OFFSET		UL(CONFIG_PAGE_OFFSET)

并在 arm 架構的 defconfig 檔案中找到了 CONFIG_PAGE_OFFSET 選項(以 arch\arm\configs\imx_alientek_emmc_defconfig 為例):

CONFIG_PAGE_OFFSET=0x80000000

而展開 UL 宏:

/*
 * Allow for constants defined here to be used from assembly code
 * by prepending the UL suffix only with actual C code compilation.
 */
#define UL(x) _AC(x, UL) //arch/arm/include/asm/memory.h

TEXT_OFFSET 的定義位置比較隱蔽,通過查看 MAKEFILE 輸出,我們可以看到(或者也可以查看 cmd 檔案 arch/arm/kernel/.head.o.cmd,其中的變數 cmd_arch/arm/kernel/head.o := arm-linux-gnueabihf-gcc …):

arm-linux-gnueabihf-gcc -Wp -WD,arch/arm/kernel/.head.o.d -nostdinc -isystem /home/ace/kernels/alpha/toolchain/gcc-linaro-4.9.4 ... (省略無關緊要的部分) -D__ASSEMBLY__ -D__KERNEL__ -D__LINUX_ARM_ARCH__=6 -DTEXT_OFFSET=0x00008000 ... (省略無關緊要的部分)

通過上述命令看到通過 gcc-D 選項將 TEXT_OFFSET 宏注入到代碼中,進而可以查到這個宏的值的定義位置在 arch/arm/Makefile 中:

TEXT_OFFSET := $(textofs-y)

同在一個檔案中,使用這可以通過 config 選擇 textofs-y 的值:

# Text offset. This list is sorted numerically by address in order to
# provide a means to avoid/resolve conflicts in multi-arch kernels.
textofs-y	:= 0x00008000
textofs-$(CONFIG_ARCH_CLPS711X) := 0x00028000
# We don't want the htc bootloader to corrupt kernel during resume
textofs-$(CONFIG_PM_H1940)      := 0x00108000
# SA1111 DMA bug: we don't want the kernel to live in precious DMA-able memory
ifeq ($(CONFIG_ARCH_SA1100),y)
textofs-$(CONFIG_SA1111) := 0x00208000
endif
textofs-$(CONFIG_ARCH_MSM8X60) := 0x00208000
textofs-$(CONFIG_ARCH_MSM8960) := 0x00208000
textofs-$(CONFIG_ARCH_AXXIA) := 0x00308000

若不在 config 中開啟這些奇怪的選項,則 textofs-y 默認的值即為 0x00008000,并且通過上文查看到的 arm-linux-gcc 命令可以看到,我在此次編譯時并沒有開啟這些選項,使用了 textofs-y 的默認值 0x00008000,這樣上文提到的宏 KERNAL_RAM_VADDR 便可以計算得:PAGE_OFFSET + TEXT_OFFSET = 0x80008000
現在已經找到了他們的定義位置及其值,下邊將分析這些值的含義
linux 內核原始碼中的注釋如下:

/*
 * swapper_pg_dir is the virtual address of the initial page table.
 * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must
 * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect
 * the least significant 16 bits to be 0x8000, but we could probably
 * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
 */
 
 /* PAGE_OFFSET - the virtual address of the start of the kernel image */

意思是說,PAGE_OFFSET 是內核映像的起始 虛擬地址,而 swapper_pg_dir 是初始頁表的 虛擬地址,由于我在編譯程序中并沒有打開 LPAE 開關,所以得到:

#define PG_DIR_SIZE	0x4000

進而通過:

.globl	swapper_pg_dir
.equ	swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

得到 swapper_pg_dir = 0x80008000 - 0x00004000 = 0x80004000
查看一下鏈接腳本,可以看到這么一個定義:

. = PAGE_OFFSET + TEXT_OFFSET;
.head.text : {
	_text = .;
	HEAD_TEXT
}

也就是說,KERNAL_RAM_VADDR 表示的地址正好是 .head.text 段的起始地址,這就需要搞清楚 .head.text 段中包含的內容是什么,鏈接腳本說,.head.text 段中包含了一個符號 _text,這個符號的值為當前地址(PAGE_OFFSET + TEXT_OFFSET = KERNAL_RAM_VADDR),我們需要記下來這個符號,可能之后會用到;然后就是 HEAD_TEXT 宏定義,必須找到 HEAD_TEXT 的定義才能知道聯結器究竟把目標檔案中的哪些段塞進了這個 .head.text 段中,在 include/asm-generic/vmlinux.lds.h 頭檔案中找到了對 HEAD_TEXT 的定義:

/* Section used for early init (in .S files) */
#define HEAD_TEXT  *(.head.text)

意思就是將所有目標檔案中的 .head.text 段聚集在了一起,別忘了我們分析的前幾行指令:

.arm
__HEAD
ENTRY(stext)
ARM_BE8(setend	be )			
THUMB(	adr	r9, BSYM(1f)	)	
THUMB(	bx	r9		)	
THUMB(	.thumb			)	
THUMB(1:			)

這里有一個 __HEAD 宏定義,在 include/linux/init.h 中可以找到其定義:

#define __HEAD		.section	".head.text","ax"

原來目前分析的代碼都在 .head.text 段下,這樣就完全清楚了:PAGE_OFFSET(0x80000000) 是整個內核映像的起始 虛擬地址,而 KERNAL_RAM_VADDR(0x80008000) 為內核映像中早期初始化可執行代碼段的起始 虛擬地址,在這兩個地址之間有一個初始頁表的起始虛擬地址,swapper_pg_dir(0x80004000) 這樣 TEXT_OFFSET 名字的由來也清楚了: TEXT 的偏移,即對 PAGE_OFFSET 的偏移,偏移了 TEXT_OFFSET 大小后即到了 代碼段(text) ,其由 KERNAL_RAM_VADDR 進行標識,最后確認一下我們分析的記憶體分布是否正確,打開 System.map 檔案,查看一下 0x80008000 虛擬地址處究竟是不是 stext 符號:

80008000 T _text
80008000 T stext
8000808c t __create_page_tables

看來是了,目前的分析是正確的,并且 _text 符號確實被編譯到可執行檔案中,位置在 0x80008000,與 stext 符號享有同樣的地址,
但是目標檔案中不僅僅在 head.S 中定義了 .head.text ,其他目標檔案中也會包含此段啊,為什么 head.S 中的 .head.text 段在所有 .head.text 段之上呢?這是因為在鏈接腳本中,定義了:

ENTRY(stext)

這就意味著 stext 符號需要當作入口在最前面,所以 head.S 中的包含 stext 符號的 .head.text 段理所當然地充當了內核映像中的 .head.text 段之首,

當清楚了這些值的含義后,可以繼續分析 linux 內核的啟動代碼,下一步就是 建立頁表
分析到這里,需要回過頭看一下 linux 內核原始碼注釋對此時硬體狀態的定義:

/*
* This is normally called from the decompressor code.  The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.*/

/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/

其中 r1 r2bootloader(uboot grub等) 進行設定,r2dtb 結構的地址,r9 是通過之前 mrc 指令讀取到的,r10 是通過 __lookup_processor_type 獲取到的 __proc_info 結構的地址,r8 由如下操作獲取(我在編譯時并沒有開啟 CONFIG_XIP_KERNEL 選項):

#ifndef CONFIG_XIP_KERNEL
adr	r3, 2f
ldmia	r3, {r4, r8}
sub	r4, r3, r4			
add	r8, r8, r4			
#else
ldr	r8, =PLAT_PHYS_OFFSET		
#endif

/*標號2如下*/
#ifndef CONFIG_XIP_KERNEL
2:	.long	.
	.long	PAGE_OFFSET
#endif

執行 adr r3 2f 獲取了標號 2 的實際地址,并將 2 下定義的兩個 .long 型的資料裝在到 r4 r8 中,其中 r4 中裝載的是 .long . 定義的資料,即當前虛擬地址(也即標號 2 在映像中的虛擬地址),而 r3 則是標號 2 的實際物理地址,執行 sub r4, r3, r4 即 r4 = r3 - r4 后,即為 映像虛擬地址與實際物理地址之間的偏移,設為 OFFSET,再執行 add r8, r8, r4 即 r8 = r8 + r4 后,r8 中的值即為 PAGE_OFFSET + OFFSET,之前提到 PAGE_OFFSET 是 linux 內核映像的起始 虛擬地址 ,這回加上計算得到的 OFFSET(虛擬地址與物理地址的偏移) 后,即得到了 linux 內核映像在物理記憶體中的實際起始地址了,即,r8 存盤的值目前是 linux 內核映像在物理記憶體中的實際起始地址,搞清楚這些暫存器中的值后,就可以開始分析初始頁表建立的程序了,

開始執行建立頁表的函式: __create_page_tables
__create_page_tables 定義在 arch/arm/kernel/head.S 中:

__create_page_tables:
	pgtbl	r4, r8				@ page table address

	/*
	 * Clear the swapper page table
	 */
	mov	r0, r4
	mov	r3, #0
	add	r6, r0, #PG_DIR_SIZE
1:	str	r3, [r0], #4
	str	r3, [r0], #4
	str	r3, [r0], #4
	str	r3, [r0], #4
	teq	r0, r6
	bne	1b

#ifdef CONFIG_ARM_LPAE
	/*
	 * Build the PGD table (first level) to point to the PMD table. A PGD
	 * entry is 64-bit wide.
	 */
	mov	r0, r4
	add	r3, r4, #0x1000			@ first PMD table address
	orr	r3, r3, #3			@ PGD block type
	mov	r6, #4				@ PTRS_PER_PGD
	mov	r7, #1 << (55 - 32)		@ L_PGD_SWAPPER
1:
#ifdef CONFIG_CPU_ENDIAN_BE8
	str	r7, [r0], #4			@ set top PGD entry bits
	str	r3, [r0], #4			@ set bottom PGD entry bits
#else
	str	r3, [r0], #4			@ set bottom PGD entry bits
	str	r7, [r0], #4			@ set top PGD entry bits
#endif
	add	r3, r3, #0x1000			@ next PMD table
	subs	r6, r6, #1
	bne	1b

	add	r4, r4, #0x1000			@ point to the PMD tables
#ifdef CONFIG_CPU_ENDIAN_BE8
	add	r4, r4, #4			@ we only write the bottom word
#endif
#endif

	ldr	r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

	/*
	 * Create identity mapping to cater for __enable_mmu.
	 * This identity mapping will be removed by paging_init().
	 */
	adr	r0, __turn_mmu_on_loc
	ldmia	r0, {r3, r5, r6}
	sub	r0, r0, r3			@ virt->phys offset
	add	r5, r5, r0			@ phys __turn_mmu_on
	add	r6, r6, r0			@ phys __turn_mmu_on_end
	mov	r5, r5, lsr #SECTION_SHIFT
	mov	r6, r6, lsr #SECTION_SHIFT

1:	orr	r3, r7, r5, lsl #SECTION_SHIFT	@ flags + kernel base
	str	r3, [r4, r5, lsl #PMD_ORDER]	@ identity mapping
	cmp	r5, r6
	addlo	r5, r5, #1			@ next section
	blo	1b

	/*
	 * Map our RAM from the start to the end of the kernel .bss section.
	 */
	add	r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
	ldr	r6, =(_end - 1)
	orr	r3, r8, r7
	add	r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:	str	r3, [r0], #1 << PMD_ORDER
	add	r3, r3, #1 << SECTION_SHIFT
	cmp	r0, r6
	bls	1b

#ifdef CONFIG_XIP_KERNEL
	/*
	 * Map the kernel image separately as it is not located in RAM.
	 */
#define XIP_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
	mov	r3, pc
	mov	r3, r3, lsr #SECTION_SHIFT
	orr	r3, r7, r3, lsl #SECTION_SHIFT
	add	r0, r4,  #(XIP_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
	str	r3, [r0, #((XIP_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
	ldr	r6, =(_edata_loc - 1)
	add	r0, r0, #1 << PMD_ORDER
	add	r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1:	cmp	r0, r6
	add	r3, r3, #1 << SECTION_SHIFT
	strls	r3, [r0], #1 << PMD_ORDER
	bls	1b
#endif

	/*
	 * Then map boot params address in r2 if specified.
	 * We map 2 sections in case the ATAGs/DTB crosses a section boundary.
	 */
	mov	r0, r2, lsr #SECTION_SHIFT
	movs	r0, r0, lsl #SECTION_SHIFT
	subne	r3, r0, r8
	addne	r3, r3, #PAGE_OFFSET
	addne	r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
	orrne	r6, r7, r0
	strne	r6, [r3], #1 << PMD_ORDER
	addne	r6, r6, #1 << SECTION_SHIFT
	strne	r6, [r3]

#if defined(CONFIG_ARM_LPAE) && defined(CONFIG_CPU_ENDIAN_BE8)
	sub	r4, r4, #4			@ Fixup page table pointer
						@ for 64-bit descriptors
#endif

#ifdef CONFIG_DEBUG_LL
#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
	/*
	 * Map in IO space for serial debugging.
	 * This allows debug messages to be output
	 * via a serial console before paging_init.
	 */
	addruart r7, r3, r0

	mov	r3, r3, lsr #SECTION_SHIFT
	mov	r3, r3, lsl #PMD_ORDER

	add	r0, r4, r3
	mov	r3, r7, lsr #SECTION_SHIFT
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
	orr	r3, r7, r3, lsl #SECTION_SHIFT
#ifdef CONFIG_ARM_LPAE
	mov	r7, #1 << (54 - 32)		@ XN
#ifdef CONFIG_CPU_ENDIAN_BE8
	str	r7, [r0], #4
	str	r3, [r0], #4
#else
	str	r3, [r0], #4
	str	r7, [r0], #4
#endif
#else
	orr	r3, r3, #PMD_SECT_XN
	str	r3, [r0], #4
#endif

#else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */
	/* we don't need any serial debugging mappings */
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif

#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
	/*
	 * If we're using the NetWinder or CATS, we also need to map
	 * in the 16550-type serial port for the debug messages
	 */
	add	r0, r4, #0xff000000 >> (SECTION_SHIFT - PMD_ORDER)
	orr	r3, r7, #0x7c000000
	str	r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
	/*
	 * Map in screen at 0x02000000 & SCREEN2_BASE
	 * Similar reasons here - for debug.  This is
	 * only for Acorn RiscPC architectures.
	 */
	add	r0, r4, #0x02000000 >> (SECTION_SHIFT - PMD_ORDER)
	orr	r3, r7, #0x02000000
	str	r3, [r0]
	add	r0, r4, #0xd8000000 >> (SECTION_SHIFT - PMD_ORDER)
	str	r3, [r0]
#endif
#endif
#ifdef CONFIG_ARM_LPAE
	sub	r4, r4, #0x1000		@ point to the PGD table
	mov	r4, r4, lsr #ARCH_PGD_SHIFT
#endif
	ret	lr
ENDPROC(__create_page_tables)

其中 pgtbl 宏的定義為(在 arch/arm/kernel/head.S 中):

.macro	pgtbl, rd, phys
add	\rd, \phys, #TEXT_OFFSET
sub	\rd, \rd, #PG_DIR_SIZE
.endm

swapper_pg_dir 的定義為(在 arch/arm/kernel/head.S 中):

.globl	swapper_pg_dir
.equ	swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

PG_DIR_SIZE 的定義為(在 arch/arm/kernel/head.S 中):

#ifdef CONFIG_ARM_LPAE
	/* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE	0x5000
#define PMD_ORDER	3
#else
#define PG_DIR_SIZE	0x4000
#define PMD_ORDER	2
#endif

未完待續

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/171743.html

標籤:其他

上一篇:坐標上海,我看見這群開發者用熱愛改變世界

下一篇:深入Linux內核(記憶體篇)—頁表映射分頁

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more