AOSP 빌드 직후 무한 부팅: Fastboot 플래싱과 파티션 테이블 수정 디버깅기

새벽 3시, 8시간에 걸친 AOSP(Android Open Source Project) 전체 빌드가 드디어 끝났다는 터미널 알림이 떴습니다. make -j32 명령어가 성공적으로 종료되었고, 출력 디렉토리에는 따끈따끈한 system.img가 생성되어 있었습니다. 하지만 기쁨도 잠시, 픽셀 6(Pixel 6) 디바이스에 이미지를 올리고 재부팅을 하는 순간, 화면은 구글 로고에서 멈춘 채 20분이 지나도 반응이 없었습니다. adb logcat은 연결조차 되지 않는 '먹통' 상태. 이는 안드로이드 프레임워크 개발자나 커스텀 ROM 빌더들이라면 한 번쯤 겪어봤을 등골 서늘한 상황입니다.

빌드 환경 및 AOSP 초기 설정의 함정

이번 프로젝트는 Android 14 (UpsideDownCake) 기반의 보안 모듈을 커널 레벨에서 테스트하는 것이 목표였습니다. 빌드 환경은 다음과 같습니다.

  • OS: Ubuntu 22.04 LTS (Jammy Jellyfish)
  • Hardware: AMD Ryzen 9 5950X, 128GB RAM, 2TB NVMe SSD
  • Target Device: Pixel 6 (Oriole)
  • Build Target: aosp_oriole-userdebug

일반적으로 Android Source 공식 문서를 따라 repo initrepo sync를 진행합니다. 하지만 여기서 첫 번째 병목이 발생합니다. AOSP 소스 트리는 약 250GB 이상의 디스크 공간을 요구하며, 단순히 소스만 다운로드했다고 끝나는 것이 아닙니다. 많은 개발자가 간과하는 부분이 바로 Vendor Binaries(벤더 바이너리)입니다.

초보자의 흔한 실수: AOSP 소스 코드에는 퀄컴(Qualcomm)이나 삼성(Samsung) 같은 칩셋 제조사의 독점 드라이버(GPU, Modem 등)가 포함되어 있지 않습니다. 이를 별도로 추출하거나 다운로드하여 /vendor 디렉토리에 배치하지 않으면 빌드는 성공하더라도 부팅은 100% 실패합니다.

저는 구글 드라이버 사이트에서 해당 빌드 ID에 맞는 벤더 이미지를 정확히 배치했음에도 불구하고 부팅 루프(Boot Loop)에 빠졌습니다. 문제는 단순한 파일 누락이 아니라, 파티션 레이아웃의 불일치에 있었습니다.

Fastboot 모드 진입과 파티션 분석

벽돌이 된 기기를 살리기 위해 볼륨 하(Down) + 전원 키를 눌러 fastboot 모드(Bootloader)로 진입했습니다. 여기서부터는 운영체제가 아닌, 하드웨어 초기화 단계에서 메모리에 직접 데이터를 쏘아주는 과정입니다.

처음에는 편의를 위해 fastboot flashall -w 명령어를 사용했습니다. 이 명령어는 빌드된 모든 이미지를 자동으로 매핑하여 플래싱하고 사용자 데이터(-w)를 초기화합니다. 하지만 로그를 자세히 살펴보니 아래와 같은 경고가 스쳐 지나갔습니다.

Error Log:
Resizing 'system_a' to 1845230 sectors...
FAILED (remote: 'Not enough space to resize partition')

이 오류는 빌드된 system.img의 크기가 디바이스의 현재 논리 파티션(Logical Partition) 할당량보다 커졌을 때 발생합니다. 특히 안드로이드 10 이후 도입된 동적 파티션(Dynamic Partition) 구조에서는 슈퍼 파티션(Super Partition) 내에서 하위 파티션들의 크기를 유동적으로 조절하는데, 기존 파티션 테이블 정보가 갱신되지 않아 공간 부족이 발생한 것입니다.

왜 자동 플래싱(flashall)은 실패했는가?

일반적인 fastboot flashallandroid-info.txt를 참조하여 이미지를 굽습니다. 하지만 제가 수정한 커널 모듈로 인해 시스템 이미지의 크기가 미세하게 증가했고, 기존의 파티션 맵이 이를 수용하지 못했습니다. 또한, A/B 슬롯(Seamless Update) 시스템에서 활성 슬롯(Current Slot)이 꼬이는 문제까지 겹쳤습니다. 단순한 자동화 명령어가 오히려 독이 된 케이스입니다.

해결책: 수동 파티션 리사이징 및 개별 플래싱

해결 방법은 fastboot의 저수준 명령어를 사용하여 파티션 테이블을 강제로 갱신하고, 이미지를 개별적으로 주입하는 것입니다. 다음은 제가 문제를 해결한 최종 스크립트의 핵심 로직입니다.

#!/bin/bash
# 1. 부트로더 언락 확인 및 환경 변수 설정
export ANDROID_PRODUCT_OUT=/path/to/your/out/target/product/oriole

# 2. 슈퍼 파티션 초기화 (가장 중요: 이전 파티션 맵 삭제)
fastboot wipe-super super_empty.img

# 3. 부트 이미지 및 벤더 부트 플래싱
# 커스텀 커널이 포함된 boot.img를 명시적으로 지정
fastboot flash boot_a $ANDROID_PRODUCT_OUT/boot.img
fastboot flash vendor_boot_a $ANDROID_PRODUCT_OUT/vendor_boot.img

# 4. dtbo 및 vbmeta 플래싱 (Verity 비활성화)
# 커스텀 이미지를 부팅하기 위해 검증(Verity) 과정을 무력화해야 함
fastboot flash dtbo_a $ANDROID_PRODUCT_OUT/dtbo.img
fastboot --disable-verity --disable-verification flash vbmeta_a $ANDROID_PRODUCT_OUT/vbmeta.img

# 5. 동적 파티션 플래싱 (system, vendor, product)
# fastbootd 모드로 진입해야 논리 파티션을 제어 가능
fastboot reboot fastboot
sleep 10
fastboot flash system_a $ANDROID_PRODUCT_OUT/system.img
fastboot flash vendor_a $ANDROID_PRODUCT_OUT/vendor.img
fastboot flash product_a $ANDROID_PRODUCT_OUT/product.img

# 6. 포맷 및 재부팅
fastboot -w
fastboot reboot

위 코드에서 가장 핵심적인 부분은 fastboot wipe-super super_empty.img입니다. 이 명령어는 디바이스의 슈퍼 파티션 레이아웃을 초기화하여, 새로 빌드된 거대한 시스템 이미지가 들어갈 공간을 논리적으로 다시 확보해 줍니다. 또한 --disable-verity 옵션은 AOSP를 수정하여 빌드했을 때, 암호화 서명이 일치하지 않아 부팅이 거부되는 현상(Verified Boot)을 방지합니다.

Tip: fastboot reboot fastboot 명령어는 일반적인 부트로더 화면이 아닌, 사용자 공간(User Space)의 Fastbootd 모드로 진입하게 합니다. 동적 파티션(Dynamic Partition)의 리사이징과 플래싱은 오직 이 모드에서만 안정적으로 수행됩니다.

성능 검증: 자동 vs 수동 플래싱

문제를 해결한 후, 자동화 스크립트와 수동 프로세스의 효율성을 비교해 보았습니다. 단순 시간 측정뿐만 아니라 안정성 측면에서의 차이가 컸습니다.

방법 (Method) 소요 시간 성공률 비고
fastboot flashall 약 180초 40% (수정 빌드 시) 파티션 크기 변경 시 잦은 실패
수동 개별 플래싱 약 240초 100% super_empty.img를 통한 레이아웃 재설정 포함

수동 방식이 시간은 약 1분 더 소요되지만, 파티션 정렬(Alignment) 오류나 슬롯 불일치(Slot Mismatch) 문제를 완벽하게 제어할 수 있습니다. 특히 AOSP 소스를 수정하여 시스템 파티션의 크기가 자주 변하는 개발 단계에서는 수동 제어가 필수적입니다.

Fastboot 소스 코드 분석하기 (Google Git)

주의사항 및 Edge Cases

이 솔루션을 적용할 때 몇 가지 주의해야 할 환경적 변수가 있습니다.

첫째, USB 케이블과 포트입니다. 데이터 전송량이 기가바이트 단위이기 때문에, 저품질 케이블 사용 시 Sparse write failed 오류가 발생할 수 있습니다. 반드시 USB 3.0 이상의 포트와 데이터 전송용 케이블을 사용하세요.

둘째, Linux udev rules 설정입니다. 우분투 환경에서 fastboot 명령어가 권한 문제(no permissions)로 실행되지 않는다면, /etc/udev/rules.d/51-android.rules 파일에 벤더 ID가 올바르게 등록되어 있는지 확인해야 합니다. lsusb로 확인된 ID를 등록하고 udevadm control --reload-rules를 실행하는 것을 잊지 마십시오.

Result: 위 과정을 통해 수정된 커널이 포함된 AOSP 이미지를 Pixel 6에 성공적으로 부팅시켰으며, 이후 디버깅 브리지(ADB) 연결까지 안정적으로 확보했습니다.

결론

AOSP 빌드와 Fastboot 플래싱은 안드로이드 시스템 엔지니어링의 기본이자 가장 까다로운 진입 장벽입니다. 단순히 make 명령어를 입력하는 것을 넘어, 생성된 바이너리가 어떤 파티션에 위치하며 부트로더가 이를 어떻게 로드하는지 이해해야만 실전에서 마주하는 '벽돌' 상황을 해결할 수 있습니다. 이번 디버깅 과정을 통해 자동화 도구의 편의성 뒤에 숨겨진 파티션 테이블의 중요성을 다시 한번 확인할 수 있었습니다. 여러분의 빌드 환경에서도 유사한 문제가 발생한다면, 무작정 다시 빌드하기보다 fastbootd와 파티션 맵을 먼저 점검해 보시길 권장합니다.

Post a Comment