Introduction: The Intricacies of Custom AVD System Image Debugging
Developing custom Android Virtual Device (AVD) system images offers unparalleled flexibility for testing, security research, and specialized deployments. However, the complexity of the Android Open Source Project (AOSP) build system and the myriad dependencies within a custom image often lead to obscure boot loops, runtime errors, and stability issues. This guide delves into advanced troubleshooting techniques, empowering developers to diagnose and resolve these challenging problems effectively.
Understanding Common Failure Modes
Before diving into debugging tools, it’s essential to recognize the typical manifestations of issues in custom AVD images:
- Boot Loops: The emulator starts, shows the Android logo, but never reaches the home screen, restarting repeatedly. This often points to critical system service failures, incorrect filesystem configurations, or kernel panics.
- System Crashes/ANRs: The system becomes unresponsive, or core Android processes (e.g., system_server, zygote) crash, leading to a non-functional or extremely unstable environment.
- Application Runtime Errors: Custom pre-installed apps or even core system apps crash unexpectedly, often due to missing libraries, incorrect permissions, or incompatible ABIs.
- Performance Degradation: The AVD boots but runs extremely slowly, indicating resource contention or inefficient system processes.
Prerequisites for Effective Debugging
A robust debugging workflow requires specific tools and environment setup:
- AOSP Build Environment: Access to the full AOSP source tree and a working build setup is crucial for modifying and rebuilding components.
- ADB (Android Debug Bridge): The primary tool for interacting with the running or partially booting AVD.
- Fastboot: For flashing new kernel, ramdisk, or system images when
adbis unavailable. - Android Emulator with
qemu-system-x86_64(oraarch64): Understanding emulator command-line options is vital. - Symbol Files: Essential for native debugging to map crash addresses back to source code.
Debugging Boot Loops: A Step-by-Step Approach
Boot loops are perhaps the most frustrating issue. The key is to gather as much information as possible from the earliest boot stages.
1. Initial Log Collection via ADB
Even if the system doesn’t fully boot, adb logcat might capture critical early messages:
# Start the emulator with a writable system and potentially verbose loggingemulator -avd my_custom_avd -writable-system -qemu -append "androidboot.console=ttyS0 console=ttyS0" -serial stdio# In a separate terminal, try to connect adbadb wait-for-deviceadb logcat -b all -d > bootloop_log.txt
Examine bootloop_log.txt for recurring error patterns, “FATAL EXCEPTION”, “debuggerd”, or “Process terminated” messages from critical services like system_server, installd, or zygote.
2. Analyzing Kernel and Init Process Logs
If adb isn’t responsive, or logs are sparse, the issue might be lower-level. Kernel panics or init failures are common culprits.
- Kernel Debugging with
dmesg(if accessible): If you can getadb shellbriefly,dmesgcan show kernel messages. Often, however, the kernel crashes beforeadbinitializes. - Serial Console Output: Configure the emulator to output serial console data, which includes kernel boot messages and
initlogs. This is often the most reliable way to catch early boot failures.
# Start emulator with serial output to a file or stdoutemulator -avd my_custom_avd -qemu -serial file:/tmp/emu_serial.log# Or to stdout for direct inspectionemulator -avd my_custom_avd -qemu -serial stdio
Look for messages like “Kernel panic – not syncing”, “init: critical service ‘service_name’ died”, or errors related to mounting filesystems (fs_mgr).
3. Modifying Init.rc for Diagnostics
The init.rc script and its associated .rc files define how Android initializes. Temporarily modifying these can help isolate issues.
- Disabling Services: Comment out or remove
serviceentries one by one in your custominit.rc(orinit.${hardware}.rcequivalent) to find which service is causing the boot loop. Rebuild theramdisk.imgafter changes. - Increasing Log Verbosity: Add
logleveloptions or modify service commands to print more output. - Launching a Shell on Boot: For severe issues preventing any boot, you can try to force a shell prompt:
# In your init.rc, locate the 'on early-init' or 'on init' section.# Add or modify a service to run a service debug-shell /system/bin/sh class core console oneshot user root group root disabled # Keep disabled, enable via 'setprop service.debug-shell 1' or during kernel boot args# Or even force the system to halt and give a shell immediately after kernel boots (advanced, use with caution)# Append to kernel command line: init=/system/bin/sh
Then, rebuild your ramdisk.img and flash it. If you get a shell, you can manually inspect directories, mount points, and try to start services one by one.
Debugging Runtime Errors and Application Crashes
Once the AVD boots, runtime issues become the focus.
1. Advanced Logcat Filtering
Beyond basic logcat, targeted filtering is crucial:
# Filter by process ID (PID) or tagadb logcat --pid=<PID_OF_CRASHING_APP>adb logcat -s PackageManager:V ActivityManager:V MyAppTag:D *:S# View kernel messages onlyadb logcat -b kernel
2. Utilizing dumpsys for System State
dumpsys provides diagnostic output for all system services. It’s invaluable for understanding the state of critical Android components.
# Dump information for a specific service, e.g., activity manageradb shell dumpsys activity# Get package manager information (e.g., about your installed apps)adb shell dumpsys package <your.package.name># View memory usageadb shell dumpsys meminfo
Look for discrepancies in expected service states, incorrect permissions, or missing component registrations.
3. Comprehensive Data Collection with bugreport
For persistent or complex issues, bugreport captures a snapshot of the entire system state, including logs, dumpsys output, and various system properties.
adb bugreport > bugreport.zip
The resulting .zip file contains extensive data for offline analysis, particularly useful when collaborating or reporting issues upstream.
4. Native Code Debugging with GDB/LLDB
For crashes in C/C++ native code (JNI, custom daemons, HAL implementations), gdbserver or lldb-server are indispensable.
- Push the debugger server to the device:
- Start the target application or process under
lldb-server: - Forward the port:
- Connect from your host debugger:
- Filesystem Mounts: Verify all expected partitions (e.g.,
/system,/vendor,/data) are correctly mounted and accessible with appropriate permissions (adb shell mount). - SELinux Permissive Mode: Temporarily set SELinux to permissive mode during debugging (
adb shell setenforce 0) to rule out policy violations as the root cause of access denied errors. Remember to re-enable enforcing mode for production. - AOSP Build Artifacts: Always double-check that you are flashing the correct
system.img,ramdisk.img, andkernel(orboot.img) for your target AVD configuration. Mismatched images are a frequent source of issues.
adb push $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/9.0.8/lib/linux/arm64/lldb-server /data/local/tmp/lldb-serveradb shell chmod 755 /data/local/tmp/lldb-server
# For an application:adb shell run-as <your.package.name> /data/local/tmp/lldb-server platform --listen "*:5039" -- <command_to_start_your_app># For a running process (attach):adb shell /data/local/tmp/lldb-server platform --listen "*:5039" --attach <PID>
adb forward tcp:5039 tcp:5039
# Example using lldblldb(lldb) platform select remote-android(lldb) platform connect connect://localhost:5039(lldb) target create <path_to_your_native_binary_on_host>(lldb) b <function_name_or_file:line>(lldb) c
Ensure you have the correct unstripped binaries and symbol files on your host machine to get meaningful stack traces.
Advanced Techniques and Best Practices
Kernel Debugging with KGDB (Remote GDB for Kernel)
For deep-seated kernel issues, kgdb allows debugging the kernel itself. This requires specific kernel build configurations (e.g., CONFIG_KGDB, CONFIG_KGDB_SERIAL_CONSOLE) and a serial connection (virtual or physical) to a host running GDB.
Example kernel command line parameters for KGDB (passed via emulator’s -qemu -append):
kgdboc=ttyS0,115200 kgdbwait
Then, connect GDB on the host to the serial port (e.g., /dev/ttyUSB0 or a named pipe created by QEMU).
Analyzing bootchart for Performance
bootchart helps visualize the boot process, identifying bottlenecks, excessive I/O, or CPU contention. Enable it in your init.rc (or via kernel command line) and collect the generated charts for analysis.
Filesystem and SELinux Policy Verification
Conclusion
Debugging custom AVD system images demands a methodical approach and a deep understanding of the Android boot process and runtime environment. By leveraging a combination of adb diagnostics, serial console output, init.rc modifications, and native debugging tools like lldb-server, developers can effectively pinpoint and resolve even the most elusive boot loops and runtime errors. Consistent log analysis, incremental changes, and thorough testing are paramount to maintaining a stable and functional custom AVD image.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →