Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
am335xbootrom — Reverse engineering the TI AM3358 boot ROM | Kitploit
도구/GitHubGitHub/sjgallagher2/am335xbootrom
Embedded Systems SecurityReverse EngineeringDebuggersHardware HackingBinary AnalysisLearning & EducationFirmware Analysis
GitHubsjgallagher2/am335xbootrom

am335xbootrom

Reverse engineering the TI AM3358 boot ROM

저장소 보기
6151년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

AM335x 부트 ROM 리버스 엔지니어링

이 보드들(Beaglebone Black)을 처음 손에 넣은 지 18개월쯤 된 것 같다. 쓰레기통에서 구해낸 보드였다. 불행히도 보드들은 처음부터 제대로 작동하지 않았다. 사실 이 보드를 다루는 것도, 아니면 어떤 싱글 보드 컴퓨터든 처음이라서, 문제가 내가 뭔가 잘못하고 있는 건지, 아니면 보드 자체에 문제가 있는 건지(어쩌면 그것이 애초에 쓰레기통으로 향했던 이유일지도) 확신할 수 없었다. 이 보드들이 실제로 부팅을 시작하게 만드는 데 꽤 오랜 시간과 많은 노력이 걸렸지만, 그래도 해냈다. 그리고 그 과정에서 배운 것들을 여기에 정리한다.

참고: Ghidra XML 파일 사용 방법

이 저장소에는 지금까지 리버스 엔지니어링으로 얻은 모든 심볼이 담긴 Ghidra에서 내보낸 xml 파일과 함께 몇 가지 유틸리티를 포함했다. 저작권 문제를 피하기 위해 실제 펌웨어 없이 내보내는 데 이 글을 사용했다. 부팅 ROM을 직접 디버그하고 싶다면 이미 JTAG를 연결했을 테니, 부팅 ROM(0x20000부터 0x2BFFF까지)을 직접 덤프할 수 있다.

심볼을 로드하려면:

  1. 새 Ghidra 프로젝트를 만든다. Ghidra에 바이너리(XML 아님)를 임포트한다: ARMv7 Little Endian을 사용하고, Options에서 기본 주소를 0x20000으로 설정하고 블록 이름을 bootrom으로 설정하면 된다.
  2. CodeBrowser에서 이 바이너리를 연다. 분석하지 마라.
  3. File > Add program으로 이동하여 XML 파일을 선택한다. 기본값으로 충분하다. 이제 리셋 핸들러를 살펴보거나 main() 또는 MMC/SD 카드 부트 핸들러로 이동할 수 있다.

문제

우선, 이 보드들이 표준 Beaglebone Black의 커스텀 버전이라는 것을 알고 있었기 때문에 초기에 보드 자체에 보드 식별자 같은 뭔가가 빠져 있는 것일 수도 있다고 판단했다. balenaEtcher로 포맷된 표준 SD 카드로 부팅했을 때 보이는 것은 아무것도 없었다. 보드의 LED가 깜빡이기 시작할 것으로 예상했고, UART-USB 케이블을 연결하면 U-Boot 프로세스를 볼 수 있을 것으로 예상했다. 그러나 UART는 조용했다. SD 카드를 빼면 UART/시리얼 부팅의 예상된 동작인 문자 C가 반복해서 출력되었다. 분명히 부팅을 시도하고 있었고 SD 카드가 이 동작을 바꾸고 있었지만, 더 이상의 가시성은 없었다. 웹상의 대부분의 문제 해결 방법은 U-Boot 출력을 진단의 출발점으로 삼았다. 나는 그런 사치를 누릴 수 없을 것 같았다.

이 시점에서 디버그 프로브를 연결하는 것이 가치가 있겠다고 판단했다. 불행히도 기존 풋프린트에 맞는 헤더가 없어서 직접 만들었다.

Beaglebone 보드에는 JTAG 연결을 노출하는 P2라는 지정자를 가진 헤더가 있다. 여기에 전선을 연결해 암컷 헤더에 연결해서 J-Link를 통해 통신할 수 있게 했다.

Ozone(Segger 디버거)을 실행하면서 J-Link를 구성하고 엔트리 포인트를 찾는 것부터 시작했다. 리셋-홀트(reset-halt)가 나를 필요한 위치로 데려다줄 것이라고 생각했고, 그 결과 엔트리 포인트가 0x2148a라는 (잘못된) 추정에 이르렀다. 물론 이 값이 일관되지 않다는 점은 확실히 알아차렸다. 나중에 AM335x 보드들은 J-Link의 리셋-홀트와 잘 맞지 않는다는 것을 깨달았다. 실제로 수백 클럭 사이클 정도의 지연이 있어서 부트 핸들러 내부 어딘가에 비결정적으로 도달하게 되는 것이다. (결국 J-Link 디버깅을 지원하는 TI의 Code Composer Studio용 GEL 파일을 작성하여 이 문제를 우회했다. 리셋 시 PC 레지스터가 리셋 핸들러로 설정되고, 레지스터가 클리어되며, 명령어 모드가 ARM으로 강제된다.)

TI 포럼의 스레드(AM335x: TI employees, where can I get the ROM Bootloader source code/symbols?)에서 몇 가지 디버깅 심볼을 얻었다: 0x231e0의 SPI Initialize, 0x23230의 SPI ReadSectors, 그리고 0x24bfa는 UART 읽기를 수행하는 루틴이다. 꽤 도움이 되는 정보였다. 부팅이 0x402f0440의 무한 루프, 즉 죽은 루프에 빠지면서 실패한다는 것을 알아차렸다. 음, 나머지 부팅 ROM과는 꽤 거리가 있는데, RAM 어딘가에 있는 게 분명하다. 이제 기술 참조 매뉴얼(TRM)을 살펴볼 때가 된 것 같다!

TRM 26장에는 부팅에 관한 엄청난 양의 정보가 들어 있다. 다음은 부팅 ROM의 구조를 보여준다:

설명:

Public ROM Code의 아키텍처는 그림 26-1에 나와 있다. 하향식(top-down) 방식으로 high-level 계층, 드라이버 계층, 하드웨어 추상화 계층(HAL)의 세 가지 주요 계층으로 나뉜다. 한 계층은 통합 인터페이스를 통해 하위 계층과 통신한다. high-level 계층은 Public ROM Code의 주요 작업, 즉 워치독 및 클록 구성과 메인 부팅 루틴을 담당한다. 드라이버 계층은 인터페이스 사양에 따라 모든 부팅 장치에 대한 논리 및 통신 프로토콜을 구현한다. 마지막으로 HAL은 하드웨어 인프라 IP와 상호 작용하기 위한 최하위 수준의 코드를 구현한다. 최종 부팅 장치는 디바이스 I/O 패드에 연결된다.

그림 26-2는 Public ROM Code 부팅 절차의 높은 수준의 흐름을 보여준다. 이 디바이스에서 Public ROM Code는 보안 시작(Secure ROM Code에 의해 수행됨)이 완료되면 시작된다. 그런 다음 ROM Code는 공용 시작 절차의 일부로 플랫폼 구성과 초기화를 수행한다. 부팅 장치 목록은 SYSBOOT 핀을 기반으로 생성된다. 부팅 장치는 메모리 부팅 장치(납땜된 플래시 메모리 또는 메모리 카드와 같은 임시 부팅 장치)이거나 호스트에 연결된 주변 인터페이스일 수 있다. 부팅 절차의 메인 루프는 부팅 장치 목록을 순회하면서 현재 선택된 부팅 장치에서 이미지를 검색하려고 시도한다. 유효한 부팅 이미지를 찾아 성공적으로 실행하거나 워치독이 만료되면 이 루프를 벗어난다. 이미지 인증 절차는 HS 디바이스에서 이미지 실행 전에 수행된다. 인증 절차가 실패하면 Secure ROM의 "dead loop"로 분기하여 워치독 리셋을 기다리게 된다.

메모리 맵! 예외 벡터! 순서도! 이 섹션에는 많은 정보가 있다. 내 작업이 훨씬 쉬워졌다.

이 시점에서 JTAG 프로브를 사용해 펌웨어를 여러 파일로 다운로드하고 Ghidra에 로드하기 시작했다. 편리한 형식으로 제공되는 SVD 파일이나 다른 레지스터 매핑은 없는 것 같았다. 정말 안타까운 일이었다. 메모리 영역과 레지스터, 그 밖의 모든 것을 수동으로 정의해야 하기 때문이다. 지루한 과정이었지만, 시간이 지나자 AM3358용 심볼을 Ghidra에 로드할 수 있는 파이썬 스크립트가 생겼다. 걱정거리가 하나 줄었다!

리버스 엔지니어링

내가 가진 파일들은 다음과 같이 매핑할 수 있는 것 같다:

흥미롭게도 0x402f_0440의 무한 루프는 내부 SRAM에 있는 "다운로드된 이미지"의 최상단에 위치하는 반면, 예외 벡터는 다른 곳에 저장된다. 어쩌면 이는 나중에 중요한 힌트가 될지도 모른다...

리셋 시 프라이빗 부트 ROM이 보안 관련 작업을 처리하고, 리셋 벡터가 들어 있는 0x2 0000으로 분기한다. 첫 번째 명령어는 엔트리 포인트로 보이는 0x2 08d0로의 분기이다. BX 명령어가 아니므로, 아마도 그 시점에는 여전히 ARM 모드에 있을 것이다.

리셋 핸들러

이것은 가장 먼저 실행되는 코드이므로, 매개변수가 있는 "함수"라기보다는 컴파일러가 생성한 시작 스크립트에 가깝다. 첫 번째 기본 블록은 다음과 같다:```arm ldr r4,[->Peripherals::CM_PER] mov r0,#0x2c ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r6,#0x2 str r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r0,#0x2c poll: ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL cmp r6,#0x2 bne poll

root@kitploit:~
이 블록은 OCMC RAM 클럭을 활성화하도록 설정합니다:
1. `CM_PER_OCMCRAM_CLKCTRL=0x2`로 설정
2. 레지스터가 설정되었는지 확인하고, 설정되지 않았다면 계속 폴링합니다
`CM_PER_OCMCRAM_CLKCTRL` 레지스터는 `MODULEMODE` 필드에 비트 0과 1을 사용하며, 이 값을 `=0x2`로 설정하면 OCMC RAM에 클럭이 활성화됩니다.

다음 기본 블록:```arm
	    ldr        r0,[PTR_control_status] 
	    ldr        r0,[r0,#0x0]=>control_status 
	    and        r0,r0,#0x700
	    mov        r0,r0, lsr #0x8
	    cmp        r0,#0x3
	    bne        skip
		ldr        r0,[PTR_control_status] 
	    ldr        r0,[r0,#0x0]=>control_status
	    cpy        r6,r0
	    and        r0,r0,#0x1f
	    cmp        r0,#0x1f
	    bleq       GPMIC_init 
skip:   ...

이 블록은 다음을 수행합니다:

  1. (control_status & 0x700) >> 8 == 0x3인지 확인하고, 아니면 건너뜁니다.
  2. control_status & 0x1f == 0x1f인지 확인하고, 그렇다면 control_status를 r6에 로드한 후 GPMC_init 함수를 호출합니다.

다음 블록은 코프로세서를 설정합니다:```arm msr cpsr_c,#0xd3 ldr r4,[->Exceptions::ROM_RESET_VECTOR] mcr p15,0x0,r4,cr12,cr0,0x0 bl LAB_00020934 bl LAB_00020938 bl LAB_0002093c bl LAB_00020940 bl LAB_00020944 bl LAB_00020948 bl LAB_0002094c bl LAB_00020950 mrc p15,0x0,r0,cr1,cr0,0x0 orr r0,r0,#0x800 mcr p15,0x0,r0,cr1,cr0,0x0 b LAB_000207f0

root@kitploit:~
이 블록에서 수행하는 작업:
1. `11010011b`를 CPSR 제어 필드로 이동합니다(`I=1`,`F=1`,`T=0`,`MODE=10011`)
	1. `I`는 인터럽트 비활성화, `F`는 빠른 인터럽트 비활성화입니다(`I=F=1`이면 인터럽트가 비활성화됨)
	2. `T`는 Thumb 모드이며 `0`으로 설정됩니다
	3. `MODE=10011`은 프로세서 모드를 Supervisor 모드로 설정합니다 ([ref](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/ARM-processor-modes?lang=en#CIHGHDGI))
	4. 자세한 내용은 [여기](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/Program-Status-Registers--PSRs-)를 참조하세요
2. ROM 리셋 벡터의 주소를 로드합니다
3. [코프로세서 15](https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers/Coprocessor-15)(시스템 제어 코프로세서) 보안 확장 레지스터 `c12`에 접근하여 ROM 리셋 벡터를 `VBAR`(벡터 베이스 주소 레지스터)에 로드합니다
![](https://assets.kitploit.com/production/public/readmes/48883/6c4ef51fde89883fcdb3a9497004830d928d6f6c716668acd86dce3b9a68c93b.png)
4. `nop` 점프처럼 보이나요? `b` 대신 `bl`을 사용하는 이유는 무엇일까요?
5. 분기 예측을 활성화합니다(시스템 제어 레지스터 `SCTLR` 비트 11 설정)
![](https://assets.kitploit.com/production/public/readmes/48883/7a99ff8bd5285136c05b9d91bdf5057a6512c614cacc2726ce740254e4c240fa.png)
*VMSA 구현에서 CP15 `c1` 레지스터(시스템 제어 레지스터)*

`SCTLR` 레지스터 설명:
> SCTLR은 메모리 시스템을 포함한 시스템의 최상위 제어를 제공합니다.
> 이 레지스터는 가상 메모리 제어 레지스터 기능 그룹에 속합니다. 

TRM B4-1687 페이지를 참조하세요. 비트 11은 *분기 예측 활성화* 비트이며, 이 비트를 활성화하면 [분기 예측](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/Common-Memory-System-Architecture-Features/Caches/Branch-predictors)이 활성화됩니다. 

6. 함수를 호출합니다(호출 명령어로의 분기를 통해)

### `0x20894`의 함수(`__main`)

이 함수는 다른 초기 루틴이 분기하는 대상입니다. `FUN_0002889c`(나중에 `main()`으로 밝혀짐!)를 호출하기 전에 스택을 초기화하고, 아마도 타이머나 워치독을 초기화하는 것으로 보입니다.```arm
		 ldr    sp,[->RESERVED_EXCEPTION_BRANCH]   ; 0x4030ce00
		 blx    load_stack_1
		 ldr    r12,[DWORD_1]
		 add    r12,r12,pc
		 tst    r12,#0x1
		 adrne  lr,0x208bd
		 cpyeq  lr,pc
		 bx     r12 ;=>init_timers_maybe
		 adr    r12,0x208bd
		 bx     r12 ;=>LAB_000208bc
000208bc bl     FUN_0002889c
000208c0 ddw    0x109
000208c4 addr   RESERVED_EXCEPTION_BRANCH
...
RESERVED_EXCEPTION_BRANCH:
         ldr    pc=>LAB_00020090,[PTR_LAB_4030ce20]
         ; 20090 is a dead loop

여기서 수행되는 작업은 다음과 같습니다:

  1. RAM 예외 테이블의 시작 부분을 스택 포인터에 로드합니다.
  2. r0,r1,r2,r3,r4,lr을 스택(메모리 맵의 공용 스택)에 푸시하는 함수로 분기합니다.
    1. 해당 함수 load_stack_1은 빈 함수 bx lr로 분기한 다음, 스택에서 r0,r1,r2,r3,r4,pc를 팝합니다(기본적으로 해당 데이터를 레지스터에 되돌리고 lr이 보유한 값을 pc에 넣어 반환합니다).
  3. pc + 0x109가 홀수인지 확인하고, 짝수이면 0x208bd를 lr에 로드하고, 그렇지 않으면 pc를 lr에 복사합니다.
  4. main 함수를 호출합니다.
  5. FUN_0002889c를 호출합니다.

이것은 부팅 흐름도에서 언급된 __main() 함수여야 합니다:

따라서 다음 함수가 main 함수가 됩니다.

Figure 26-8의 상단에 표시된 것처럼, CPU는 보안 부팅 초기화를 완료하면 Public ROM Code 리셋 벡터로 점프합니다. 공용 모드에서 시스템 시작 시 CPU는 공용 측 초기화 및 스택 설정(컴파일러가 자동 생성한 C-초기화 또는 “scatter loading”)을 수행합니다. 그런 다음 워치독 타이머 1(3분으로 설정)을 구성하고 시스템 클록 구성을 수행합니다. 마지막으로 부팅 루틴으로 점프합니다.

메인 함수 (0x209b0)

main이 호출되면 SP 레지스터는 0x4030ce00을 가리킵니다. 이것이 스택이 시작되는 지점이며, 0x4030 b800 방향으로 아래로 자랍니다. 그리고 4개의 레지스터를 푸시한 후(16바이트 또는 4워드 차이) 주소 0x4030 cdf0을 가리키고 있으므로, AAPCS에서와 같이 전체 하강 스택(full descending stack) 을 사용하고 있습니다. 즉, SP는 스택에서 가장 최근의 워드를 가리키며 아래로 자랍니다.

다음은 디컴파일된 main() 함수입니다:```c int main() { uint local_10; uint local_c;

local_c = 0; local_10 = 0; check_stack_prm(&local_10); update_coldreset_tracing_vector(local_10); update_current_tracing_vector(1); main_clock_init(6,0); watchdog_softreset(); watchdog_write_disable_seq_data2(); set_watchdog(300000); if ((local_10 & 1) != 0) { update_current_tracing_vector(2); local_c = local_c & 0xffff | 1; } timer_func_1(); clock_init_func_4(&local_c); run_booting_loop(&local_c,local_10 & 0xff); return 0; }

root@kitploit:~
내 목적에 가장 흥미로운 것은 `0x20a10`에 있는 `run_booting_loop` 함수이다.

### X-Loader 참고 사항

시작 절차를 살펴본 뒤 "ISSW", "CHSETTINGS", "X-LOADER" 같은 문자열이 나오는 이 부분에 도달했을 때, 나는 이 문자열들이 U-Boot 관련 컨텍스트에서 다른 곳에도 나타나는지 찾아보기 시작했다. Nook 펌웨어를 리버스 엔지니어링하거나 크래킹하는 사람들의 [이 스레드](https://forum.xda-developers.com/t/discussion-on-the-boot-loader-cracked.1378886/)를 우연히 발견했고, [x-loader 소스](https://github.com/joelagnel/x-loader/blob/f3c74bc9b01dac58e553393d6ec1041353f2f1f7/scripts/signGP.c)에는 `CHSETTINGS` 같은 것들에 대한 참조가 포함되어 있다. 여기저기 찾아보니 "ISSW"는 [비메모리 장치에서 부팅하는 것](https://github.com/u-boot/u-boot/blob/master/doc/README.ti-secure)을 의미하는 것 같다.

초기화 문서에서 다뤘던 상위 수준 코드를 다시 떠올려 보자:
![](https://assets.kitploit.com/production/public/readmes/48883/3b7ecd05acbf1f0aa5497009b4df65d17a7b0e133b9b711bebaf9f5bebf546cf.png)

흥미로운 항목:
- RNDIS
- FAR
- XMODEM
- BOOTP
- TFTP
- DFT

다시 라이브 디버깅을 시도해 볼 때가 된 것 같다. 내가 이해할 수 없는 방대한 양의 데이터를 고려하면 그 모든 구조체를 리버스 엔지니어링하는 것은 아마 고통스러울 것이다...

만세! 내가 찾은 벡터를 사용해 PC와 SP를 수동으로 설정하면 라이브 디버깅이 작동한다:
![](https://assets.kitploit.com/production/public/readmes/48883/3886242947cd1cef1093f3cb02dbbcc02faf27f1298708dd849e69eae7b75f97.png)

소스와 트레이싱 벡터 덕분에 다양한 부팅 옵션과 각 옵션에 할당된 장치 번호를 매핑할 수 있었다. SD/MMC 부팅 핸들러에서 중단점을 설정할 때 MMC0 (8)과 MMCSD1 (9)을 구분해야 했기 때문에 이는 나중에 매우 유용했다.

| 유형       | 장치            | 장치 ID   |
| ---------- | ----------------- | ----------- |
| 메모리     | XIP (MUX2)        | 1           |
| 메모리     | XIP w/WAIT (MUX2) | 2           |
| 메모리     | XIP (MUX1)        | 3           |
| 메모리     | XIP w/WAIT (MUX1) | 4           |
| 메모리     | NAND              | 5           |
| 메모리     | MMCSD1            | 7, 9 (eMMC) |
| 메모리     | NAND_I2C          | 10          |
| 메모리     | MMC0              | 8, 12 (SD)  |
| 주변장치 | UART0             | 16          |
| 주변장치 | USB               | 20          |
| 주변장치 | GPGMAC0           | 22          |

프로세서가 부팅되는 방식에 대한 정보는 TRM과 [스택 익스체인지의 이 답변](https://stackoverflow.com/a/31252989/8565545)을 참조하라. 요약하면:
1. 부트 ROM이 SD 카드에서 MLO(Mmc LOader) 파일을 식별하여 SRAM으로 복사한다.
2. 이것은 보조 프로그램 로더로, 전체 RAM을 초기화하고 전체 U-Boot 바이너리를 실행을 위해 그곳에 복사하는 더 작은 부트로더이다.
3. U-Boot 바이너리가 실행된 후, 우리(보다 정확히는 U-Boot)가 마침내 커널을 부팅한다.

### `run_booting_loop()`

이것은 메인 부트 루프이다. 무한히 실행되거나, 실행이 RAM에 로드될 다른 부트로더로 분기할 때까지 실행된다.

트레이싱 벡터 업데이트 없이 프로시저의 시작:
- 장치 유형 조회
	- 장치 유형이 5(보안 장치)인 경우 다른 초기화를 수행
- `build_boot_list(int,buffer[],data[],int)` 실행  
	- `buffer[]`는 `0xff`로 초기화되고, `data[]`에는 장치 유형이 포함된다(아마도)```c
void run_booting_loop(uint32_t *r0_config,undefined4 param_2,undefined4 param_3,
                     undefined4 default_list)

{
  int iVar1;
  uint j;
  uint i;
  int device_type;
  byte alt_list [12];
  undefined4 boot_status;
  byte boot_list [8];
  uint8_t local_buffer [8];
  
  update_current_tracing_vector(3);
                    /* Device type is 3 */
  lookup_device_type(&device_type);
  if ((device_type == AM335X_HIGH_SECURITY) && (iVar1 = return_zero_4(), iVar1 != 0)) {
    init_something_1_small(&STATIC_DATA_1);
  }
                    /* param1 = 1
                       param2 = 4030 ebc4
                       param3 = 4030 ebb4 */
  build_boot_list(*(ushort *)r0_config,boot_list,alt_list,default_list);
  do {
    i = 0;
    local_buffer[0] = 0xff;
    local_buffer[1] = 0xff;
    local_buffer[2] = 0xff;
    local_buffer[3] = 0xff;
    do {
      if (boot_list[i] - 1 < 12) {
        update_current_tracing_vector(4);
                    /* No return unless there is an error */
        boot_device_1(r0_config,boot_list[i],local_buffer);
      }
      else if (boot_list[i] - 65 < 8) {
        update_current_tracing_vector(5);
        watchdog_write_disable_seq_data2();
        boot_status = 0xffffffff;
        boot_device_2((uint32_t)r0_config,boot_list[i],&boot_status,local_buffer);
        watchdog_write_enable_seq_data2();
        if (boot_status != 0xffffffff) {
          local_buffer[0] = (undefined)boot_status;
          local_buffer[1] = boot_status._1_1_;
          local_buffer[2] = boot_status._2_1_;
          local_buffer[3] = boot_status._3_1_;
          if ((boot_status & 0xffff00ff) == 0xf0030006) {
            update_current_tracing_vector(9);
            boot_list[i + 1] = (byte)(boot_status >> 8);
          }
          else if (boot_status != 0xf0030002) {
            update_current_tracing_vector(8);
            j = 0;
            do {
              if (63 < boot_list[j]) {
                boot_list[j] = 0;
              }
              j = j + 1 & 0xff;
            } while (j < 8);
          }
        }
      }
      i = i + 1 & 0xff;
    } while (i < 8);
    update_current_tracing_vector(6);
  } while( true );
}

SRAM으로 부팅

저는 0x23d7a에 boot_into_SRAM()이라고 부르는 함수가 있다는 것을 발견했습니다. 이 함수는 SRAM으로 분기하기 직전에 호출되는 마지막 함수였고, 그런 다음 거기에서 예외 처리기로 들어갑니다. 이전에는 RAM의 다른 상태를 저장해 두었지만, 한 번 더 실시간 디버깅을 하면서(지금은 1년이 지난 2024년 7월) 무슨 일이 벌어지고 있는지 깨달았습니다. 보드는 SD 카드에서 데이터를 성공적으로 읽고 있었고, 카드에서 로드한 코드를 실행하고 있었습니다! 이를 확인하려면 SRAM에서 SD 카드에 있는 것과 동일한 바이트를 찾아야 했습니다. 알고 보니 am335x-evm-linux-sdk-bin-.../board-support/prebuilt-images/ 안에 u-boot-spl.bin-am335x-evm이라는 바이너리 파일이 있고, 이 바이너리의 코드는 SRAM에 나타나는 코드와 일치합니다. 우리는 uboot SPL에 도달했습니다!

UART 터미널

우리는 SRAM으로 성공적으로 부팅했습니다. 이제 uboot에 대한 정보를 표시해야 하는 UART 터미널이 궁금해졌습니다. 하드웨어 연결은 아래와 같습니다.

CuteCom으로 115200 @ 8-N-1 설정으로 장치에 연결하면 SD 카드를 삽입하지 않은 상태에서는 단순히 C를 반복적으로 출력합니다.

하지만 SD 카드를 삽입하면 UART에서 아무것도 출력되지 않습니다. 메시지도, 문자도 없습니다. 결함이 부팅 프로세스 중 너무 일찍 발생하는 것일까요? 하지만 이제 어떤 코드를 실행 중인지도 알고(그리고 그 소스도 있으므로) 디버깅 심볼을 빌드하고 제대로 된 디버그 세션을 시작할 수 있을 것입니다. 이것은 간단하지 않을 수 있습니다. 동일한 방식으로 코드를 컴파일해야 하고, SDK가 내 SD 카드에 정확히 무엇을 로드했는지, 그리고 그것을 어떻게 빌드하는지 배우는 데 시간이 걸릴 수도 있습니다.

U-Boot가 UART로 텍스트를 출력해야 하는데 아무것도 보이지 않으므로, 우리는 SPL 어딘가에서 예외를 잡고 있는 것 같습니다.

이 시점에서 저는 Ghidra에서 부트 ROM 디컴파일 소스를 리버스 엔지니어링하고 정리하는 데 아주 많은 시간을 보냈습니다. 구조체와 각 데이터 멤버가 함수들 사이에서 어떻게 사용되는지(때로는 중첩되어) 살펴보면서 온갖 혼란을 겪었습니다. 그 일을 미뤄 두는 동안, 이제는 직접 코드를 디버깅하고 컴파일하기 시작할 때라고 생각했습니다. 어차피 우리는 RAM에 있으니, SPL 심볼을 로드해서 무슨 일이 일어나는지 확인해 보는 것이 어떨까요?

SDK 개발

디버깅

J-Link용 Segger 디버거인 Ozone으로 디버깅할 수 있습니다. 또한 TI 자체의 Code Composer Studio(CCS)나 VSCode 스타일의 "라이트" 버전인 CCS Theia를 사용할 수도 있습니다. 저는 다음 비디오를 따라 SDK의 모든 것을 빌드할 수 있었습니다: Sitara Linux Board Porting Series: Module 6. 빌드할 구성 요소는 세 가지입니다:

  • 프로세서 구성
  • U-Boot 바이너리
  • U-Boot 2차 프로그램 로더(SPL)

위 시리즈의 Module 7 비디오를 따라 작업을 정상적으로 동작시킬 수 있었습니다. 몇 가지 참고 사항이 있습니다:

  1. s_init()는 더 이상 존재하지 않습니다.
  2. J-Link를 사용한 하드웨어 중단점은 J-Link 제어판(트레이 아이콘 참조)을 통해 설정해야 합니다. 이것으로 코드를 로드하는 방법은 잘 모르겠습니다. 아마도 Ozone을 통해서일 것입니다.

심볼이 있다니? 멋지네요. 이제 리셋 핸들러 reset()에서 시작하여 실행 중 어떤 일이 일어나는지, 그리고 우리의 예외가 발생하는 위치를 볼 수 있습니다. 추적하기 위해 예외 처리기의 0x402f 0440에 중단점을 설정하고 링크 레지스터를 확인했습니다. 링크 레지스터에는 가장 최근 함수의 주소가 여전히 저장되어 있었습니다. 그 주소는 0x402f 76ce인 것으로 확인되었지만, 일관적이지 않은 것 같습니다. 무엇이 오류를 일으키는 걸까요?

참고: 디버깅 시 Module 7 비디오에 따라 0x402f 0400까지 실행한 다음 Load Memory() 작업을 수행하세요. 이 작업은 재시작할 때마다 수행해야 합니다.

우리는 device_probe() 함수(0x402f 74c4)로 들어간 다음, 다른 함수 몇 개로 들어갑니다. 그런 다음 0x402f 07fc에서 분기된 do_setup_dpll()에서 빠져나오지 않으므로 계속 그 함수를 따라가 봅시다. 단계별로 진행하면 crt0.S에 있는 _main()(0x402f 14e0)으로 돌아옵니다. board_init_f()에서 나와 spl_relocate_stack_gd()로 진행하는 것 같습니다. 이 호출은 빠져나오지 않습니다. dm_fixup_for_gd_move()에 도달합니다. 이 함수에는 0x402f 76c2에 실패하는 명령어가 있습니다. 아마도 이것이 원인인 것 같습니다: 0x81ff ff20에 접근하려고 하고 있습니다. 분명히 안 되는 것 같습니다. SDRAM 구성에 문제가 있다는 직감이 듭니다. 비슷한 문제와 관련된 TI 포럼의 모든 스레드를 추적했고, 도움이 될 만한 힌트가 들어 있는 게 대여섯 개 정도 있었습니다. 아마도 (a) EMIF 튜닝 또는 (b) 소프트웨어 레벨링과 관련된 문제일 것이라고 결론지었습니다.

DDR3 RAM 구성

제 보드는 아래에 표시된 보드입니다.

메모리는 Micron 제품입니다. 한편 제가 가진 BeagleBone Black(rev C3)의 회로도는 Kingston DDR3 메모리, 구체적으로 D2516EC4BXGGB를 사용합니다. DDR3는 U12이며, Micron 마킹 디코더 페이지를 사용하여 부품을 찾을 수 있습니다:

  • MT41K256M16TW-107 XIT:P
    • 256 Meg x 16
    • 96-ball 8mm x 14mm FBGA, rev P
    • $t_{CK}$=1.07ns, CL = 13

메모리가 실제로 동작하는지 확인하기 위해 먼저 전원이 공급되는지 간단히 확인했습니다. 데이터시트에 따르면 1.5V +/- 0.075V여야 합니다. 보드 아래쪽의 R6 양단에서 1.506V를 측정했습니다. TP1과 TP2라는 두 개의 테스트 포인트가 있습니다.

혹시 도움이 될 수 있으니 테스트 포인트 몇 개를 여기에 정리했습니다.

  • Memory Device

클록 인에이블 라인을 확인해 봅시다. R96의 양쪽을 확인할 수 있는데, 한쪽은 접지되어 있어야 하고 다른 쪽은 하이로 유지되어야 합니다. 확인했습니다. CKE에 1.5V가 인가되어 있습니다.

다음 단계는 클록 신호를 확인하는 것입니다. 안테나를 연결한 tinySA를 RAM 칩 방향으로 대략적으로 가리켜서 할 수 있는 만큼 확인했습니다. 이런 식으로 "스니핑"을 해보니 적어도 지금으로서는 클록이 존재한다고 꽤 확신할 수 있습니다.

이제 외부 메모리 인터페이스를 살펴볼 차례입니다. 자주 등장하는 것 중 하나가 GEL 파일 개념입니다. 이 언어는 Code Composer Studio용으로 Texas Instruments가 개발한 인터프리터 언어이며, General Extension Language의 약자입니다.

  • Creating Device Initialization GEL Files

GEL 파일은 DDR 메모리 구성 도구에 포함되어 있습니다.

좋습니다! 저는 튜닝 절차(가능한 한 최선을 다해)를 따라 GEL 파일에 대한 최적의 값을 찾을 수 있었습니다.```


root@kitploit:~
The Slave Ratio Search Program Values are... 

PARAMETER MAX | MIN | OPTIMUM | RANGE


DATA_PHY_RD_DQS_SLAVE_RATIO 0x071 | 0x005 | 0x03b | 0x06c DATA_PHY_FIFO_WE_SLAVE_RATIO 0x1b3 | 0x046 | 0x0fc | 0x16d DATA_PHY_WR_DQS_SLAVE_RATIO 0x0f7 | 0x01a | 0x088 | 0x0dd DATA_PHY_WR_DATA_SLAVE_RATIO 0x137 | 0x05a | 0x0c8 | 0x0dd


root@kitploit:~
Memory Browser에서 RAM 관련 설정을 조정해 보는 중... 우와! 작동한다!

그래서 메모리는 확실히 작동하는 것 같지만, SPL은 여전히 실패하고 있다. 어쩌면 SPL이 SDRAM을 초기화하는 방식에 뭔가 문제가 있는 걸까? 아, 맞다. 물론 튜닝 절차에 더 필요한 부분이 있다! 실제로 SPL을 업데이트해야 한다...

이제 점점 가까워지고 있다. `board.c` 파일은 보드 종류를 확인하여 DDR을 초기화한다. 그런데 이 보드의 경우 모든 함수(`board_is_evm_sk()`, `board_is_icev2()`, `board_is_bone_lt()`, 등)가 false를 반환하므로 `config_ddr(266, ...)`이 기본값으로 사용된다. 여기서 266은 MHz 단위의 클록 주파수인데, 그 주파수는 400 MHz*여야* 한다. 그것은 분명히 문제가 될 것이다.

`board_is_bone_lt`를 우회하여 항상 true를 반환하게 해야 한다. 나는 그렇게 했고, 조금 더 진행되었다. 하지만 뭔가 걸린다. 새 파일 MLO를 SD 카드에 로드하는 것은 작동하지 않는다. 프로그램을 직접 로드하면 잘 작동하는데도 말이다. 무슨 일이 일어나고 있는 걸까? SRAM에 로드되는 코드가 내가 컴파일한 코드와 같지 않다는 것을 알 수 있다. 사실 SD 카드를 포맷하기까지 했는데, 기본 SPL이 어쨌든 SRAM으로 로드되고 있는 것 같다! 다른 SD 카드를 사용하면 부팅 프로세스가 계속되지 않는다는 것도 확인했다. 따라서 부트로더는 분명 SD 카드의 부트 파티션을 찾은 다음 실행을 SRAM으로 옮기지만, 아직 데이터를 복사하지 않은 것일까? 윽. 그건 도대체 어디서 오는 걸까??

이 문제는 나에게 적지 않은 골칫거리를 안겨 주었다. 모든 파티션을 삭제하고, MBR을 0으로 채우고, 부트 파티션도 0으로 채웠으며, 다른 SD 카드들도 시험해 보았다. 그런데도 내 카드만 여전히 부팅할 수 있었다. 따라서 그 카드 어딘가에는 *어떤* 부팅 가능한 데이터가 아직 남아 있었음에 틀림없다. 마침내 카드 *전체*를 0으로 채워서 이 광란을 끝낼 수 있었다.

또한 이 시점에서 부트 ROM 문제를 해결할 때 접근할 수 있는 **추적 벡터**에 대해 배웠다. 이것은 리버싱에도 매우 유용하게 쓰였다. 모든 추적 호출이 어디서 오는지 알게 되었고, 그 호출들을 기반으로 함수 이름 등을 지정할 수 있었기 때문이다. 나는 이 추적 벡터들을 해석하는 스프레드시트를 만들어서, 카드 파라미터를 변경할 때 부팅 절차가 어떻게 바뀌는지 빠르게 이해하는 데 사용했다. 그리고 예상대로, 내가 SD 카드를 포맷하고 다시 포맷하는 동안, 필사적으로 전체를 0으로 채우기 전까지 CHSETTINGS를 계속 찾는다고 보고했다.

TI 프로세서가 카드를 읽는 방식대로 카드를 읽어 보려면 `dd`를 사용하면 된다. 블록 크기를 512로 지정하고, 처음 `n`개 섹터를 건너뛰어 첫 번째 섹터를 지정한다(어느 섹터인지 확인하려면 GParted를 사용해 보라). 예: 첫 번째 섹터가 2048이고 장치가 `sda`라면, 첫 번째 섹터만 읽을 것이다:```
sudo dd if=/dev/sda1 of=/home/sam/sector2048 bs=512 skip=2048 count=1

이걸 사용해 SD 카드에서 직접 MBR 이미지와 부트 파티션 시작 부분을 내려받았는데, 둘 다 나중에 유용하게 쓰였다.

SD 카드 파고들기

Ghidra에서의 리버싱 작업 덕분에 SD 카드 부트 핸들러 함수들에 도달할 수 있었고, 이제 카드에 SD 명령을 보내는 함수들을 볼 수 있었으며, 단계별로 실행하면서 카드가 무엇으로 응답하는지 확인할 수 있었다. 내가 올바른 위치를 보고 있고 카드가 모두 0을 반환한다고 생각했다. 나중에 알게 된 사실은, 아마도 내가 eMMC 핸들러(동일한 핸들러지만 다른 장치 ID를 사용하는)를 단계별로 실행하고 있었거나, 다른 문제가 있었을 것이라는 점이다. SD 카드에는 아무 문제가 없었기 때문이다. 그럼에도 불구하고, 이제 이 카드들이 어떻게 동작하는지 배울 때라고 생각했다.

카드가 각 블록 요청 중에 계속 모두 0을 반환하는 이유가 궁금했다. 분명히 카드는 정상 작동하며, 소프트웨어도 과거에 이미 해본 적이 있으므로 카드에서 읽을 수 있다. 그래도 이제 배선을 연결하고 로직 분석기로 살펴볼 때가 되었다. 약간의 미세 납땜과 UV 경화 에폭시로 30awg 전선을 고정하고, 내 Saleae로 클립을 연결하니 작동하는 무언가가 만들어졌다.

데이터를 분석하기 위해 이 분석기를 사용했다. 처음에는 카드를 삽입하지 않은 상태로 시도했다.

처음 몇 개의 명령에서는 클록 레이트가 120 kHz이다. 당연히 카드는 응답하지 않는다 (카드가 없으니까).``` CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND ... CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND

root@kitploit:~
카드가 실제로 삽입되면 구성 후 주파수가 약 6MHz로 뛰어오릅니다.

![](https://assets.kitploit.com/production/public/readmes/48883/80a4aca50543bd6766805e8491ba504f07a660b61804ec7a673f738dac0b03cc.png)

SD 모드를 사용하면서 겪은 몇 가지 크래시를 극복한 후(팁: 이 SD 카드에서도 MMC 모드를 사용해야 합니다) 카드가 합리적인 데이터를 제공하는지 확인할 수 있었습니다. 그 후 SD 카드 부트 핸들러를 이해하는 데 다시 집중했고, 올바른 데이터 *가* 실제로 읽히고 있었으며 그 데이터가 수동으로 `dd`하여 카드에서 얻은 데이터와 동일하다는 것을 깨달았습니다. 그래서 이 문제는 여기서 접었습니다. 음, 재미있는 우회로였고 카드가 작동한다는 확신을 갖는 데 도움이 되었습니다.

### 문제 찾기

데이터는 주소 `0x0000`과 장치 `8`에 대해 `0x25c2e`의 분기를 실행한 후 주소 `0x4030c928`(스택 변수, 512바이트 배열)에서 옵니다(`0x4030d00c`의 정적 데이터 참조). 제가 `MBR_detection`이라고 부르는 메서드로 들어가면 프로그램은 매직 바이트 `0xaa55`를 확인합니다. 먼저 두 번째 두 바이트인 `0xaa`를 로드한 다음, 첫 번째 `0xaa`를 로드합니다.```
r0 = data[0x1ff]
r1 = data[0x1fe]
orr r0,r1,r0,lsl #8
sub r1,r0,#0xaa00
subs r1,#0x55
bne <return FAIL>

이것은 성공합니다. 하지만 다음 검사는 실패합니다:``` r0 = data[0xc] => 0 r1 = data[0xb] => 0 orr r0,r1,r0, lsl #8 cmp r0,#0x200 bne

root@kitploit:~
디컴파일러 의사 코드:```c
if (
	 data[0x1fe] != 0x55aa || 
	 data[0xb]   != 0x200  ||
	(data[0xd] != 1 &&     // bit 0
	 data[0xd] != 2 &&     // bit 1
	 data[0xd] != 4 &&     // bit 2
	 data[0xd] != 8 &&     // bit 3
	 data[0xd] != 0x10 &&  // bit 4
	 data[0xd] != 0x20 &&  // bit 5
	 data[0xd] != 0x40 &&  // bit 6
	 data[0xd] != 0x80)    // bit 7
	) 
{
	return 1;
}

This checks if the byte at 0xb = 11 is equal to 0x200, and checks whether the byte at 0xd is equal to a single-bit value. Both conditions must be met, or it returns a failure.

After the detection function returns a 1, the boot handler next tries to read it as an MBR and loads up the offset of the first partition to try and see if that is a bootable partition. Here's the procedure:```C // Call block read function // mmc_block_read_something(boot_device *dev,blk_read_struct blk) ret = ((code *)blockread_struct->block_read_func)(blockread_struct->device_ptr,&block_read_info); if (ret != 0) { return 1; } // Check if device doesn't use MBR ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct); if (ret != 0) { // It uses MBR, try each partition in the partition entries for a bootable // partition ret = MBR_check_entries(block_data,blockread_struct); if (ret != 0) { return 1; } ret = MBR_parse_entries(block_data,&blockread_struct->part_entry); if (ret != 0) { return 1; } // Get bootable partition offset block_read_info = (blk_read_struct *)(blockread_struct->part_entry).first_sect_pos; uStack_220 = 1; pbStack_21c = block_data; // Call block read function // mmc_block_read_something(boot_device *dev,blk_read_struct blk) ret = ((code *)blockread_struct->block_read_func) (blockread_struct->device_ptr,&block_read_info); if (ret != 0) { return 1; } // Try and verify bootable partition again ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct); if (ret != 0) { return 1; } }

root@kitploit:~
그래서 이제 `0x800`에서 데이터를 읽는 지점으로 점프하여 올바른 덤프를 얻습니다. 하지만 MBR 감지 방법은 올바른 데이터가 있어도 여전히 1을 반환합니다(그리고 따라가야 했던 몇 겹의 간접 계층이 있습니다, 으으). 그래서 문제는 바로 거기에 있을 것입니다.

이제 막바지입니다. SD 카드에서 첫 번째 메모리 읽기는 MBR이며, 최대 4개의 파티션 테이블 항목이 있습니다(TRM 표 26-20, 26-21 참조). 부팅 파티션의 파티션 테이블 항목은 파티션에 `0x40000` 섹터가 포함된다고 말합니다. 그러나 파티션 파일 시스템(TRM 표 26-23 참조)에는 어떤 이유로 `0x3fff8`만 있다고 표시되며, 부트 ROM은 이를 감지하고 실패합니다.

실험으로 문제를 일으키던 점프를 건너뛰었습니다(안 좋게 끝날 수도 있지만... 행운을 빌어요...). 프로그램은 확실히 계속되었지만, 어디로 이어졌는지는 확실하지 않아 쓰레기처럼 보입니다. 하지만 그걸 무시하면 장치가 실제로 부팅됩니다! `0x402f0400`(로드된 이미지의 시작 부분)에 중단점을 설정하면 모든 것이 제대로 진행됩니다. 이제 UART를 연결할 때일까요? UART는 좋습니다!

SD 카드 문제는요? [Unix SE에 질문](https://unix.stackexchange.com/questions/781715/why-do-the-mbr-partition-entry-and-partition-filesystem-disagree-on-the-number-o/781755)을 올렸지만, 당면한 문제에는 별 도움이 되지 않았습니다(그래도 좋은 정보를 얻었습니다). 그로부터 마침내 고쳤습니다! FAT16 파일 시스템을 만들기 위한 `mkfs` 명령을 검토하던 중 [다른 참조](https://blog.billvanleeuwen.ca/porting-u-boot-onto-the-beaglebone)가 정렬을 비활성화하는 -a 플래그를 사용한다는 것을 발견했습니다. 이것이 핵심이었습니다. 해당 플래그를 추가하고 다시 빌드하자 섹터 수가 일치(`0x40000`)하고 시스템이 부팅됩니다. 부트 ROM이 그런 종류의 정렬을 지원하지 않는 것 같습니다.

이제 카드를 꽂은 상태에서 아래 메시지가 부팅 루프로 나타납니다. 만세! 커널이 시작되지 않는 이유만 알아내면 됩니다. 그러면 다 된 겁니다! 그 모든 작업이 마침내 몇 개의 사용 가능한 beaglebone 보드로 결실을 맺을지도 모릅니다. 그만한 가치가 있었나요? 누가 판단하겠어요.```
U-Boot SPL 2021.01-00001-gc59bf25a382-dirty (Jul 24 2024 - 20:38:49 -0400)
Trying to boot from MMC1


U-Boot 2021.01-00001-gc59bf25a382-dirty (Jul 28 2024 - 20:36:46 -0400)

CPU  : AM335X-GP rev 2.1
Model: TI AM335x BeagleBone Black
DRAM:  512 MiB
WDT:   Started with servicing (60s timeout)
NAND:  0 MiB
MMC:   OMAP SD/MMC: 0, OMAP SD/MMC: 1
Loading Environment from FAT... *** Warning - bad CRC, using default environment

<ethaddr> not set. Validating first E-fuse MAC
Net:   eth2: ethernet@4a100000, eth3: usb_ether
Hit any key to stop autoboot:  2 <0x08><0x08><0x08> 1 <0x08><0x08><0x08> 0 
WARNING: Could not determine device tree to use
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
Failed to load 'boot.scr'
Failed to load 'uEnv.txt'
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
<0x1b>7<0x1b>[r<0x1b>[999;999H<0x1b>[6n<0x1b>8Scanning disk [email protected]...
Scanning disk [email protected]...
** Unrecognized filesystem type **
Found 4 disks
No EFI system partition
BootOrder not defined
EFI boot manager: Cannot load any image
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
4997632 bytes read in 353 ms (13.5 MiB/s)
Failed to load '/boot/undefined'

Starting kernel ...

그래서 디바이스 트리에 문제가 있습니다("WARNING: Could not determine device tree to use"). 커널과 커널 부팅을 처음 접해 보는 터라 이것이 무엇을 의미하는지 전혀 모릅니다.

"커널 부팅"은 제가 이해하기로는 다음과 같습니다:

  1. U-Boot가 커널 이미지(zImage)를 로드하고 찾습니다. 커널 이미지는 압축된 커널 바이너리이며, zImage는 자체 압축 해제 방식입니다
  2. 커널 이미지가 메모리에 로드된 다음, 자체적으로(zImage) 또는 U-Boot(uImage)에 의해 압축이 해제됩니다
  3. 커널은 평소의 저수준 작업을 수행한 다음 init 프로그램/데몬을 실행합니다

커널 부팅 과정의 중요한 측면은 디바이스 트리입니다. 이는 보드용 .dtb 파일(디바이스 트리 바이너리; .dts 디바이스 트리 소스 파일과 비교)에 저장되어 있습니다.

지금 제 문제는 U-Boot가 보드의 디바이스 트리를 로드하지 않는다는 것입니다. 로그에 reading /am335x-boneblack.dtb 메시지가 없기 때문입니다. 대신 WARNING: Could not determine device tree to use 메시지가 표시됩니다. 그러니 좋은 증거입니다! EEPROM 보드 ID가 없어서 그런 것 같습니다.

보드를 식별하는 방법에 대한 자세한 내용은 TI 포럼의 이 스레드에서 확인할 수 있습니다.

그렇다면 U-Boot는 어떻게 스스로를 구성하고 제대로 부팅하는 방법을 알까요? 우리가 빌드한 U-Boot 소스에는 다양한 보드용 defconfig 파일을 저장하는 configs/라는 폴더가 있습니다. 이 파일들은 부팅 명령을 포함한 U-Boot의 다양한 구성 매개변수를 정의하며, 부팅 명령은 다음과 같은 형태일 수 있습니다:``` if test ${boot_fit} -eq 1; then run update_to_fit; fi; run findfdt; run init_console; run envboot; run finduuid; run distro_bootcmd

root@kitploit:~
우리는 `make <boardname>_config` 대상을 실행할 때 사용할 구성을 정의합니다. `findfdt` 함수는 현재 실행 중인 보드를 식별하고 장치 트리를 올바르게 구성하는 데 사용됩니다. 이는 다음과 같습니다 (`am335x_evm.h`에 정의됨):```
"findfdt="\
		"if test $board_name = A335BONE; then " \
			"setenv fdtfile am335x-bone.dtb; fi; " \
		"if test $board_name = A335BNLT; then " \
			"setenv fdtfile am335x-boneblack.dtb; fi; " \
		"if test $board_name = A335PBGL; then " \
			"setenv fdtfile am335x-pocketbeagle.dtb; fi; " \
		"if test $board_name = BBBW; then " \
			"setenv fdtfile am335x-boneblack-wireless.dtb; fi; " \
		"if test $board_name = BBG1; then " \
			"setenv fdtfile am335x-bonegreen.dtb; fi; " \
		"if test $board_name = BBGW; then " \
			"setenv fdtfile am335x-bonegreen-wireless.dtb; fi; " \
		"if test $board_name = BBBL; then " \
			"setenv fdtfile am335x-boneblue.dtb; fi; " \
		"if test $board_name = BBEN; then " \
			"setenv fdtfile am335x-sancloud-bbe.dtb; fi; " \
		"if test $board_name = A33515BB; then " \
			"setenv fdtfile am335x-evm.dtb; fi; " \
		"if test $board_name = A335X_SK; then " \
			"setenv fdtfile am335x-evmsk.dtb; fi; " \
		"if test $board_name = A335_ICE && test $ice_mii = rmii; then " \
			"setenv fdtfile am335x-icev2.dtb; fi; " \
		"if test $board_name = A335_ICE && test $ice_mii = mii; then " \
			"setenv fdtfile am335x-icev2-prueth.dtb; fi; " \
		"if test $fdtfile = undefined; then " \
			"echo WARNING: Could not determine device tree to use; fi; \0" \

기본 동작을 원한다면 board_name 변수만 변경하면 되는 거죠? 글쎄요, 아닐 수도 있습니다. 적어도 전 어디서 변경하는 것이 가장 좋은지 모르겠습니다. 하지만 board_name을 설정하는 것 자체로는 작동하지 않았지만, 실제로 기본 .dtb 파일도 업데이트했더니 작동했습니다!``` _____                    _____           _         _    
|  _  |___ ___ ___ ___   |  _  |___ ___  ||__ | |  
|     |  _| .'| . | . |  |   |  | . | | | -|  _|  _|
|
|
|| |__,|  ||  ||  || ||| ||||   
             |
|                    |___|             

Arago Project http://arago-project.org am335x-evm ttyS0

Arago 2021.09 am335x-evm ttyS0

am335x-evm login: root
root@am335x-evm:~#

root@kitploit:~
드디어 터미널에 도착했다. 내 폐기물 보드들이 살아났다!

### 누락된 EEPROM ID 수정

마지막 단계로, EEPROM에 올바른 보드 ID를 기록하겠다. 이는 Linux 사용자 공간에서 쉽게 할 수 있다. SPL 소스에서 다양한 보드 ID는 다음과 같다:
- `A335BONE` - Beaglebone 보드
- `A335BNLT` - Beaglebone Black 보드
- `A335PBGL`
- `A335X_SK`
- `A33515BB`
- `A335_ICE`

그리고 선택적인 보드 개정판도 있다. 회로도를 기준으로 EEPROM은 I2C0에 있으며, 칩 자체(내 회로도에서는 24LC32A, '256Kx8로 표시되어 있지만)는 I2C 장치 주소 `0x50`(이진 `b1010` 뒤에 `000` 칩 주소, 5핀 패키지에는 추가 주소 핀이 없으므로)을 제공한다. 마지막으로 한 가지 주목할 점: WP 핀은 10k 풀업으로 HIGH에 연결되어 있어 기본적으로 쓰기 보호가 활성화된다. 쓰기가 발생하려면 LOW로 연결해야 하며, 그렇지 않으면 응답은 하지만 아무것도 쓰지 않는다.

EEPROM은 커널의 `/sys/bus/i2c/devices/0-0050`을 통해 접근할 수 있으며, 그 안에 `eeprom`이라는 파일이 있다. 따라서 WP 핀을 LOW로 당긴 상태(DC 잭 근처 상단의 TP4를 접지에 연결)에서 `echo` 호출 몇 번이면 충분하다. Beaglebone Black System Reference Manual에 형식이 나와 있다. 나는 [여기](https://groups.google.com/g/beagleboard/c/di5O5JCl4yw)에서 이 내용을 각색했다.```sh
root@am335x-evm:~# cat fix_eeprom.sh 
#!/bin/bash
# Fix board ID EEPROM

EEPROM_FILE=/tmp/eeprom.tmp
EEPROM=/sys/bus/i2c/devices/0-0050/eeprom

# header bytes
echo -ne "\xaa\x55\x33\xee" > ${EEPROM_FILE}
# Board ID
echo -n "A335BNLT" >> ${EEPROM_FILE}
# serial number (I left this basically as the template)
echo -n "000C24wwBBoxxxx" >> ${EEPROM_FILE}

dd if=${EEPROM_FILE} of=${EEPROM}

Using less to confirm, and we should have no issue running a default SD card from now on. Changes to the SPL and the U-Boot config can be rolled back, once all boards have their EEPROMs written. less를 사용하여 확인하면, 이제부터 기본 SD 카드를 실행하는 데 문제가 없을 것입니다. 모든 보드에 EEPROM이 기록되면 SPL 및 U-Boot 구성 변경 사항은 롤백할 수 있습니다.

도구 다운로드
영역시작 주소길이
Boot ROM (Public)0x4002_00000xBFFF
Boot ROM (Public, alias)0x0002_00000xBFFF
SRAM Internal0x402F_04000xFC00
L3 OCM00x4030_00000x10000
테스트 포인트연결회로도 시트보드 면
TP1DGND2 (D1)상단
TP2VDD_MPUON (VDD_MPU_MON)5 (C4)상단
TP3TESTOUT5 (B2)상단
TP4Board ID WP11 (B1)상단
메모리 회로에 대한 자세한 설명은 하드웨어 설계 페이지에 있습니다.