OP-TEE OS · source analysis

OP-TEE OS 빌드 산출물

tee.elf에서 tee.bin, pager, pageable 조각과 v2 header가 생성되는 규칙을 읽고 TF-A BL32 입력과 연결합니다.

4.10.0은 이 글의 소스 분석 기준이며 최신 보안 버전 권고가 아니다. 제품 적용 시 공식 보안 공지와 사용 플랫폼의 지원 버전을 확인한다.

Series
01 / 12
Baseline
4.10.0
Commit
753afbbee168
Source
core/arch/arm/kernel/link.mk:133-221 (발췌 범위)

같은 ELF에서 서로 다른 이미지 형식을 만든다

link.mk는 오브젝트 파일, 라이브러리, 전처리한 링커 스크립트를 입력으로 tee.elf를 링크한다. 그 뒤 gen_tee_bin.py를 실행하는 개별 Make 규칙이 tee.bin과 v2 이미지 파일을 만든다. 네 출력 파일을 차례로 변환하는 과정이 아니다.

tee.bin은 v1 헤더와 데이터를 한 파일에 담는다. v2는 헤더, 초기 적재 데이터, 나머지 pageable 데이터를 세 파일로 나눈다. 이 글은 OP-TEE OS 4.10.0의 파일 생성 규칙과 바이트 구성을 다룬다. 실제 적재 주소와 부팅 성공 여부는 사용 플랫폼의 부트로더 설정 및 실행 기록으로 별도 확인해야 한다.

링크에 필요한 세 가지 입력

link-objs와 libdeps는 링크할 오브젝트와 라이브러리다. $(link-script-pp)는 원본 링커 스크립트를 전처리한 kern.ld다. 이들은 모두 tee.elf의 선행 조건이며, 오브젝트가 링커 스크립트로 변환되는 것은 아니다.

링커의 -Map 옵션이 같은 링크 과정에서 tee.map을 만든다. 맵 파일은 심벌과 배치를 확인하는 자료이지, v2 데이터 파일 뒤에 적재되는 메모리 구간이 아니다.

ELF를 공유하는 네 출력 규칙

그림 1. 링크 입력이 모인 뒤 네 파일로 갈라지는 의존 관계
오브젝트
link-objs
라이브러리
libdeps
전처리 스크립트
kern.ld

tee.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 등 다른 산출물은 생략했다.

규칙별 입력과 생성 파일

규칙입력결과와 주의점
$(link-script-pp)원본 링커 스크립트와 추가 의존 파일전처리한 kern.ld
tee.elflink-objs, libdeps, $(link-script-pp); 필요 시 FORCE_LINK링커가 tee.elf와 tee.map 생성
tee.bintee.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 파일에 실제로 들어가는 바이트

P는 get_pager_bin(), Q는 get_pageable_bin(), E는 get_embdata_bin()의 결과다. I는 ELF 심벌 __init_size의 값이다. Q에는 이름이 _pageable 또는 _init으로 끝나는 적재 가능한 섹션이 들어간다.

그림 2. 각 파일의 바이트 구성과 헤더에 기록되는 크기

tee-header_v2.bin

12바이트 공통 헤더
+ 이미지 정보 16바이트 × N

N = 1 또는 2

tee-pager_v2.bin

  1. P: pager 데이터
  2. Q[:I]: 초기화 부분
  3. 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 파일을 단순히 “항상 상주하는 코드만 담은 파일”로 설명할 수 없다.

원본 코드

아래 코드는 OP-TEE OS 4.10.0의 core/arch/arm/kernel/link.mk에서 133-221줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.

commit753afbbee1682f5d16fd30e87b31058a4fd4f4b8anchor · L157$(link-out-dir)/tee.elf:
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

발췌 범위와 읽기 기준

원문 133-221줄에는 앞뒤 문맥이 포함됩니다. 기준 구문 $(link-out-dir)/tee.elf:은 L157에 있습니다. 발췌 첫 줄을 함수 정의의 시작으로 해석하지 않습니다.

Make 규칙의 선행 파일과 출력 옵션을 먼저 읽고, 아래 Python 발췌에서 각 옵션이 어떤 함수를 호출하는지 대조한다.

함께 읽어야 하는 원본 코드

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

01. tee.elf section을 pager/pageable image로 변환

scripts/gen_tee_bin.py 365-411줄이다.

filescripts/gen_tee_bin.pyanchor · L383def main():
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줄이다.

filescripts/gen_tee_bin.pyanchor · L305def output_header_v2(elffile, outf):
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'),

출력 옵션이 선택하는 함수

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 규칙은 각각 하나의 출력 옵션으로 실행한다.

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 헤더가 압축 정보를 전달한다고 설명할 근거도 이 생성 코드에는 없다.

소스와 파일을 대조할 기준

링크 결과와 생성기 입력

확인 대상소스에서 확인되는 관계
tee.elf최종 이미지 파일들의 공통 입력이다. 심벌과 섹션 정보도 생성기가 읽는다.
tee.map링커의 -Map 출력이다. 생성기의 네 출력 규칙은 이 맵 파일을 직접 입력으로 받지 않는다.
_startget_init_load_addr()가 읽어 헤더의 초기 적재 주소 상·하위 값으로 나눈다.
__init_sizepageable 데이터의 초기화 부분과 나머지를 나누는 기준이다.

v2 헤더 필드

위치필드생성 값
공통 헤더magic / version / arch0x4554504f / 2 / ARM 0 또는 AArch64 1
공통 헤더flags / nb_images0 / paged_size가 0이면 1, 아니면 2
이미지 0load_addr_hi / load_addr_lo / image_id / size_start의 상·하위 / 0 / init_size
이미지 1, 있을 때load_addr_hi / load_addr_lo / image_id / size0xffffffff / 0xffffffff / 1 / paged_size

파일 이름보다 출력 함수를 확인한다

파일함수포함 내용
tee.binoutput_header_v1()v1 헤더와 데이터 전체
tee-header_v2.binoutput_header_v2()v2 헤더와 이미지 정보만
tee-pager_v2.binoutput_pager_v2()pager + 초기화 부분 + 내장 데이터
tee-pageable_v2.binoutput_pageable_v2()초기화 부분을 제외한 pageable 데이터

빌드와 부팅 검증은 별개다

단계확인 가능한 것이 단계만으로 입증되지 않는 것
Make 규칙 검토입력 의존성과 출력 옵션실제 컴파일·링크 성공
생성 파일 비교같은 빌드인지, 헤더 크기와 파일 길이가 맞는지부트로더의 파일 선택과 적재 성공
플랫폼 부팅 시험부트로더 로그와 실제 진입·오류다른 보드와 설정에서의 동일 동작

같은 빌드의 파일을 한 묶음으로 보관한다

자료보관 이유대조 기준
tee.elf·tee.map원본 심벌과 링크 배치같은 소스 커밋, 플랫폼, 빌드 설정
v2 헤더와 두 데이터 파일헤더가 기술하는 데이터의 출처 유지init_size와 pager 파일 길이, paged_size와 pageable 파일 길이
tee.binv1 형식을 사용하는 구성의 산출물v2 파일을 만드는 중간 입력으로 취급하지 않음
설정·빌드 로그·파일 해시서로 다른 빌드의 파일 혼합 방지PLATFORM, 툴체인, 소스 버전 및 실제 생성 파일

파일 이름만 같다는 이유로 다른 빌드의 헤더와 데이터를 섞지 않는다. TF-A FIP를 쓰는 플랫폼이라면 해당 플랫폼의 BL32 입력 선택과 패키징 규칙까지 별도로 대조한다.

오류가 난 단계부터 원인을 좁힌다

확인한 문제먼저 볼 자료판단할 때 주의할 점
tee.elf 링크 실패링커 오류, link-objs·libdeps·kern.ld아직 이미지 생성이나 보드 실행 단계가 아니다.
생성기 실행 실패실행 옵션, 입력 ELF, 누락 심벌·재배치 오류 메시지실패를 보드의 인터럽트나 캐시 상태로 설명하지 않는다.
v2 헤더 크기와 데이터 길이가 다름헤더의 두 크기 값과 실제 파일 해시다른 빌드의 파일이 섞였는지, 잘못된 출력 형식을 골랐는지 확인한다.
부트로더가 이미지를 거부하거나 진입 후 실패해당 플랫폼의 로더 기록, 실제 적재 주소, 같은 빌드의 ELF파일 생성 규칙만으로 특정 fault의 원인을 단정할 수 없다.

실제 산출물에서 확인할 자료

아래는 확인 방법이며 이 글의 실행 결과가 아니다. 실제 출력 경로와 코어 아키텍처에 맞는 툴체인을 사용한다.

목적명령 또는 자료확인 내용
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 내용빌드 산출물이 실제로 선택되고 적재됐는지 확인

구성을 바꿔 비교할 항목

  1. 동일한 소스와 플랫폼에서 pager 설정만 바꾼 두 빌드의 ELF 심벌 및 각 파일 길이를 비교한다. 설정 차이와 실제 생성 결과를 함께 기록한다.
  2. v2 헤더의 nb_images와 paged_size를 확인한다. 나머지 pageable 데이터가 없는 경우에도 생성 대상 파일과 헤더의 이미지 수를 구분한다.
  3. 실제 부트로더가 사용하는 형식을 확인한 뒤, 선택된 파일의 해시를 빌드 산출물과 대조한다. 보드 시험을 수행했다면 적재 주소와 로그를 별도 기록한다.

파일 목록이 준비됐다는 사실만으로 TF-A 연동이나 보드 부팅이 검증된 것은 아니다.

원문과 다음 글