Android System & AOSP Engineering/AOSP Framework & Custom Services

AOSP 빌드 및 디버깅 가이드: Custom Framework Service 플래싱부터 dumpsys 검증까지

임베디드 친구 2025. 5. 27. 19:40
반응형

앞선 포스팅들을 통해 안드로이드 프레임워크 서비스의 아키텍처 구조를 이해하고 AIDL을 활용해 나만의 커스텀 자바 서비스 코드를 작성해 보았습니다. 하지만 코드를 완벽히 코딩했더라도 이를 빌드 시스템(Soong)에 태워 바이너리 이미지로 뽑아내고, 실제 타겟 하드웨어 보드나 에뮬레이터에 구워내기 전까지는 정상 동작을 보장할 수 없습니다.

AOSP(Android Open Source Project) 빌드는 사소한 스크립트 오타 하나로도 몇 시간 동안 컴파일 실패 에러를 뿜어내며 개발자의 진을 빼놓기 일쑤입니다. 게다가 이미지를 올린 뒤 화면이 켜지지 않는 무한 부팅 리부팅 현상이 생겼을 때, 어떤 디버깅 도구로 추적해야 하는지 모르면 칠흑 같은 어둠 속에 갇히게 되죠. 이번 포스팅에서는 커스텀 프레임워크 서비스를 반영하기 위한 Android.bp 설정과 컴파일 스크립트 실행, 그리고 fastboot을 이용한 이미지 플래싱부터 logcat과 dumpsys를 활용해 내 서비스가 시스템 내부 바인더 맵에 제대로 안착했는지 검증하는 실전 트러블슈팅 파이프라인을 자세히 다뤄보겠습니다.

Generated by Gemini AI.

📌 핵심 요약 3줄

  1. 작성한 자바 시스템 서비스 소스는 AOSP 빌드 시스템(Soong)이 인식하도록 프레임워크 서비스 빌드 스크립트(Android.bp)에 정확히 타겟 소스로 등록해야 합니다.
  2. 빌드가 완료되면 에뮬레이터 환경(emulator -writable-system)이나 실제 기기의 fastboot flashall 명령을 통해 시스템 파티션을 완전히 갱신합니다.
  3. OS 부팅 후에는 adb logcat으로 시스템 서버 가동 로그를 실시간 추적하고, adb shell dumpsys 인터페이스를 호출해 바인더 등록 상태를 계측합니다.

1. 빌드 및 디버깅 명령어 퀵 체크 가이드

AOSP 컴파일 환경 설정부터 단말기 플래싱, 부팅 후 시스템 런타임 상태를 계측하는 핵심 도구와 명령어 세트입니다.

타겟 개발 파이프라인 핵심 실행 명령어 (adb / fastboot) 명령어 수행 주요 목적 및 기능
AOSP 빌드 환경 로드 source build/envsetup.sh 컴파일에 필요한 전용 셸 함수 및 lunch, m 등 매크로 명령어 활성화
타겟 아키텍처 빌드 세팅 lunch aosp_arm64-userdebug 타겟 하드웨어 프로파일 및 디버깅 셸 권한(userdebug) 스펙 확정
특정 프레임워크 콤포넌트 컴파일 m services.core framework 전체 이미지 빌드 전, 시스템 서비스 코어 모듈만 타겟팅하여 고속 증분 빌드
실제 기기 파티션 플래싱 fastboot flashall 부트로더 모드 진입 단말에 컴파일 완료된 부트, 시스템, 벤더 이미지 전체 라이팅
실시간 시스템 데몬 로그 추적 adb logcat -s "CustomService" 시스템 서버 내부에서 내 커스텀 서비스가 뿌리는 초기화 로그만 필터링 모니터링
바인더 런타임 자원 덤프 adb shell dumpsys custom ServiceManager에 등록된 바인더 상태 정보를 터미널 콘솔에 출력 및 작동 점검

2. 1단계: 빌드 시스템 타겟팅 및 컴파일 가동

내가 작성한 자바 프레임워크 코드가 누락 없이 services.jar에 포함되려면 청사진 스크립트 파일인 Android.bp를 정교하게 튜닝해 주어야 합니다.

2.1 자바 프레임워크 서비스 빌드 구성 (Android.bp)

주로 frameworks/base/services/core/Android.bp 파일 내부에 소스 경로를 병합 처리합니다.

코드 스니펫
 
// frameworks/base/services/core/Android.bp
java_library_static {
    name: "services.core",
    srcs: [
        // 기존 AOSP 코어 자바 파일 소스 리스트 사이에 내 커스텀 자바 서비스 소스를 추가합니다.
        "java/com/android/server/custom/CustomService.java",
    ],
    libs: [
        "framework",
        "services.management",
    ],
}

2.2 빌드 매크로 가동

스크립트 등록이 끝났다면 터미널 환경에서 크로스 컴파일러 환경을 메모리에 빌드 스택으로 올리고 컴파일을 땡깁니다.

Bash
 
# 1. 빌드 전용 유틸리티 셸 함수 로드
$ source build/envsetup.sh

# 2. 타겟 디바이스 아키텍처 환경 선택
$ lunch aosp_arm64-userdebug

# 3. 변경 사항이 반영된 프레임워크 아티팩트 컴파일 실행 (멀티코어 j 옵션 적용)
$ m -j8 services.core framework

3. 2단계: 에뮬레이터 가동 및 실제 보드 이미지 플래싱

컴파일 결과물로 뽑혀 나온 시스템 파티션 이미지 파일들을 테스트용 가상 환경이나 리얼 타겟 하드웨어 보드에 굽는 프로세스입니다.

3.1 에뮬레이터(가상 환경)로 테스트할 경우

시스템 파티션 내부에 동적으로 디버깅 파일을 쓰거나 런타임 수정을 허용하기 위해 시스템 파티션 쓰기 권한 플래그를 주고 에뮬레이터를 올립니다.

Bash
 
$ emulator -avd aosp_arm64 -writable-system

3.2 물리 기기(리얼 하드웨어 보드)에 다운로드할 경우

단말기를 USB로 호스트 PC와 연결한 뒤 부트로더 화면(fastboot mode)으로 진입시키고 빌드 결과물을 한방에 밀어 넣습니다.

Bash
 
# 타겟 장치가 fastboot 인터페이스로 연결되었는지 아이디 확인
$ fastboot devices

# 컴파일로 생성된 $OUT 디렉터리의 부트, 시스템, 벤더 이미지 전체 쓰기 실행
$ fastboot flashall

4. 3단계 & 4단계: logcat 및 dumpsys 활용 검증 기법

기기가 부팅을 마치고 홈 화면에 진입했다면, 이제 내 서비스가 정상적으로 메모리에 상주하여 다른 앱들의 요청을 받아들일 런타임 준비가 끝났는지 디버깅 툴로 검증해 볼 차례입니다.

4.1 logcat을 통한 부팅 시퀀스 로그 추적

SystemServer 가동 단계에서 내 서비스가 정상적으로 인스턴스화되었는지 로그 버퍼를 낚아챕니다.

Bash
 
# 내 서비스 태그 이름인 "CustomService" 로그만 필터링하여 실시간 관측
$ adb logcat -s CustomService
Plaintext
 
[로그 아웃풋 예시]
--------- beginning of system
05-17 17:40:12.421  1520  1520 I CustomService: Custom Service Started Successfully

4.2 dumpsys를 활용한 바인더 맵 등록 상태 체크

안드로이드 ServiceManager에 문자열 식별자 키값으로 내 서비스가 정식 바인딩되었는지 덤프 통계를 요청합니다. 만약 SystemServer.java에서 ServiceManager.addService("custom", new CustomService())라고 등록했다면 키값은 custom입니다.

Bash
 
# 등록된 바인더 이름인 'custom'의 상태 덤프 호출
$ adb shell dumpsys custom

만약 내 코드가 권한 문제나 크래시로 가동되지 않았다면 터미널창에 아래와 같은 절망적인 메시지가 출력됩니다.

Plaintext
 
Can't find service: custom

이 경고를 마주쳤다면 당황하지 말고 SystemServer.java 초기화 블록에서 에러 예외가 터져 catch문으로 튕겨 나갔는지 adb logcat -b system 전체 시스템 로그를 다시 톺아봐야 합니다.


🛠️ 개발을 위한 팁 (Tips)

  1. adb sync 명령을 활용한 퀵 디버깅: 코드 한두 줄 수정했다고 매번 fastboot flashall을 때리고 전체 재부팅을 기다리는 건 개발 생산성을 극도로 떨어뜨립니다. 에뮬레이터나 userdebug 빌드가 올라간 단말기라면 코드를 수정한 뒤 빌드 창에 m services.core로 해당 모듈만 빠르게 빌드해 주세요. 그 다음 터미널에 adb remount를 치고 adb sync 명령어를 때리면 변경된 모듈 바이너리(services.jar)만 단말기 시스템 폴더로 번개처럼 동기화됩니다. 이후 adb shell stop && adb shell start로 안드로이드 프레임워크 런타임만 재시작해 주면 10초 만에 수정 코드를 테스트할 수 있습니다.
  2. dumpsys 내부 전용 오버라이드 함수 구현: dumpsys를 쳤을 때 서비스의 내부 변수 값이나 디바이스 제어 상태 이력을 이쁘게 출력해 주고 싶다면, 내 시스템 서비스 클래스 내부에 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) 메서드를 오버라이드해 두세요. 바인더 가동 시 이 함수가 트리거되어 시스템 런타임 내부의 핵심 메모리 상태를 콘솔 창에 텍스트 형태로 다이렉트 스트리밍할 수 있어 디버깅 강도가 급격히 낮아집니다.

⚠️ 흔히 하는 실수 (Common Mistakes)

  1. dumpsys 호출 이름과 ServiceManager 등록 이름의 불일치: 많은 초보 엔지니어분들이 클래스 파일명이 CustomService.java라는 이유만으로 터미널 창에 무심코 adb shell dumpsys customservice를 날리곤 합니다. 하지만 dumpsys 도구는 자바 클래스명이 아니라 SystemServer에서 ServiceManager.addService("등록키값", ...) 메서드의 첫 번째 인자로 던진 문자열 이름표를 기준으로 매핑 테이블을 스캔합니다. 내가 지정한 고유 바인더 식별자 이름(예: custom)을 명확하게 매칭시켜 호출해야 합니다.
  2. flashall 도중 ANDROID_PRODUCT_OUT 환경 변수 유실 에러: 컴파일을 끝내고 터미널 창을 새로 열어 fastboot flashall을 실행하면 Neither -p product nor ANDROID_PRODUCT_OUT env variables set이라는 빌드 경로 유실 오류를 뿜으며 실행이 거부되는 실수를 자주 겪습니다. fastboot flashall 명령어는 내가 빌드한 아웃풋 이미지들이 어느 타겟 디렉터리에 박혀있는지 환경 변수를 참조하기 때문인데요, 터미널 창을 새로 생성했다면 필히 빌드 디렉터리 내에서 source build/envsetup.sh && lunch 타겟번호를 다시 한번 재가동해 주어 환경 변수 셋을 동기화시켜 주어야 경로 인식이 깔끔하게 복구됩니다.

5. 결론

AOSP 플랫폼 아키텍처 하에서 내가 설계한 소스 코드가 최종 타겟 보드 위에서 매끄럽게 컴파일되어 정상 구동되는 것을 확인하는 일은 하부 리눅스 시스템과 자바 시스템 매니저의 유기적 톱니바퀴를 완벽히 이해했다는 증거이기도 합니다.

새로운 컴파일 형식을 빌드 스크립트(Android.bp)에 얹는 훈련부터, fastboot 레이어 핸들링, 그리고 런타임에 logcat과 dumpsys 덤프 데이터를 정밀 분석하는 역량까지 장착하신다면 어떤 제조사 커스텀 장비를 만나도 두려움 없이 OS 레벨을 주무르는 마스터 엔지니어로 성장하실 수 있습니다. 시스템 이미지를 올린 뒤 특정 바인더 트랜잭션 에러 코드가 찍히며 무한 재부팅이 걸리거나 컴파일 종속성이 꼬여 빌드가 중간에 멈추신다면 주저 말고 하단에 로그 공유해 주세요. 같이 소스 뜯어보면서 해결책 찾아봅시다!

반응형