Barebox · source analysis

boot entry provider와 boot 명령

문자열, cdev, directory에서 boot entry를 수집하고 우선순위에 따라 실행하는 공통 boot framework를 읽습니다.

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

Series
14 / 19
Baseline
v2026.06.1
Commit
314c679eea2d
Source
common/boot.c:159-247 (발췌 범위)

boot entry provider와 boot 명령 단계에서 실제로 바뀌는 상태는 무엇인가?

entry 발견과 실행을 분리한다. provider가 만든 객체의 release callback, dry-run과 실패 후 다음 entry 이동 조건을 확인한다.

entry는 실행이 끝날 때까지 provider data를 소유하고 실패가 다음 후보로 전파될 수 있는 명확한 return code를 제공해야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.

객체와 주소가 놓이는 구조

그림 1. boot entry provider와 boot 명령에서 입력, 내부 상태, 출력의 경계
입력과 전제내부 상태외부로 공개되는 결과
01bootentries collection
02bootentry object
03provider data
04boot result

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

실행 흐름

그림 2. 정상 경로의 주요 호출과 상태 전달
boot command
provider scan
bootentry list
boot_entry
bootm or custom boot

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

단계별 입력과 출력

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

#단계진입 시 신뢰할 상태성공 뒤 남아야 할 상태다음 소비자
01boot command부트 CPU의 동기 실행 문맥bootentries collectionprovider scan
02provider scanboot command 완료 상태bootentry objectbootentry list
03bootentry listprovider scan 완료 상태provider databoot_entry
04boot_entrybootentry list 완료 상태boot resultbootm or custom boot
05bootm or custom bootboot_entry 완료 상태boot result이 단계의 처리 결과를 사용하는 코드

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

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

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

그림 3. provider scan에서 bootentry 실행까지

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

01boot argument
02provider 선택
03entry collection
04priority 정렬
05dry-run 표시
06boot_entry
07실패 시 next
08release
그림 4. bootentries collection과 개별 bootentry

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

Collection

01여러 provider 결과

02정렬·iteration

03공통 release

04fallback 상태

05display list

Entry

01title·path

02provider private data

03boot callback

04한 번의 결과

05개별 release

원본 코드

아래 코드는 Barebox v2026.06.1의 common/boot.c에서 159-247줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.

commit314c679eea2d0ca847c64d1b646a1cf91225c0ecanchor · L183int boot_entry(struct bootentry *be
159	if (!global_boot_default)
160		global_boot_default = xstrdup(
161			IF_ENABLED(CONFIG_EFI_LOADER_BOOTMGR,  "efibootmgr ")
162			IF_ENABLED(CONFIG_BOOT_DEFAULTS,       "bootsource ")
163			IF_ENABLED(CONFIG_BOOT_DEFAULTS,       "storage.builtin.nonbootsource ")
164			IF_ENABLED(CONFIG_BOOT_DEFAULTS,       "storage.removable.nonbootsource ")
165			IF_ENABLED(CONFIG_NET,                 "net")
166			""
167		);
168
169	globalvar_add_simple_string("boot.default", &global_boot_default);
170	globalvar_add_simple_int("boot.watchdog_timeout",
171				 &boot_watchdog_timeout, "%u");
172	globalvar_add_simple("linux.bootargs.base", NULL);
173	global_user = xstrdup("none");
174	globalvar_add_simple_string("user", &global_user);
175
176	return 0;
177}
178late_initcall(init_boot);
179
180BAREBOX_MAGICVAR(global.boot.watchdog_timeout,
181		"Watchdog enable timeout in seconds before booting");
182
183int boot_entry(struct bootentry *be, int verbose, int dryrun)
184{
185	int ret;
186
187	pr_info("Booting entry '%s'\n", be->title);
188
189	if (IS_ENABLED(CONFIG_WATCHDOG) && boot_watchdog_timeout) {
190		boot_enabled_watchdog = watchdog_get_default();
191
192		ret = watchdog_set_timeout(boot_enabled_watchdog, boot_watchdog_timeout);
193		if (ret) {
194			pr_warn("Failed to enable watchdog: %pe\n", ERR_PTR(ret));
195			boot_enabled_watchdog = NULL;
196		}
197	}
198
199	ret = be->boot(be, verbose, dryrun);
200	if (ret && ret != -ENOMEDIUM)
201		pr_err("Booting entry '%s' failed: %pe\n", be->title, ERR_PTR(ret));
202
203	globalvar_set_match("linux.bootargs.dyn.", "");
204
205	return ret;
206}
207
208static void bootsource_action(struct menu *m, struct menu_entry *me)
209{
210	struct bootentry *be = container_of(me, struct bootentry, me);
211
212	boot_entry(be, 0, 0);
213
214	printf("Press any key to continue\n");
215
216	read_key();
217}
218
219static void bootscript_entry_release(struct bootentry *entry)
220{
221	free(entry);
222}
223
224/*
225 * bootscript_create_entry - create a boot entry from a script name
226 */
227static int bootscript_create_entry(struct bootentries *bootentries, const char *name)
228{
229	struct bootentry_script *bs;
230	enum filetype type;
231	int ret;
232
233	ret = file_name_detect_type(name, &type);
234	if (ret)
235		return ret;
236
237	if (type != filetype_sh)
238		return -EINVAL;
239
240	bs = xzalloc(sizeof(*bs));
241	bs->entry.me.type = MENU_ENTRY_NORMAL;
242	bs->entry.release = bootscript_entry_release;
243	bs->entry.boot = bootscript_boot;
244	bs->entry.path = xstrdup_const(name);
245	bs->entry.title = xstrdup_const(kbasename(bs->entry.path));
246	bs->entry.description = basprintf("script: %s", name);
247	bootentries_add_entry(bootentries, &bs->entry);

발췌 범위와 읽기 기준

원문 159-247줄에는 앞뒤 문맥이 포함됩니다. 기준 구문 int boot_entry(struct bootentry *be은 L183에 있습니다. 발췌 첫 줄을 함수 정의의 시작으로 해석하지 않습니다.

entry 발견과 실행을 분리한다. provider가 만든 객체의 release callback, dry-run과 실패 후 다음 entry 이동 조건을 확인한다.

함께 읽어야 하는 원본 코드

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

01. 문자열 이름에서 provider와 boot entry를 찾는 함수

common/boot.c 441-527줄이다.

filecommon/boot.canchor · L451int bootentry_create_from_name
441 * - a full path of a boot script
442 * - a full path of a bootloader spec entry
443 * - a device name
444 * - a cdev name
445 * - a full path of a directory containing bootloader spec entries
446 * - a full path of a directory containing bootscripts
447 * - a nfs:// path
448 *
449 * Returns the number of entries found or a negative error code.
450 */
451int bootentry_create_from_name(struct bootentries *bootentries,
452				      const char *name)
453{
454	struct bootentry_provider *p;
455	int found = 0, ret;
456	char *nfspath;
457
458	nfspath = parse_nfs_url(name);
459	if (nfspath)
460		name = nfspath;
461
462	list_for_each_entry(p, &bootentry_providers, list) {
463		BOOTENTRIES(provider_bootentries);
464
465		ret = p->generate(&provider_bootentries, name);
466		if (ret > 0)
467			found += ret;
468
469		/* We want to allow for providers to sort their bootentries as
470		 * they see fit, so they are passed an empty list above with
471		 * only their own entries and then we aggregate here
472		 */
473		bootentries_merge(bootentries, &provider_bootentries);
474	}
475
476	free(nfspath);
477
478	if (IS_ENABLED(CONFIG_COMMAND_SUPPORT) && !found) {
479		const char *path;
480
481		if (*name != '/')
482			path = basprintf("/env/boot/%s", name);
483		else
484			path = xstrdup_const(name);
485
486		ret = bootscript_scan_path(bootentries, path);
487		if (ret > 0)
488			found += ret;
489
490		free_const(path);
491	}
492
493	return found;
494}
495
496/*
497 * bootsources_menu - show a menu from an array of names
498 */
499void bootsources_menu(struct bootentries *bootentries,
500		      unsigned default_entry, int timeout)
501{
502	struct bootentry *entry;
503	struct menu_entry *back_entry;
504	int i = 1;
505
506	if (!IS_ENABLED(CONFIG_MENU)) {
507		pr_warn("no menu support available\n");
508		return;
509	}
510
511	bootentries_for_each_entry(bootentries, entry) {
512		if (!entry->me.display)
513			entry->me.display = xstrdup_const(entry->title);
514		entry->me.action = bootsource_action;
515		menu_add_entry(bootentries->menu, &entry->me);
516
517		if (i++ == default_entry)
518			bootentries->menu->selected = &entry->me;
519	}
520
521	back_entry = xzalloc(sizeof(*back_entry));
522	back_entry->display = "back";
523	back_entry->type = MENU_ENTRY_NORMAL;
524	back_entry->non_re_ent = 1;
525	menu_add_entry(bootentries->menu, back_entry);
526
527	if (i == default_entry)

내부 동작을 더 깊게 읽기

01

진입 조건을 먼저 고정한다

boot command에서 들어온 실행은 부트 CPU의 동기 실행 문맥에 놓여 있다. 이때 interrupt, MMU/cache, stack, heap 중 무엇이 이미 준비되었는지 소스의 호출자까지 올라가 확인한다. 같은 함수라도 SPL, relocation 전후, app thread처럼 호출 문맥이 달라지면 허용되는 API와 지연 시간이 달라진다.

entry 발견과 실행을 분리한다. provider가 만든 객체의 release callback, dry-run과 실패 후 다음 entry 이동 조건을 확인한다.

02

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

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

빌드 산출물 관점에서는 최종 부트로더 이미지 안에 해당 symbol과 section이 실제로 포함되었는지도 map과 objdump로 검증한다.

03

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

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

04

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

provider scan → bootentry list → boot_entry 구간에서는 부분 초기화 상태가 생길 수 있다. flag, list insertion, callback 등록, storage write 완료 중 무엇이 성공의 기준인지 찾는다. hardware write나 DMA가 포함되면 함수 반환과 장치 완료가 같은 시점인지도 확인한다.

다른 CPU, 인터럽트 처리기, USB 완료 콜백, 셸 명령에서도 이 상태를 읽는다면 메모리 접근 순서와 잠금 범위를 함께 확인한다.

05

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

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

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

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

01

entry 발견과 entry 실행을 분리한다

provider는 device, directory, script에서 후보를 만들고 struct bootentry에 title, path, handler와 private data를 연결한다. scan 단계가 hardware를 영구 변경하거나 image를 미리 실행하지 않아야 dry-run과 fallback이 가능하다.

provider private data와 mount의 수명은 entry 실행 또는 release까지 유지되어야 한다.

02

오류 코드는 다음 후보 선택 정책이다

entry 없음, 일시적 media 오류, image 검증 실패와 실제 kernel 반환을 구분해야 한다. 모든 오류를 같은 값으로 만들면 boot command가 fallback해야 할지 중단해야 할지 결정할 수 없다.

첫 후보에 각 오류를 주입해 다음 entry 이동 여부를 기록한다.

03

반복 scan에서 객체와 mount를 정리한다

boot command를 여러 번 실행할 때 entry list, allocated strings와 filesystem mount가 누적되지 않아야 한다. release callback이 성공과 실패 양쪽에서 호출되는지 확인한다.

반복 전후 heap과 device reference를 비교한다.

객체와 수명

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

실패 지점과 증상

#조건관찰되는 증상먼저 볼 단계
01release 누락반복 scan leakprovider scan
02오류 코드 뭉개기fallback 중단bootentry list
03같은 entry 중복 등록의도치 않은 재시도boot_entry

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

소스 밖에서 확인할 증거

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

#목적명령 또는 계측판정 기준
01빌드 구성make <board>_defconfig && make V=1Kconfig, linker section과 최종 barebox image 생성 명령을 보관한다.
02symbol과 section${CROSS_COMPILE}nm -n barebox | grep 'int boot_entry'initcall, command, driver descriptor가 image에 남았는지 확인한다.
03주소와 resourceiomemimage, malloc, stack, device resource와 reserved 영역의 겹침을 확인한다.
04device 상태devinfo; drvinfo; of_dumpDT node 생성, device 등록, driver probe를 서로 구분한다.
05boot 정책boot -d -v; state -dentry 탐색 결과와 persistent state가 실제 선택에 미친 영향을 확인한다.

직접 확인할 실험

  1. 01
    boot -d -v로 scan 결과만 확인

    provider scan 진입 전후에 bootentries collection의 주소·크기·반환값과 timestamp를 함께 남긴다.

  2. 02
    첫 entry를 실패시켜 fallback 순서 기록

    bootentry list 진입 전후에 bootentry object의 주소·크기·반환값과 timestamp를 함께 남긴다.

  3. 03
    반복 scan 전후 heap 사용량 비교

    boot_entry 진입 전후에 provider data의 주소·크기·반환값과 timestamp를 함께 남긴다.

원문과 다음 글