← Bootloader DUJINLABS.COM

U-Boot · source analysis

x86 reset entry, Cache-As-RAM과 gd

32-bit cold reset 또는 이전 loader에서 들어온 CPU가 GDT/IDT를 정리하고 Cache-As-RAM에 stack과 global data를 만든 뒤 board_init_f로 들어가는 경계를 읽습니다.

Series
19 / 22
Baseline
v2026.07
Commit
ece349ade297
Source
arch/x86/cpu/start.S:29

cache와 DRAM을 믿을 수 없는 x86 CPU가 어떤 memory를 stack과 gd로 사용해 첫 C 초기화에 도달하는가?

x86는 DRAM 전 stack을 Cache-As-RAM 또는 platform temporary RAM에 둔다. _x86boot_start_start, TPL/SPL/U-Boot proper의 실제 선택을 먼저 고정하고 CAR top에서 stack, gd와 malloc_f가 차지하는 주소를 계산한다.

board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.

객체와 주소가 놓이는 구조

그림 1. x86 reset entry, Cache-As-RAM과 gd에서 입력, 내부 상태, 출력의 경계
입력과 전제내부 상태외부로 공개되는 결과
01BIST and table pointer
02segment registers
03Cache-As-RAM range
04temporary stack
05global_data
06pre-relocation init
INVARIANT

board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다.

CAR stack / gd_t / x86 GDT를 중심에 놓고 왼쪽의 입력이 어떤 검사를 거쳐 오른쪽 결과로 공개되는지 표시했다. 실제 디버깅에서는 각 블록의 주소와 크기를 로그에 대입한다.

실행 흐름

그림 2. 정상 경로의 주요 호출과 상태 전달
cold reset or previous loader
32-bit protected entry
GDT and blank IDT
car_init
CAR stack
gd and malloc_f
board_init_f

화살표는 단순 호출 순서만 뜻하지 않는다. 각 단계가 성공을 반환할 때 다음 단계가 읽을 수 있는 상태가 무엇인지 함께 확인한다. 오류 반환이 발생하면 바로 다음 화살표로 진행하지 않고 해당 단계의 정리 경로를 따라간다.

단계별 입력과 출력

호출 순서를 함수 이름으로만 외우지 않고, 각 단계가 무엇을 받아 무엇을 공개하는지 적은 표다. 실제 소스에서 생산 필드가 다르면 표를 고치는 방식으로 사용한다.

#단계진입 시 신뢰할 상태성공 뒤 남아야 할 상태다음 소비자
01cold reset or previous loaderDRAM과 정상 exception handler가 없는 32-bit 초기 문맥BIST and table pointer32-bit protected entry
0232-bit protected entrycold reset or previous loader 완료 상태segment registersGDT and blank IDT
03GDT and blank IDT32-bit protected entry 완료 상태Cache-As-RAM rangecar_init
04car_initGDT and blank IDT 완료 상태temporary stackCAR stack
05CAR stackcar_init 완료 상태global_datagd and malloc_f
06gd and malloc_fCAR stack 완료 상태pre-relocation initboard_init_f
07board_init_fgd and malloc_f 완료 상태pre-relocation init최종 x86 U-Boot pre-relocation image 또는 다음 stage

공통 불변 조건: board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다. 한 단계의 출력이 다음 단계의 입력 조건을 만족하지 않으면 오류가 실제로 드러난 위치보다 앞의 생산 단계부터 조사한다.

주소와 객체의 이동을 그림으로 읽기

아래 그림은 호출 이름보다 주소, 객체 수명과 handoff 경계를 먼저 볼 수 있도록 구성했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, map과 linker symbol을 대입한다.

그림 3. Cache-As-RAM 안의 초기 stack과 global data 배치

크기는 개념 비율이다. 실제 분석에서는 map, symbol과 runtime 출력의 시작·끝 주소를 대입한다.

높은 주소↓ 주소 감소
MRC / FSP temporary dataplatform이 CAR top에 요구하는 보존 영역
x86 GDT + gd_tFS:0이 가리키는 global data
malloc_frelocation 전 early allocation
active stackESP에서 아래 주소로 성장
unused CAR marginworst-case call depth 여유
낮은 주소예약 cursor 진행 방향
그림 4. x86 U-Boot의 32-bit entry 두 경로

두 열의 객체는 이름이 비슷해도 생성 시점, 사용 가능한 API와 해제 책임이 다르다.

Cold reset `_start`

01start16에서 protected mode 진입

02EAX의 BIST 보존

03ECX table pointer

04GDT/segment 재확인

05car_init으로 이동

Fail-safe `_x86boot_start`

01coreboot/EFI 등 previous loader

02BIST는 0으로 간주

03cache 상속 상태 제거

0432-bit bootstrap 재정리

05label 1에서 공통 경로

그림 5. x86 reset에서 첫 C 초기화까지

호출 이름만 나열하지 않고, 각 단계가 다음 단계에 넘기는 상태를 기준으로 실행 순서를 표시한다.

01_start / _x86boot_start
02GDT와 segment
03blank IDT
04car_init
05CAR stack
06gd + malloc_f
07board_init_f

원본 코드

아래 코드는 U-Boot v2026.07의 arch/x86/cpu/start.S에서 29-174줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.

commitece349ade2973e220f524ce59e59711cc919263fsymbol_start:
29#include <generated/asm-offsets.h>
30#include <linux/linkage.h>
31
32.section .text.start
33.code32
34.globl _start
35.type _start, @function
36.globl _x86boot_start
37_x86boot_start:
38	/*
39	 * This is the fail-safe 32-bit bootstrap entry point.
40	 *
41	 * This code is used when booting from another boot loader like
42	 * coreboot or EFI. So we repeat some of the same init found in
43	 * start16.
44	 */
45	cli
46	cld
47
48	/* Turn off cache (this might require a 486-class CPU) */
49	movl	%cr0, %eax
50	orl	$(X86_CR0_NW | X86_CR0_CD), %eax
51	movl	%eax, %cr0
52	wbinvd
53
54	/*
55	 * Zero the BIST (Built-In Self Test) value since we don't have it.
56	 * It must be 0 or the previous loader would have reported an error.
57	 */
58	movl	$0, %ebp
59
60	jmp	1f
61
62	/* Add a way for tools to discover the _start entry point */
63	.align	4
64	.long	0x12345678
65_start:
66	/* This is the 32-bit cold-reset entry point, coming from start16 */
67
68	/* Save BIST */
69	movl	%eax, %ebp
701:
71
72	/* Save table pointer */
73	movl	%ecx, %esi
74
75#ifdef CONFIG_X86_LOAD_FROM_32_BIT
76	lgdt	gdt_ptr2
77#endif
78
79	/* Load the segment registers to match the GDT loaded in start16.S */
80	movl	$(X86_GDT_ENTRY_32BIT_DS * X86_GDT_ENTRY_SIZE), %eax
81	movw	%ax, %fs
82	movw	%ax, %ds
83	movw	%ax, %gs
84	movw	%ax, %es
85	movw	%ax, %ss
86
87	/* Clear the interrupt vectors */
88	lidt	blank_idt_ptr
89
90#ifdef CONFIG_USE_EARLY_BOARD_INIT
91	/*
92	 * Critical early platform init - generally not used, we prefer init
93	 * to happen later when we have a console, in case something goes
94	 * wrong.
95	 */
96	jmp	early_board_init
97.globl early_board_init_ret
98early_board_init_ret:
99#endif
100
101	post_code(POST_START)
102
103	/* Initialise Cache-As-RAM */
104	jmp	car_init
105.globl car_init_ret
106car_init_ret:
107#ifdef CONFIG_USE_CAR
108	/*
109	 * We now have CONFIG_SYS_CAR_SIZE bytes of Cache-As-RAM (or SRAM,
110	 * or fully initialised SDRAM - we really don't care which)
111	 * starting at CONFIG_SYS_CAR_ADDR to be used as a temporary stack
112	 * and early malloc() area. The MRC requires some space at the top.
113	 *
114	 * Stack grows down from top of CAR. We have:
115	 *
116	 * top-> CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE
117	 *	MRC area
118	 *	global_data with x86 global descriptor table
119	 *	early malloc area
120	 *	stack
121	 * bottom-> CONFIG_SYS_CAR_ADDR
122	 */
123	movl	$(CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE - 4), %esp
124#ifdef CONFIG_DCACHE_RAM_MRC_VAR_SIZE
125	subl	$CONFIG_DCACHE_RAM_MRC_VAR_SIZE, %esp
126#endif
127#else
128	/*
129	 * Instructions for FSP1, but not FSP2:
130	 * U-Boot enters here twice. For the first time it comes from
131	 * car_init_done() with esp points to a temporary stack and esi
132	 * set to zero. For the second time it comes from fsp_init_done()
133	 * with esi holding the HOB list address returned by the FSP.
134	 */
135#endif
136	/* Set up global data */
137	mov	%esp, %eax
138	call	board_init_f_alloc_reserve
139	mov	%eax, %esp
140	call	board_init_f_init_reserve
141
142#ifdef CONFIG_DEBUG_UART
143	call	debug_uart_init
144#endif
145
146	/* Get address of global_data */
147	mov	%fs:0, %edx
148#if defined(CONFIG_USE_HOB) && !defined(CONFIG_USE_CAR)
149	/* Store the HOB list if we have one */
150	test	%esi, %esi
151	jz	skip_hob
152	movl	%esi, GD_HOB_LIST(%edx)
153
154#ifdef CONFIG_HAVE_FSP
155	/*
156	 * After fsp_init() returns, the stack has already been switched to a
157	 * place within system memory as defined by CONFIG_FSP_TEMP_RAM_ADDR.
158	 * Enlarge the size of malloc() pool before relocation since we have
159	 * plenty of memory now.
160	 */
161	subl	$CONFIG_FSP_SYS_MALLOC_F_LEN, %esp
162	movl	%esp, GD_MALLOC_BASE(%edx)
163#endif
164skip_hob:
165#else
166	/* Store table pointer */
167	movl	%esi, GD_TABLE(%edx)
168#endif
169	/* Store BIST */
170	movl	%ebp, GD_BIST(%edx)
171
172	/* Set parameter to board_init_f() to boot flags */
173	post_code(POST_START_DONE)
174	xorl	%eax, %eax

29-174줄 해설

원본에 보이는 모든 줄을 순서대로 설명한다. 빈 줄도 block 경계로 남겨, 코드와 설명의 위치가 어긋나지 않게 했다.

29#include <generated/asm-offsets.h>

#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

30#include <linux/linkage.h>

#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

31(빈 줄)

#include <linux/linkage.h>까지의 동작과 .section .text.start에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 Cache-As-RAM range 상태가 아래 블록의 입력으로 사용되는 경계다.

32.section .text.start

원본 32번 줄의 .section .text.start는 앞의 이전 block 경계 결과를 받아 다음 .code32로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

33.code32

원본 33번 줄의 .code32는 앞의 .section .text.start 결과를 받아 다음 .globl _start로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

34.globl _start

원본 34번 줄의 .globl _start는 앞의 .code32 결과를 받아 다음 .type _start, @function로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

35.type _start, @function

원본 35번 줄의 .type _start, @function는 앞의 .globl _start 결과를 받아 다음 .globl _x86boot_start로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

36.globl _x86boot_start

원본 36번 줄의 .globl _x86boot_start는 앞의 .type _start, @function 결과를 받아 다음 _x86boot_start:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

37_x86boot_start:

'_x86boot_start' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

38/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

39* This is the fail-safe 32-bit bootstrap entry point.

원본 주석이 'This is the fail-safe 32-bit bootstrap entry point.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

40*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

41* This code is used when booting from another boot loader like

원본 주석이 'This code is used when booting from another boot loader like'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

42* coreboot or EFI. So we repeat some of the same init found in

원본 주석이 'coreboot or EFI. So we repeat some of the same init found in'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

43* start16.

원본 주석이 'start16.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

44*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

45cli

원본 45번 줄의 cli는 앞의 */ 결과를 받아 다음 cld로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

46cld

원본 46번 줄의 cld는 앞의 cli 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

47(빈 줄)

cld까지의 동작과 /* Turn off cache (this might require a 486-class CPU) */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

48/* Turn off cache (this might require a 486-class CPU) */

원본 주석이 'Turn off cache (this might require a 486-class CPU)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

49movl %cr0, %eax

원본 49번 줄의 movl %cr0, %eax는 앞의 /* Turn off cache (this might require a 486-class CPU) */ 결과를 받아 다음 orl $(X86_CR0_NW | X86_CR0_CD), %eax로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

50orl $(X86_CR0_NW | X86_CR0_CD), %eax

원본 50번 줄의 orl $(X86_CR0_NW | X86_CR0_CD), %eax는 앞의 movl %cr0, %eax 결과를 받아 다음 movl %eax, %cr0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

51movl %eax, %cr0

원본 51번 줄의 movl %eax, %cr0는 앞의 orl $(X86_CR0_NW | X86_CR0_CD), %eax 결과를 받아 다음 wbinvd로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

52wbinvd

원본 52번 줄의 wbinvd는 앞의 movl %eax, %cr0 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

53(빈 줄)

wbinvd까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

54/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

55* Zero the BIST (Built-In Self Test) value since we don't have it.

원본 주석이 'Zero the BIST (Built-In Self Test) value since we don't have it.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

56* It must be 0 or the previous loader would have reported an error.

원본 주석이 'It must be 0 or the previous loader would have reported an error.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

57*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

58movl $0, %ebp

원본 58번 줄의 movl $0, %ebp는 앞의 */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

59(빈 줄)

movl $0, %ebp까지의 동작과 jmp 1f에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

60jmp 1f

원본 60번 줄의 jmp 1f는 앞의 이전 block 경계 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

61(빈 줄)

jmp 1f까지의 동작과 /* Add a way for tools to discover the _start entry point */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

62/* Add a way for tools to discover the _start entry point */

원본 주석이 'Add a way for tools to discover the _start entry point'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

63.align 4

원본 63번 줄의 .align 4는 앞의 /* Add a way for tools to discover the _start entry point */ 결과를 받아 다음 .long 0x12345678로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

64.long 0x12345678

원본 64번 줄의 .long 0x12345678는 앞의 .align 4 결과를 받아 다음 _start:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

65_start:

16-bit cold-reset path가 32-bit protected mode를 만든 뒤 도착하는 고정 entry다. fail-safe _x86boot_start와 label 1에서 합쳐진다.

66/* This is the 32-bit cold-reset entry point, coming from start16 */

원본 주석이 'This is the 32-bit cold-reset entry point, coming from start16'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

67(빈 줄)

/* This is the 32-bit cold-reset entry point, coming from start16 */까지의 동작과 /* Save BIST */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

68/* Save BIST */

원본 주석이 'Save BIST'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

69movl %eax, %ebp

cold reset 경로에서 firmware/CPU BIST 결과를 callee-saved EBP에 보존한다.

701:

원본 70번 줄의 1:는 앞의 movl %eax, %ebp 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

71(빈 줄)

1:까지의 동작과 /* Save table pointer */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

72/* Save table pointer */

원본 주석이 'Save table pointer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

73movl %ecx, %esi

coreboot table 또는 firmware가 넘긴 table pointer를 board_init_f 전까지 보존한다.

74(빈 줄)

movl %ecx, %esi까지의 동작과 #ifdef CONFIG_X86_LOAD_FROM_32_BIT에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

75#ifdef CONFIG_X86_LOAD_FROM_32_BIT

#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_X86_LOAD_FROM_32_BIT이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

76lgdt gdt_ptr2

원본 76번 줄의 lgdt gdt_ptr2는 앞의 #ifdef CONFIG_X86_LOAD_FROM_32_BIT 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

77#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

78(빈 줄)

#endif까지의 동작과 /* Load the segment registers to match the GDT loaded in start16.S */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

79/* Load the segment registers to match the GDT loaded in start16.S */

원본 주석이 'Load the segment registers to match the GDT loaded in start16.S'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

80movl $(X86_GDT_ENTRY_32BIT_DS * X86_GDT_ENTRY_SIZE), %eax

원본 80번 줄의 movl $(X86_GDT_ENTRY_32BIT_DS * X86_GDT_ENTRY_SIZE), %eax는 앞의 /* Load the segment registers to match the GDT loaded in start16.S */ 결과를 받아 다음 movw %ax, %fs로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

81movw %ax, %fs

원본 81번 줄의 movw %ax, %fs는 앞의 movl $(X86_GDT_ENTRY_32BIT_DS * X86_GDT_ENTRY_SIZE), %eax 결과를 받아 다음 movw %ax, %ds로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

82movw %ax, %ds

원본 82번 줄의 movw %ax, %ds는 앞의 movw %ax, %fs 결과를 받아 다음 movw %ax, %gs로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

83movw %ax, %gs

원본 83번 줄의 movw %ax, %gs는 앞의 movw %ax, %ds 결과를 받아 다음 movw %ax, %es로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

84movw %ax, %es

원본 84번 줄의 movw %ax, %es는 앞의 movw %ax, %gs 결과를 받아 다음 movw %ax, %ss로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

85movw %ax, %ss

원본 85번 줄의 movw %ax, %ss는 앞의 movw %ax, %es 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

86(빈 줄)

movw %ax, %ss까지의 동작과 /* Clear the interrupt vectors */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

87/* Clear the interrupt vectors */

원본 주석이 'Clear the interrupt vectors'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

88lidt blank_idt_ptr

limit/base가 0인 IDT를 적재한다. 정상 handler를 준비한 것이 아니라 예기치 않은 interrupt를 차단하는 초기 상태다.

89(빈 줄)

lidt blank_idt_ptr까지의 동작과 #ifdef CONFIG_USE_EARLY_BOARD_INIT에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

90#ifdef CONFIG_USE_EARLY_BOARD_INIT

#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_USE_EARLY_BOARD_INIT이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

91/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

92* Critical early platform init - generally not used, we prefer init

원본 주석이 'Critical early platform init - generally not used, we prefer init'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

93* to happen later when we have a console, in case something goes

원본 주석이 'to happen later when we have a console, in case something goes'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

94* wrong.

원본 주석이 'wrong.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

95*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

96jmp early_board_init

원본 96번 줄의 jmp early_board_init는 앞의 */ 결과를 받아 다음 .globl early_board_init_ret로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

97.globl early_board_init_ret

원본 97번 줄의 .globl early_board_init_ret는 앞의 jmp early_board_init 결과를 받아 다음 early_board_init_ret:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

98early_board_init_ret:

'early_board_init_ret' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

99#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

100(빈 줄)

#endif까지의 동작과 post_code(POST_START)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

101post_code(POST_START)

post_code(POST_START)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

102(빈 줄)

post_code(POST_START)까지의 동작과 /* Initialise Cache-As-RAM */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

103/* Initialise Cache-As-RAM */

원본 주석이 'Initialise Cache-As-RAM'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

104jmp car_init

platform별 Cache-As-RAM 또는 temporary RAM 생산자로 비복귀 진입한다.

105.globl car_init_ret

원본 105번 줄의 .globl car_init_ret는 앞의 jmp car_init 결과를 받아 다음 car_init_ret:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

106car_init_ret:

'car_init_ret' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

107#ifdef CONFIG_USE_CAR

#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_USE_CAR이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

108/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

109* We now have CONFIG_SYS_CAR_SIZE bytes of Cache-As-RAM (or SRAM,

원본 주석이 'We now have CONFIG_SYS_CAR_SIZE bytes of Cache-As-RAM (or SRAM,'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

110* or fully initialised SDRAM - we really don't care which)

원본 주석이 'or fully initialised SDRAM - we really don't care which)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

111* starting at CONFIG_SYS_CAR_ADDR to be used as a temporary stack

원본 주석이 'starting at CONFIG_SYS_CAR_ADDR to be used as a temporary stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

112* and early malloc() area. The MRC requires some space at the top.

원본 주석이 'and early malloc() area. The MRC requires some space at the top.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

113*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

114* Stack grows down from top of CAR. We have:

원본 주석이 'Stack grows down from top of CAR. We have:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

115*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

116* top-> CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE

원본 주석이 'top-> CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

117* MRC area

원본 주석이 'MRC area'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

118* global_data with x86 global descriptor table

원본 주석이 'global_data with x86 global descriptor table'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

119* early malloc area

원본 주석이 'early malloc area'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

120* stack

원본 주석이 'stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

121* bottom-> CONFIG_SYS_CAR_ADDR

원본 주석이 'bottom-> CONFIG_SYS_CAR_ADDR'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

122*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

123movl $(CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE - 4), %esp

CAR top에서 32-bit stack을 아래 방향으로 시작한다.

124#ifdef CONFIG_DCACHE_RAM_MRC_VAR_SIZE

#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_DCACHE_RAM_MRC_VAR_SIZE이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

125subl $CONFIG_DCACHE_RAM_MRC_VAR_SIZE, %esp

원본 125번 줄의 subl $CONFIG_DCACHE_RAM_MRC_VAR_SIZE, %esp는 앞의 #ifdef CONFIG_DCACHE_RAM_MRC_VAR_SIZE 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

126#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

127#else

#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

128/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

129* Instructions for FSP1, but not FSP2:

원본 주석이 'Instructions for FSP1, but not FSP2:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

130* U-Boot enters here twice. For the first time it comes from

원본 주석이 'U-Boot enters here twice. For the first time it comes from'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

131* car_init_done() with esp points to a temporary stack and esi

원본 주석이 'car_init_done() with esp points to a temporary stack and esi'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

132* set to zero. For the second time it comes from fsp_init_done()

원본 주석이 'set to zero. For the second time it comes from fsp_init_done()'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

133* with esi holding the HOB list address returned by the FSP.

원본 주석이 'with esi holding the HOB list address returned by the FSP.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

134*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

135#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

136/* Set up global data */

원본 주석이 'Set up global data'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

137mov %esp, %eax

mov %esp, %eax 명령이 register, memory 또는 PC를 직접 바꾼다. DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

138call board_init_f_alloc_reserve

현재 stack cursor 아래에 gd와 malloc_f 등 early runtime 영역을 예약한다.

139mov %eax, %esp

mov %eax, %esp 명령이 register, memory 또는 PC를 직접 바꾼다. DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

140call board_init_f_init_reserve

원본 140번 줄의 call board_init_f_init_reserve는 앞의 mov %eax, %esp 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

141(빈 줄)

call board_init_f_init_reserve까지의 동작과 #ifdef CONFIG_DEBUG_UART에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

142#ifdef CONFIG_DEBUG_UART

#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_DEBUG_UART이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

143call debug_uart_init

원본 143번 줄의 call debug_uart_init는 앞의 #ifdef CONFIG_DEBUG_UART 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

144#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

145(빈 줄)

#endif까지의 동작과 /* Get address of global_data */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

146/* Get address of global_data */

원본 주석이 'Get address of global_data'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

147mov %fs:0, %edx

x86 segment 기반 global-data pointer를 읽는다. FS selector/GDT가 이 주소 계산의 일부다.

148#if defined(CONFIG_USE_HOB) && !defined(CONFIG_USE_CAR)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_USE_HOB, CONFIG_USE_CAR이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

149/* Store the HOB list if we have one */

원본 주석이 'Store the HOB list if we have one'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

150test %esi, %esi

원본 150번 줄의 test %esi, %esi는 앞의 /* Store the HOB list if we have one */ 결과를 받아 다음 jz skip_hob로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

151jz skip_hob

원본 151번 줄의 jz skip_hob는 앞의 test %esi, %esi 결과를 받아 다음 movl %esi, GD_HOB_LIST(%edx)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

152movl %esi, GD_HOB_LIST(%edx)

GD_HOB_LIST(%edx)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

153(빈 줄)

movl %esi, GD_HOB_LIST(%edx)까지의 동작과 #ifdef CONFIG_HAVE_FSP에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

154#ifdef CONFIG_HAVE_FSP

#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_HAVE_FSP이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

155/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

156* After fsp_init() returns, the stack has already been switched to a

원본 주석이 'After fsp_init() returns, the stack has already been switched to a'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

157* place within system memory as defined by CONFIG_FSP_TEMP_RAM_ADDR.

원본 주석이 'place within system memory as defined by CONFIG_FSP_TEMP_RAM_ADDR.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

158* Enlarge the size of malloc() pool before relocation since we have

원본 주석이 'Enlarge the size of malloc() pool before relocation since we have'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

159* plenty of memory now.

원본 주석이 'plenty of memory now.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

160*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

161subl $CONFIG_FSP_SYS_MALLOC_F_LEN, %esp

원본 161번 줄의 subl $CONFIG_FSP_SYS_MALLOC_F_LEN, %esp는 앞의 */ 결과를 받아 다음 movl %esp, GD_MALLOC_BASE(%edx)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

162movl %esp, GD_MALLOC_BASE(%edx)

GD_MALLOC_BASE(%edx)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

163#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

164skip_hob:

'skip_hob' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

165#else

#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

166/* Store table pointer */

원본 주석이 'Store table pointer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

167movl %esi, GD_TABLE(%edx)

GD_TABLE(%edx)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

168#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

169/* Store BIST */

원본 주석이 'Store BIST'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

170movl %ebp, GD_BIST(%edx)

GD_BIST(%edx)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

171(빈 줄)

movl %ebp, GD_BIST(%edx)까지의 동작과 /* Set parameter to board_init_f() to boot flags */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

172/* Set parameter to board_init_f() to boot flags */

원본 주석이 'Set parameter to board_init_f() to boot flags'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

173post_code(POST_START_DONE)

post_code(POST_START_DONE)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

174xorl %eax, %eax

원본 174번 줄의 xorl %eax, %eax는 앞의 post_code(POST_START_DONE) 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

함께 읽어야 하는 원본 코드

첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.

01. 64-bit loader가 기존 stack에서 직접 시작하는 entry

arch/x86/cpu/start64.S 7-50줄이다.

filearch/x86/cpu/start64.Sanchor_start:
7 */
8
9#include <config.h>
10
11.section .text.start
12.code64
13.globl _start
14.type _start, @function
15_start:
16	/* Set up memory using the existing stack */
17	mov	%rsp, %rdi
18	call	board_init_f_alloc_reserve
19
20	mov	%rax, %rsp
21	mov	%rsp, %rdi
22
23	call	board_init_f_init_reserve
24
25	xor	%rdi, %rdi
26	call	board_init_f
27	call	board_init_f_r
28
29	/* Should not return here */
30	jmp	.
31
32.globl board_init_f_r_trampoline64
33.type board_init_f_r_trampoline64, @function
34board_init_f_r_trampoline64:
35	/*
36	 * SDRAM has been initialised, U-Boot code has been copied into
37	 * RAM, BSS has been cleared and relocation adjustments have been
38	 * made. It is now time to jump into the in-RAM copy of U-Boot
39	 *
40	 * %eax = Address of top of new stack
41	 */
42
43	/* Stack grows down from top of SDRAM */
44	movq	%rsi, %rsp
45
46	/* New gd is in rdi */
47
48	/* Re-enter U-Boot by calling board_init_f_r() */
49	call	board_init_f_r
50

7-50줄 해설

7*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

8(빈 줄)

*/까지의 동작과 #include <config.h>에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 segment registers 상태가 아래 블록의 입력으로 사용되는 경계다.

9#include <config.h>

#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

10(빈 줄)

#include <config.h>까지의 동작과 .section .text.start에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 temporary stack 상태가 아래 블록의 입력으로 사용되는 경계다.

11.section .text.start

원본 11번 줄의 .section .text.start는 앞의 이전 block 경계 결과를 받아 다음 .code64로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

12.code64

원본 12번 줄의 .code64는 앞의 .section .text.start 결과를 받아 다음 .globl _start로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

13.globl _start

원본 13번 줄의 .globl _start는 앞의 .code64 결과를 받아 다음 .type _start, @function로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

14.type _start, @function

원본 14번 줄의 .type _start, @function는 앞의 .globl _start 결과를 받아 다음 _start:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

15_start:

16-bit cold-reset path가 32-bit protected mode를 만든 뒤 도착하는 고정 entry다. fail-safe _x86boot_start와 label 1에서 합쳐진다.

16/* Set up memory using the existing stack */

원본 주석이 'Set up memory using the existing stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

17mov %rsp, %rdi

mov %rsp, %rdi 명령이 register, memory 또는 PC를 직접 바꾼다. DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

18call board_init_f_alloc_reserve

현재 stack cursor 아래에 gd와 malloc_f 등 early runtime 영역을 예약한다.

19(빈 줄)

call board_init_f_alloc_reserve까지의 동작과 mov %rax, %rsp에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

20mov %rax, %rsp

mov %rax, %rsp 명령이 register, memory 또는 PC를 직접 바꾼다. DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

21mov %rsp, %rdi

mov %rsp, %rdi 명령이 register, memory 또는 PC를 직접 바꾼다. DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

22(빈 줄)

mov %rsp, %rdi까지의 동작과 call board_init_f_init_reserve에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

23call board_init_f_init_reserve

원본 23번 줄의 call board_init_f_init_reserve는 앞의 이전 block 경계 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

24(빈 줄)

call board_init_f_init_reserve까지의 동작과 xor %rdi, %rdi에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

25xor %rdi, %rdi

원본 25번 줄의 xor %rdi, %rdi는 앞의 이전 block 경계 결과를 받아 다음 call board_init_f로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

26call board_init_f

DRAM 발견, top-down reservation과 x86 copy/fixup initcall을 실행한다. 정상 경로는 이 call로 돌아오지 않는다.

27call board_init_f_r

원본 27번 줄의 call board_init_f_r는 앞의 call board_init_f 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

28(빈 줄)

call board_init_f_r까지의 동작과 /* Should not return here */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

29/* Should not return here */

원본 주석이 'Should not return here'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

30jmp .

원본 30번 줄의 jmp .는 앞의 /* Should not return here */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

31(빈 줄)

jmp .까지의 동작과 .globl board_init_f_r_trampoline64에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

32.globl board_init_f_r_trampoline64

원본 32번 줄의 .globl board_init_f_r_trampoline64는 앞의 이전 block 경계 결과를 받아 다음 .type board_init_f_r_trampoline64, @function로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

33.type board_init_f_r_trampoline64, @function

원본 33번 줄의 .type board_init_f_r_trampoline64, @function는 앞의 .globl board_init_f_r_trampoline64 결과를 받아 다음 board_init_f_r_trampoline64:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

34board_init_f_r_trampoline64:

'board_init_f_r_trampoline64' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

35/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

36* SDRAM has been initialised, U-Boot code has been copied into

원본 주석이 'SDRAM has been initialised, U-Boot code has been copied into'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

37* RAM, BSS has been cleared and relocation adjustments have been

원본 주석이 'RAM, BSS has been cleared and relocation adjustments have been'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

38* made. It is now time to jump into the in-RAM copy of U-Boot

원본 주석이 'made. It is now time to jump into the in-RAM copy of U-Boot'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

39*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

40* %eax = Address of top of new stack

원본 주석이 '%eax = Address of top of new stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

41*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

42(빈 줄)

*/까지의 동작과 /* Stack grows down from top of SDRAM */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

43/* Stack grows down from top of SDRAM */

원본 주석이 'Stack grows down from top of SDRAM'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

44movq %rsi, %rsp

원본 44번 줄의 movq %rsi, %rsp는 앞의 /* Stack grows down from top of SDRAM */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

45(빈 줄)

movq %rsi, %rsp까지의 동작과 /* New gd is in rdi */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

46/* New gd is in rdi */

원본 주석이 'New gd is in rdi'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

47(빈 줄)

/* New gd is in rdi */까지의 동작과 /* Re-enter U-Boot by calling board_init_f_r() */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

48/* Re-enter U-Boot by calling board_init_f_r() */

원본 주석이 'Re-enter U-Boot by calling board_init_f_r()'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

49call board_init_f_r

원본 49번 줄의 call board_init_f_r는 앞의 /* Re-enter U-Boot by calling board_init_f_r() */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

50(빈 줄)

call board_init_f_r까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

02. CAR top에서 gd와 malloc_f를 예약하는 공통 helper

common/init/board_init.c 74-135줄이다.

filecommon/init/board_init.canchorulong board_init_f_alloc_reserve(ulong top)
74 *   alignment constraint of the architecture for which we are bulding.
75 *
76 *  - GD is allocated last, so that the return value of this functions is
77 *   both the bottom of the reserved area and the address of GD, should
78 *   the calling context need it.
79 */
80
81ulong board_init_f_alloc_reserve(ulong top)
82{
83	/* Reserve early malloc arena */
84#ifndef CFG_MALLOC_F_ADDR
85#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
86	top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
87#endif
88#endif
89	/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
90	top = rounddown(top-sizeof(struct global_data), 16);
91
92	return top;
93}
94
95/*
96 * Initialize reserved space (which has been safely allocated on the C
97 * stack from the C runtime environment handling code).
98 *
99 * Notes:
100 *
101 * Actual reservation was done by the caller; the locations from base
102 * to base+size-1 (where 'size' is the value returned by the allocation
103 * function above) can be accessed freely without risk of corrupting the
104 * C runtime environment.
105 *
106 * IMPORTANT:
107 *
108 * Upon return from the allocation function above, on some architectures
109 * the caller will set gd to the lowest reserved location. Therefore, in
110 * this initialization function, the global data MUST be placed at base.
111 *
112 * ALSO IMPORTANT:
113 *
114 * On some architectures, gd will already be good when entering this
115 * function. On others, it will only be good once arch_setup_gd() returns.
116 * Therefore, global data accesses must be done:
117 *
118 * - through gd_ptr if before the call to arch_setup_gd();
119 *
120 * - through gd once arch_setup_gd() has been called.
121 *
122 * Do not use 'gd->' until arch_setup_gd() has been called!
123 *
124 * IMPORTANT TOO:
125 *
126 * Initialization for each "chunk" (GD, early malloc arena...) ends with
127 * an incrementation line of the form 'base += <some size>'. The last of
128 * these incrementations seems useless, as base will not be used any
129 * more after this incrementation; but if/when a new "chunk" is appended,
130 * this increment will be essential as it will give base right value for
131 * this new chunk (which will have to end with its own incrementation
132 * statement). Besides, the compiler's optimizer will silently detect
133 * and remove the last base incrementation, therefore leaving that last
134 * (seemingly useless) incrementation causes no code increase.
135 */

74-135줄 해설

74* alignment constraint of the architecture for which we are bulding.

원본 주석이 'alignment constraint of the architecture for which we are bulding.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

75*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

76* - GD is allocated last, so that the return value of this functions is

원본 주석이 '- GD is allocated last, so that the return value of this functions is'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

77* both the bottom of the reserved area and the address of GD, should

원본 주석이 'both the bottom of the reserved area and the address of GD, should'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

78* the calling context need it.

원본 주석이 'the calling context need it.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

79*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

80(빈 줄)

*/까지의 동작과 ulong board_init_f_alloc_reserve(ulong top)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

81ulong board_init_f_alloc_reserve(ulong top)

board_init_f_alloc_reserve 함수 정의가 시작된다. 입력은 ulong top이며, DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.

82{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 CAR stack / gd_t / x86 GDT의 수명에 맞춰 묶어 읽는다.

83/* Reserve early malloc arena */

원본 주석이 'Reserve early malloc arena'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

84#ifndef CFG_MALLOC_F_ADDR

#ifndef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

85#if CONFIG_IS_ENABLED(SYS_MALLOC_F)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

86top -= CONFIG_VAL(SYS_MALLOC_F_LEN);

topCONFIG_VAL(SYS_MALLOC_F_LEN)를 -= 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

87#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

88#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

89/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */

원본 주석이 'LAST : reserve GD (rounded up to a multiple of 16 bytes)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

90top = rounddown(top-sizeof(struct global_data), 16);

top = rounddown(top-sizeof(struct global_data), 16)를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 CAR stack / gd_t / x86 GDT를 직접 소유하는지 pointer만 빌리는지, DRAM과 정상 exception handler가 없는 32-bit 초기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.

91(빈 줄)

top = rounddown(top-sizeof(struct global_data), 16);까지의 동작과 return top;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

92return top;

top를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 CAR stack / gd_t / x86 GDT를 정리하거나 다음 단계로 진행하는지 확인한다.

93}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

94(빈 줄)

}까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

95/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

96* Initialize reserved space (which has been safely allocated on the C

원본 주석이 'Initialize reserved space (which has been safely allocated on the C'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

97* stack from the C runtime environment handling code).

원본 주석이 'stack from the C runtime environment handling code).'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

98*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

99* Notes:

원본 주석이 'Notes:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

100*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

101* Actual reservation was done by the caller; the locations from base

원본 주석이 'Actual reservation was done by the caller; the locations from base'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

102* to base+size-1 (where 'size' is the value returned by the allocation

원본 주석이 'to base+size-1 (where 'size' is the value returned by the allocation'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

103* function above) can be accessed freely without risk of corrupting the

원본 주석이 'function above) can be accessed freely without risk of corrupting the'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

104* C runtime environment.

원본 주석이 'C runtime environment.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

105*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

106* IMPORTANT:

원본 주석이 'IMPORTANT:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

107*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

108* Upon return from the allocation function above, on some architectures

원본 주석이 'Upon return from the allocation function above, on some architectures'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

109* the caller will set gd to the lowest reserved location. Therefore, in

원본 주석이 'the caller will set gd to the lowest reserved location. Therefore, in'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

110* this initialization function, the global data MUST be placed at base.

원본 주석이 'this initialization function, the global data MUST be placed at base.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

111*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

112* ALSO IMPORTANT:

원본 주석이 'ALSO IMPORTANT:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

113*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

114* On some architectures, gd will already be good when entering this

원본 주석이 'On some architectures, gd will already be good when entering this'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

115* function. On others, it will only be good once arch_setup_gd() returns.

원본 주석이 'function. On others, it will only be good once arch_setup_gd() returns.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

116* Therefore, global data accesses must be done:

원본 주석이 'Therefore, global data accesses must be done:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

117*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

118* - through gd_ptr if before the call to arch_setup_gd();

원본 주석이 '- through gd_ptr if before the call to arch_setup_gd();'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

119*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

120* - through gd once arch_setup_gd() has been called.

원본 주석이 '- through gd once arch_setup_gd() has been called.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

121*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

122* Do not use 'gd->' until arch_setup_gd() has been called!

원본 주석이 'Do not use 'gd->' until arch_setup_gd() has been called!'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

123*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

124* IMPORTANT TOO:

원본 주석이 'IMPORTANT TOO:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

125*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

126* Initialization for each "chunk" (GD, early malloc arena...) ends with

원본 주석이 'Initialization for each "chunk" (GD, early malloc arena...) ends with'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

127* an incrementation line of the form 'base += <some size>'. The last of

원본 주석이 'an incrementation line of the form 'base += <some size>'. The last of'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

128* these incrementations seems useless, as base will not be used any

원본 주석이 'these incrementations seems useless, as base will not be used any'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

129* more after this incrementation; but if/when a new "chunk" is appended,

원본 주석이 'more after this incrementation; but if/when a new "chunk" is appended,'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

130* this increment will be essential as it will give base right value for

원본 주석이 'this increment will be essential as it will give base right value for'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

131* this new chunk (which will have to end with its own incrementation

원본 주석이 'this new chunk (which will have to end with its own incrementation'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

132* statement). Besides, the compiler's optimizer will silently detect

원본 주석이 'statement). Besides, the compiler's optimizer will silently detect'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

133* and remove the last base incrementation, therefore leaving that last

원본 주석이 'and remove the last base incrementation, therefore leaving that last'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

134* (seemingly useless) incrementation causes no code increase.

원본 주석이 '(seemingly useless) incrementation causes no code increase.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

135*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

03. x86이 FS segment에 gd pointer를 연결하는 초기화

arch/x86/cpu/i386/cpu.c 118-167줄이다.

filearch/x86/cpu/i386/cpu.canchorvoid arch_setup_gd(gd_t *new_gd)
118
119	gdt.len = (num_entries * X86_GDT_ENTRY_SIZE) - 1;
120	gdt.ptr = (ulong)boot_gdt;
121
122	asm volatile("lgdtl %0\n" : : "m" (gdt));
123}
124
125void arch_setup_gd(gd_t *new_gd)
126{
127	u64 *gdt_addr;
128
129	gdt_addr = new_gd->arch.gdt;
130
131	/*
132	 * CS: code, read/execute, 4 GB, base 0
133	 *
134	 * Some OS (like VxWorks) requires GDT entry 1 to be the 32-bit CS
135	 */
136	gdt_addr[X86_GDT_ENTRY_UNUSED] = GDT_ENTRY(0xc09b, 0, 0xfffff);
137	gdt_addr[X86_GDT_ENTRY_32BIT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff);
138
139	/* DS: data, read/write, 4 GB, base 0 */
140	gdt_addr[X86_GDT_ENTRY_32BIT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff);
141
142	/*
143	 * FS: data, read/write, sizeof (Global Data Pointer),
144	 * base (Global Data Pointer)
145	 */
146	new_gd->arch.gd_addr = new_gd;
147	gdt_addr[X86_GDT_ENTRY_32BIT_FS] = GDT_ENTRY(0x8093,
148					(ulong)&new_gd->arch.gd_addr,
149					sizeof(new_gd->arch.gd_addr) - 1);
150
151	/* 16-bit CS: code, read/execute, 64 kB, base 0 */
152	gdt_addr[X86_GDT_ENTRY_16BIT_CS] = GDT_ENTRY(0x009b, 0, 0x0ffff);
153
154	/* 16-bit DS: data, read/write, 64 kB, base 0 */
155	gdt_addr[X86_GDT_ENTRY_16BIT_DS] = GDT_ENTRY(0x0093, 0, 0x0ffff);
156
157	gdt_addr[X86_GDT_ENTRY_16BIT_FLAT_CS] = GDT_ENTRY(0x809b, 0, 0xfffff);
158	gdt_addr[X86_GDT_ENTRY_16BIT_FLAT_DS] = GDT_ENTRY(0x8093, 0, 0xfffff);
159	gdt_addr[X86_GDT_ENTRY_64BIT_CS] = GDT_ENTRY(0xaf9b, 0, 0xfffff);
160	gdt_addr[X86_GDT_ENTRY_64BIT_TS1] = GDT_ENTRY(0x8980, 0, 0xfffff);
161	gdt_addr[X86_GDT_ENTRY_64BIT_TS2] = 0;
162
163	load_gdt(gdt_addr, X86_GDT_NUM_ENTRIES);
164	load_ds(X86_GDT_ENTRY_32BIT_DS);
165	load_es(X86_GDT_ENTRY_32BIT_DS);
166	load_gs(X86_GDT_ENTRY_32BIT_DS);
167	load_ss(X86_GDT_ENTRY_32BIT_DS);

118-167줄 해설

118(빈 줄)

이전 block 경계까지의 동작과 gdt.len = (num_entries * X86_GDT_ENTRY_SIZE) - 1;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 BIST and table pointer 상태가 아래 블록의 입력으로 사용되는 경계다.

119gdt.len = (num_entries * X86_GDT_ENTRY_SIZE) - 1;

gdt.len(num_entries * X86_GDT_ENTRY_SIZE) - 1를 = 연산으로 반영해 CAR stack / gd_t / x86 GDT와 연결된 field를 갱신한다. 이후 32-bit protected entry 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

120gdt.ptr = (ulong)boot_gdt;

gdt.ptr = (ulong)boot_gdt를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 CAR stack / gd_t / x86 GDT를 직접 소유하는지 pointer만 빌리는지, DRAM과 정상 exception handler가 없는 32-bit 초기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.

121(빈 줄)

gdt.ptr = (ulong)boot_gdt;까지의 동작과 asm volatile("lgdtl %0\n" : : "m" (gdt));에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 temporary stack 상태가 아래 블록의 입력으로 사용되는 경계다.

122asm volatile("lgdtl %0\n" : : "m" (gdt));

volatile("lgdtl %0\n" : : "m" (gdt))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 global_data 항목과 대조한다.

123}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

124(빈 줄)

}까지의 동작과 void arch_setup_gd(gd_t *new_gd)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

125void arch_setup_gd(gd_t *new_gd)

arch_setup_gd 함수 정의가 시작된다. 입력은 gd_t *new_gd이며, DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.

126{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 CAR stack / gd_t / x86 GDT의 수명에 맞춰 묶어 읽는다.

127u64 *gdt_addr;

원본 127번 줄의 u64 *gdt_addr;는 앞의 { 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

128(빈 줄)

u64 *gdt_addr;까지의 동작과 gdt_addr = new_gd->arch.gdt;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

129gdt_addr = new_gd->arch.gdt;

gdt_addrnew_gd->arch.gdt를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

130(빈 줄)

gdt_addr = new_gd->arch.gdt;까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

131/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

132* CS: code, read/execute, 4 GB, base 0

원본 주석이 'CS: code, read/execute, 4 GB, base 0'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

133*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

134* Some OS (like VxWorks) requires GDT entry 1 to be the 32-bit CS

원본 주석이 'Some OS (like VxWorks) requires GDT entry 1 to be the 32-bit CS'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

135*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

136gdt_addr[X86_GDT_ENTRY_UNUSED] = GDT_ENTRY(0xc09b, 0, 0xfffff);

gdt_addr[X86_GDT_ENTRY_UNUSED]GDT_ENTRY(0xc09b, 0, 0xfffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

137gdt_addr[X86_GDT_ENTRY_32BIT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff);

gdt_addr[X86_GDT_ENTRY_32BIT_CS]GDT_ENTRY(0xc09b, 0, 0xfffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

138(빈 줄)

gdt_addr[X86_GDT_ENTRY_32BIT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff);까지의 동작과 /* DS: data, read/write, 4 GB, base 0 */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

139/* DS: data, read/write, 4 GB, base 0 */

원본 주석이 'DS: data, read/write, 4 GB, base 0'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

140gdt_addr[X86_GDT_ENTRY_32BIT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff);

gdt_addr[X86_GDT_ENTRY_32BIT_DS]GDT_ENTRY(0xc093, 0, 0xfffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

141(빈 줄)

gdt_addr[X86_GDT_ENTRY_32BIT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff);까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

142/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

143* FS: data, read/write, sizeof (Global Data Pointer),

원본 주석이 'FS: data, read/write, sizeof (Global Data Pointer),'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

144* base (Global Data Pointer)

원본 주석이 'base (Global Data Pointer)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

145*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

146new_gd->arch.gd_addr = new_gd;

new_gd->arch.gd_addrnew_gd를 = 연산으로 반영해 CAR stack / gd_t / x86 GDT와 연결된 field를 갱신한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

147gdt_addr[X86_GDT_ENTRY_32BIT_FS] = GDT_ENTRY(0x8093,

GDT_ENTRY(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

148(ulong)&new_gd->arch.gd_addr,

원본 148번 줄의 (ulong)&new_gd->arch.gd_addr,는 앞의 gdt_addr[X86_GDT_ENTRY_32BIT_FS] = GDT_ENTRY(0x8093, 결과를 받아 다음 sizeof(new_gd->arch.gd_addr) - 1);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

149sizeof(new_gd->arch.gd_addr) - 1);

원본 149번 줄의 sizeof(new_gd->arch.gd_addr) - 1);는 앞의 (ulong)&new_gd->arch.gd_addr, 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다' 조건이 유지되는지 확인한다.

150(빈 줄)

sizeof(new_gd->arch.gd_addr) - 1);까지의 동작과 /* 16-bit CS: code, read/execute, 64 kB, base 0 */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

151/* 16-bit CS: code, read/execute, 64 kB, base 0 */

원본 주석이 '16-bit CS: code, read/execute, 64 kB, base 0'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

152gdt_addr[X86_GDT_ENTRY_16BIT_CS] = GDT_ENTRY(0x009b, 0, 0x0ffff);

gdt_addr[X86_GDT_ENTRY_16BIT_CS]GDT_ENTRY(0x009b, 0, 0x0ffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

153(빈 줄)

gdt_addr[X86_GDT_ENTRY_16BIT_CS] = GDT_ENTRY(0x009b, 0, 0x0ffff);까지의 동작과 /* 16-bit DS: data, read/write, 64 kB, base 0 */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

154/* 16-bit DS: data, read/write, 64 kB, base 0 */

원본 주석이 '16-bit DS: data, read/write, 64 kB, base 0'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

155gdt_addr[X86_GDT_ENTRY_16BIT_DS] = GDT_ENTRY(0x0093, 0, 0x0ffff);

gdt_addr[X86_GDT_ENTRY_16BIT_DS]GDT_ENTRY(0x0093, 0, 0x0ffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

156(빈 줄)

gdt_addr[X86_GDT_ENTRY_16BIT_DS] = GDT_ENTRY(0x0093, 0, 0x0ffff);까지의 동작과 gdt_addr[X86_GDT_ENTRY_16BIT_FLAT_CS] = GDT_ENTRY(0x809b, 0, 0xfffff);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

157gdt_addr[X86_GDT_ENTRY_16BIT_FLAT_CS] = GDT_ENTRY(0x809b, 0, 0xfffff);

gdt_addr[X86_GDT_ENTRY_16BIT_FLAT_CS]GDT_ENTRY(0x809b, 0, 0xfffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

158gdt_addr[X86_GDT_ENTRY_16BIT_FLAT_DS] = GDT_ENTRY(0x8093, 0, 0xfffff);

gdt_addr[X86_GDT_ENTRY_16BIT_FLAT_DS]GDT_ENTRY(0x8093, 0, 0xfffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

159gdt_addr[X86_GDT_ENTRY_64BIT_CS] = GDT_ENTRY(0xaf9b, 0, 0xfffff);

gdt_addr[X86_GDT_ENTRY_64BIT_CS]GDT_ENTRY(0xaf9b, 0, 0xfffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

160gdt_addr[X86_GDT_ENTRY_64BIT_TS1] = GDT_ENTRY(0x8980, 0, 0xfffff);

gdt_addr[X86_GDT_ENTRY_64BIT_TS1]GDT_ENTRY(0x8980, 0, 0xfffff)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

161gdt_addr[X86_GDT_ENTRY_64BIT_TS2] = 0;

gdt_addr[X86_GDT_ENTRY_64BIT_TS2]0를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

162(빈 줄)

gdt_addr[X86_GDT_ENTRY_64BIT_TS2] = 0;까지의 동작과 load_gdt(gdt_addr, X86_GDT_NUM_ENTRIES);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 pre-relocation init 상태가 아래 블록의 입력으로 사용되는 경계다.

163load_gdt(gdt_addr, X86_GDT_NUM_ENTRIES);

load_gdt(gdt_addr, X86_GDT_NUM_ENTRIES)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

164load_ds(X86_GDT_ENTRY_32BIT_DS);

load_ds(X86_GDT_ENTRY_32BIT_DS)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

165load_es(X86_GDT_ENTRY_32BIT_DS);

load_es(X86_GDT_ENTRY_32BIT_DS)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

166load_gs(X86_GDT_ENTRY_32BIT_DS);

load_gs(X86_GDT_ENTRY_32BIT_DS)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

167load_ss(X86_GDT_ENTRY_32BIT_DS);

load_ss(X86_GDT_ENTRY_32BIT_DS)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 CAR stack / gd_t / x86 GDT의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 pre-relocation init 항목과 대조한다.

내부 동작을 더 깊게 읽기

01

진입 조건을 먼저 고정한다

cold reset or previous loader에서 들어온 실행은 DRAM과 정상 exception handler가 없는 32-bit 초기 문맥에 놓여 있다. 이때 interrupt, MMU/cache, stack, heap 중 무엇이 이미 준비되었는지 소스의 호출자까지 올라가 확인한다. 같은 함수라도 SPL, relocation 전후, app thread처럼 호출 문맥이 달라지면 허용되는 API와 지연 시간이 달라진다.

x86는 DRAM 전 stack을 Cache-As-RAM 또는 platform temporary RAM에 둔다. _x86boot_start_start, TPL/SPL/U-Boot proper의 실제 선택을 먼저 고정하고 CAR top에서 stack, gd와 malloc_f가 차지하는 주소를 계산한다.

02

중심 객체의 생성과 공개를 나눈다

이 글의 중심 객체는 CAR stack / gd_t / x86 GDT다. 메모리를 확보한 시점, 필드를 채운 시점, 전역 list나 다른 subsystem에 공개한 시점을 구분한다. 공개 뒤 오류가 발생한다면 목록에서 제거하고 child, buffer, reference를 역순으로 정리하는지 확인한다.

빌드 산출물 관점에서는 x86 U-Boot pre-relocation image 안에 해당 symbol과 section이 실제로 포함되었는지도 map과 objdump로 검증한다.

03

주소, 크기와 정렬을 계산한다

부트 코드의 오류는 논리보다 주소 계산에서 먼저 드러나는 경우가 많다. source range, destination range, header가 말하는 payload size, block 또는 page 단위를 표로 적고 각 구간의 끝 주소를 직접 계산한다. 끝 주소는 start + size - 1인지 exclusive end인지 API 계약을 확인한다.

board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다.

04

성공 flag와 실제 완료 시점을 맞춘다

32-bit protected entry → GDT and blank IDT → car_init → CAR stack → gd and malloc_f 구간에서는 부분 초기화 상태가 생길 수 있다. flag, list insertion, callback 등록, storage write 완료 중 무엇이 성공의 기준인지 찾는다. hardware write나 DMA가 포함되면 함수 반환과 장치 완료가 같은 시점인지도 확인한다.

다른 CPU, interrupt handler, USB completion 또는 shell command가 상태를 관찰할 수 있다면 memory ordering과 lock 범위도 함께 읽는다.

05

마지막 handoff의 계약을 적는다

정상 경로는 board_init_f에서 끝난다. 이 단계가 함수 반환인지, scheduler 전환인지, 다른 image로의 비복귀 분기인지 구분한다. 비복귀 handoff라면 cache clean/invalidate, interrupt disable, 장치 quiesce, argument register와 FDT 또는 image address가 최종 점검 항목이다.

반환하는 경로라면 caller가 오류와 부분 성공을 구분하고 다음 후보 또는 복구 경로를 선택하는지 확인한다.

구현을 읽을 때 놓치기 쉬운 부분

01

x86의 첫 entry는 하나가 아니라 cold reset, fail-safe 32-bit, SPL/TPL 경로로 갈린다

arch/x86/cpu/start.S_x86boot_start는 coreboot나 EFI처럼 다른 loader가 이미 32-bit 환경을 만든 경우를 위한 fail-safe entry이고, _startstart16에서 넘어오는 cold-reset 경로다. 두 경로는 label 1에서 합쳐지지만 BIST와 table pointer의 출처가 다르다. 현재 board가 TPL, SPL, U-Boot proper 중 어느 stage에 이 파일을 쓰는지도 source 상단 주석과 최종 map에서 확인해야 한다.

64-bit loader가 arch/x86/cpu/start64.S로 직접 들어오는 설정은 또 다른 계약이다. 그 경로는 기존 %rsp가 유효하다고 전제하고 CAR 초기화 없이 board_init_f_alloc_reserve()부터 시작한다. 함수 이름이 같아도 진입 mode와 stack 생산자가 다르므로 한 개의 reset 흐름도로 합치면 안 된다.

02

cache를 끄는 동작과 Cache-As-RAM을 만드는 동작은 모순이 아니다

fail-safe entry는 CR0.NW/CD를 세우고 wbinvd로 이전 loader의 cache state를 정리한다. 이후 car_init은 platform별 MTRR, cache way 또는 FSP temporary RAM 계약으로 특정 physical range를 stack과 early malloc에 쓸 수 있게 만든다. 앞의 cache disable은 불명확한 상속 상태 제거이고 뒤의 CAR은 제어된 임시 memory 생성이다.

QEMU, coreboot, Intel FSP, Slim Bootloader는 car_init 구현이 서로 다르다. start.S만 읽고 cache line이 실제 DRAM처럼 write-back되는 과정을 추정하지 말고 link된 arch/x86/lib/*/car.S와 호출 후 %esp가 가리키는 range를 함께 확인한다.

03

blank IDT는 예외를 처리하는 표가 아니라 예외가 나면 즉시 멈추게 하는 상태다

lidt blank_idt_ptr는 limit와 base가 0인 IDT를 적재한다. 아직 정상 handler와 stack frame을 만들 수 없는 시점에 잘못된 interrupt vector를 따라가는 것을 막지만, #PF나 #GP가 발생하면 double/triple fault와 reset로 이어질 수 있다. 따라서 이 구간의 무출력 reset은 예외가 없었다는 증거가 아니다.

초기 디버깅에서는 serial보다 POST code, hardware debugger, QEMU -d int,cpu_reset이 더 직접적이다. _start, car_init_ret, board_init_f 직전에 breakpoint를 두고 CR0, GDTR, IDTR, ESP를 기록한다.

04

CAR top에서 stack, MRC area, gd와 early malloc의 실제 배치를 계산한다

CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE - 4가 첫 stack top이고, 필요하면 MRC variable 영역만큼 더 내린다. board_init_f_alloc_reserve()는 이 cursor에서 gd_t, 초기 GDT와 malloc_f 공간을 정렬해 예약하고 새 stack pointer를 반환한다. 단순히 CAR size가 크다고 안전한 것이 아니라 가장 깊은 call chain과 early allocation의 합이 bottom을 넘지 않아야 한다.

map에 표시된 sizeof(gd_t).configSYS_MALLOC_F_LEN, DCACHE_RAM_MRC_VAR_SIZE를 대입해 각 start/end를 표로 만들 수 있다. stack watermark 또는 poison pattern을 CAR bottom부터 채우면 실제 최대 사용량을 확인할 수 있다.

05

x86의 gd pointer는 ARM64 x18과 다르게 FS segment를 통해 읽힌다

board_init_f_init_reserve()가 초기 global data를 만든 뒤 x86 assembly는 %fs:0에서 gd 주소를 읽는다. segment selector와 GDT base가 틀리면 일반 pointer처럼 보이는 첫 gd access부터 잘못된 주소를 사용한다. 이 때문에 segment register load, GDT descriptor와 gd 초기화를 같은 경계로 봐야 한다.

HOB를 쓰는 FSP 경로에서는 %esi가 firmware hand-off block list를 보존하고, 일반 경로에서는 table pointer를 GD_TABLE에 저장한다. 같은 register가 build option에 따라 다른 metadata를 전달한다.

06

board_init_f가 돌아오면 정상 경로가 아니다

x86에서는 board_init_f()의 initcall 목록 안에 copy, ELF relocation fixup, BSS clear와 jump_to_copy()가 포함될 수 있다. 정상 경로는 board_init_f_r_trampoline으로 비복귀 이동한다. 따라서 call board_init_f 다음의 POST 0x85와 die는 성공 continuation이 아니라 예상하지 못한 return을 잡는 경계다.

ARM64의 crt0_64.Sboard_init_f() 반환 뒤 relocate_code를 호출하는 구조와 가장 크게 다른 지점이다. architecture별 initcall_run_f() 전처리 결과를 보지 않으면 relocation 책임을 잘못 배치하게 된다.

07

초기 latency는 CAR 준비와 DRAM training을 나눠 측정한다

CAR 생성은 platform register/FSP 호출에 따라 시간이 달라지고, DRAM training은 이후 board_init_f initcall에서 더 큰 지연을 만들 수 있다. reset부터 첫 console까지 한 구간으로만 재면 어느 단계가 느린지 알 수 없다.

POST code와 TSC를 _start, car_init_ret, dram_init, jump_to_copy, board_init_f_r에 기록해 구간별 upper bound를 만든다. cold boot, warm reset, training cache 사용 여부도 분리한다.

08

CAR 128KiB 예제로 stack, gd와 malloc_f의 경계를 직접 계산한다

CONFIG_SYS_CAR_ADDR=0xfef00000, CONFIG_SYS_CAR_SIZE=0x20000이면 첫 ESP는 0xfef1fffc다. 여기서 16-byte 정렬 뒤 gd_t=0x1c0, 초기 GDT=0x40, SYS_MALLOC_F_LEN=0x4000을 차례로 내리면 early runtime cursor는 대략 0xfef1be00 아래가 된다. MRC variable 0x4000을 CAR 상단에 따로 둔다면 그만큼을 stack top 계산 전에 먼저 빼야 한다.

남은 [CAR bottom, runtime cursor)가 call frame과 local variable의 실제 stack budget이다. 예제 값만 고정해서 쓰지 말고 최종 sizeof(struct global_data), Kconfig 값과 objdump의 stack usage를 대입한다. CAR를 0xa5로 채운 뒤 가장 낮게 바뀐 주소를 찾으면 정적 계산과 실제 high-water의 차이도 얻을 수 있다.

09

FS selector, descriptor base와 fs:0의 최종 주소를 한 식으로 검산한다

예를 들어 FS selector가 0x18이면 GDT index는 0x18 >> 3 = 3이다. index 3 descriptor의 base가 0xfef1be00이고 instruction이 %fs:0을 읽으면 linear address도 0xfef1be00이다. 그 위치에 저장된 word가 new gd pointer여야 C의 gd macro가 기대한 값을 얻는다.

selector의 RPL/TI bit, GDTR.base, descriptor base field, %fs:offset 네 값을 분리해 기록한다. %fs 숫자만 맞아도 descriptor가 old CAR/GDT copy를 가리키면 relocation 뒤 stale gd를 읽는다.

10

x86 early boot의 시간 상한은 stage별 최대값의 합으로 둔다

부팅 상한을 Tentry + Tcar + Tdram + Treloc + Thandoff로 나눈다. 3.0GHz TSC에서 CAR 구간이 9,000,000 cycle이면 3ms이고 DRAM training이 1,500,000,000 cycle이면 500ms다. console 출력 시각 하나만 보면 503ms 전부를 assembly startup 지연으로 오해할 수 있다.

각 checkpoint는 CPU frequency가 고정되기 전일 수 있으므로 invariant TSC 지원 여부를 확인하거나 platform timer와 교차 검증한다. cold/warm 각각 100회에서 maximum과 percentile을 따로 보관하면 training cache miss 같은 긴 tail을 찾을 수 있다.

객체와 수명

대상만들어지는 시점유효 범위확인할 조건
CAR stack / gd_t / x86 GDT32-bit protected entryboard_init_f 또는 오류 정리 완료까지board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다.
입력 buffer / metadatacold reset or previous loaderparse와 검증이 끝날 때까지길이, 정렬, 소유권, 변조 가능성
등록된 list / descriptorGDT and blank IDTsubsystem 종료 또는 image handoff까지이중 등록, dangling pointer, 오류 unwind
hardware 또는 persistent state실제 write/probe가 완료된 뒤reset 또는 명시적 해제·갱신까지flush, timeout, 전원 차단, rollback
x86 U-Boot pre-relocation imagelink/image 생성 시점다음 stage가 새 image로 교체할 때까지load address, entry, section과 header 일치

실패 지점과 증상

#조건관찰되는 증상먼저 볼 단계
01blank IDT 구간의 faulttriple fault와 즉시 reset32-bit protected entry
02CAR 크기 부족stack과 gd 또는 malloc_f 충돌GDT and blank IDT
03FS/GDT 오류첫 gd access에서 잘못된 주소car_init
04BIST/table pointer 훼손platform handoff metadata 소실CAR stack

로그가 끊긴 마지막 함수만 고치지 않는다. 그 함수가 받은 주소, size, flag가 만들어진 앞 단계까지 올라가고, 오류 뒤 등록 객체와 hardware 상태가 남았는지도 확인한다.

소스 밖에서 확인할 증거

소스 해석은 실제 빌드 산출물과 target 로그로 닫아야 한다. 아래 명령의 보드 이름과 toolchain prefix는 사용 중인 빌드 환경에 맞게 바꾼다.

#목적명령 또는 계측판정 기준
01전처리 결과make <board>_defconfig && make V=1선택된 CONFIG와 실제 compile/link 명령을 보관한다.
02symbol과 section${CROSS_COMPILE}nm -n u-boot | grep '_start'symbol 주소가 기대한 stage와 image에 들어갔는지 확인한다.
03어셈블리 대조${CROSS_COMPILE}objdump -drS u-bootC 한 줄이 실제 branch, relocation, memory access로 어떻게 바뀌었는지 본다.
04주소 배치bdinforelocaddr, malloc, FDT, stack과 DRAM bank 주소를 한 번에 기록한다.
05runtime 관찰dm tree; dm uclass; printenvdriver model과 환경이 이 단계에서 실제로 공개되었는지 확인한다.

직접 확인할 실험

  1. 01
    QEMU에서 _start, car_init_ret, board_init_f에 breakpoint

    32-bit protected entry 진입 전후에 BIST and table pointer의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

  2. 02
    CAR 전체를 pattern으로 채워 stack high-water 측정

    GDT and blank IDT 진입 전후에 segment registers의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

  3. 03
    GD 주소와 FS base/GDT descriptor 대조

    car_init 진입 전후에 Cache-As-RAM range의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

  4. 04
    POST code에 TSC를 붙여 CAR와 DRAM training 시간 분리

    CAR stack 진입 전후에 temporary stack의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_f에 들어갈 때 writable stack, 유효한 FS 기반 gd pointer, 보존된 BIST/table/FDT metadata가 CAR 범위 안에서 서로 겹치지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

원문과 다음 글