안드로이드 플랫폼 엔지니어의 일상에서 가장 짜릿하면서도 고통스러운 순간을 꼽으라면, 내가 직접 구현한 커스텀 HAL 바이너리를 타깃 보드에 포팅하고 첫 부팅을 시도하는 순간일 것입니다. 하지만 안타깝게도 하드웨어는 단 한 번에 상위 프레임워크와 매끄럽게 연결되는 법이 없습니다. 부팅 직후 상위 자바 앱 레이어에서 하드웨어 호출 API를 눌렀을 때 아무런 반응이 없거나 시스템 서버가 통째로 얼어붙는 현상은 개발 초기 단계의 통과의례와도 같은데요.
이때 "코드가 잘못되었나?"라며 무작정 소스코드만 쳐다보는 것은 시간낭비입니다. 안드로이드는 시스템 레이어와 커널 영역 전반에 걸쳐 하드웨어가 왜 죽었는지, 어디서 권한이 막혔는지 명확한 흔적을 로그로 남겨두기 때문입니다. 이번 포스팅에서는 유저 스페이스 영역의 logcat과 커널 공간의 dmesg를 입체적으로 분석하는 법부터 시작해, 현대 안드로이드 아키텍처의 디버깅 특효약인 lshal 명령어 활용법, 그리고 가장 빈번하게 빌목을 잡는 SELinux 거부(avc: denied) 에러를 칼같이 해결하는 실전 디버깅 패키지를 소개해 드리겠습니다.

📌 핵심 요약 3줄
- HAL 서비스 디버깅의 첫걸음은 유저 스페이스 바인더 통신을 감시하는 **logcat**과 최하단 디바이스 드라이버의 숨결이 찍히는 dmesg 로그의 교차 검증입니다.
- 프레임워크가 HAL을 찾지 못할 때는 lshal 명령어로 바인더 인터페이스 등록 상태를 점검하고, 필요 시 벤더 경로의 바이너리를 수동 가동하여 크래시 로그를 유도해야 합니다.
- 무사히 빌드된 HAL이 구동되지 않는 원인의 90%는 보안 차단벽인 SELinux 정책 위반이며, 이는 avc: denied 패턴 분석과 audit2allow 도구로 정밀 조율할 수 있습니다.
1. 안드로이드 디버깅 로그 채널 및 핵심 타깃 맵
문제가 발생한 레이어의 성격에 따라 어떤 로그 도구를 사용해야 하고, 무엇을 필터링해야 하는지 명확하게 정리했습니다.
| 디버깅 도구 (Command) | 분석 대상 레이어 (Target) | 핵심 필터링 키워드 및 태그 | 모니터링하는 주요 오류 현상 |
| adb logcat | 유저 스페이스 / 프레임워크 | ServiceManager, AidlDeathRecipient | 바인더 링크 단절, 인터페이스 미등록, 널 포인터 크래시 |
| adb shell dmesg | 리눅스 커널 스페이스 | avc: denied, binder_alloc | SELinux 보안 거부 에러, 드라이버 메모리 패닉, OOM 커널 킬 |
| adb shell lshal | HAL 인터페이스 레지스트리 | android.hardware.* | AIDL/HIDL 서비스 레지스트리 누락 및 스레드풀 고갈 현상 |
2. 실전 HAL 로그 확인 및 추적 기법
2.1 logcat을 활용한 유저 공간 추적
상위 시스템 서비스가 내가 만든 HAL 프로세스와 바인더 연결을 맺는 과정에서의 트래픽을 관측합니다.
# 1. 내 커스텀 HAL 프로세스명이나 관련 태그만 쏙 골라서 실시간 모니터링
adb logcat | grep -i "example-service"
# 2. 시스템 오디오나 카메라 같은 특정 도메인의 전반적인 통신 흐름 추적
adb logcat -v time *:E | grep -E "AudioHAL|AudioFlinger"
# 3. 특정 HAL 데몬의 프로세스 아이디(PID)를 알아내어 해당 프로세스의 출력 로그만 격리 분석
adb shell pidof android.hardware.example-service # PID가 1234라고 가정
adb logcat --pid=1234
2.2 dmesg를 활용한 커널 및 드라이버 공간 추적
HAL이 디바이스 노드(/dev/*)에 시스템 콜을 날렸을 때 커널 하부 드라이버가 뿜어내는 경고와 패닉 메시지를 긁어옵니다.
# 1. HAL 내부 파일 입출력 시 발생하는 커널 에러 및 리눅스 시스템 에러 로그 검색
adb shell dmesg | grep -i -E "hal|init|binder"
# 2. 드라이버가 메모리 오프셋 미스매치로 쓰러지기 직전 뱉은 printk 커널 메시지 덤프
adb shell "dmesg -w" | grep -i "example_device"
3. HAL이 정상 구동되지 않을 때의 4단계 점검 프로세스
보드가 켜졌는데도 하드웨어가 묵묵부답이라면 당황하지 말고 아래의 아키텍처 점검 파이프라인을 따라가세요.
[1단계] 프로세스 생존 여부 확인 (ps)
서비스 데몬 자체가 백그라운드에 살아있는지 점검합니다. 목록에 안 뜬다면 부팅 스크립트(init.rc) 진입 단계에서 죽은 것입니다.
adb shell ps -A | grep "hardware.example"
[2단계] 바인더 통신망 등록 상태 조회 (lshal)
프로세스는 떠 있는데 프레임워크가 "서비스를 찾을 수 없다"고 아우성친다면, 바인더 네임서버에 인터페이스 명함을 제대로 못 내민 상태입니다. 안드로이드 통합 HAL 매니저 명령어인 lshal로 레지스트리를 저인망식으로 뒤져봅니다.
adb shell lshal | grep "example"
만약 목록에서 내 서비스의 AIDL 패키지명이 식별되지 않는다면, 서비스 메인 함수(main.cpp) 내부의 AServiceManager_addService 등록 구문이 실패했거나 VINTF 매니페스트 .xml 명세서 파일이 빌드 시 누락된 것입니다.
[3단계] 강제 수동 실행을 통한 크래시 로그 유도
자동 시작 루틴에서 왜 죽었는지 도무지 알 수 없다면, adb shell로 들어가 벤더 경로의 바이너리를 수동으로 쾅 실행해 버리는 것이 가장 직관적입니다.
adb root
adb shell /vendor/bin/hw/android.hardware.example-service
이렇게 하면 프로세스가 초기화 로직을 수행하다가 터지는 순간의 네이티브 세그멘테이션 폴트 시그널 로그나 std::runtime_error 예외 메시지가 터미널 화면에 다이렉트로 꽂히므로 버그 수정이 아주 수월해집니다.
[4단계] 통곡의 벽, SELinux 보안 거부 메시지 분석
보안이 극도로 강화된 현대 안드로이드 환경에서는 HAL 디버깅 에러의 지분 90%가 SELinux 권한 거부입니다. dmesg를 열어 avc: denied라는 주홍글씨가 찍혔는지 검사합니다.
adb shell dmesg | grep "avc: denied"
로그 예시: type=1400 audit(1715873988.120:24): avc: denied { read } for pid=1234 comm="example-service" name="example_device" dev="tmpfs" ino=15763 scontext=u:r:hal_example_default:s0 tcontext=u:object_r:device:s0 tclass=chr_file permissive=0
위 로그의 의미는 hal_example_default라는 도메인을 가진 내 서비스가 device라는 일반 라벨을 가진 크롬 캐릭터 파일(chr_file)을 읽으려고(read) 시도했으나 정책상 차단(denied)되었다는 뜻입니다.
💡 HAL 디버깅과 트러블슈팅을 위한 실전 팁
- 개발 초기 단계에는 SELinux를 잠시 해제(Permissive)하고 진입: 신규 하드웨어 기능을 개발할 때는 순수 코드 버그인지 SELinux 정책 차단 버그인지 분간하기 어렵습니다. 이때는 터미널에 adb shell setenforce 0 명령을 내려 시스템 보안 장벽을 잠시 Permissive(허용) 모드로 낮춰두고 테스트해 보세요. 이렇게 하면 권한 거부 로그만 뱉을 뿐 실제 파일 접근을 차단하지 않으므로, HAL 로직이 완벽하게 도는 것을 확인한 뒤 마지막에 보안 정책 조율을 편안하게 짚고 넘어갈 수 있습니다.
- audit2allow 도구를 활용한 단단한 .te 정책 스크립트 추출: SELinux 허용 모드 상태에서 하드웨어 테스트를 풀코스로 구동하면 시스템 로그에 필요한 권한 허용 명세가 고스란히 쌓입니다. 이 로그 텍스트를 복사하여 개발 PC 우분투 환경에서 아래와 같이 구글 오디트 툴을 가동해 보세요.이 도구가 제안해 주는 allow hal_example_default device:chr_file { read open }; 같은 허용 구문을 벤더 소스 트리 내 보안 정책 파일(*.te)에 이식해 주면 SELinux 트러블슈팅이 깔끔하게 종결됩니다.
-
Bash
# 로그캣에서 수집한 avc 거부 메시지를 바탕으로 필요한 te 정책 구문을 자동 코딩해줍니다. cat my_avc_logs.txt | audit2allow -p $OUT/obj/ETC/sepolicy_neverallows_intermediates/policy.conf
⚠️ 흔히 하는 실수
- Neverallow 보안 규격 위반 정책 강제 추가 시 빌드 크래시: audit2allow 도구는 편리하지만 기계적으로 허용 정책을 제안하기 때문에 보안상 매우 위험한 구문을 만들어내기도 합니다. 예를 들어 내 커스텀 HAL이 시스템 코어 영역의 파일이나 일반 /dev/device 노드를 통째로 넘나들도록 허용하는 정책을 벤더 .te 파일에 무심코 추가하면, 안드로이드 전체 빌드 시 구글이 절대 허용하지 않는 보안 마지노선 정책 위반 검사 도구인 Neverallow Assert에 걸려 빌드 시스템 자체가 컴파일을 거부하는 대참사가 납니다. 내 HAL 전용 가상 파일 라벨(type example_device_t, dev_type;)을 별도로 명시하여 샌드박스 권한을 쪼개놓는 아키텍처 설계가 기본 매너입니다.
- HAL 프로세스 크래시 무한 루프로 인한 바인더 Starvation 유발: HAL 내부 C++ 코드의 포인터 실수로 자꾸 세그멘테이션 폴트가 나서 프로세스가 죽는데, init.rc 스크립트가 해당 서비스를 무한 재시작(restart)하도록 방치하는 경우가 있습니다. HAL 데몬이 0.5초 간격으로 죽고 살아나기를 반복하면 상위 프레임워크(system_server) 내부의 바인더 스레드풀은 죽어버린 HAL의 응답을 기다리며 타임아웃 대기 상태로 하나씩 묶이게 됩니다. 결국 시스템 전체 바인더 자원이 말라버려 스마트폰 UI 자체가 완전히 멈춰버리는 락(Lock) 버그로 번지게 되므로, 초기화 실패 시엔 명시적으로 에러 로그를 남기고 프로세스를 안전하게 대기 상태로 유지하는 예외 처리가 가미되어야 합니다.
4. 결론
안드로이드 HAL 트러블슈팅은 시스템 레이어의 유저 영역과 커널 영역 사이의 숨겨진 퍼즐 조각을 맞춰가는 고도의 디버깅 예술입니다. logcat이 보여주는 컴포넌트 간의 고차원 대화와 dmesg가 들려주는 하부 시스템 드라이버의 생생한 비명을 입체적으로 연결하고, SELinux라는 엄격한 보안 가디언의 규칙을 지혜롭게 조율할 때 비로소 어떤 가혹한 환경에서도 죽지 않는 튼튼한 하드웨어 플랫폼이 완성됩니다.
'Android System & AOSP Engineering > AOSP Framework & Custom Services' 카테고리의 다른 글
| 안드로이드 Bionic libc 구조 분석: glibc 차이점부터 AOSP syscall 구현까지 (0) | 2025.03.30 |
|---|---|
| 안드로이드 네이티브 라이브러리 분석: Bionic부터 libc++까지 AOSP 코어 완전 정복 (0) | 2025.03.29 |
| 안드로이드 HAL과 커널 드라이버 연동 가이드: 소스코드 기반 디바이스 노드 제어법 (0) | 2025.03.27 |
| 안드로이드 14+ 커스텀 HAL 모듈 만들기: 최신 Stable AIDL 기반 실전 AOSP 빌드 가이드 (0) | 2025.03.26 |
| 안드로이드 미디어 스택의 양대 산맥: Camera HAL3와 Audio HAL 구조 및 AOSP 코드 완벽 분석 (0) | 2025.03.25 |