Android System & AOSP Engineering/AOSP Framework & Custom Services

AOSP 실무: 커스텀 시스템 서비스 런타임 디버깅 및 dumpsys·logcat·dmesg 완벽 분석 가이드

임베디드 친구 2025. 6. 10. 22:15
반응형

안드로이드 frameworks/base/ 소스를 수정해 AIDL을 심고 부팅 시퀀스에 서비스 인스턴스를 올리는 단계까지 성공했다면, 이제 가장 험난하고도 중요한 '트러블슈팅 및 성능 검증'의 시간이 우리를 기다리고 있습니다. 플랫폼 컴파일을 무사히 통과했더라도 런타임 단말 환경에서는 자바 가상머신의 초기화 타이밍 꼬임, 바인더 스레드 풀 먹통, 혹은 SELinux 보안 차단 등 수많은 돌발 변수가 도사리고 있기 때문이죠.

안드로이드 소스 트리는 개발자가 시스템 서버 내부의 깊숙한 런타임 메모리 상태를 들여다볼 수 있도록 강력한 디버깅 도구 트리오를 기본 제공합니다. 시스템 전반의 텍스트 로그를 실시간으로 긁어모으는 logcat, 리눅스 커널의 로우 레벨 충돌과 보안 경고를 모니터링하는 dmesg, 그리고 내 서비스의 전역 변수와 활성화 상태를 셸 환경에서 스냅샷으로 인출해 내는 dumpsys가 그 주인공입니다. 이번 포스팅에서는 각 도구의 내부 로깅 메커니즘을 명확히 이해하고, 서비스가 부팅 도중 사망하거나 명령을 씹는 현상이 발생했을 때 맥을 짚어 해결하는 실전 디버깅 공정을 상세히 공유해 드리겠습니다.

Generated by Gemini AI.

📌 핵심 요약 3줄

  1. 자바의 Log.d()와 C++ 네이티브의 __android_log_print()는 모두 로깅 데몬(logd)의 유저 영역 버퍼로 전송되므로 반드시 logcat으로 통합 추적해야 합니다.
  2. dmesg는 커널 레벨의 하드웨어 드라이버 메시지나 SELinux 보안 거부(avc: denied), OOM 프로세스 킬러 등 로우 레벨의 치명적 경고를 포착할 때 사용합니다.
  3. 내 시스템 서비스 내부의 AIDL Stub 객체에 dump() 메서드를 정석대로 오버라이드해 두면, 앱을 짜지 않고도 adb shell dumpsys 명령어로 실시간 변수 상태를 리포트받을 수 있습니다.

1. 안드로이드 코어 디버깅 도구 아키텍처 비교

내 서비스에 발생한 에러의 성격에 따라 어떤 터미널 명령어를 꺼내 들어야 하는지 명확하게 정리한 대조표입니다.

디버깅 도구 명칭 수집하는 핵심 로그 버퍼 영역 실무 주요 추적 유스케이스 실전 핵심 필터링 명령어 가이드
logcat logd 유저 스페이스 버퍼 (Main, System, Crash, Radio) 자바 프레임워크 런타임 예외(NullPointerException), 네이티브 C++ 비즈니스 로직 로그 추적 adb logcat -s MyServiceTag 또는 adb logcat *:E
dmesg 리눅스 Kernel 링 버퍼 (printk) OS 부팅 초기 단계 크래시, 하부 드라이버 통신 오류, SELinux 정책 거부 현상 모니터링 adb shell dmesg | grep avc
dumpsys 시스템 서버 내부의 실시간 메모리 인스턴스 데이터 상태 상태 스캔 서비스가 현재 메모리에 살아있는지 여부 판별, 내장 멤버 변수의 누적 값 및 활성화 플래그 스냅샷 인출 adb shell dumpsys custom_service

2. logcat과 dmesg를 활용한 런타임 로그 추적 기법

2.1 자바 프레임워크 레이어 로깅 및 logcat 필터링

자바 기반의 시스템 서비스를 빌드할 때는 시스템 서버 전역의 무수한 로그 대홍수 속에서 내 소스 코드만 명확히 필터링할 수 있도록 고유 태그를 심어야 합니다.

Java
 
package com.android.server;

import android.content.Context;
import android.util.Slog; // 시스템 서버 전역 서비스용 표준 로깅 유틸리티 사용 권장
import com.android.server.SystemService;

public class MyCustomService extends SystemService {
    // 내 서비스 영역만 정밀 타격하여 필터링할 유니크한 태그 선언
    private static final String TAG = "MyCustomServiceCore";

    public MyCustomService(Context context) {
        super(context);
        Slog.d(TAG, "MyCustomService 인스턴스가 힙 메모리에 생성되었습니다.");
    }

    @Override
    public void onStart() {
        Slog.i(TAG, "MyCustomService 수명 주기가 가동되었습니다. 바인더 서비스 게시 완료.");
    }
}

터미널 창을 열고 아래 명령어를 조합하면 다른 순정 서비스의 로그는 배제하고 내 서비스의 행적만 깔끔하게 추적할 수 있습니다.

Bash
 
# 특정 태그의 로그만 실시간으로 뽑아내기
adb logcat -s MyCustomServiceCore

# 특정 프로세스 ID(PID)를 추적하여 해당 서비스 스레드 전체의 흐름 관찰하기
adb shell pidof system_server
adb logcat --pid=<위에서 얻은 system_server_PID>

2.2 C++ 네이티브 레이어 및 커널 로그 분별법

C++ 공유 라이브러리나 독립 네이티브 데몬 서비스 내부에서 표준 로깅 매크로를 사용하여 로그를 남기는 구조입니다.

C++
 
#include <log/log.h> // AOSP 표준 네이티브 로그 헤더 링크

#undef LOG_TAG
#define LOG_TAG "MyServiceNative"

void executeHardwareCalculation() {
    // 이 네이티브 로그 역시 커널 dmesg가 아닌 유저 레벨의 logcat 버퍼로 들어갑니다.
    ALOGD("C++ 네이티브 하드웨어 로직 진입 완료.");
    
    if (false) {
        ALOGE("치명적인 하드웨어 레지스터 제어 실패!");
    }
}

반면, 커널이 직접 뱉어내는 로우 레벨 경고나 SELinux 가드레일 정책 위반 에러는 아래와 같이 dmesg 파이프라인을 통해서 인출해야 정석입니다.

Bash
 
# SELinux 보안 정책 누락으로 인한 차단(avc: denied) 로그만 정확하게 모니터링하기
adb shell dmesg | grep avc

3. dumpsys 고유 진단 인터페이스 오버라이딩 구현

dumpsys 명령어를 내 서비스 이름으로 날렸을 때, 내부 메모리 데이터 객체 구조를 텍스트 서식으로 시원하게 리포트해 주도록 진단 콜백을 구현합니다. 이 기법은 AIDL Stub 객체 내부에 구현해야 프레임워크가 인지합니다.

Java
 
// frameworks/base/services/core/java/com/android/server/CustomService.java
package com.android.server;

import android.os.ICustomService;
import android.os.RemoteException;
import java.io.FileDescriptor;
import java.io.PrintWriter;

public class CustomService extends ICustomService.Stub {
    
    // 서비스의 실시간 런타임 상태를 대변할 멤버 변수 예시
    private boolean mIsHardwareActive = true;
    private int mAccumulatedConnectionCount = 42;

    @Override
    public void customMethod() throws RemoteException {
        // 비즈니스 로직 수행...
    }

    /**
     * adb shell dumpsys 명령어가 이 서비스를 찌를 때 바인더 드라이버가 역으로 호출해 주는 시스템 콜백 함수입니다.
     */
    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        // 덤프 명령을 날린 주체가 시스템 권한을 가졌는지 검증하는 방어 코드를 두면 더욱 안전합니다.
        pw.println("==================================================");
        pw.println("   [My Custom System Service 실시간 자원 진단 리포트]   ");
        pw.println("==================================================");
        pw.println("1. 현재 타깃 하드웨어 액티브 링크 상태: " + (mIsHardwareActive ? "RUNNING" : "STOPPED"));
        pw.println("2. 부팅 이후 현재까지 누적된 클라이언트 IPC 트래픽 횟수: " + mAccumulatedConnectionCount + "회");
        pw.println("==================================================");
    }
}

이제 클라이언트 테스트 앱을 실행할 필요 없이 타깃 터미널 창에 서비스 등록 명칭을 던져주면 내부 변수 스냅샷이 즉각 출력됩니다.

Bash
 
adb shell dumpsys custom_service

4. 시나리오별 시스템 서비스 부동(起動) 실패 트러블슈팅 매트릭스

서비스 가동 단계에서 발생하는 대표적인 장애 징후와 검증 체크리스트 아카이브입니다.

장애 발생 징후 (Symptom) 유력한 기술적 원인 분석 (Root Cause) 실전 해결 및 교정 가이드라인 (Action Item)
부팅 도중 로고 화면에서 멈추며 무한 재부팅(Bootloop) 발생 SystemServer.java에 서비스를 올릴 때 생성자 내부에서 무리하게 초기 파일 I/O를 수행했거나 다른 미생성 서비스의 컨텍스트를 당겨 써서 NullPointerException 유발 서비스 생성자 내부 로직을 최대한 가볍게 비우고, 무거운 초기화 루틴은 추상 클래스의 onBootPhase() 콜백 내부로 격리 이주
service list 명단에 내 서비스 이름이 아예 누락됨 Android.bp에 소스 파일 경로가 누락되어 컴파일 대상에서 제외되었거나, SystemServer 코드가 구동될 때 내 서비스 적재 함수가 아예 패스됨 adb logcat -s SystemServer 로그를 열어 내 서비스 가동 로그나 예외 크래시 스택이 찍혔는지 제일 먼저 대조
호출 시 SecurityException: Permission Denied 발생 AndroidManifest.xml에 명시한 서명(Signature) 권한 가드레일을 앱이 통과하지 못했거나 커널의 SELinux 도메인 허용 규칙 명세가 완전히 누락됨 테스트 앱의 서명 키가 플랫폼 인증 키와 일치하는지 확인하고, `dmesg

🛠️ 개발을 위한 팁 (Tips)

  1. dumpsys 콜백 내 인자값(String[] args) 분기 처리: dump(FileDescriptor fd, PrintWriter pw, String[] args) 메서드로 들어오는 문자열 배열 인자값은 사용자가 adb shell dumpsys custom_service --reset 과 같이 터미널 뒤에 붙인 옵션 플래그 데이터입니다. 메서드 내부에서 if (args.length > 0 && "--reset".equals(args[0])) 형태로 분기 구조를 짜두면, 디버깅 도중 실시간으로 내 서비스의 내부 카운터 변수를 초기화하거나 특정 로그 레벨 플래그를 동적으로 켜고 끄는 강력한 '비밀 엔지니어링 스위치'를 탑재할 수 있습니다.
  2. logcat -v time -b system 타깃 버퍼 좁혀보기: 시스템 서버 내부의 프레임워크 동작을 분석할 때는 그냥 logcat을 치면 서드파티 앱들의 잡다한 로그까지 섞여 올라와 스크롤이 감당되지 않습니다. 이때 -b system 플래그를 붙여 시스템 전용 로그 버퍼만 딱 타깃 지정하고, -v time으로 밀리초 단위 시간 포맷을 확보하면 내 서비스가 다른 코어 서비스들과 통신하는 정확한 병목 타이밍을 나노 단위로 포착할 수 있습니다.

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

  1. dmesg 버퍼에서 네이티브 ALOGD 로그를 찾는 행위: 안드로이드 플랫폼 소스 개조에 갓 입문한 개발자들이 가장 많이 헤매는 영역입니다. 일반 Linux 환경의 임베디드 개발 버릇으로 인해 C++ 소스 코드에 __android_log_print()나 ALOGD()로 로그를 작성해 두고, adb shell dmesg 창을 띄워놓고 내 로그가 왜 안 나오냐며 소스 빌드가 안 된 것으로 착각하는 케이스입니다. 안드로이드 프레임워크 네이티브 래퍼 로그들은 리눅스 커널 링 버퍼가 아니라 전부 유저 스페이스의 logd 프로세스로 집중되므로, 네이티브 코드 로그 역시 adb logcat -s 태그명으로 뒤져야 정상 발견됩니다. dmesg 공간은 오직 시스템 커널 패닉이나 SELinux의 강제 거부 내역만 올라오는 별개의 방입니다.
  2. dump() 메서드 내부에서 블로킹(Blocking) 동기화 코드 사용: dumpsys 콜백 함수 내부에 파일이나 소켓의 응답을 대기하는 블로킹 동기화 함수를 무심코 심어두는 실수입니다. dumpsys 명령어는 바인더 스레드를 점유하여 실행되는데, 여기서 리턴 처리가 지연되어 락(Lock)이 걸리면 시스템 서버 메인 루프의 워치독(Watchdog) 컴포넌트가 "시스템 서버의 핵심 바인더 스레드가 응답하지 않고 죽었다"고 판단하여 단말기 가상머신 자체를 터뜨리고 강제 소프트 재부팅을 유발하는 무서운 연쇄 트리거가 될 수 있으니 dump() 내부는 무조건 즉시 반환되는 가벼운 문자열 출력 위주로 채워야 합니다.

5. 결론

안드로이드 Open Source Project(AOSP) 커스텀 개발 과정에서 완성도 높은 고품질 시스템 서비스를 배출해 내는 핵심 원동력은 화려한 코딩 기법 그 자체보다, 꼬여버린 실타래 속에서 에러의 원인을 정확히 짚어내는 로깅 및 상태 검증 능력에 있습니다.

자바와 네이티브 영역의 로그 버퍼 경로 특성을 명확히 구분하여 logcat과 dmesg를 능숙하게 오가고, dumpsys 진단 콜백을 설계 단계부터 내장해 두는 좋은 습관을 들여야만 예기치 못한 부팅 무한 루프나 권한 누락 현상을 마주했을 때 삽질 시간을 극적으로 단축할 수 있습니다. 내가 만든 서비스가 특정 앱의 호출 타이밍에만 침묵을 지키거나, dumpsys 결과창이 먹통이 되며 단말기가 뻗어버리는 난해한 병목 현상에 부딪히셨다면 망설이지 말고 하단 댓글 창에 디버깅 로그 조각을 복사해 주세요. 프레임워크 런타임 맥락에서 바인더 실타래를 깔끔하게 같이 풀어드리겠습니다!

반응형