QUESTION
x86는 왜 board_init_f 안에서 relocation을 끝내며 CAR stack의 수명은 정확히 어느 줄에서 끝나는가?
ARM처럼 board_init_f가 돌아온 뒤 assembly relocate_code를 찾으면 안 된다. x86는 C initcall 목록에서 copy, fixup, BSS clear와 jump를 완료하며 trampoline은 새 stack을 활성화한 뒤에만 CAR를 해제한다.
destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.
STRUCTURE
객체와 주소가 놓이는 구조
destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다.
relocated x86 image / new stack / new gd를 중심에 놓고 왼쪽의 입력이 어떤 검사를 거쳐 오른쪽 결과로 공개되는지 표시했다. 실제 디버깅에서는 각 블록의 주소와 크기를 로그에 대입한다.
CALL PATH
실행 흐름
화살표는 단순 호출 순서만 뜻하지 않는다. 각 단계가 성공을 반환할 때 다음 단계가 읽을 수 있는 상태가 무엇인지 함께 확인한다. 오류 반환이 발생하면 바로 다음 화살표로 진행하지 않고 해당 단계의 정리 경로를 따라간다.
STATE LEDGER
단계별 입력과 출력
호출 순서를 함수 이름으로만 외우지 않고, 각 단계가 무엇을 받아 무엇을 공개하는지 적은 표다. 실제 소스에서 생산 필드가 다르면 표를 고치는 방식으로 사용한다.
| # | 단계 | 진입 시 신뢰할 상태 | 성공 뒤 남아야 할 상태 | 다음 소비자 |
|---|---|---|---|---|
| 01 | board_init_f reservation | board_init_f의 마지막 x86 initcall과 비복귀 trampoline | source image | copy_uboot_to_ram |
| 02 | copy_uboot_to_ram | board_init_f reservation 완료 상태 | gd->relocaddr | ELF relocation fixups |
| 03 | ELF relocation fixups | copy_uboot_to_ram 완료 상태 | .rela.dyn | clear BSS |
| 04 | clear BSS | ELF relocation fixups 완료 상태 | new BSS | jump_to_copy |
| 05 | jump_to_copy | clear BSS 완료 상태 | new gd and GDT | new DRAM stack |
| 06 | new DRAM stack | jump_to_copy 완료 상태 | runtime stack | car_uninit |
| 07 | car_uninit | new DRAM stack 완료 상태 | runtime stack | board_init_f_r |
| 08 | board_init_f_r | car_uninit 완료 상태 | runtime stack | 최종 DRAM의 relocated U-Boot proper 또는 다음 stage |
공통 불변 조건: destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다. 한 단계의 출력이 다음 단계의 입력 조건을 만족하지 않으면 오류가 실제로 드러난 위치보다 앞의 생산 단계부터 조사한다.
VISUAL WALKTHROUGH
주소와 객체의 이동을 그림으로 읽기
아래 그림은 호출 이름보다 주소, 객체 수명과 handoff 경계를 먼저 볼 수 있도록 구성했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, map과 linker symbol을 대입한다.
크기는 개념 비율이다. 실제 분석에서는 map, symbol과 runtime 출력의 시작·끝 주소를 대입한다.
호출 이름만 나열하지 않고, 각 단계가 다음 단계에 넘기는 상태를 기준으로 실행 순서를 표시한다.
왼쪽 생산자가 가운데 상태를 완성하고, 오른쪽 소비자가 그 상태를 처음 사용하는 관계다.
reserve_uboot→gd->relocaddr→copy_uboot_to_ramcopy→destination bytes→ELF fixup.rela.dyn→new absolute pointers→runtime Cclear_bss→zero-init globals→board_init_f_rnew stack→CAR 독립 call frame→car_uninitUPSTREAM SOURCE
원본 코드
아래 코드는 U-Boot v2026.07의 arch/x86/cpu/start.S에서 173-224줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.
173 post_code(POST_START_DONE)
174 xorl %eax, %eax
175
176 /* Enter, U-Boot! */
177 call board_init_f
178
179 /* indicate (lack of) progress */
180 movw $0x85, %ax
181 jmp die
182
183.globl board_init_f_r_trampoline
184.type board_init_f_r_trampoline, @function
185board_init_f_r_trampoline:
186 /*
187 * SDRAM has been initialised, U-Boot code has been copied into
188 * RAM, BSS has been cleared and relocation adjustments have been
189 * made. It is now time to jump into the in-RAM copy of U-Boot
190 *
191 * %eax = Address of top of new stack
192 */
193
194 /* Stack grows down from top of SDRAM */
195 movl %eax, %esp
196
197 /* See if we need to disable CAR */
198 call car_uninit
199
200 /* Re-enter U-Boot by calling board_init_f_r() */
201 call board_init_f_r
202
203#ifdef CONFIG_TPL
204.globl jump_to_spl
205.type jump_to_spl, @function
206jump_to_spl:
207 /* Reset stack to the top of CAR space */
208 movl $(CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE - 4), %esp
209#ifdef CONFIG_DCACHE_RAM_MRC_VAR_SIZE
210 subl $CONFIG_DCACHE_RAM_MRC_VAR_SIZE, %esp
211#endif
212
213 jmp *%eax
214#endif
215
216die:
217 hlt
218 jmp die
219 hlt
220
221WEAK(car_uninit)
222 ret
223ENDPROC(car_uninit)
224
LINE BY LINE
173-224줄 해설
원본에 보이는 모든 줄을 순서대로 설명한다. 빈 줄도 block 경계로 남겨, 코드와 설명의 위치가 어긋나지 않게 했다.
post_code(POST_START_DONE)post_code(POST_START_DONE)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 source image 항목과 대조한다.
xorl %eax, %eax원본 174번 줄의 xorl %eax, %eax는 앞의 post_code(POST_START_DONE) 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)xorl %eax, %eax까지의 동작과 /* Enter, U-Boot! */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 .rela.dyn 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Enter, U-Boot! */원본 주석이 'Enter, U-Boot!'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call board_init_f원본 177번 줄의 call board_init_f는 앞의 /* Enter, U-Boot! */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)call board_init_f까지의 동작과 /* indicate (lack of) progress */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* indicate (lack of) progress */원본 주석이 'indicate (lack of) progress'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
movw $0x85, %ax원본 180번 줄의 movw $0x85, %ax는 앞의 /* indicate (lack of) progress */ 결과를 받아 다음 jmp die로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
jmp die원본 181번 줄의 jmp die는 앞의 movw $0x85, %ax 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jmp die까지의 동작과 .globl board_init_f_r_trampoline에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
.globl board_init_f_r_trampoline원본 183번 줄의 .globl board_init_f_r_trampoline는 앞의 이전 block 경계 결과를 받아 다음 .type board_init_f_r_trampoline, @function로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
.type board_init_f_r_trampoline, @function원본 184번 줄의 .type board_init_f_r_trampoline, @function는 앞의 .globl board_init_f_r_trampoline 결과를 받아 다음 board_init_f_r_trampoline:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
board_init_f_r_trampoline:복사와 relocation fixup이 끝난 destination image에서 새 stack으로 runtime을 재개하는 32-bit trampoline이다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* SDRAM has been initialised, U-Boot code has been copied into원본 주석이 'SDRAM has been initialised, U-Boot code has been copied into'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* RAM, BSS has been cleared and relocation adjustments have been원본 주석이 'RAM, BSS has been cleared and relocation adjustments have been'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* %eax = Address of top of new stack원본 주석이 '%eax = Address of top of new stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
(빈 줄)*/까지의 동작과 /* Stack grows down from top of SDRAM */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Stack grows down from top of SDRAM */원본 주석이 'Stack grows down from top of SDRAM'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
movl %eax, %espcaller가 EAX로 넘긴 DRAM stack top을 활성 stack으로 만든다. 이후 old CAR stack은 더 이상 사용하지 않는다.
(빈 줄)movl %eax, %esp까지의 동작과 /* See if we need to disable CAR */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* See if we need to disable CAR */원본 주석이 'See if we need to disable CAR'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call car_uninitstack을 DRAM으로 옮긴 뒤 Cache-As-RAM을 해제한다. 두 줄의 순서를 바꾸면 return address부터 사라진다.
(빈 줄)call car_uninit까지의 동작과 /* Re-enter U-Boot by calling board_init_f_r() */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Re-enter U-Boot by calling board_init_f_r() */원본 주석이 'Re-enter U-Boot by calling board_init_f_r()'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call board_init_f_rnew stack과 relocated image에서 runtime C 초기화의 후반부로 들어간다.
(빈 줄)call board_init_f_r까지의 동작과 #ifdef CONFIG_TPL에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
#ifdef CONFIG_TPL#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_TPL이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
.globl jump_to_spl원본 204번 줄의 .globl jump_to_spl는 앞의 #ifdef CONFIG_TPL 결과를 받아 다음 .type jump_to_spl, @function로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
.type jump_to_spl, @function원본 205번 줄의 .type jump_to_spl, @function는 앞의 .globl jump_to_spl 결과를 받아 다음 jump_to_spl:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
jump_to_spl:'jump_to_spl' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
/* Reset stack to the top of CAR space */원본 주석이 'Reset stack to the top of CAR space'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
movl $(CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE - 4), %esp원본 208번 줄의 movl $(CONFIG_SYS_CAR_ADDR + CONFIG_SYS_CAR_SIZE - 4), %esp는 앞의 /* Reset stack to the top of CAR space */ 결과를 받아 다음 #ifdef CONFIG_DCACHE_RAM_MRC_VAR_SIZE로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
#ifdef CONFIG_DCACHE_RAM_MRC_VAR_SIZE#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_DCACHE_RAM_MRC_VAR_SIZE이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
subl $CONFIG_DCACHE_RAM_MRC_VAR_SIZE, %esp원본 210번 줄의 subl $CONFIG_DCACHE_RAM_MRC_VAR_SIZE, %esp는 앞의 #ifdef CONFIG_DCACHE_RAM_MRC_VAR_SIZE 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 jmp *%eax에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
jmp *%eax원본 213번 줄의 jmp *%eax는 앞의 이전 block 경계 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 die:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
die:'die' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
hlt원본 217번 줄의 hlt는 앞의 die: 결과를 받아 다음 jmp die로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
jmp die원본 218번 줄의 jmp die는 앞의 hlt 결과를 받아 다음 hlt로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
hlt원본 219번 줄의 hlt는 앞의 jmp die 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)hlt까지의 동작과 WEAK(car_uninit)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
WEAK(car_uninit)WEAK(car_uninit)를 호출한다. 반환 결과는 현재 줄 또는 다음 ret에서 검사되는 흐름이다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
retret 명령이 register, memory 또는 PC를 직접 바꾼다. board_init_f의 마지막 x86 initcall과 비복귀 trampoline에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
ENDPROC(car_uninit)ENDPROC(car_uninit)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
(빈 줄)ENDPROC(car_uninit)까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
RELATED SOURCE
함께 읽어야 하는 원본 코드
첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.
01. U-Boot image byte를 gd->relocaddr로 복사
arch/x86/lib/relocate.c 19-59줄이다.
19#include <asm/global_data.h>
20#include <asm/u-boot-x86.h>
21#include <asm/sections.h>
22#include <elf.h>
23
24DECLARE_GLOBAL_DATA_PTR;
25
26int copy_uboot_to_ram(void)
27{
28 size_t len = (uintptr_t)__data_end - (uintptr_t)__text_start;
29
30 if (gd->flags & GD_FLG_SKIP_RELOC)
31 return 0;
32 memcpy((void *)gd->relocaddr, (void *)__text_start, len);
33
34 return 0;
35}
36
37#ifndef CONFIG_EFI_APP
38int clear_bss(void)
39{
40 ulong dst_addr = (ulong)__bss_start + gd->reloc_off;
41 size_t len = (uintptr_t)__bss_end - (uintptr_t)__bss_start;
42
43 if (gd->flags & GD_FLG_SKIP_RELOC)
44 return 0;
45 memset((void *)dst_addr, 0x00, len);
46
47 return 0;
48}
49#endif
50
51#if CONFIG_IS_ENABLED(X86_64)
52static void do_elf_reloc_fixups64(unsigned int text_base, uintptr_t size,
53 Elf64_Rela *re_src, Elf64_Rela *re_end)
54{
55 Elf64_Addr *offset_ptr_rom, *last_offset = NULL;
56 Elf64_Addr *offset_ptr_ram;
57
58 do {
59 unsigned long long type = ELF64_R_TYPE(re_src->r_info);
19-59줄 해설
#include <asm/global_data.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#include <asm/u-boot-x86.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#include <asm/sections.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#include <elf.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#include <elf.h>까지의 동작과 DECLARE_GLOBAL_DATA_PTR;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 new gd and GDT 상태가 아래 블록의 입력으로 사용되는 경계다.
DECLARE_GLOBAL_DATA_PTR;원본 24번 줄의 DECLARE_GLOBAL_DATA_PTR;는 앞의 이전 block 경계 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)DECLARE_GLOBAL_DATA_PTR;까지의 동작과 int copy_uboot_to_ram(void)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
int copy_uboot_to_ram(void)copy_uboot_to_ram 함수 정의가 시작된다. 입력은 void이며, board_init_f의 마지막 x86 initcall과 비복귀 trampoline에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
size_t len = (uintptr_t)__data_end - (uintptr_t)__text_start;size_t len = (uintptr_t)__data_end - (uintptr_t)__text_start를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated x86 image / new stack / new gd를 직접 소유하는지 pointer만 빌리는지, board_init_f의 마지막 x86 initcall과 비복귀 trampoline를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
(빈 줄)size_t len = (uintptr_t)__data_end - (uintptr_t)__text_start;까지의 동작과 if (gd->flags & GD_FLG_SKIP_RELOC)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
if (gd->flags & GD_FLG_SKIP_RELOC)gd->flags & GD_FLG_SKIP_RELOC를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
memcpy((void *)gd->relocaddr, (void *)__text_start, len);memcpy((void *)gd->relocaddr, (void *)__text_start, len)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
(빈 줄)memcpy((void *)gd->relocaddr, (void *)__text_start, len);까지의 동작과 return 0;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 #ifndef CONFIG_EFI_APP에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
#ifndef CONFIG_EFI_APP#ifndef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_EFI_APP이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
int clear_bss(void)clear_bss 함수 정의가 시작된다. 입력은 void이며, board_init_f의 마지막 x86 initcall과 비복귀 trampoline에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
ulong dst_addr = (ulong)__bss_start + gd->reloc_off;ulong dst_addr = (ulong)__bss_start + gd->reloc_off를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated x86 image / new stack / new gd를 직접 소유하는지 pointer만 빌리는지, board_init_f의 마지막 x86 initcall과 비복귀 trampoline를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
size_t len = (uintptr_t)__bss_end - (uintptr_t)__bss_start;size_t len = (uintptr_t)__bss_end - (uintptr_t)__bss_start를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated x86 image / new stack / new gd를 직접 소유하는지 pointer만 빌리는지, board_init_f의 마지막 x86 initcall과 비복귀 trampoline를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
(빈 줄)size_t len = (uintptr_t)__bss_end - (uintptr_t)__bss_start;까지의 동작과 if (gd->flags & GD_FLG_SKIP_RELOC)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
if (gd->flags & GD_FLG_SKIP_RELOC)gd->flags & GD_FLG_SKIP_RELOC를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
memset((void *)dst_addr, 0x00, len);memset((void *)dst_addr, 0x00, len)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
(빈 줄)memset((void *)dst_addr, 0x00, len);까지의 동작과 return 0;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 #if CONFIG_IS_ENABLED(X86_64)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
#if CONFIG_IS_ENABLED(X86_64)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
static void do_elf_reloc_fixups64(unsigned int text_base, uintptr_t size,do_elf_reloc_fixups64(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
Elf64_Rela *re_src, Elf64_Rela *re_end)원본 53번 줄의 Elf64_Rela *re_src, Elf64_Rela *re_end)는 앞의 static void do_elf_reloc_fixups64(unsigned int text_base, uintptr_t size, 결과를 받아 다음 {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
Elf64_Addr *offset_ptr_rom, *last_offset = NULL;Elf64_Addr *offset_ptr_rom, *last_offset에 NULL를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
Elf64_Addr *offset_ptr_ram;원본 56번 줄의 Elf64_Addr *offset_ptr_ram;는 앞의 Elf64_Addr *offset_ptr_rom, *last_offset = NULL; 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)Elf64_Addr *offset_ptr_ram;까지의 동작과 do {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
do {원본 58번 줄의 do {는 앞의 이전 block 경계 결과를 받아 다음 unsigned long long type = ELF64_R_TYPE(re_src->r_info);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
unsigned long long type = ELF64_R_TYPE(re_src->r_info);ELF64 r_info에서 relocation type을 분리한다. 이 구현은 R_X86_64_RELATIVE만 지원하므로 다른 type은 같은 식으로 고치지 않는다.
02. x86-64 ELF relocation entry를 새 image에 적용
arch/x86/lib/relocate.c 47-120줄이다.
47 return 0;
48}
49#endif
50
51#if CONFIG_IS_ENABLED(X86_64)
52static void do_elf_reloc_fixups64(unsigned int text_base, uintptr_t size,
53 Elf64_Rela *re_src, Elf64_Rela *re_end)
54{
55 Elf64_Addr *offset_ptr_rom, *last_offset = NULL;
56 Elf64_Addr *offset_ptr_ram;
57
58 do {
59 unsigned long long type = ELF64_R_TYPE(re_src->r_info);
60
61 if (type != R_X86_64_RELATIVE) {
62 printf("%s: unsupported relocation type 0x%llx "
63 "at %p, ", __func__, type, re_src);
64 printf("offset = 0x%llx\n", re_src->r_offset);
65 continue;
66 }
67
68 /* Get the location from the relocation entry */
69 offset_ptr_rom = (Elf64_Addr *)(uintptr_t)re_src->r_offset;
70
71 /* Check that the location of the relocation is in .text */
72 if (offset_ptr_rom >= (Elf64_Addr *)(uintptr_t)text_base &&
73 offset_ptr_rom > last_offset) {
74 /* Switch to the in-RAM version */
75 offset_ptr_ram = (Elf64_Addr *)((ulong)offset_ptr_rom +
76 gd->reloc_off);
77
78 /* Check that the target points into .text */
79 if (*offset_ptr_ram >= text_base &&
80 *offset_ptr_ram <= text_base + size) {
81 *offset_ptr_ram = gd->reloc_off +
82 re_src->r_addend;
83 } else {
84 debug(" %p: %lx: rom reloc %lx, ram %p, value %lx, limit %lX\n",
85 re_src, (ulong)re_src->r_info,
86 (ulong)re_src->r_offset, offset_ptr_ram,
87 (ulong)*offset_ptr_ram, text_base + size);
88 }
89 } else {
90 debug(" %p: %lx: rom reloc %lx, last %p\n", re_src,
91 (ulong)re_src->r_info, (ulong)re_src->r_offset,
92 last_offset);
93 }
94 last_offset = offset_ptr_rom;
95
96 } while (++re_src < re_end);
97}
98#else
99static void do_elf_reloc_fixups32(unsigned int text_base, uintptr_t size,
100 Elf32_Rel *re_src, Elf32_Rel *re_end)
101{
102 Elf32_Addr *offset_ptr_rom, *last_offset = NULL;
103 Elf32_Addr *offset_ptr_ram;
104
105 do {
106 unsigned int type = ELF32_R_TYPE(re_src->r_info);
107
108 if (type != R_386_RELATIVE) {
109 printf("%s: unsupported relocation type 0x%x "
110 "at %p, ", __func__, type, re_src);
111 printf("offset = 0x%x\n", re_src->r_offset);
112 continue;
113 }
114
115 /* Get the location from the relocation entry */
116 offset_ptr_rom = (Elf32_Addr *)(uintptr_t)re_src->r_offset;
117
118 /* Check that the location of the relocation is in .text */
119 if (offset_ptr_rom >= (Elf32_Addr *)(uintptr_t)text_base &&
120 offset_ptr_rom > last_offset) {
47-120줄 해설
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 #if CONFIG_IS_ENABLED(X86_64)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 new BSS 상태가 아래 블록의 입력으로 사용되는 경계다.
#if CONFIG_IS_ENABLED(X86_64)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
static void do_elf_reloc_fixups64(unsigned int text_base, uintptr_t size,do_elf_reloc_fixups64(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
Elf64_Rela *re_src, Elf64_Rela *re_end)원본 53번 줄의 Elf64_Rela *re_src, Elf64_Rela *re_end)는 앞의 static void do_elf_reloc_fixups64(unsigned int text_base, uintptr_t size, 결과를 받아 다음 {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
Elf64_Addr *offset_ptr_rom, *last_offset = NULL;Elf64_Addr *offset_ptr_rom, *last_offset에 NULL를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
Elf64_Addr *offset_ptr_ram;원본 56번 줄의 Elf64_Addr *offset_ptr_ram;는 앞의 Elf64_Addr *offset_ptr_rom, *last_offset = NULL; 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)Elf64_Addr *offset_ptr_ram;까지의 동작과 do {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
do {원본 58번 줄의 do {는 앞의 이전 block 경계 결과를 받아 다음 unsigned long long type = ELF64_R_TYPE(re_src->r_info);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
unsigned long long type = ELF64_R_TYPE(re_src->r_info);ELF64 r_info에서 relocation type을 분리한다. 이 구현은 R_X86_64_RELATIVE만 지원하므로 다른 type은 같은 식으로 고치지 않는다.
(빈 줄)unsigned long long type = ELF64_R_TYPE(re_src->r_info);까지의 동작과 if (type != R_X86_64_RELATIVE) {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
if (type != R_X86_64_RELATIVE) {type != R_X86_64_RELATIVE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
printf("%s: unsupported relocation type 0x%llx "printf(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
"at %p, ", __func__, type, re_src);원본 63번 줄의 "at %p, ", __func__, type, re_src);는 앞의 printf("%s: unsupported relocation type 0x%llx " 결과를 받아 다음 printf("offset = 0x%llx\n", re_src->r_offset);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
printf("offset = 0x%llx\n", re_src->r_offset);printf("offset에 0x%llx\n", re_src->r_offset)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
continue;'continue;'로 직선 경로를 벗어난다. 이동 대상에서 relocated x86 image / new stack / new gd에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /* Get the location from the relocation entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Get the location from the relocation entry */원본 주석이 'Get the location from the relocation entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
offset_ptr_rom = (Elf64_Addr *)(uintptr_t)re_src->r_offset;offset_ptr_rom에 (Elf64_Addr *)(uintptr_t)re_src->r_offset를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)offset_ptr_rom = (Elf64_Addr *)(uintptr_t)re_src->r_offset;까지의 동작과 /* Check that the location of the relocation is in .text */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Check that the location of the relocation is in .text */원본 주석이 'Check that the location of the relocation is in .text'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if (offset_ptr_rom >= (Elf64_Addr *)(uintptr_t)text_base &&원본 72번 줄의 if (offset_ptr_rom >= (Elf64_Addr *)(uintptr_t)text_base &&는 앞의 /* Check that the location of the relocation is in .text */ 결과를 받아 다음 offset_ptr_rom > last_offset) {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
offset_ptr_rom > last_offset) {원본 73번 줄의 offset_ptr_rom > last_offset) {는 앞의 if (offset_ptr_rom >= (Elf64_Addr *)(uintptr_t)text_base && 결과를 받아 다음 /* Switch to the in-RAM version */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
/* Switch to the in-RAM version */원본 주석이 'Switch to the in-RAM version'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
offset_ptr_ram = (Elf64_Addr *)((ulong)offset_ptr_rom +link-time patch 주소에 gd->reloc_off를 더해 destination image에서 실제로 쓸 8-byte word 위치를 만든다.
gd->reloc_off);원본 76번 줄의 gd->reloc_off);는 앞의 offset_ptr_ram = (Elf64_Addr *)((ulong)offset_ptr_rom + 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)gd->reloc_off);까지의 동작과 /* Check that the target points into .text */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Check that the target points into .text */원본 주석이 'Check that the target points into .text'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if (*offset_ptr_ram >= text_base &&원본 79번 줄의 if (*offset_ptr_ram >= text_base &&는 앞의 /* Check that the target points into .text */ 결과를 받아 다음 *offset_ptr_ram <= text_base + size) {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
*offset_ptr_ram <= text_base + size) {원본 주석이 'offset_ptr_ram <= text_base + size) {'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*offset_ptr_ram = gd->reloc_off +R_X86_64_RELATIVE addend는 link-time address이므로 signed relocation offset을 더해 destination runtime address로 바꾼다.
re_src->r_addend;원본 82번 줄의 re_src->r_addend;는 앞의 *offset_ptr_ram = gd->reloc_off + 결과를 받아 다음 } else {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
} else {원본 83번 줄의 } else {는 앞의 re_src->r_addend; 결과를 받아 다음 debug(" %p: %lx: rom reloc %lx, ram %p, value %lx, limit %lX\n",로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
debug(" %p: %lx: rom reloc %lx, ram %p, value %lx, limit %lX\n",debug(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
re_src, (ulong)re_src->r_info,원본 85번 줄의 re_src, (ulong)re_src->r_info,는 앞의 debug(" %p: %lx: rom reloc %lx, ram %p, value %lx, limit %lX\n", 결과를 받아 다음 (ulong)re_src->r_offset, offset_ptr_ram,로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(ulong)re_src->r_offset, offset_ptr_ram,원본 86번 줄의 (ulong)re_src->r_offset, offset_ptr_ram,는 앞의 re_src, (ulong)re_src->r_info, 결과를 받아 다음 (ulong)*offset_ptr_ram, text_base + size);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(ulong)*offset_ptr_ram, text_base + size);(ulong)*offset_ptr_ram, text_base + size)를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated x86 image / new stack / new gd를 직접 소유하는지 pointer만 빌리는지, board_init_f의 마지막 x86 initcall과 비복귀 trampoline를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
} else {원본 89번 줄의 } else {는 앞의 } 결과를 받아 다음 debug(" %p: %lx: rom reloc %lx, last %p\n", re_src,로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
debug(" %p: %lx: rom reloc %lx, last %p\n", re_src,debug(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
(ulong)re_src->r_info, (ulong)re_src->r_offset,원본 91번 줄의 (ulong)re_src->r_info, (ulong)re_src->r_offset,는 앞의 debug(" %p: %lx: rom reloc %lx, last %p\n", re_src, 결과를 받아 다음 last_offset);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
last_offset);원본 92번 줄의 last_offset);는 앞의 (ulong)re_src->r_info, (ulong)re_src->r_offset, 결과를 받아 다음 }로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
last_offset = offset_ptr_rom;last_offset에 offset_ptr_rom를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)last_offset = offset_ptr_rom;까지의 동작과 } while (++re_src < re_end);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
} while (++re_src < re_end);원본 96번 줄의 } while (++re_src < re_end);는 앞의 이전 block 경계 결과를 받아 다음 }로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
static void do_elf_reloc_fixups32(unsigned int text_base, uintptr_t size,do_elf_reloc_fixups32(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
Elf32_Rel *re_src, Elf32_Rel *re_end)원본 100번 줄의 Elf32_Rel *re_src, Elf32_Rel *re_end)는 앞의 static void do_elf_reloc_fixups32(unsigned int text_base, uintptr_t size, 결과를 받아 다음 {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
Elf32_Addr *offset_ptr_rom, *last_offset = NULL;Elf32_Addr *offset_ptr_rom, *last_offset에 NULL를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
Elf32_Addr *offset_ptr_ram;원본 103번 줄의 Elf32_Addr *offset_ptr_ram;는 앞의 Elf32_Addr *offset_ptr_rom, *last_offset = NULL; 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)Elf32_Addr *offset_ptr_ram;까지의 동작과 do {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
do {원본 105번 줄의 do {는 앞의 이전 block 경계 결과를 받아 다음 unsigned int type = ELF32_R_TYPE(re_src->r_info);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
unsigned int type = ELF32_R_TYPE(re_src->r_info);unsigned int type = ELF32_R_TYPE(re_src->r_info)를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated x86 image / new stack / new gd를 직접 소유하는지 pointer만 빌리는지, board_init_f의 마지막 x86 initcall과 비복귀 trampoline를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
(빈 줄)unsigned int type = ELF32_R_TYPE(re_src->r_info);까지의 동작과 if (type != R_386_RELATIVE) {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
if (type != R_386_RELATIVE) {type != R_386_RELATIVE를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
printf("%s: unsupported relocation type 0x%x "printf(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
"at %p, ", __func__, type, re_src);원본 110번 줄의 "at %p, ", __func__, type, re_src);는 앞의 printf("%s: unsupported relocation type 0x%x " 결과를 받아 다음 printf("offset = 0x%x\n", re_src->r_offset);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
printf("offset = 0x%x\n", re_src->r_offset);printf("offset에 0x%x\n", re_src->r_offset)를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
continue;'continue;'로 직선 경로를 벗어난다. 이동 대상에서 relocated x86 image / new stack / new gd에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /* Get the location from the relocation entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Get the location from the relocation entry */원본 주석이 'Get the location from the relocation entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
offset_ptr_rom = (Elf32_Addr *)(uintptr_t)re_src->r_offset;offset_ptr_rom에 (Elf32_Addr *)(uintptr_t)re_src->r_offset를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)offset_ptr_rom = (Elf32_Addr *)(uintptr_t)re_src->r_offset;까지의 동작과 /* Check that the location of the relocation is in .text */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Check that the location of the relocation is in .text */원본 주석이 'Check that the location of the relocation is in .text'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if (offset_ptr_rom >= (Elf32_Addr *)(uintptr_t)text_base &&원본 119번 줄의 if (offset_ptr_rom >= (Elf32_Addr *)(uintptr_t)text_base &&는 앞의 /* Check that the location of the relocation is in .text */ 결과를 받아 다음 offset_ptr_rom > last_offset) {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
offset_ptr_rom > last_offset) {원본 120번 줄의 offset_ptr_rom > last_offset) {는 앞의 if (offset_ptr_rom >= (Elf32_Addr *)(uintptr_t)text_base && 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
03. x86 relocation initcall의 실제 순서
common/board_f.c 990-1051줄이다.
990 CONFIG_IS_ENABLED(OF_INITIAL_DTB_READONLY)
991 INITCALL(reloc_fdt);
992 INITCALL(fix_fdt);
993#endif
994 INITCALL(reserve_bootstage);
995 INITCALL(reserve_bloblist);
996 INITCALL(reserve_arch);
997 INITCALL(reserve_stacks);
998 INITCALL(dram_init_banksize);
999 INITCALL(show_dram_config);
1000 WATCHDOG_RESET();
1001 INITCALL(setup_bdinfo);
1002 INITCALL(display_new_sp);
1003 WATCHDOG_RESET();
1004#if !CONFIG_IS_ENABLED(OF_BOARD_FIXUP) || \
1005 !CONFIG_IS_ENABLED(INITIAL_DTB_READONLY)
1006 INITCALL(reloc_fdt);
1007#endif
1008 INITCALL(reloc_bootstage);
1009 INITCALL(reloc_bloblist);
1010 INITCALL(setup_reloc);
1011#if CONFIG_IS_ENABLED(X86) || CONFIG_IS_ENABLED(ARC)
1012 INITCALL(copy_uboot_to_ram);
1013 INITCALL(do_elf_reloc_fixups);
1014#endif
1015 INITCALL(clear_bss);
1016 /*
1017 * Deregister all cyclic functions before relocation, so that
1018 * gd->cyclic_list does not contain any references to pre-relocation
1019 * devices. Drivers will register their cyclic functions anew when the
1020 * devices are probed again.
1021 *
1022 * This should happen as late as possible so that the window where a
1023 * watchdog device is not serviced is as small as possible.
1024 */
1025 INITCALL(cyclic_unregister_all);
1026#if !CONFIG_IS_ENABLED(ARM) && !CONFIG_IS_ENABLED(SANDBOX)
1027 INITCALL(jump_to_copy);
1028#endif
1029}
1030
1031void board_init_f(ulong boot_flags)
1032{
1033 struct board_f boardf;
1034
1035 gd->flags = boot_flags;
1036 gd->flags &= ~GD_FLG_HAVE_CONSOLE;
1037 gd->boardf = &boardf;
1038
1039 initcall_run_f();
1040
1041#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
1042 !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
1043 !defined(CONFIG_ARC)
1044 /* NOTREACHED - jump_to_copy() does not return */
1045 hang();
1046#endif
1047}
1048
1049#if defined(CONFIG_X86) || defined(CONFIG_ARC)
1050/*
1051 * For now this code is only used on x86.
990-1051줄 해설
CONFIG_IS_ENABLED(OF_INITIAL_DTB_READONLY)CONFIG_IS_ENABLED(OF_INITIAL_DTB_READONLY)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 source image 항목과 대조한다.
INITCALL(reloc_fdt);reloc_fdt()을 실행한다. 원래 FDT를 예약한 목적 buffer로 복사하고 active FDT pointer를 새 주소로 바꾼다.
INITCALL(fix_fdt);fix_fdt()을 실행한다. 성공은 0 반환이며, 실패하면 INITCALL 매크로가 함수 이름을 남기고 hang()으로 진행을 중단한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
INITCALL(reserve_bootstage);reserve_bootstage()을 실행한다. bootstage 기록을 relocation 뒤에도 유지하기 위한 새 buffer를 예약한다.
INITCALL(reserve_bloblist);reserve_bloblist()을 실행한다. handoff bloblist 전체를 옮길 구간을 예약한다. header가 선언한 total size와 목적 buffer 크기를 대조해야 한다.
INITCALL(reserve_arch);reserve_arch()을 실행한다. 성공은 0 반환이며, 실패하면 INITCALL 매크로가 함수 이름을 남기고 hang()으로 진행을 중단한다.
INITCALL(reserve_stacks);reserve_stacks()을 실행한다. 새 runtime stack과 architecture별 추가 stack을 예약한다. 최종 SP 정렬과 guard 여유를 확인한다.
INITCALL(dram_init_banksize);dram_init_banksize()을 실행한다. 단일 총량을 넘어 bank별 base와 size를 board info에 기록한다. Linux에 넘길 memory topology의 근거가 된다.
INITCALL(show_dram_config);show_dram_config()을 실행한다. 성공은 0 반환이며, 실패하면 INITCALL 매크로가 함수 이름을 남기고 hang()으로 진행을 중단한다.
WATCHDOG_RESET();WATCHDOG_RESET(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
INITCALL(setup_bdinfo);setup_bdinfo()을 실행한다. 예약된 board info에 DRAM bank와 platform 정보를 채워 runtime 소비자가 읽을 수 있게 한다.
INITCALL(display_new_sp);display_new_sp()을 실행한다. 성공은 0 반환이며, 실패하면 INITCALL 매크로가 함수 이름을 남기고 hang()으로 진행을 중단한다.
WATCHDOG_RESET();WATCHDOG_RESET(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
#if !CONFIG_IS_ENABLED(OF_BOARD_FIXUP) || \#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
!CONFIG_IS_ENABLED(INITIAL_DTB_READONLY)CONFIG_IS_ENABLED(INITIAL_DTB_READONLY)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
INITCALL(reloc_fdt);reloc_fdt()을 실행한다. 원래 FDT를 예약한 목적 buffer로 복사하고 active FDT pointer를 새 주소로 바꾼다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
INITCALL(reloc_bootstage);reloc_bootstage()을 실행한다. bootstage 기록을 새 buffer로 옮겨 relocation 전 timestamp를 보존한다.
INITCALL(reloc_bloblist);reloc_bloblist()을 실행한다. bloblist를 예약된 DRAM 위치로 옮기고 header·entry pointer를 새 주소 체계에 맞춘다.
INITCALL(setup_reloc);setup_reloc()을 실행한다. 현재 image 주소와 목적 주소의 차이인 relocation offset을 계산하고 새 gd에 필요한 relocation metadata를 확정한다.
#if CONFIG_IS_ENABLED(X86) || CONFIG_IS_ENABLED(ARC)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
INITCALL(copy_uboot_to_ram);copy_uboot_to_ram()을 실행한다. 성공은 0 반환이며, 실패하면 INITCALL 매크로가 함수 이름을 남기고 hang()으로 진행을 중단한다.
INITCALL(do_elf_reloc_fixups);do_elf_reloc_fixups()을 실행한다. 성공은 0 반환이며, 실패하면 INITCALL 매크로가 함수 이름을 남기고 hang()으로 진행을 중단한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
INITCALL(clear_bss);clear_bss()을 실행한다. configuration과 architecture가 요구하는 시점에 BSS를 0으로 만든다. 아직 필요한 pre-relocation 임시 상태가 BSS에 남아 있지 않아야 한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Deregister all cyclic functions before relocation, so that원본 주석이 'Deregister all cyclic functions before relocation, so that'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* gd->cyclic_list does not contain any references to pre-relocation원본 주석이 'gd->cyclic_list does not contain any references to pre-relocation'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* devices. Drivers will register their cyclic functions anew when the원본 주석이 'devices. Drivers will register their cyclic functions anew when the'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* devices are probed again.원본 주석이 'devices are probed again.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* This should happen as late as possible so that the window where a원본 주석이 'This should happen as late as possible so that the window where a'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* watchdog device is not serviced is as small as possible.원본 주석이 'watchdog device is not serviced is as small as possible.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
INITCALL(cyclic_unregister_all);cyclic_unregister_all()을 실행한다. relocation 전 device 주소를 품은 cyclic callback을 제거한다. 새 주소에서 device가 probe될 때 다시 등록된다.
#if !CONFIG_IS_ENABLED(ARM) && !CONFIG_IS_ENABLED(SANDBOX)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
INITCALL(jump_to_copy);jump_to_copy()을 실행한다. 새 image 주소로 제어를 비복귀 전달한다. 이 호출이 정상 반환하면 바로 뒤의 hang 경로가 실행된다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 void board_init_f(ulong boot_flags)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
void board_init_f(ulong boot_flags)board_init_f 함수 정의가 시작된다. 입력은 ulong boot_flags이며, board_init_f의 마지막 x86 initcall과 비복귀 trampoline에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
struct board_f boardf;struct board_f boardf를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated x86 image / new stack / new gd를 직접 소유하는지 pointer만 빌리는지, board_init_f의 마지막 x86 initcall과 비복귀 trampoline를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
(빈 줄)struct board_f boardf;까지의 동작과 gd->flags = boot_flags;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
gd->flags = boot_flags;gd->flags에 boot_flags를 = 연산으로 반영해 relocated x86 image / new stack / new gd와 연결된 field를 갱신한다. 바로 다음 gd->flags &= ~GD_FLG_HAVE_CONSOLE;가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
gd->flags &= ~GD_FLG_HAVE_CONSOLE;gd->flags에 ~GD_FLG_HAVE_CONSOLE를 &= 연산으로 반영해 relocated x86 image / new stack / new gd와 연결된 field를 갱신한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
gd->boardf = &boardf;gd->boardf에 &boardf를 = 연산으로 반영해 relocated x86 image / new stack / new gd와 연결된 field를 갱신한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_f_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)gd->boardf = &boardf;까지의 동작과 initcall_run_f();에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
initcall_run_f();initcall_run_f(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
(빈 줄)initcall_run_f();까지의 동작과 #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_ARM, CONFIG_SANDBOX이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \CONFIG_IS_ENABLED(X86_64)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
!defined(CONFIG_ARC)defined(CONFIG_ARC)를 호출한다. 반환 결과는 현재 줄 또는 다음 /* NOTREACHED - jump_to_copy() does not return */에서 검사되는 흐름이다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
/* NOTREACHED - jump_to_copy() does not return */원본 주석이 'NOTREACHED - jump_to_copy() does not return'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
hang();hang(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 #if defined(CONFIG_X86) || defined(CONFIG_ARC)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
#if defined(CONFIG_X86) || defined(CONFIG_ARC)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_X86, CONFIG_ARC이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* For now this code is only used on x86.원본 주석이 'For now this code is only used on x86.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
04. 64-bit 새 stack과 gd를 받는 trampoline
arch/x86/cpu/start64.S 26-50줄이다.
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
26-50줄 해설
call board_init_f원본 26번 줄의 call board_init_f는 앞의 이전 block 경계 결과를 받아 다음 call board_init_f_r로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
call board_init_f_rnew stack과 relocated image에서 runtime C 초기화의 후반부로 들어간다.
(빈 줄)call board_init_f_r까지의 동작과 /* Should not return here */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 .rela.dyn 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Should not return here */원본 주석이 'Should not return here'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
jmp .원본 30번 줄의 jmp .는 앞의 /* Should not return here */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jmp .까지의 동작과 .globl board_init_f_r_trampoline64에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
.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를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
.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를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
board_init_f_r_trampoline64:'board_init_f_r_trampoline64' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* SDRAM has been initialised, U-Boot code has been copied into원본 주석이 'SDRAM has been initialised, U-Boot code has been copied into'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* RAM, BSS has been cleared and relocation adjustments have been원본 주석이 'RAM, BSS has been cleared and relocation adjustments have been'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* %eax = Address of top of new stack원본 주석이 '%eax = Address of top of new stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
(빈 줄)*/까지의 동작과 /* Stack grows down from top of SDRAM */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Stack grows down from top of SDRAM */원본 주석이 'Stack grows down from top of SDRAM'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
movq %rsi, %rsp원본 44번 줄의 movq %rsi, %rsp는 앞의 /* Stack grows down from top of SDRAM */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건이 유지되는지 확인한다.
(빈 줄)movq %rsi, %rsp까지의 동작과 /* New gd is in rdi */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* New gd is in rdi */원본 주석이 'New gd is in rdi'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
(빈 줄)/* New gd is in rdi */까지의 동작과 /* Re-enter U-Boot by calling board_init_f_r() */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Re-enter U-Boot by calling board_init_f_r() */원본 주석이 'Re-enter U-Boot by calling board_init_f_r()'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call board_init_f_rnew stack과 relocated image에서 runtime C 초기화의 후반부로 들어간다.
(빈 줄)call board_init_f_r까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
05. 새 gd와 stack을 architecture trampoline으로 넘기는 jump_to_copy
common/board_f.c 744-785줄이다.
744{
745 return board_fix_fdt((void *)gd->fdt_blob);
746}
747#endif
748
749/* ARM calls relocate_code from its crt0.S */
750#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)
751
752static int jump_to_copy(void)
753{
754 if (gd->flags & GD_FLG_SKIP_RELOC)
755 return 0;
756 /*
757 * x86 is special, but in a nice way. It uses a trampoline which
758 * enables the dcache if possible.
759 *
760 * For now, other archs use relocate_code(), which is implemented
761 * similarly for all archs. When we do generic relocation, hopefully
762 * we can make all archs enable the dcache prior to relocation.
763 */
764#if defined(CONFIG_X86) || defined(CONFIG_ARC)
765 /*
766 * SDRAM and console are now initialised. The final stack can now
767 * be setup in SDRAM. Code execution will continue in Flash, but
768 * with the stack in SDRAM and Global Data in temporary memory
769 * (CPU cache)
770 */
771 arch_setup_gd(gd->new_gd);
772# if CONFIG_IS_ENABLED(X86_64)
773 board_init_f_r_trampoline64(gd->new_gd, gd->start_addr_sp);
774# else
775 board_init_f_r_trampoline(gd->start_addr_sp);
776# endif
777#else
778 relocate_code(gd->start_addr_sp, gd->new_gd, gd->relocaddr);
779#endif
780
781 return 0;
782}
783#endif
784
785/* Record the board_init_f() bootstage (after arch_cpu_init()) */
744-785줄 해설
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
return board_fix_fdt((void *)gd->fdt_blob);board_fix_fdt((void *)gd->fdt_blob)를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /* ARM calls relocate_code from its crt0.S */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 new gd and GDT 상태가 아래 블록의 입력으로 사용되는 경계다.
/* ARM calls relocate_code from its crt0.S */원본 주석이 'ARM calls relocate_code from its crt0.S'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_ARM, CONFIG_SANDBOX이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)까지의 동작과 static int jump_to_copy(void)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
static int jump_to_copy(void)pre-relocation initcall의 마지막 비복귀 경계를 C 코드에서 정의한다. skip-reloc flag가 아니면 새 gd와 stack을 architecture trampoline에 넘긴다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated x86 image / new stack / new gd의 수명에 맞춰 묶어 읽는다.
if (gd->flags & GD_FLG_SKIP_RELOC)gd->flags & GD_FLG_SKIP_RELOC를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* x86 is special, but in a nice way. It uses a trampoline which원본 주석이 'x86 is special, but in a nice way. It uses a trampoline which'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* enables the dcache if possible.원본 주석이 'enables the dcache if possible.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* For now, other archs use relocate_code(), which is implemented원본 주석이 'For now, other archs use relocate_code(), which is implemented'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* similarly for all archs. When we do generic relocation, hopefully원본 주석이 'similarly for all archs. When we do generic relocation, hopefully'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* we can make all archs enable the dcache prior to relocation.원본 주석이 'we can make all archs enable the dcache prior to relocation.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
#if defined(CONFIG_X86) || defined(CONFIG_ARC)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_X86, CONFIG_ARC이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* SDRAM and console are now initialised. The final stack can now원본 주석이 'SDRAM and console are now initialised. The final stack can now'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* be setup in SDRAM. Code execution will continue in Flash, but원본 주석이 'be setup in SDRAM. Code execution will continue in Flash, but'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* with the stack in SDRAM and Global Data in temporary memory원본 주석이 'with the stack in SDRAM and Global Data in temporary memory'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* (CPU cache)원본 주석이 '(CPU cache)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
arch_setup_gd(gd->new_gd);x86의 FS/GDT 기반 gd access가 임시 gd가 아니라 DRAM의 새 global data를 가리키도록 바꾼다.
# if CONFIG_IS_ENABLED(X86_64)# 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
board_init_f_r_trampoline64(gd->new_gd, gd->start_addr_sp);SysV x86-64 ABI로 RDI=new gd, RSI=new stack을 넘긴다. trampoline은 RSP를 바꾼 뒤 CAR 수명을 종료한다.
# else# 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
board_init_f_r_trampoline(gd->start_addr_sp);board_init_f_r_trampoline(gd->start_addr_sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
# endif# 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
relocate_code(gd->start_addr_sp, gd->new_gd, gd->relocaddr);relocate_code(gd->start_addr_sp, gd->new_gd, gd->relocaddr)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated x86 image / new stack / new gd의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 runtime stack 항목과 대조한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 return 0;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated x86 image / new stack / new gd를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /* Record the board_init_f() bootstage (after arch_cpu_init()) */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 runtime stack 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Record the board_init_f() bootstage (after arch_cpu_init()) */원본 주석이 'Record the board_init_f() bootstage (after arch_cpu_init())'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
DETAILS
내부 동작을 더 깊게 읽기
진입 조건을 먼저 고정한다
board_init_f reservation에서 들어온 실행은 board_init_f의 마지막 x86 initcall과 비복귀 trampoline에 놓여 있다. 이때 interrupt, MMU/cache, stack, heap 중 무엇이 이미 준비되었는지 소스의 호출자까지 올라가 확인한다. 같은 함수라도 SPL, relocation 전후, app thread처럼 호출 문맥이 달라지면 허용되는 API와 지연 시간이 달라진다.
ARM처럼 board_init_f가 돌아온 뒤 assembly relocate_code를 찾으면 안 된다. x86는 C initcall 목록에서 copy, fixup, BSS clear와 jump를 완료하며 trampoline은 새 stack을 활성화한 뒤에만 CAR를 해제한다.
중심 객체의 생성과 공개를 나눈다
이 글의 중심 객체는 relocated x86 image / new stack / new gd다. 메모리를 확보한 시점, 필드를 채운 시점, 전역 list나 다른 subsystem에 공개한 시점을 구분한다. 공개 뒤 오류가 발생한다면 목록에서 제거하고 child, buffer, reference를 역순으로 정리하는지 확인한다.
빌드 산출물 관점에서는 DRAM의 relocated U-Boot proper 안에 해당 symbol과 section이 실제로 포함되었는지도 map과 objdump로 검증한다.
주소, 크기와 정렬을 계산한다
부트 코드의 오류는 논리보다 주소 계산에서 먼저 드러나는 경우가 많다. source range, destination range, header가 말하는 payload size, block 또는 page 단위를 표로 적고 각 구간의 끝 주소를 직접 계산한다. 끝 주소는 start + size - 1인지 exclusive end인지 API 계약을 확인한다.
destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다.
성공 flag와 실제 완료 시점을 맞춘다
copy_uboot_to_ram → ELF relocation fixups → clear BSS → jump_to_copy → new DRAM stack → car_uninit 구간에서는 부분 초기화 상태가 생길 수 있다. flag, list insertion, callback 등록, storage write 완료 중 무엇이 성공의 기준인지 찾는다. hardware write나 DMA가 포함되면 함수 반환과 장치 완료가 같은 시점인지도 확인한다.
다른 CPU, interrupt handler, USB completion 또는 shell command가 상태를 관찰할 수 있다면 memory ordering과 lock 범위도 함께 읽는다.
마지막 handoff의 계약을 적는다
정상 경로는 board_init_f_r에서 끝난다. 이 단계가 함수 반환인지, scheduler 전환인지, 다른 image로의 비복귀 분기인지 구분한다. 비복귀 handoff라면 cache clean/invalidate, interrupt disable, 장치 quiesce, argument register와 FDT 또는 image address가 최종 점검 항목이다.
반환하는 경로라면 caller가 오류와 부분 성공을 구분하고 다음 후보 또는 복구 경로를 선택하는지 확인한다.
IMPLEMENTATION NOTES
구현을 읽을 때 놓치기 쉬운 부분
x86 relocation은 board_init_f의 C initcall 목록 안에서 끝난다
common/board_f.c의 x86/ARC 조건부 목록은 copy_uboot_to_ram(), do_elf_reloc_fixups(), clear_bss(), jump_to_copy()를 차례로 실행한다. ARM의 assembly relocation 경로를 찾는 대신, 분석 대상 .config로 전처리한 initcall 목록에서 이 함수들이 실제로 남는지 확인해야 한다.
jump_to_copy()는 gd->start_addr_sp, 새 gd, gd->relocaddr를 architecture trampoline에 넘긴다. 그 순간부터 old CAR stack과 ROM/flash copy는 정상 runtime의 소유자가 아니다.
copy_uboot_to_ram은 LMA에서 relocaddr로 image byte를 옮긴다
source는 현재 실행 image의 linker/runtime 위치이고 destination은 board_init_f reservation 단계에서 정한 gd->relocaddr다. copy 범위는 text만이 아니라 relocation 전 image의 실제 size와 section 경계를 따라야 한다. source와 destination overlap 여부도 방향과 memcpy 계약에 영향을 준다.
copy 직후 destination byte가 같다는 것만으로 실행 가능하지 않다. absolute pointer와 relocation entry, BSS, stack, gd, GDT가 새 주소 체계에 맞아야 한다.
ELF relocation entry는 복사된 image 안의 pointer를 새 base로 고친다
do_elf_reloc_fixups64()는 dynamic relocation table을 순회해 relocation type과 addend를 읽고 destination image의 target word를 갱신한다. text byte copy와 pointer fixup을 분리해 봐야 '코드는 옮겼는데 왜 old address를 읽는가'를 설명할 수 있다.
gd->reloc_off는 무조건 모든 pointer에 더하는 값이 아니다. relocation table이 지정한 대상과 type 계약만 수정해야 하며, runtime에 이미 새 address로 생성된 pointer를 다시 보정하면 이중 relocation이 된다.
BSS는 복사하는 구간이 아니라 새 runtime에서 0으로 만드는 구간이다
BSS는 file image에 payload byte가 없으므로 copy source에서 가져올 내용이 없다. relocation destination의 __bss_start부터 __bss_end를 clear해야 C의 zero-initialization 계약이 성립한다. old image의 BSS를 복사하는 방식으로 이해하면 size 계산과 persistent garbage 문제를 놓친다.
BSS clear가 relocation fixup 대상이나 새 stack/gd를 덮지 않도록 linker symbol과 reservation map을 대조한다. 특히 linker script 변경 뒤에는 __bss_start, __bss_end, relocation table 순서를 다시 확인한다.
32-bit trampoline과 64-bit trampoline의 argument register가 다르다
32-bit board_init_f_r_trampoline은 %eax의 new stack top을 %esp로 옮기고 car_uninit() 후 board_init_f_r()을 호출한다. 64-bit board_init_f_r_trampoline64는 %rsi를 %rsp로 사용하고 %rdi에 new gd가 들어온다. source 주석의 %eax 문구만 믿지 말고 실제 instruction과 caller ABI를 기준으로 읽어야 한다.
새 stack을 선택하기 전에 old stack의 return address에 의존하는 일반 return을 해서는 안 된다. trampoline은 비복귀 경계이며 이후 backtrace의 바닥도 여기에서 끊긴다.
CAR uninit은 새 DRAM stack으로 바꾼 뒤에 해야 한다
CAR가 현재 stack을 담고 있는데 먼저 car_uninit()을 호출하면 함수 call 자체의 return address와 local data가 사라진다. 그래서 trampoline은 %esp를 새 DRAM stack으로 옮긴 다음 CAR를 해제한다. 이 두 줄의 순서는 x86 relocation에서 가장 직접적인 lifetime 경계다.
CAR 해제 뒤 old gd, early malloc object, FSP temporary pointer 중 무엇을 계속 참조하는지 점검한다. 필요한 metadata는 HOB list처럼 DRAM 수명이 보장되는 영역으로 이전되어야 한다.
relocation 완료 증거는 PC 하나가 아니라 다섯 주소의 일치다
board_init_f_r 진입에서 PC가 gd->relocaddr range에 있고 SP가 gd->start_addr_sp 아래에 있으며 gd가 new DRAM copy를 가리켜야 한다. GDT/FS base, BSS zero, relocation target pointer까지 합쳐야 정상 runtime을 증명할 수 있다.
GDB script로 PC, SP, gd, __bss_start, relocation table start/end를 한 번에 출력하고 old CAR range를 poison하거나 접근 금지로 바꾸면 stale reference를 찾기 쉽다.
reloc_off를 source와 destination의 차이로 직접 계산한다
source image base가 0xfff00000, destination gd->relocaddr=0x7f000000이면 signed 차이는 reloc_off = 0x7f000000 - 0xfff00000 = -0x80f00000이다. symbol board_init_r의 old runtime address가 0xfff48210이면 새 target은 0x7f048210이다. unsigned 32-bit 출력만 보면 wrap된 큰 양수처럼 보이므로 pointer width와 signed 표현을 함께 적는다.
같은 offset을 PC, linker symbol, relocation entry target에 무조건 더하면 안 된다. 코드 분기 target 계산, ELF relocation type, 이미 destination 기준으로 생성한 runtime pointer의 소유자를 구분한다.
R_X86_64_RELATIVE 한 건을 addend부터 새 memory word까지 계산한다
link base가 0xfff00000, destination base가 0x7f000000이면 gd->reloc_off=-0x80f00000이다. relocation offset 0xfff5a120의 patch 주소는 0x7f05a120이고, addend가 link-time address 0xfff31f40이면 기록할 64-bit 값은 addend + reloc_off = 0x7f031f40이다. readelf -rW u-boot의 offset/type/addend와 fixup 전후 word를 이 식으로 대조한다.
symbol lookup이 필요 없는 RELATIVE와 symbol value를 소비하는 다른 relocation을 같은 식으로 처리하면 안 된다. unsupported type을 만났을 때 로그와 return path가 무엇인지도 함께 확인한다.
CAR를 끄기 직전 old/new 객체의 소유권 표를 만든다
PC는 destination image, SP와 gd는 DRAM, GDT/FS base도 DRAM, BSS는 zero, HOB/FDT pointer는 보존 영역을 가리켜야 한다. 반대로 old CAR stack frame, early malloc object와 source image의 temporary pointer는 이 지점에서 만료된다.
GDB에서 old CAR range에 hardware watchpoint를 걸거나 emulator에서 접근 금지로 바꾼 뒤 board_init_f_r까지 진행한다. stale read가 잡히면 단순 pointer 값이 아니라 그 pointer를 누가 언제 만들었는지 producer까지 거슬러 올라간다.
OBJECT LIFETIME
객체와 수명
| 대상 | 만들어지는 시점 | 유효 범위 | 확인할 조건 |
|---|---|---|---|
relocated x86 image / new stack / new gd | copy_uboot_to_ram | board_init_f_r 또는 오류 정리 완료까지 | destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다. |
| 입력 buffer / metadata | board_init_f reservation | parse와 검증이 끝날 때까지 | 길이, 정렬, 소유권, 변조 가능성 |
| 등록된 list / descriptor | ELF relocation fixups | subsystem 종료 또는 image handoff까지 | 이중 등록, dangling pointer, 오류 unwind |
| hardware 또는 persistent state | 실제 write/probe가 완료된 뒤 | reset 또는 명시적 해제·갱신까지 | flush, timeout, 전원 차단, rollback |
DRAM의 relocated U-Boot proper | link/image 생성 시점 | 다음 stage가 새 image로 교체할 때까지 | load address, entry, section과 header 일치 |
FAILURE PATH
실패 지점과 증상
| # | 조건 | 관찰되는 증상 | 먼저 볼 단계 |
|---|---|---|---|
| 01 | copy 범위 오류 | destination text/data 일부 손상 | copy_uboot_to_ram |
| 02 | relocation type 또는 offset 오류 | old image pointer 참조 | ELF relocation fixups |
| 03 | BSS clear 범위 오류 | new gd/stack 덮음 | clear BSS |
| 04 | CAR 조기 해제 | return address와 local frame 소실 | jump_to_copy |
로그가 끊긴 마지막 함수만 고치지 않는다. 그 함수가 받은 주소, size, flag가 만들어진 앞 단계까지 올라가고, 오류 뒤 등록 객체와 hardware 상태가 남았는지도 확인한다.
EVIDENCE
소스 밖에서 확인할 증거
소스 해석은 실제 빌드 산출물과 target 로그로 닫아야 한다. 아래 명령의 보드 이름과 toolchain prefix는 사용 중인 빌드 환경에 맞게 바꾼다.
| # | 목적 | 명령 또는 계측 | 판정 기준 |
|---|---|---|---|
| 01 | 전처리 결과 | make <board>_defconfig && make V=1 | 선택된 CONFIG와 실제 compile/link 명령을 보관한다. |
| 02 | symbol과 section | ${CROSS_COMPILE}nm -n u-boot | grep 'board_init_f_r_trampoline' | symbol 주소가 기대한 stage와 image에 들어갔는지 확인한다. |
| 03 | 어셈블리 대조 | ${CROSS_COMPILE}objdump -drS u-boot | C 한 줄이 실제 branch, relocation, memory access로 어떻게 바뀌었는지 본다. |
| 04 | 주소 배치 | bdinfo | relocaddr, malloc, FDT, stack과 DRAM bank 주소를 한 번에 기록한다. |
| 05 | runtime 관찰 | dm tree; dm uclass; printenv | driver model과 환경이 이 단계에서 실제로 공개되었는지 확인한다. |
LAB
직접 확인할 실험
- 01readelf -rW와 relocation target 몇 개를 손계산
copy_uboot_to_ram진입 전후에 source image의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다. 조건이 처음 깨지는 줄을 기록한다. - 02copy 전후 image hash 비교
ELF relocation fixups진입 전후에 gd->relocaddr의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다. 조건이 처음 깨지는 줄을 기록한다. - 03board_init_f_r 진입에서 PC/SP/gd/FS base 동시 기록
clear BSS진입 전후에 .rela.dyn의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다. 조건이 처음 깨지는 줄을 기록한다. - 04CAR를 uninit 직후 poison해 stale pointer 탐지
jump_to_copy진입 전후에 new BSS의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 destination image의 code byte, relocation target, BSS, gd/GDT와 stack이 모두 새 DRAM 주소 체계에 일치한 뒤 old CAR와 source image의 수명을 끝내야 한다. 조건이 처음 깨지는 줄을 기록한다.
PRIMARY REFERENCES