U-Boot · source analysis

x86 image copy, ELF relocation과 CAR 종료

board_init_f의 x86 전용 initcall이 image를 DRAM으로 복사하고 ELF relocation과 BSS clear를 끝낸 뒤 새 stack에서 board_init_f_r로 들어가는 과정을 읽습니다.

표의 버전은 소스 분석 기준이며 최신 보안 버전 권고가 아닙니다. TF-A LTS 2.14.3 이후에는 보안 수정이 있습니다. 제품 적용 시 공식 변경 기록과 사용 플랫폼의 errata를 확인하세요.

Series
20 / 22
Baseline
v2026.07
Commit
ece349ade297
Source
arch/x86/cpu/start.S:173-224 (발췌 범위)

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의 수명을 끝내야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.

객체와 주소가 놓이는 구조

그림 1. x86 image copy, ELF relocation과 CAR 종료에서 입력, 내부 상태, 출력의 경계
입력과 전제내부 상태외부로 공개되는 결과
01source image
02gd->relocaddr
03.rela.dyn
04new BSS
05new gd and GDT
06runtime stack

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

실행 흐름

그림 2. 정상 경로의 주요 호출과 상태 전달
board_init_f reservation
copy_uboot_to_ram
ELF relocation fixups
clear BSS
jump_to_copy
new DRAM stack
car_uninit
board_init_f_r

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

단계별 입력과 출력

각 단계의 입력과 처리 후 바뀌는 상태를 정리한 표다. 사용 중인 소스 버전에서 다른 필드를 변경한다면 그에 맞춰 표도 수정한다.

#단계진입 시 신뢰할 상태성공 뒤 남아야 할 상태다음 소비자
01board_init_f reservationboard_init_f의 마지막 x86 initcall과 비복귀 trampolinesource imagecopy_uboot_to_ram
02copy_uboot_to_ramboard_init_f reservation 완료 상태gd->relocaddrELF relocation fixups
03ELF relocation fixupscopy_uboot_to_ram 완료 상태.rela.dynclear BSS
04clear BSSELF relocation fixups 완료 상태new BSSjump_to_copy
05jump_to_copyclear BSS 완료 상태new gd and GDTnew DRAM stack
06new DRAM stackjump_to_copy 완료 상태runtime stackcar_uninit
07car_uninitnew DRAM stack 완료 상태runtime stackboard_init_f_r
08board_init_f_rcar_uninit 완료 상태runtime stack최종 DRAM의 relocated U-Boot proper 또는 다음 stage

분석 범위의 전제와 비교해, 다음 단계에 잘못된 입력을 넘긴 부분부터 조사한다.

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

아래 그림은 주소, 객체를 사용하는 기간, 제어권을 넘기는 시점을 중심으로 정리했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, 맵 파일과 링커 심벌로 확인한다.

그림 3. x86 relocation 전후의 memory 소유권

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

높은 주소↓ 주소 감소
DRAM top reservationsnew stack, gd, GDT, FDT
gd->relocaddr destinationcopied text/data + fixed pointers
new BSScopy가 아니라 zero clear
free DRAMmalloc과 payload가 이후 사용
old CAR / source imagetrampoline 뒤 수명 종료
낮은 주소예약 cursor 진행 방향
그림 4. board_init_f 안에서 끝나는 x86 relocation

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

01reserve destination
02copy_uboot_to_ram
03do_elf_reloc_fixups
04clear_bss
05jump_to_copy
06new SP
07car_uninit
08board_init_f_r
그림 5. relocation 단계가 생산하고 다음 단계가 소비하는 값

왼쪽 생산자가 가운데 상태를 완성하고, 오른쪽 소비자가 그 상태를 처음 사용하는 관계다.

01reserve_ubootgd->relocaddrcopy_uboot_to_ram
02copydestination bytesELF fixup
03.rela.dynnew absolute pointersruntime C
04clear_bsszero-init globalsboard_init_f_r
05new stackCAR 독립 call framecar_uninit

원본 코드

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

commitece349ade2973e220f524ce59e59711cc919263fanchor · L185board_init_f_r_trampoline:
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

발췌 범위와 읽기 기준

원문 173-224줄에는 앞뒤 문맥이 포함됩니다. 기준 구문 board_init_f_r_trampoline:은 L185에 있습니다. 발췌 첫 줄을 함수 정의의 시작으로 해석하지 않습니다.

ARM처럼 board_init_f가 돌아온 뒤 assembly relocate_code를 찾으면 안 된다. x86는 C initcall 목록에서 copy, fixup, BSS clear와 jump를 완료하며 trampoline은 새 stack을 활성화한 뒤에만 CAR를 해제한다.

185board_init_f_r_trampoline:

복사와 relocation fixup이 끝난 destination image에서 새 stack으로 runtime을 재개하는 32-bit trampoline이다.

195 movl %eax, %esp

caller가 EAX로 넘긴 DRAM stack top을 활성 stack으로 만든다. 이후 old CAR stack은 더 이상 사용하지 않는다.

198 call car_uninit

stack을 DRAM으로 옮긴 뒤 Cache-As-RAM을 해제한다. 두 줄의 순서를 바꾸면 return address부터 사라진다.

201 call board_init_f_r

new stack과 relocated image에서 runtime C 초기화의 후반부로 들어간다.

함께 읽어야 하는 원본 코드

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

01. U-Boot image byte를 gd->relocaddr로 복사

arch/x86/lib/relocate.c 19-59줄이다.

filearch/x86/lib/relocate.canchor · L26int copy_uboot_to_ram(void)
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);

주요 구문 해설

59 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줄이다.

filearch/x86/lib/relocate.canchor · L52static void do_elf_reloc_fixups64
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) {

주요 구문 해설

59 unsigned long long type = ELF64_R_TYPE(re_src->r_info);

ELF64 r_info에서 relocation type을 분리한다. 이 구현은 R_X86_64_RELATIVE만 지원하므로 다른 type은 같은 식으로 고치지 않는다.

75 offset_ptr_ram = (Elf64_Addr *)((ulong)offset_ptr_rom +

link-time patch 주소에 gd->reloc_off를 더해 destination image에서 실제로 쓸 8-byte word 위치를 만든다.

81 *offset_ptr_ram = gd->reloc_off +

R_X86_64_RELATIVE addend는 link-time address이므로 signed relocation offset을 더해 destination runtime address로 바꾼다.

03. x86 relocation initcall의 실제 순서

common/board_f.c 990-1051줄이다.

filecommon/board_f.canchor · L1012INITCALL(copy_uboot_to_ram);
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.

04. 64-bit 새 stack과 gd를 받는 trampoline

arch/x86/cpu/start64.S 26-49줄이다.

filearch/x86/cpu/start64.Sanchor · L34board_init_f_r_trampoline64:
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

주요 구문 해설

27 call board_init_f_r

new stack과 relocated image에서 runtime C 초기화의 후반부로 들어간다.

49 call board_init_f_r

new stack과 relocated image에서 runtime C 초기화의 후반부로 들어간다.

05. 새 gd와 stack을 architecture trampoline으로 넘기는 jump_to_copy

common/board_f.c 744-785줄이다.

filecommon/board_f.canchor · L752static int jump_to_copy(void)
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()) */

주요 구문 해설

752static int jump_to_copy(void)

pre-relocation initcall의 마지막 비복귀 경계를 C 코드에서 정의한다. skip-reloc flag가 아니면 새 gd와 stack을 architecture trampoline에 넘긴다.

771 arch_setup_gd(gd->new_gd);

x86의 FS/GDT 기반 gd access가 임시 gd가 아니라 DRAM의 새 global data를 가리키도록 바꾼다.

773 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 수명을 종료한다.

내부 동작을 더 깊게 읽기

01

진입 조건을 먼저 고정한다

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를 해제한다.

02

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

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

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

03

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

부트 코드에서는 주소 계산 오류를 주의해야 한다. 원본과 목적지의 주소 범위, 헤더에 기록된 데이터 크기, 블록·페이지 단위를 표로 정리하고 각 구간의 끝 주소를 계산한다. 끝 주소가 start + size - 1처럼 마지막 바이트를 가리키는지, 구간 바로 다음 주소를 가리키는지는 API 규약에서 확인한다.

04

성공 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, 인터럽트 처리기, USB 완료 콜백, 셸 명령에서도 이 상태를 읽는다면 메모리 접근 순서와 잠금 범위를 함께 확인한다.

05

제어권을 넘기기 전에 확인할 조건을 정리한다

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

호출한 함수로 돌아오는 경로라면 오류와 일부 처리만 완료된 상태를 구분하는지 확인한다. 이어서 다음 후보를 시도할지, 복구 절차를 실행할지 살펴본다.

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

01

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의 소유자가 아니다.

02

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가 새 주소 체계에 맞아야 한다.

03

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이 된다.

04

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 순서를 다시 확인한다.

05

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의 바닥도 여기에서 끊긴다.

06

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 수명이 보장되는 영역으로 이전되어야 한다.

07

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를 찾기 쉽다.

08

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의 소유자를 구분한다.

09

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가 무엇인지도 함께 확인한다.

10

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까지 거슬러 올라간다.

객체와 수명

대상만들어지는 시점유효 범위확인할 조건
relocated x86 image / new stack / new gdcopy_uboot_to_ramboard_init_f_r 또는 오류 정리 완료까지진입 전제와 상태 계약
입력 buffer / metadataboard_init_f reservationparse와 검증이 끝날 때까지길이, 정렬, 소유권, 변조 가능성
등록된 list / descriptorELF relocation fixups하위 시스템을 종료하거나 다음 이미지에 제어권을 넘길 때까지중복 등록, 해제된 객체를 가리키는 포인터, 오류 후 자원 정리
hardware 또는 persistent state실제 write/probe가 완료된 뒤reset 또는 명시적 해제·갱신까지쓰기 완료, 시간 초과, 전원 차단, 이전 상태 복원
DRAM의 relocated U-Boot properlink/image 생성 시점다음 stage가 새 image로 교체할 때까지적재 주소, 진입점, 섹션, 헤더의 일치 여부

실패 지점과 증상

#조건관찰되는 증상먼저 볼 단계
01copy 범위 오류destination text/data 일부 손상copy_uboot_to_ram
02relocation type 또는 offset 오류old image pointer 참조ELF relocation fixups
03BSS clear 범위 오류new gd/stack 덮음clear BSS
04CAR 조기 해제return address와 local frame 소실jump_to_copy

로그가 끊긴 함수뿐 아니라 그 함수에 주소, 크기, 플래그를 전달한 앞 단계도 조사한다. 오류 처리 후 등록된 객체나 하드웨어 설정이 그대로 남아 있는지도 확인한다.

소스 밖에서 확인할 증거

소스에서 파악한 동작을 실제 빌드 결과와 대상 보드의 로그로 확인한다. 아래 명령의 보드 이름과 툴체인 접두사는 사용 중인 빌드 환경에 맞게 바꾼다.

#목적명령 또는 계측판정 기준
01전처리 결과make <board>_defconfig && make V=1선택된 CONFIG와 실제 compile/link 명령을 보관한다.
02symbol과 section${CROSS_COMPILE}nm -n u-boot | grep 'board_init_f_r_trampoline'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
    readelf -rW와 relocation target 몇 개를 손계산

    copy_uboot_to_ram 진입 전후에 source image의 주소·크기·반환값과 timestamp를 함께 남긴다.

  2. 02
    copy 전후 image hash 비교

    ELF relocation fixups 진입 전후에 gd->relocaddr의 주소·크기·반환값과 timestamp를 함께 남긴다.

  3. 03
    board_init_f_r 진입에서 PC/SP/gd/FS base 동시 기록

    clear BSS 진입 전후에 .rela.dyn의 주소·크기·반환값과 timestamp를 함께 남긴다.

  4. 04
    CAR를 uninit 직후 poison해 stale pointer 탐지

    jump_to_copy 진입 전후에 new BSS의 주소·크기·반환값과 timestamp를 함께 남긴다.

원문과 다음 글