Introduction
Debugging Binder issues in Android requires understanding the intersection between kernel space and user space. Because Binder handles inter-process communication (IPC) for virtually every core Android service, identifying deadlocks, thread starvation, and transaction failures is essential for maintaining system stability.
Exploring /sys/kernel/debug/binder/
The kernel's debugfs exposes detailed information about the Binder driver's state. You can access these files via ADB shell on a rooted device:
adb shell cat /sys/kernel/debug/binder/state
adb shell cat /sys/kernel/debug/binder/transactions
adb shell cat /sys/kernel/debug/binder/failed_transaction_log
The state file dumps the current state of every process interacting with Binder, showing allocated nodes, active references, and available thread pool sizes. The failed_transaction_log is particularly useful for tracking down BR_FAILED_REPLY errors or transactions that failed due to lack of memory or invalid handles.
Binder Transactions in Systrace
Systrace (or Perfetto) is invaluable for tracking Binder transactions across process boundaries. A typical trace shows a thread entering binder_transaction, while the target process's thread wakes up to handle the binder_reply.
In code, Android framework uses Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binder_transaction") or native ATRACE_CALL() to annotate these paths. When analyzing a Perfetto trace, look for the "binder transaction" slice. If you see a long blocking call on the client side, you must track the corresponding thread in the server process to identify what is causing the delay.
Binder Thread Starvation
Binder maintains a thread pool for handling incoming requests. By default, processes can spawn up to 15 binder threads (defined by ProcessState::setThreadPoolMaxThreadCount()). If all threads are blocked on synchronous calls (e.g., waiting for an external hardware resource or a slow disk I/O), new transactions will queue up in the kernel, eventually leading to thread starvation.
You can spot thread starvation in dumpsys binder when ready threads is consistently 0, and the transactions queue grows large. To fix this, offload long-running operations from Binder threads to background handlers or use asynchronous (oneway) calls.
Binder Deadlocks
Binder deadlocks typically occur in cyclic dependencies: Process A calls Process B, and before B can reply, B attempts a synchronous call to A. Since A's thread is already blocked waiting for B, if B uses the same thread or if A's thread pool is exhausted, the system deadlocks.
// Process A
synchronized (mLock) {
bProxy.doSomething(); // Blocks until B responds
}
// Process B
public void doSomething() {
synchronized (mLockB) {
aProxy.callback(); // Blocks until A responds - DEADLOCK!
}
}
The system Watchdog (SystemServer watchdog) periodically checks for these deadlocks by posting messages to core service threads (AMS, PMS, WMS). If a thread fails to respond, it triggers a kernel panic or an abort, capturing the state.
Analyzing with dumpsys binder
The dumpsys binder command provides high-level statistics about memory usage and thread counts without requiring debugfs access.
adb shell dumpsys activity binder
This dumps the Binder transaction history, active proxies, and any pending transactions for the ActivityManager. In native code, IPCThreadState::self()->getCallingPid() and getCallingUid() are frequently used to log the source of problematic transactions.