SCOPE
같은 ELF에서 서로 다른 이미지 형식을 만든다
link.mk는 오브젝트 파일, 라이브러리, 전처리한 링커 스크립트를 입력으로 tee.elf를 링크한다. 그 뒤 gen_tee_bin.py를 실행하는 개별 Make 규칙이 tee.bin과 v2 이미지 파일을 만든다. 네 출력 파일을 차례로 변환하는 과정이 아니다.
tee.bin은 v1 헤더와 데이터를 한 파일에 담는다. v2는 헤더, 초기 적재 데이터, 나머지 pageable 데이터를 세 파일로 나눈다. 이 글은 OP-TEE OS 4.10.0의 파일 생성 규칙과 바이트 구성을 다룬다. 실제 적재 주소와 부팅 성공 여부는 사용 플랫폼의 부트로더 설정 및 실행 기록으로 별도 확인해야 한다.
BUILD INPUTS
링크에 필요한 세 가지 입력
link-objs와 libdeps는 링크할 오브젝트와 라이브러리다. $(link-script-pp)는 원본 링커 스크립트를 전처리한 kern.ld다. 이들은 모두 tee.elf의 선행 조건이며, 오브젝트가 링커 스크립트로 변환되는 것은 아니다.
링커의 -Map 옵션이 같은 링크 과정에서 tee.map을 만든다. 맵 파일은 심벌과 배치를 확인하는 자료이지, v2 데이터 파일 뒤에 적재되는 메모리 구간이 아니다.
DEPENDENCIES
ELF를 공유하는 네 출력 규칙
link-objslibdepskern.ldtee.elf + scripts/gen_tee_bin.py
각 규칙이 아래 옵션으로 스크립트를 별도 실행
--out_tee_bintee.binv1 헤더 + 데이터--out_header_v2tee-header_v2.binv2 헤더와 이미지 정보--out_pager_v2tee-pager_v2.bin초기 적재 데이터--out_pageable_v2tee-pageable_v2.bin초기화 부분을 뺀 pageable 데이터네 파일 모두 all의 대상이다. 같은 ELF를 입력으로 쓰지만 서로의 출력을 입력으로 삼지는 않는다. 그림은 이 글에서 다루는 네 규칙만 표시하며, tee.dmp·tee-raw.bin 등 다른 산출물은 생략했다.
MAKE RULES
규칙별 입력과 생성 파일
| 규칙 | 입력 | 결과와 주의점 |
|---|---|---|
$(link-script-pp) | 원본 링커 스크립트와 추가 의존 파일 | 전처리한 kern.ld |
tee.elf | link-objs, libdeps, $(link-script-pp); 필요 시 FORCE_LINK | 링커가 tee.elf와 tee.map 생성 |
tee.bin | tee.elf와 gen_tee_bin.py | --out_tee_bin → output_header_v1() |
| v2 파일 세 개 | 각 규칙이 같은 ELF와 스크립트에 의존 | --out_header_v2, --out_pager_v2, --out_pageable_v2 |
| 구형 pager/pageable 파일 | 같은 ELF와 스크립트 | tee-pager.bin·tee-pageable.bin은 이 파일에서 all에 추가되지 않으며, 실행 시 deprecated 경고 출력 |
파일 이름이 비슷해도 구형 출력 옵션과 v2 옵션은 다른 함수를 호출한다. 구형 tee-pager.bin을 v2 초기 적재 파일과 같은 구성으로 취급하면 안 된다.
V2 FILE CONTENTS
v2 파일에 실제로 들어가는 바이트
P는 get_pager_bin(), Q는 get_pageable_bin(), E는 get_embdata_bin()의 결과다. I는 ELF 심벌 __init_size의 값이다. Q에는 이름이 _pageable 또는 _init으로 끝나는 적재 가능한 섹션이 들어간다.
tee-header_v2.bin
12바이트 공통 헤더
+ 이미지 정보 16바이트 × N
N = 1 또는 2
tee-pager_v2.bin
P: pager 데이터Q[:I]: 초기화 부분E: 해시·재배치 정보
init_size = len(P) + min(I, len(Q)) + len(E)
tee-pageable_v2.bin
Q[I:]
초기화 부분 뒤의 데이터
paged_size = len(Q) - min(I, len(Q))
paged_size == 0이면 헤더는 이미지 하나만 기술한다. 이때도 all에는 pageable 파일 생성 규칙이 있으며, 출력 내용은 비어 있을 수 있다. 각 상자는 파일 구성이지 물리 주소 지도나 크기 비율이 아니다.
E는 전체 길이와 항목 수, 해시 및 재배치 데이터의 위치·길이, 정렬 패딩을 담는다. get_hashes_bin()은 전체 Q를 4KiB씩 나눠 SHA-256을 계산한다. 따라서 v2 pager 파일을 단순히 “항상 상주하는 코드만 담은 파일”로 설명할 수 없다.
UPSTREAM SOURCE
원본 코드
아래 코드는 OP-TEE OS 4.10.0의 core/arch/arm/kernel/link.mk에서 133-221줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.
133link-script-extra-deps += $(link-out-dir)/rodata_init.ld.S
134link-script-extra-deps += $(conf-file)
135cleanfiles += $(link-script-pp) $(link-script-dep)
136$(link-script-pp): $(link-script) $(link-script-extra-deps)
137 @$(cmd-echo-silent) ' CPP $@'
138 @mkdir -p $(dir $@)
139 $(q)$(CPPcore) -P -MT $@ -MD -MP -MF $(link-script-dep) \
140 $(link-script-cppflags) $< -o $@
141
142$(link-out-dir)/version.o:
143 $(call gen-version-o)
144
145-include $(link-out-dir)/.tee.elf.cmd
146define check-link-objs
147$(if $(strip $(filter-out $(link-objs), $(old-link-objs))
148 $(filter-out $(old-link-objs), $(link-objs))), FORCE_LINK := FORCE)
149endef
150$(eval $(call check-link-objs))
151
152all: $(link-out-dir)/tee.elf
153cleanfiles += $(link-out-dir)/tee.elf $(link-out-dir)/tee.map
154cleanfiles += $(link-out-dir)/version.o
155cleanfiles += $(link-out-dir)/.buildcount
156cleanfiles += $(link-out-dir)/.tee.elf.cmd
157$(link-out-dir)/tee.elf: $(link-objs) $(libdeps) $(link-script-pp) $(FORCE_LINK)
158 @echo "old-link-objs := $(link-objs)" >$(link-out-dir)/.tee.elf.cmd
159 @$(cmd-echo-silent) ' LD $@'
160 $(q)$(LDcore) $(ldargs-tee.elf) -o $@
161
162all: $(link-out-dir)/tee.dmp
163cleanfiles += $(link-out-dir)/tee.dmp
164$(link-out-dir)/tee.dmp: $(link-out-dir)/tee.elf
165 @$(cmd-echo-silent) ' OBJDUMP $@'
166 $(q)$(OBJDUMPcore) -l -x -d $< > $@
167
168cleanfiles += $(link-out-dir)/tee-pager.bin
169$(link-out-dir)/tee-pager.bin: $(link-out-dir)/tee.elf scripts/gen_tee_bin.py
170 @echo Warning: $@ is deprecated
171 @$(cmd-echo-silent) ' GEN $@'
172 $(q)$(PYTHON3) scripts/gen_tee_bin.py --input $< --out_tee_pager_bin $@
173
174cleanfiles += $(link-out-dir)/tee-pageable.bin
175$(link-out-dir)/tee-pageable.bin: $(link-out-dir)/tee.elf scripts/gen_tee_bin.py
176 @echo Warning: $@ is deprecated
177 @$(cmd-echo-silent) ' GEN $@'
178 $(q)$(PYTHON3) scripts/gen_tee_bin.py --input $< --out_tee_pageable_bin $@
179
180all: $(link-out-dir)/tee.bin
181cleanfiles += $(link-out-dir)/tee.bin
182$(link-out-dir)/tee.bin: $(link-out-dir)/tee.elf scripts/gen_tee_bin.py
183 @$(cmd-echo-silent) ' GEN $@'
184 $(q)$(PYTHON3) scripts/gen_tee_bin.py --input $< --out_tee_bin $@
185
186all: $(link-out-dir)/tee-header_v2.bin
187cleanfiles += $(link-out-dir)/tee-header_v2.bin
188$(link-out-dir)/tee-header_v2.bin: $(link-out-dir)/tee.elf \
189 scripts/gen_tee_bin.py
190 @$(cmd-echo-silent) ' GEN $@'
191 $(q)$(PYTHON3) scripts/gen_tee_bin.py --input $< --out_header_v2 $@
192
193all: $(link-out-dir)/tee-pager_v2.bin
194cleanfiles += $(link-out-dir)/tee-pager_v2.bin
195$(link-out-dir)/tee-pager_v2.bin: $(link-out-dir)/tee.elf scripts/gen_tee_bin.py
196 @$(cmd-echo-silent) ' GEN $@'
197 $(q)$(PYTHON3) scripts/gen_tee_bin.py --input $< --out_pager_v2 $@
198
199all: $(link-out-dir)/tee-pageable_v2.bin
200cleanfiles += $(link-out-dir)/tee-pageable_v2.bin
201$(link-out-dir)/tee-pageable_v2.bin: $(link-out-dir)/tee.elf \
202 scripts/gen_tee_bin.py
203 @$(cmd-echo-silent) ' GEN $@'
204 $(q)$(PYTHON3) scripts/gen_tee_bin.py --input $< --out_pageable_v2 $@
205
206all: $(link-out-dir)/tee.symb_sizes
207cleanfiles += $(link-out-dir)/tee.symb_sizes
208$(link-out-dir)/tee.symb_sizes: $(link-out-dir)/tee.elf
209 @$(cmd-echo-silent) ' GEN $@'
210 $(q)$(NMcore) --print-size --reverse-sort --size-sort $< > $@
211
212cleanfiles += $(link-out-dir)/tee.mem_usage
213ifneq ($(filter mem_usage,$(MAKECMDGOALS)),)
214mem_usage: $(link-out-dir)/tee.mem_usage
215
216$(link-out-dir)/tee.mem_usage: $(link-out-dir)/tee.elf
217 @$(cmd-echo-silent) ' GEN $@'
218 $(q)$(PYTHON3) ./scripts/mem_usage.py $< > $@
219endif
220
221all: $(link-out-dir)/tee-raw.bin
READING CONTRACT
발췌 범위와 읽기 기준
원문 133-221줄에는 앞뒤 문맥이 포함됩니다. 기준 구문 $(link-out-dir)/tee.elf:은 L157에 있습니다. 발췌 첫 줄을 함수 정의의 시작으로 해석하지 않습니다.
Make 규칙의 선행 파일과 출력 옵션을 먼저 읽고, 아래 Python 발췌에서 각 옵션이 어떤 함수를 호출하는지 대조한다.
RELATED SOURCE
함께 읽어야 하는 원본 코드
첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.
01. tee.elf section을 pager/pageable image로 변환
scripts/gen_tee_bin.py 365-411줄이다.
365 required=False, type=argparse.FileType('wb'),
366 help='The output tee_pageable.bin')
367
368 parser.add_argument('--out_header_v2',
369 required=False, type=argparse.FileType('wb'),
370 help='The output tee_header_v2.bin')
371
372 parser.add_argument('--out_pager_v2',
373 required=False, type=argparse.FileType('wb'),
374 help='The output tee_pager_v2.bin')
375
376 parser.add_argument('--out_pageable_v2',
377 required=False, type=argparse.FileType('wb'),
378 help='The output tee_pageable_v2.bin')
379
380 return parser.parse_args()
381
382
383def main():
384 args = get_args()
385
386 elffile = ELFFile(args.input)
387
388 if args.out_tee_raw_bin:
389 output_raw_bin(elffile, args.out_tee_raw_bin)
390
391 if args.out_tee_bin:
392 output_header_v1(elffile, args.out_tee_bin)
393
394 if args.out_tee_pager_bin:
395 output_pager_bin(elffile, args.out_tee_pager_bin)
396
397 if args.out_tee_pageable_bin:
398 output_pageable_bin(elffile, args.out_tee_pageable_bin)
399
400 if args.out_header_v2:
401 output_header_v2(elffile, args.out_header_v2)
402
403 if args.out_pager_v2:
404 output_pager_v2(elffile, args.out_pager_v2)
405
406 if args.out_pageable_v2:
407 output_pageable_v2(elffile, args.out_pageable_v2)
408
409
410if __name__ == "__main__":
411 main()
02. v2 image header와 segment descriptor를 출력
scripts/gen_tee_bin.py 293-377줄이다.
293 magic = 0x4554504f # 'OPTE'
294 version = 1
295 flags = 0
296 outf.write(struct.pack('<IBBHIIIII', magic, version, arch_id, flags,
297 init_size, init_load_addr[0], init_load_addr[1],
298 init_mem_usage, paged_size))
299 outf.write(pager_bin)
300 outf.write(pageable_bin[:init_bin_size])
301 outf.write(embdata_bin)
302 outf.write(pageable_bin[init_bin_size:])
303
304
305def output_header_v2(elffile, outf):
306 arch_id = get_arch_id(elffile)
307 init_load_addr = get_init_load_addr(elffile)
308 init_bin_size = get_symbol(elffile, '__init_size')['st_value']
309 pager_bin_size = len(get_pager_bin(elffile))
310 paged_area_size = len(get_pageable_bin(elffile))
311 embdata_bin_size = len(get_embdata_bin(elffile))
312
313 init_size = (pager_bin_size + min(init_bin_size, paged_area_size) +
314 embdata_bin_size)
315 paged_size = paged_area_size - min(init_bin_size, paged_area_size)
316
317 magic = 0x4554504f # 'OPTE'
318 version = 2
319 flags = 0
320 nb_images = 1 if paged_size == 0 else 2
321 outf.write(struct.pack('<IBBHI', magic, version, arch_id, flags,
322 nb_images))
323 outf.write(struct.pack('<IIII', init_load_addr[0], init_load_addr[1],
324 0, init_size))
325 if nb_images == 2:
326 outf.write(struct.pack('<IIII', 0xffffffff, 0xffffffff, 1, paged_size))
327
328
329def output_pager_v2(elffile, outf):
330 init_bin_size = get_symbol(elffile, '__init_size')['st_value']
331 pager_bin = get_pager_bin(elffile)
332 pageable_bin = get_pageable_bin(elffile)
333 embdata_bin = get_embdata_bin(elffile)
334
335 outf.write(pager_bin)
336 outf.write(pageable_bin[:init_bin_size])
337 outf.write(embdata_bin)
338
339
340def output_pageable_v2(elffile, outf):
341 init_bin_size = get_symbol(elffile, '__init_size')['st_value']
342 outf.write(get_pageable_bin(elffile)[init_bin_size:])
343
344
345def get_args():
346 parser = argparse.ArgumentParser()
347
348 parser.add_argument('--input',
349 required=True, type=argparse.FileType('rb'),
350 help='The input tee.elf')
351
352 parser.add_argument('--out_tee_bin',
353 required=False, type=argparse.FileType('wb'),
354 help='The output tee.bin')
355
356 parser.add_argument('--out_tee_raw_bin',
357 required=False, type=argparse.FileType('wb'),
358 help='The output tee_raw.bin')
359
360 parser.add_argument('--out_tee_pager_bin',
361 required=False, type=argparse.FileType('wb'),
362 help='The output tee_pager.bin')
363
364 parser.add_argument('--out_tee_pageable_bin',
365 required=False, type=argparse.FileType('wb'),
366 help='The output tee_pageable.bin')
367
368 parser.add_argument('--out_header_v2',
369 required=False, type=argparse.FileType('wb'),
370 help='The output tee_header_v2.bin')
371
372 parser.add_argument('--out_pager_v2',
373 required=False, type=argparse.FileType('wb'),
374 help='The output tee_pager_v2.bin')
375
376 parser.add_argument('--out_pageable_v2',
377 required=False, type=argparse.FileType('wb'),
GENERATOR
출력 옵션이 선택하는 함수
main()은 입력 ELF를 열고 각 출력 인자를 별도의 if로 검사한다. --out_tee_bin은 output_header_v1()을 호출한다. 함수 이름에 header가 있어도 v1 헤더만 쓰는 것이 아니라 pager, 초기화 부분, 내장 데이터, 나머지 pageable 데이터를 이어 쓴다.
v2에서는 output_header_v2()가 헤더만, output_pager_v2()와 output_pageable_v2()가 각 데이터 파일을 쓴다. 스크립트는 한 번에 여러 출력 인자를 받을 수 있지만, 이 link.mk의 네 Make 규칙은 각각 하나의 출력 옵션으로 실행한다.
HEADER FORMAT
v1과 v2의 헤더 필드를 혼동하지 않기
v2 공통 헤더는 struct.pack('<IBBHI', ...)로 쓴다. 리틀 엔디언으로 magic, version, arch, flags, 이미지 수를 기록한다. 이어지는 이미지 정보는 <IIII 형식의 적재 주소 상위·하위 32비트, 이미지 ID, 크기다.
첫 이미지 ID는 0이며 주소는 _start 심벌에서 얻는다. 나머지 pageable 데이터가 있으면 ID 1의 항목을 추가한다. 이 항목의 주소 두 필드는 모두 0xffffffff다. 이를 실제 적재 주소로 사용하지 말고 부트로더가 이 값을 어떻게 처리하는지 확인해야 한다.
이 버전에서 v2 flags는 0이다. init_mem_usage는 아래 발췌에 보이는 v1 헤더 필드이며, v2 이미지 정보에는 포함되지 않는다. v2 헤더가 압축 정보를 전달한다고 설명할 근거도 이 생성 코드에는 없다.
SOURCE CHECKS
소스와 파일을 대조할 기준
링크 결과와 생성기 입력
| 확인 대상 | 소스에서 확인되는 관계 |
|---|---|
tee.elf | 최종 이미지 파일들의 공통 입력이다. 심벌과 섹션 정보도 생성기가 읽는다. |
tee.map | 링커의 -Map 출력이다. 생성기의 네 출력 규칙은 이 맵 파일을 직접 입력으로 받지 않는다. |
_start | get_init_load_addr()가 읽어 헤더의 초기 적재 주소 상·하위 값으로 나눈다. |
__init_size | pageable 데이터의 초기화 부분과 나머지를 나누는 기준이다. |
v2 헤더 필드
| 위치 | 필드 | 생성 값 |
|---|---|---|
| 공통 헤더 | magic / version / arch | 0x4554504f / 2 / ARM 0 또는 AArch64 1 |
| 공통 헤더 | flags / nb_images | 0 / paged_size가 0이면 1, 아니면 2 |
| 이미지 0 | load_addr_hi / load_addr_lo / image_id / size | _start의 상·하위 / 0 / init_size |
| 이미지 1, 있을 때 | load_addr_hi / load_addr_lo / image_id / size | 0xffffffff / 0xffffffff / 1 / paged_size |
파일 이름보다 출력 함수를 확인한다
| 파일 | 함수 | 포함 내용 |
|---|---|---|
tee.bin | output_header_v1() | v1 헤더와 데이터 전체 |
tee-header_v2.bin | output_header_v2() | v2 헤더와 이미지 정보만 |
tee-pager_v2.bin | output_pager_v2() | pager + 초기화 부분 + 내장 데이터 |
tee-pageable_v2.bin | output_pageable_v2() | 초기화 부분을 제외한 pageable 데이터 |
빌드와 부팅 검증은 별개다
| 단계 | 확인 가능한 것 | 이 단계만으로 입증되지 않는 것 |
|---|---|---|
| Make 규칙 검토 | 입력 의존성과 출력 옵션 | 실제 컴파일·링크 성공 |
| 생성 파일 비교 | 같은 빌드인지, 헤더 크기와 파일 길이가 맞는지 | 부트로더의 파일 선택과 적재 성공 |
| 플랫폼 부팅 시험 | 부트로더 로그와 실제 진입·오류 | 다른 보드와 설정에서의 동일 동작 |
ARTIFACT SET
같은 빌드의 파일을 한 묶음으로 보관한다
| 자료 | 보관 이유 | 대조 기준 |
|---|---|---|
tee.elf·tee.map | 원본 심벌과 링크 배치 | 같은 소스 커밋, 플랫폼, 빌드 설정 |
| v2 헤더와 두 데이터 파일 | 헤더가 기술하는 데이터의 출처 유지 | init_size와 pager 파일 길이, paged_size와 pageable 파일 길이 |
tee.bin | v1 형식을 사용하는 구성의 산출물 | v2 파일을 만드는 중간 입력으로 취급하지 않음 |
| 설정·빌드 로그·파일 해시 | 서로 다른 빌드의 파일 혼합 방지 | PLATFORM, 툴체인, 소스 버전 및 실제 생성 파일 |
파일 이름만 같다는 이유로 다른 빌드의 헤더와 데이터를 섞지 않는다. TF-A FIP를 쓰는 플랫폼이라면 해당 플랫폼의 BL32 입력 선택과 패키징 규칙까지 별도로 대조한다.
FAILURE CHECKS
오류가 난 단계부터 원인을 좁힌다
| 확인한 문제 | 먼저 볼 자료 | 판단할 때 주의할 점 |
|---|---|---|
tee.elf 링크 실패 | 링커 오류, link-objs·libdeps·kern.ld | 아직 이미지 생성이나 보드 실행 단계가 아니다. |
| 생성기 실행 실패 | 실행 옵션, 입력 ELF, 누락 심벌·재배치 오류 메시지 | 실패를 보드의 인터럽트나 캐시 상태로 설명하지 않는다. |
| v2 헤더 크기와 데이터 길이가 다름 | 헤더의 두 크기 값과 실제 파일 해시 | 다른 빌드의 파일이 섞였는지, 잘못된 출력 형식을 골랐는지 확인한다. |
| 부트로더가 이미지를 거부하거나 진입 후 실패 | 해당 플랫폼의 로더 기록, 실제 적재 주소, 같은 빌드의 ELF | 파일 생성 규칙만으로 특정 fault의 원인을 단정할 수 없다. |
EVIDENCE
실제 산출물에서 확인할 자료
아래는 확인 방법이며 이 글의 실행 결과가 아니다. 실제 출력 경로와 코어 아키텍처에 맞는 툴체인을 사용한다.
| 목적 | 명령 또는 자료 | 확인 내용 |
|---|---|---|
| ELF 배치 | readelf -W -lS <build>/core/tee.elf | 섹션, 적재 구간, 파일 크기와 메모리 크기의 차이 |
| 기준 심벌 | nm -n <build>/core/tee.elf | _start·__init_size·__data_end 값 |
| 동일 빌드 기록 | 생성한 각 파일의 SHA-256, 소스 커밋, 설정과 빌드 로그 | 이름이 같은 다른 빌드의 파일이 섞이지 않았는지 확인 |
| v2 길이 대조 | 헤더의 init_size·paged_size와 파일 길이 | 헤더가 가리키는 데이터 길이가 실제 출력과 일치하는지 확인 |
| 플랫폼 연동 | 부트로더 설정과 로더 로그; FIP를 쓰면 해당 FIP 내용 | 빌드 산출물이 실제로 선택되고 적재됐는지 확인 |
CHECKS TO RUN
구성을 바꿔 비교할 항목
- 동일한 소스와 플랫폼에서 pager 설정만 바꾼 두 빌드의 ELF 심벌 및 각 파일 길이를 비교한다. 설정 차이와 실제 생성 결과를 함께 기록한다.
- v2 헤더의
nb_images와paged_size를 확인한다. 나머지 pageable 데이터가 없는 경우에도 생성 대상 파일과 헤더의 이미지 수를 구분한다. - 실제 부트로더가 사용하는 형식을 확인한 뒤, 선택된 파일의 해시를 빌드 산출물과 대조한다. 보드 시험을 수행했다면 적재 주소와 로그를 별도 기록한다.
파일 목록이 준비됐다는 사실만으로 TF-A 연동이나 보드 부팅이 검증된 것은 아니다.
PRIMARY REFERENCES