Skip to contentSkip to conversation

rust_binder : Implement dynamic debug logging mask

PATCH Superseded Jahnavi MN 4 participants 12 messages Jul 13 – Jul 16

Subsystems (2)

v3 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Jahnavi MN

author
#0 lore raw

v3 cover rust_binder : Implement dynamic debug logging mask

When a user-space application sends malformed data or makes a lifecycle mistake, the driver rejects it with a generic error code (like -EINVAL). Without internal logs, the driver acts as a "black box," forcing developers to guess which check failed.

In the legacy C Binder driver, this issue is solved using a dynamic debug_mask module parameter that toggles verbose logs for specific subsystems. This series brings the same critical capability to the Rust Binder driver to provide developers with clear, real-time feedback.

Instead of rebuilds, reboots, or guessing:
- Developers can enable logs instantly on a running device by writing to /sys/module/rust_binder/parameters/debug_mask.
- It prints the exact reason for failures (such as alignment errors, mismatched call stacks, or invalid handle references) directly into dmesg, reducing debugging time from hours to seconds.
- It protects system logs by keeping logging off by default and only enabling it when developers are actively troubleshooting.

Based on top of:
https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git

---
Changes in v3:
- Rebase the entire series on top of the latest char-misc-testing tree.
- Define rust_binder_debug_mask in Rust as an Atomic<u32> and export it to
C as extern u32 to avoid raw volatile reads and FFI UB.
- Use kernel::bits::bit_u32 instead of raw bit shifts.
- Simplify binder_debug! macro rules and remove the redundant raw helper.
- Wrap the raw mask in DebugMasks to use type-safe .contains(mask).
- Link to v2: https://lore.kernel.org/r/[email protected]

Changes in v2:
- Defined the debug mask categories using bitflags (impl_flags) to
resolve potential Undefined Behavior and match Rust idioms.
- Added a tgid helper to rust/kernel/task.rs to expose the task
group ID and optimize default "PID:TID" prefixing.
- Implemented the BINDER_DEBUG_DEATH_NOTIFICATION mask to log OOM
failures, lifecycle updates, and async delivery to user-space.
- Added pid fields to ThreadError, DeliverCode, and FreezeMessage
to ensure cancellation logs show the correct process PID instead
of background kworker PIDs.
- Removed duplicate PID printing across workqueue, transaction
failure, and stack unwinding logs.
- Refactored log formatting (e.g. formatted BinderError with {:?},
used "strong"/"weak" strings, and improved message wording).
- Fixed logic bugs (restored missing update_ref block, moved manager
lookup warnings).
- Cleaned up spurious newlines, corrected indentations, and adjusted
all commit titles to be shorter and more meaningful.
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Jahnavi MN (7):
rust_binder: Add dynamic debug logging mask
rust_binder: Implement BINDER_DEBUG_USER_ERROR for freezer-related operation
rust_binder: Implement BINDER_DEBUG_USER_ERROR for refcounting and death notifications
rust_binder: Implement BINDER_DEBUG_USER_ERROR for transaction parsing failures
rust_binder: Implement BINDER_DEBUG_FAILED_TRANSACTION
rust_binder: Implement BINDER_DEBUG_DEATH_NOTIFICATION
rust_binder: Implement BINDER_DEBUG_DEAD_TRANSACTION

 drivers/android/binder/debug.rs            |  76 +++++++++++++++++++
 drivers/android/binder/freeze.rs           |  64 +++++++++++-----
 drivers/android/binder/node.rs             |  24 ++++--
 drivers/android/binder/process.rs          |  56 ++++++++++++--
 drivers/android/binder/rust_binder_main.rs |  16 +++-
 drivers/android/binder/rust_binderfs.c     |   3 +
 drivers/android/binder/thread.rs           | 117 ++++++++++++++++++++---------
 drivers/android/binder/transaction.rs      |  15 ++++
 rust/kernel/task.rs                        |   7 ++
 9 files changed, 309 insertions(+), 69 deletions(-)

---
base-commit: f8d269390cd2a7a9fb5a31f153e7c7b709defea0
change-id: 20260702-rust_binder_debug_mask-636737015624

Best regards,

Signature · 1 line
Jahnavi MN <[email protected]>
author
#1 lore raw

PATCH v3 1/7 rust_binder: Add dynamic debug logging mask

Implement a dynamic debug logging mask (debug_mask) for the rust_binder module to allow dynamic runtime configuration of log levels. This enables parity with the legacy C driver's debug mask.

Since the Rust module! macro in the current kernel build does not yet support declaring module parameters directly in Rust, we define the debug_mask variable in Rust as an Atomic<u32> exported via FFI using #[no_mangle], and link to it as extern in a C companion file to expose it to the kernel runtime.

To verify the setup, instrument process lifecycle events (open, flush, and release) in process.rs under the new BINDER_DEBUG_OPEN_CLOSE logging mask. These entry-point events are chosen for initial validation because they represent the start of the Binder lifecycle and occur at low frequency, allowing simple runtime verification of the dynamic toggle without log noise.

---
 drivers/android/binder/debug.rs            | 76 ++++++++++++++++++++++++++++++
 drivers/android/binder/process.rs          |  7 ++-
 drivers/android/binder/rust_binder_main.rs |  2 +
 drivers/android/binder/rust_binderfs.c     |  3 ++
 rust/kernel/task.rs                        |  7 +++
 5 files changed, 94 insertions(+), 1 deletion(-)
drivers/android/binder/debug.rs new file +76 -0
new file mode 100644
index 000000000000..824b10c004c3
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2026 Google LLC.
+
+//! Binder debugging helpers.
+
+#![allow(dead_code)]
+
+use kernel::bits::bit_u32;
+use kernel::sync::atomic::Atomic;
+
+kernel::impl_flags!(
+    /// Represents multiple debug mask flags.
+    #[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
+    pub struct DebugMasks(u32);
+
+    /// Represents a single debug mask category.
+    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+    pub enum DebugMask {
+        UserError = bit_u32(0),
+        FailedTransaction = bit_u32(1),
+        DeadTransaction = bit_u32(2),
+        OpenClose = bit_u32(3),
+        DeadBinder = bit_u32(4),
+        DeathNotification = bit_u32(5),
+        ReadWrite = bit_u32(6),
+        UserRefs = bit_u32(7),
+        Threads = bit_u32(8),
+        Transaction = bit_u32(9),
+        TransactionComplete = bit_u32(10),
+        FreeBuffer = bit_u32(11),
+        InternalRefs = bit_u32(12),
+        PriorityCap = bit_u32(13),
+        Spinlocks = bit_u32(14),
+    }
+);
+
+#[no_mangle]
+pub(crate) static rust_binder_debug_mask: Atomic<u32> = Atomic::new(
+    (DebugMask::UserError as u32)
+        | (DebugMask::FailedTransaction as u32)
+        | (DebugMask::DeadTransaction as u32),
+);
+
+/// Checks if the given debug logging category is enabled in the mask.
+pub(crate) fn debug_mask_enabled(mask: DebugMask) -> bool {
+    let current_mask = rust_binder_debug_mask.load(kernel::sync::atomic::Relaxed);
+    DebugMasks(current_mask).contains(mask)
+}
+
+/// Prints a debug log if the specified mask category is enabled.
+#[macro_export]
+macro_rules! binder_debug {
+    // Rule to explicitly specify a PID (used in kworkers).
+    (pid=$pid:expr, $mask:ident, $($arg:tt)*) => {
+        if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) {
+            kernel::pr_info!(
+                "{}: {}\n",
+                $pid,
+                kernel::prelude::fmt!($($arg)*)
+            );
+        }
+    };
+
+    // Default rule (automatically prepends "PID:TID" of the current calling thread).
+    ($mask:ident, $($arg:tt)*) => {
+        if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) {
+            let thread = kernel::current!();
+            kernel::pr_info!(
+                "{}:{} {}\n",
+                thread.tgid(),
+                thread.pid(),
+                kernel::prelude::fmt!($($arg)*)
+            );
+        }
+    };
+}
drivers/android/binder/process.rs +6 -1
index 1abeb83684e4..6dd39939e7ae 100644
@@ -1327,6 +1327,7 @@ pub(crate) fn lock_with_nodes(&self) -> WithNodes<'_> {
     }
 
     fn deferred_flush(&self) {
+        binder_debug!(pid = self.task.pid(), OpenClose, "flushing process");
         let inner = self.inner.lock();
         for thread in inner.threads.values() {
             thread.exit_looper();
@@ -1334,6 +1335,8 @@ fn deferred_flush(&self) {
     }
 
     fn deferred_release(self: Arc<Self>) {
+        binder_debug!(pid = self.task.pid(), OpenClose, "releasing process");
+
         let is_manager = {
             let mut inner = self.inner.lock();
             inner.is_dead = true;
@@ -1627,7 +1630,9 @@ fn ioctl_write_read(
 /// The file operations supported by `Process`.
 impl Process {
     pub(crate) fn open(ctx: ArcBorrow<'_, Context>, file: &File) -> Result<Arc<Process>> {
-        Self::new(ctx.into(), ARef::from(file.cred()))
+        let proc = Self::new(ctx.into(), ARef::from(file.cred()))?;
+        binder_debug!(OpenClose, "opened process");
+        Ok(proc)
     }
 
     pub(crate) fn release(this: Arc<Process>, _file: &File) {
drivers/android/binder/rust_binder_main.rs +2 -0
index 432390aab25b..29829cb210a4 100644
@@ -31,6 +31,8 @@
 mod context;
 mod deferred_close;
 mod defs;
+#[macro_use]
+mod debug;
 mod error;
 mod node;
 mod page_range;
drivers/android/binder/rust_binderfs.c +3 -0
index ade1c4d92499..300cc65562d1 100644
@@ -51,6 +51,9 @@ DEFINE_SHOW_ATTRIBUTE(rust_binder_proc);
 char *rust_binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES;
 module_param_named(rust_devices, rust_binder_devices_param, charp, 0444);
 
+extern u32 rust_binder_debug_mask;
+module_param_named(debug_mask, rust_binder_debug_mask, uint, 0644);
+
 static dev_t binderfs_dev;
 static DEFINE_MUTEX(binderfs_minors_mutex);
 static DEFINE_IDA(binderfs_minors);
rust/kernel/task.rs +7 -0
index 38273f4eedb5..1b290c61714d 100644
@@ -210,6 +210,13 @@ pub fn pid(&self) -> Pid {
         unsafe { *ptr::addr_of!((*self.as_ptr()).pid) }
     }
 
+    /// Returns the TGID (Thread Group ID / Process ID) of the given task.
+    pub fn tgid(&self) -> Pid {
+        // SAFETY: The tgid of a task never changes after initialization, so reading this field is
+        // not a data race.
+        unsafe { *ptr::addr_of!((*self.as_ptr()).tgid) }
+    }
+
     /// Returns the UID of the given task.
     #[inline]
     pub fn uid(&self) -> Kuid {
Signature · 1 line
2.55.0.795.g602f6c329a-goog
author
#2 lore raw

PATCH v3 2/7 rust_binder: Implement BINDER_DEBUG_USER_ERROR for freezer-related operation

In reply to Jahnavi MN

This adds dynamic debug logs for:
- Requesting freeze notifications on invalid references, duplicate cookies, or already active registrations.
- Completing freeze notifications that are not pending or not found.
- Clearing freeze notifications on invalid references, inactive notifications, or cookie mismatches.

---
 drivers/android/binder/freeze.rs | 40 ++++++++++++++++++++++++++++++----------
 1 file changed, 30 insertions(+), 10 deletions(-)
drivers/android/binder/freeze.rs +30 -10
index 918c4e98b66f..2c99e0995554 100644
@@ -182,12 +182,15 @@ pub(crate) fn request_freeze_notif(
             info = match node_refs.by_handle.get_mut(&handle) {
                 Some(info) => info,
                 None => {
-                    pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION invalid ref {}\n", handle);
+                    binder_debug!(
+                        UserError,
+                        "BC_REQUEST_FREEZE_NOTIFICATION invalid ref {handle}"
+                    );
                     return Err(EINVAL);
                 }
             };
             if info.freeze().is_some() {
-                pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION already set\n");
+                binder_debug!(UserError, "BC_REQUEST_FREEZE_NOTIFICATION already set");
                 return Err(EINVAL);
             }
             let node_ref = info.node_ref();
@@ -195,7 +198,7 @@ pub(crate) fn request_freeze_notif(
 
             if let rbtree::Entry::Occupied(ref dupe) = freeze_entry {
                 if !dupe.get().allow_duplicate(&node_ref.node) {
-                    pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie\n");
+                    binder_debug!(UserError, "BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie");
                     return Err(EINVAL);
                 }
             }
@@ -260,7 +263,11 @@ pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader)
         let mut node_refs_guard = self.node_refs.lock();
         let node_refs = &mut *node_refs_guard;
         let Some(freeze) = node_refs.freeze_listeners.get_mut(&cookie) else {
-            pr_warn!("BC_FREEZE_NOTIFICATION_DONE {:016x} not found\n", cookie.0);
+            binder_debug!(
+                UserError,
+                "BC_FREEZE_NOTIFICATION_DONE {:016x} not found",
+                cookie.0
+            );
             return Err(EINVAL);
         };
         let mut clear_msg = None;
@@ -270,8 +277,9 @@ pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader)
             freeze.num_cleared_duplicates += 1;
         } else {
             if !freeze.is_pending {
-                pr_warn!(
-                    "BC_FREEZE_NOTIFICATION_DONE {:016x} not pending\n",
+                binder_debug!(
+                    UserError,
+                    "BC_FREEZE_NOTIFICATION_DONE {:016x} not pending",
                     cookie.0
                 );
                 return Err(EINVAL);
@@ -300,19 +308,31 @@ pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader)
         let mut node_refs_guard = self.node_refs.lock();
         let node_refs = &mut *node_refs_guard;
         let Some(info) = node_refs.by_handle.get_mut(&handle) else {
-            pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid ref {}\n", handle);
+            binder_debug!(
+                UserError,
+                "BC_CLEAR_FREEZE_NOTIFICATION invalid ref {handle}"
+            );
             return Err(EINVAL);
         };
         let Some(info_cookie) = info.freeze() else {
-            pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n");
+            binder_debug!(
+                UserError,
+                "BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active"
+            );
             return Err(EINVAL);
         };
         if *info_cookie != cookie {
-            pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch\n");
+            binder_debug!(
+                UserError,
+                "BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch"
+            );
             return Err(EINVAL);
         }
         let Some(listener) = node_refs.freeze_listeners.get_mut(&cookie) else {
-            pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {}\n", handle);
+            binder_debug!(
+                UserError,
+                "BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {handle}"
+            );
             return Err(EINVAL);
         };
         listener.is_clearing = true;
Signature · 1 line
2.55.0.795.g602f6c329a-goog
author
#3 lore raw

PATCH v3 3/7 rust_binder: Implement BINDER_DEBUG_USER_ERROR for refcounting and death notifications

In reply to Jahnavi MN

This adds dynamic debug logs for:
- Decrementing handle reference counts that are already zero.
- Mismatched reference states (calling inc_ref_done with no active inc_refs, or using a weak reference as a strong reference).
- Requesting or clearing death notifications on invalid references, already active notifications, or with mismatched cookies.

---
 drivers/android/binder/node.rs    | 10 ++++++----
 drivers/android/binder/process.rs | 35 ++++++++++++++++++++++++++++-------
 2 files changed, 34 insertions(+), 11 deletions(-)
drivers/android/binder/node.rs +6 -4
index fb57c0b20888..3f0757058b84 100644
@@ -345,7 +345,7 @@ pub(crate) fn inc_ref_done_locked(
     ) -> Option<DLArc<Node>> {
         let inner = self.inner.access_mut(owner_inner);
         if inner.active_inc_refs == 0 {
-            pr_err!("inc_ref_done called when no active inc_refs");
+            binder_debug!(UserError, "inc_ref_done called when no active inc_refs");
             return None;
         }
 
@@ -819,6 +819,7 @@ pub(crate) fn get_count(&self) -> (usize, usize) {
 
     pub(crate) fn clone(&self, strong: bool) -> Result<NodeRef> {
         if strong && self.strong_count == 0 {
+            binder_debug!(UserError, "tried to use weak ref as strong ref");
             return Err(EINVAL);
         }
         Ok(self
@@ -859,9 +860,10 @@ pub(crate) fn update(&mut self, inc: bool, strong: bool) -> bool {
             *count += 1;
         } else {
             if *count == 0 {
-                pr_warn!(
-                    "pid {} performed invalid decrement on ref\n",
-                    kernel::current!().pid()
+                binder_debug!(
+                    UserError,
+                    "performed invalid {} decrement on ref",
+                    if strong { "strong" } else { "weak" }
                 );
                 return false;
             }
drivers/android/binder/process.rs +28 -7
index 6dd39939e7ae..38190aaa462d 100644
@@ -908,7 +908,13 @@ pub(crate) fn get_transaction_node(&self, handle: u32) -> BinderResult<NodeRef>
         if handle == 0 {
             Ok(self.ctx.get_manager_node(true)?)
         } else {
-            Ok(self.get_node_from_handle(handle, true)?)
+            match self.get_node_from_handle(handle, true) {
+                Ok(node_ref) => Ok(node_ref),
+                Err(err) => {
+                    binder_debug!(UserError, "got transaction to invalid handle {handle}");
+                    Err(err.into())
+                }
+            }
         }
     }
 
@@ -983,7 +989,7 @@ pub(crate) fn update_ref(
         } else {
             // All refs are cleared in process exit, so this warning is expected in that case.
             if !self.inner.lock().is_dead {
-                pr_warn!("{}: no such ref {handle}\n", self.pid_in_current_ns());
+                binder_debug!(UserError, "no such ref {handle}");
             }
         }
         Ok(())
@@ -1236,13 +1242,19 @@ pub(crate) fn request_death(
         })?;
         let mut refs = self.node_refs.lock();
         let Some(info) = refs.by_handle.get_mut(&handle) else {
-            pr_warn!("BC_REQUEST_DEATH_NOTIFICATION invalid ref {handle}\n");
+            binder_debug!(
+                UserError,
+                "BC_REQUEST_DEATH_NOTIFICATION invalid ref {handle}"
+            );
             return Ok(());
         };
 
         // Nothing to do if there is already a death notification request for this handle.
         if info.death().is_some() {
-            pr_warn!("BC_REQUEST_DEATH_NOTIFICATION death notification already set\n");
+            binder_debug!(
+                UserError,
+                "BC_REQUEST_DEATH_NOTIFICATION death notification already set"
+            );
             return Ok(());
         }
 
@@ -1279,17 +1291,26 @@ pub(crate) fn clear_death(&self, reader: &mut UserSliceReader, thread: &Thread)
 
         let mut refs = self.node_refs.lock();
         let Some(info) = refs.by_handle.get_mut(&handle) else {
-            pr_warn!("BC_CLEAR_DEATH_NOTIFICATION invalid ref {handle}\n");
+            binder_debug!(
+                UserError,
+                "BC_CLEAR_DEATH_NOTIFICATION invalid ref {handle}"
+            );
             return Ok(());
         };
 
         let Some(death) = info.death().take() else {
-            pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification not active\n");
+            binder_debug!(
+                UserError,
+                "BC_CLEAR_DEATH_NOTIFICATION death notification not active"
+            );
             return Ok(());
         };
         if death.cookie != cookie {
             *info.death() = Some(death);
-            pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch\n");
+            binder_debug!(
+                UserError,
+                "BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch"
+            );
             return Ok(());
         }
 
Signature · 1 line
2.55.0.795.g602f6c329a-goog
author
#4 lore raw

PATCH v3 4/7 rust_binder: Implement BINDER_DEBUG_USER_ERROR for transaction parsing failures

In reply to Jahnavi MN

This adds dynamic debug logs in thread.rs for:
- File descriptor array (FDA) parent offset and parent buffer address alignment misalignments.
- Memory copy, write, and translation failures during transaction serialization (including out-of-bounds pointer fixups).
- Incoming transactions or replies that do not match the expected thread calling stack (such as out-of-order replies).

---
 drivers/android/binder/thread.rs | 54 ++++++++++++++++++++++++----------------
 1 file changed, 32 insertions(+), 22 deletions(-)
drivers/android/binder/thread.rs +32 -22
index 87298a8c597d..072cb4674172 100644
@@ -721,11 +721,12 @@ fn translate_object(
                 let alloc_offset = match sg_state.unused_buffer_space.claim_next(obj_length) {
                     Ok(alloc_offset) => alloc_offset,
                     Err(err) => {
-                        pr_warn!(
-                            "Failed to claim space for a BINDER_TYPE_PTR. (offset: {}, limit: {}, size: {})",
+                        binder_debug!(
+                            UserError,
+                            "failed to claim space for a BINDER_TYPE_PTR (offset: {}, limit: {}, size: {})",
                             sg_state.unused_buffer_space.offset,
                             sg_state.unused_buffer_space.limit,
-                            obj_length,
+                            obj_length
                         );
                         return Err(err.into());
                     }
@@ -804,6 +805,7 @@ fn translate_object(
                 let fds_len = num_fds.checked_mul(size_of::<u32>()).ok_or(EINVAL)?;
 
                 if !is_aligned(parent_offset, size_of::<u32>()) {
+                    binder_debug!(UserError, "FDA parent offset not aligned correctly");
                     return Err(EINVAL.into());
                 }
 
@@ -822,6 +824,7 @@ fn translate_object(
                 };
 
                 if !is_aligned(parent_entry.sender_uaddr, size_of::<u32>()) {
+                    binder_debug!(UserError, "FDA parent buffer not aligned correctly");
                     return Err(EINVAL.into());
                 }
 
@@ -905,12 +908,9 @@ fn apply_sg(&self, alloc: &mut Allocation, sg_state: &mut ScatterGatherState) ->
 
                 let target_offset_end = fixup_offset.checked_add(fixup_len).ok_or(EINVAL)?;
                 if fixup_offset < end_of_previous_fixup || offset_end < target_offset_end {
-                    pr_warn!(
-                        "Fixups oob {} {} {} {}",
-                        fixup_offset,
-                        end_of_previous_fixup,
-                        offset_end,
-                        target_offset_end
+                    binder_debug!(
+                        UserError,
+                        "fixups oob {fixup_offset} {end_of_previous_fixup} {offset_end} {target_offset_end}"
                     );
                     return Err(EINVAL.into());
                 }
@@ -918,18 +918,21 @@ fn apply_sg(&self, alloc: &mut Allocation, sg_state: &mut ScatterGatherState) ->
                 let copy_off = end_of_previous_fixup;
                 let copy_len = fixup_offset - end_of_previous_fixup;
                 if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) {
-                    pr_warn!("Failed copying into alloc: {:?}", err);
+                    binder_debug!(UserError, "failed copying into alloc: {err:?}");
                     return Err(err.into());
                 }
                 if let PointerFixupEntry::Fixup { pointer_value, .. } = fixup {
                     let res = alloc.write::<u64>(fixup_offset, pointer_value);
                     if let Err(err) = res {
-                        pr_warn!("Failed copying ptr into alloc: {:?}", err);
+                        binder_debug!(UserError, "failed copying ptr into alloc: {err:?}");
                         return Err(err.into());
                     }
                 }
                 if let Err(err) = reader.skip(fixup_len) {
-                    pr_warn!("Failed skipping {} from reader: {:?}", fixup_len, err);
+                    binder_debug!(
+                        UserError,
+                        "failed skipping {fixup_len} from reader: {err:?}"
+                    );
                     return Err(err.into());
                 }
                 end_of_previous_fixup = target_offset_end;
@@ -937,7 +940,7 @@ fn apply_sg(&self, alloc: &mut Allocation, sg_state: &mut ScatterGatherState) ->
             let copy_off = end_of_previous_fixup;
             let copy_len = offset_end - end_of_previous_fixup;
             if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) {
-                pr_warn!("Failed copying remainder into alloc: {:?}", err);
+                binder_debug!(UserError, "failed copying remainder into alloc: {err:?}");
                 return Err(err.into());
             }
         }
@@ -1041,7 +1044,7 @@ pub(crate) fn copy_transaction_data(
                 let offset: usize = offset.try_into().map_err(|_| EINVAL)?;
 
                 if offset < end_of_previous_object || !is_aligned(offset, size_of::<u32>()) {
-                    pr_warn!("Got transaction with invalid offset.");
+                    binder_debug!(UserError, "got transaction with invalid offset");
                     return Err(EINVAL.into());
                 }
 
@@ -1066,7 +1069,7 @@ pub(crate) fn copy_transaction_data(
                 ) {
                     Ok(()) => end_of_previous_object = offset + object.size(),
                     Err(err) => {
-                        pr_warn!("Error while translating object.");
+                        binder_debug!(UserError, "error while translating object: {err:?}");
                         return Err(err);
                     }
                 }
@@ -1086,15 +1089,12 @@ pub(crate) fn copy_transaction_data(
         )?;
 
         if let Some(sg_state) = sg_state.as_mut() {
-            if let Err(err) = self.apply_sg(&mut alloc, sg_state) {
-                pr_warn!("Failure in apply_sg: {:?}", err);
-                return Err(err);
-            }
+            self.apply_sg(&mut alloc, sg_state)?;
         }
 
         if let Some((off_out, secctx)) = secctx.as_mut() {
             if let Err(err) = alloc.write(secctx_off, secctx.as_bytes()) {
-                pr_warn!("Failed to write security context: {:?}", err);
+                binder_debug!(UserError, "failed to write security context: {err:?}");
                 return Err(err.into());
             }
             **off_out = secctx_off;
@@ -1282,7 +1282,7 @@ fn transaction_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResu
         {
             let mut inner = self.inner.lock();
             if !transaction.is_stacked_on(&inner.current_transaction) {
-                pr_warn!("Transaction stack changed during transaction!");
+                binder_debug!(UserError, "got new transaction with bad transaction stack");
                 return Err(EINVAL.into());
             }
             inner.current_transaction = Some(transaction.clone_arc());
@@ -1305,8 +1305,18 @@ fn transaction_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResu
     }
 
     fn reply_inner(self: &Arc<Self>, info: &mut TransactionInfo) -> BinderResult {
-        let orig = self.inner.lock().pop_transaction_to_reply(self)?;
+        let orig = match self.inner.lock().pop_transaction_to_reply(self) {
+            Ok(orig) => orig,
+            Err(err) => {
+                binder_debug!(UserError, "got reply transaction with no transaction stack");
+                return Err(err.into());
+            }
+        };
         if !orig.from.is_current_transaction(&orig) {
+            binder_debug!(
+                UserError,
+                "got reply transaction with bad transaction stack"
+            );
             return Err(EINVAL.into());
         }
 
Signature · 1 line
2.55.0.795.g602f6c329a-goog
author
#5 lore raw

PATCH v3 5/7 rust_binder: Implement BINDER_DEBUG_FAILED_TRANSACTION

In reply to Jahnavi MN

This adds dynamic debug logs for:
- Failed replies, target process deaths, and error code deliveries.
- Detailed transaction failure diagnostics (including sender/receiver PIDs, TIDs, transaction IDs, buffer sizes, and error codes).

---
 drivers/android/binder/thread.rs      | 21 ++++++++++++++++-----
 drivers/android/binder/transaction.rs |  8 ++++++++
 2 files changed, 24 insertions(+), 5 deletions(-)
drivers/android/binder/thread.rs +16 -5
index 072cb4674172..26925d094a59 100644
@@ -1254,11 +1254,22 @@ fn transaction(self: &Arc<Self>, cmd: u32, reader: &mut UserSliceReader) -> Resu
                     ee.param = source.to_errno();
                 }
 
-                pr_warn!(
-                    "{}:{} transaction to {} failed: {source:?}",
-                    info.from_pid,
-                    info.from_tid,
-                    info.to_pid
+                binder_debug!(
+                    FailedTransaction,
+                    "transaction {} to {}:{} failed {:?}, code {} size {}-{}",
+                    if info.is_reply {
+                        "reply"
+                    } else if info.is_oneway() {
+                        "async"
+                    } else {
+                        "call"
+                    },
+                    info.to_pid,
+                    info.to_tid,
+                    err,
+                    info.code,
+                    info.data_size,
+                    info.offsets_size
                 );
             }
         }
drivers/android/binder/transaction.rs +8 -0
index 38795224a784..b56dca55662d 100644
@@ -405,6 +405,14 @@ fn do_work(
         } else {
             // On failure to process the list, we send a reply back to the sender and ignore the
             // transaction on the recipient.
+            binder_debug!(
+                FailedTransaction,
+                "transaction {} to {} failed, fd fixups failed, size {}-{}",
+                self.debug_id,
+                self.to.task.pid(),
+                self.data_size,
+                self.offsets_size
+            );
             return Ok(true);
         };
 
Signature · 1 line
2.55.0.795.g602f6c329a-goog
author
#6 lore raw

PATCH v3 6/7 rust_binder: Implement BINDER_DEBUG_DEATH_NOTIFICATION

In reply to Jahnavi MN

This adds dynamic debug logs for:
- Memory allocation (OOM) failures when requesting death notifications
- Registration and cancellation lifecycle events (BC_REQUEST / BC_CLEAR)
- Delivery of death notification events to userspace (BR_DEAD_BINDER)

---
 drivers/android/binder/node.rs    |  5 +++++
 drivers/android/binder/process.rs | 14 ++++++++++++++
 2 files changed, 19 insertions(+)
drivers/android/binder/node.rs +5 -0
index 3f0757058b84..87a2e613a816 100644
@@ -1105,6 +1105,11 @@ fn do_work(
             // We're still holding the inner lock, so it cannot be aborted while we insert it into
             // the delivered list.
             process_inner.death_delivered(self.clone());
+            binder_debug!(
+                DeathNotification,
+                "sending death notification, cookie {:016x}",
+                cookie
+            );
             BR_DEAD_BINDER
         };
 
drivers/android/binder/process.rs +14 -0
index 38190aaa462d..42330fd409c4 100644
@@ -1239,6 +1239,10 @@ pub(crate) fn request_death(
         // Queue BR_ERROR if we can't allocate memory for the death notification.
         let death = UniqueArc::new_uninit(GFP_KERNEL).inspect_err(|_| {
             thread.push_return_work(BR_ERROR);
+            binder_debug!(
+                DeathNotification,
+                "BC_REQUEST_DEATH_NOTIFICATION failed due to memory allocation failure"
+            );
         })?;
         let mut refs = self.node_refs.lock();
         let Some(info) = refs.by_handle.get_mut(&handle) else {
@@ -1282,6 +1286,11 @@ pub(crate) fn request_death(
                 info.node_ref().node.add_death(death, &mut owner_inner);
             }
         }
+        binder_debug!(
+            DeathNotification,
+            "BC_REQUEST_DEATH_NOTIFICATION handle {handle} cookie {:016x}",
+            cookie
+        );
         Ok(())
     }
 
@@ -1325,6 +1334,11 @@ pub(crate) fn clear_death(&self, reader: &mut UserSliceReader, thread: &Thread)
             }
         }
 
+        binder_debug!(
+            DeathNotification,
+            "BC_CLEAR_DEATH_NOTIFICATION handle {handle} cookie {:016x}",
+            cookie
+        );
         Ok(())
     }
 
Signature · 1 line
2.55.0.795.g602f6c329a-goog
author
#7 lore raw

PATCH v3 7/7 rust_binder: Implement BINDER_DEBUG_DEAD_TRANSACTION

In reply to Jahnavi MN

This adds dynamic debug logs for:
- Releasing active transactions during thread stack unwinding.
- Discarded transaction error codes when a thread exits.
- Undelivered transaction acknowledgments (TRANSACTION_COMPLETE) upon thread exit.
- Undelivered process death and freeze notifications when processes exit or die.
- Undelivered transactions canceled due to target process death.

We now store the process PID in ThreadError, DeliverCode, and FreezeMessage to ensure the correct PID is logged on cancellation. This is necessary because cancel() runs from background kworkers, which would otherwise print the wrong PID.

---
 drivers/android/binder/freeze.rs           | 24 +++++++++++------
 drivers/android/binder/node.rs             |  9 ++++++-
 drivers/android/binder/rust_binder_main.rs | 14 ++++++++--
 drivers/android/binder/thread.rs           | 42 +++++++++++++++++++++++-------
 drivers/android/binder/transaction.rs      |  7 +++++
 5 files changed, 76 insertions(+), 20 deletions(-)
drivers/android/binder/freeze.rs +16 -8
index 2c99e0995554..7b825ffba557 100644
@@ -60,6 +60,7 @@ fn allow_duplicate(&self, node: &DArc<Node>) -> bool {
 /// Represents a notification that the freeze state has changed.
 pub(crate) struct FreezeMessage {
     cookie: FreezeCookie,
+    pid: i32,
 }
 
 kernel::list::impl_list_arc_safe! {
@@ -73,8 +74,8 @@ fn new(flags: kernel::alloc::Flags) -> Result<UninitFM, AllocError> {
         UniqueArc::new_uninit(flags)
     }
 
-    fn init(ua: UninitFM, cookie: FreezeCookie) -> DLArc<FreezeMessage> {
-        match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie })) {
+    fn init(ua: UninitFM, cookie: FreezeCookie, pid: i32) -> DLArc<FreezeMessage> {
+        match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie, pid })) {
             Ok(msg) => ListArc::from(msg),
             Err(err) => match err {},
         }
@@ -140,7 +141,14 @@ fn do_work(
         }
     }
 
-    fn cancel(self: DArc<Self>) {}
+    fn cancel(self: DArc<Self>) {
+        binder_debug!(
+            pid = self.pid,
+            DeadTransaction,
+            "undelivered freeze notification, {:016x}",
+            self.cookie.0
+        );
+    }
 
     fn should_sync_wakeup(&self) -> bool {
         false
@@ -251,7 +259,7 @@ pub(crate) fn request_freeze_notif(
         }
 
         *info.freeze() = Some(cookie);
-        let msg = FreezeMessage::init(msg, cookie);
+        let msg = FreezeMessage::init(msg, cookie, self.task.pid());
         drop(node_refs_guard);
         let _ = self.push_work(msg);
         Ok(())
@@ -272,7 +280,7 @@ pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader)
         };
         let mut clear_msg = None;
         if freeze.num_pending_duplicates > 0 {
-            clear_msg = Some(FreezeMessage::init(alloc, cookie));
+            clear_msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid()));
             freeze.num_pending_duplicates -= 1;
             freeze.num_cleared_duplicates += 1;
         } else {
@@ -287,7 +295,7 @@ pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader)
             let is_frozen = freeze.node.owner.inner.lock().is_frozen.is_fully_frozen();
             if freeze.is_clearing || freeze.last_is_frozen != Some(is_frozen) {
                 // Immediately send another FreezeMessage.
-                clear_msg = Some(FreezeMessage::init(alloc, cookie));
+                clear_msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid()));
             }
             freeze.is_pending = false;
         }
@@ -340,7 +348,7 @@ pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader)
         *info.freeze() = None;
         let mut msg = None;
         if !listener.is_pending {
-            msg = Some(FreezeMessage::init(alloc, cookie));
+            msg = Some(FreezeMessage::init(alloc, cookie, self.task.pid()));
         }
         drop(node_refs_guard);
 
@@ -420,7 +428,7 @@ pub(crate) fn prepare_freeze_messages(&self) -> Result<FreezeMessages, AllocErro
                 continue;
             };
             let msg_alloc = FreezeMessage::new(GFP_KERNEL)?;
-            let msg = FreezeMessage::init(msg_alloc, cookie);
+            let msg = FreezeMessage::init(msg_alloc, cookie, proc.task.pid());
             batch.push((proc, msg), GFP_KERNEL)?;
         }
 
drivers/android/binder/node.rs +8 -1
index 87a2e613a816..b9e21b8ec251 100644
@@ -1120,7 +1120,14 @@ fn do_work(
         Ok(cmd != BR_DEAD_BINDER)
     }
 
-    fn cancel(self: DArc<Self>) {}
+    fn cancel(self: DArc<Self>) {
+        binder_debug!(
+            pid = self.process.task.pid(),
+            DeadTransaction,
+            "undelivered death notification, {:016x}",
+            self.cookie
+        );
+    }
 
     fn should_sync_wakeup(&self) -> bool {
         false
drivers/android/binder/rust_binder_main.rs +12 -2
index 29829cb210a4..15c7b65928d8 100644
@@ -221,6 +221,7 @@ fn arc_pin_init(init: impl PinInit<T>) -> Result<DLArc<T>, kernel::error::Error>
 struct DeliverCode {
     code: u32,
     skip: Atomic<bool>,
+    pid: i32,
 }
 
 kernel::list::impl_list_arc_safe! {
@@ -228,10 +229,11 @@ struct DeliverCode {
 }
 
 impl DeliverCode {
-    fn new(code: u32) -> Self {
+    fn new(code: u32, pid: i32) -> Self {
         Self {
             code,
             skip: Atomic::new(false),
+            pid,
         }
     }
 
@@ -256,7 +258,15 @@ fn do_work(
         Ok(true)
     }
 
-    fn cancel(self: DArc<Self>) {}
+    fn cancel(self: DArc<Self>) {
+        if !self.skip.load(Relaxed) {
+            binder_debug!(
+                pid = self.pid,
+                DeadTransaction,
+                "undelivered TRANSACTION_COMPLETE"
+            );
+        }
+    }
 
     fn should_sync_wakeup(&self) -> bool {
         false
drivers/android/binder/thread.rs +33 -9

103 lines not on this page · raw message

drivers/android/binder/transaction.rs +7 -0
index b56dca55662d..0dbc7a9d16f9 100644
@@ -489,6 +489,13 @@ fn cancel(self: DArc<Self>) {
         if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 {
             let reply = Err(BR_DEAD_REPLY);
             self.from.deliver_reply(reply, &self);
+        } else {
+            binder_debug!(
+                pid = self.to.task.pid(),
+                DeadTransaction,
+                "undelivered transaction {}, process died",
+                self.debug_id
+            );
         }
 
         self.drop_outstanding_txn();
Signature · 1 line
2.55.0.795.g602f6c329a-goog
maintainer Reviewed-by on v3 7/7
#8 lore raw

Re: [PATCH v3 7/7] rust_binder: Implement BINDER_DEBUG_DEAD_TRANSACTION

Quoted 11 lines · On Mon, Jul 13, 2026 at 12:35:29PM +0000, Jahnavi MN via B4 Relay wrote:

From: Jahnavi MN <[email protected]>

This adds dynamic debug logs for:
- Releasing active transactions during thread stack unwinding.
- Discarded transaction error codes when a thread exits.
- Undelivered transaction acknowledgments (TRANSACTION_COMPLETE) upon thread exit.
- Undelivered process death and freeze notifications when processes exit or die.
- Undelivered transactions canceled due to target process death.

Quoted 8 lines

We now store the process PID in ThreadError, DeliverCode, and FreezeMessage to ensure the correct PID is logged on cancellation. This is necessary because cancel() runs from background kworkers, which would otherwise print the wrong PID.

---

Thanks Jahnavi,

maintainer
#9 lore raw
In reply to Jahnavi MN
Quoted 1 line · On Mon, Jul 13, 2026 at 12:35:22PM +0000, Jahnavi MN via B4 Relay wrote:

base-commit: f8d269390cd2a7a9fb5a31f153e7c7b709defea0

Greg, a quick practical question. This doesn't apply anymore because of the merge commit, and it actually also has a conflict with the fix "rust_binder: only print failure if error has source". (Both conflicts are in the same chunk of code.)

The rebase of this series that I'm currently using for my development is this one:
https://github.com/Darksonn/linux/commits/rust-binder-base/

That rebase happens to fix the conflict with the merge commit in a way that implicitly fixes the same issue as "rust_binder: only print failure if error has source". So with that rebase, the fix patch is only relevant for 7.2 and the stable backports. (i.e. merge conflict fix is to keep char-misc-next side)

Do you want a resend of this that looks like that?

As for my other series, the netlink series [1] applies cleanly on char-misc-next both before and after applying the debug mask changes (or at least, they cherry-pick cleanly after 'git am'ing on the original base commit).

And the ratelimited printing [2] and synchronize_rcu() series [3] both need new versions with changes, but I'd like to base them on top of the debug mask and netlink patchsets.

Any concerns?

[1]: https://lore.kernel.org/r/[email protected]/
[2]: https://lore.kernel.org/r/[email protected]/
[3]: https://lore.kernel.org/all/[email protected]/

Alice

#10 lore raw
Quoted 12 lines · Alice Ryhl
Quoted 1 line · On Mon, Jul 13, 2026 at 12:35:22PM +0000, Jahnavi MN via B4 Relay wrote:

base-commit: f8d269390cd2a7a9fb5a31f153e7c7b709defea0

Greg, a quick practical question. This doesn't apply anymore because of the merge commit, and it actually also has a conflict with the fix "rust_binder: only print failure if error has source". (Both conflicts are in the same chunk of code.)

The rebase of this series that I'm currently using for my development is this one:
https://github.com/Darksonn/linux/commits/rust-binder-base/

Quoted 7 lines

That rebase happens to fix the conflict with the merge commit in a way that implicitly fixes the same issue as "rust_binder: only print failure if error has source". So with that rebase, the fix patch is only relevant for 7.2 and the stable backports. (i.e. merge conflict fix is to keep char-misc-next side)

Do you want a resend of this that looks like that?

Yes please send a new series that I can actually apply.

Quoted 4 lines

As for my other series, the netlink series [1] applies cleanly on char-misc-next both before and after applying the debug mask changes (or at least, they cherry-pick cleanly after 'git am'ing on the original base commit).

Are they ready to be applied? If so, I'll pick them up.

Quoted 3 lines

And the ratelimited printing [2] and synchronize_rcu() series [3] both need new versions with changes, but I'd like to base them on top of the debug mask and netlink patchsets.

No problems, just send those again too please.

thanks,

greg k-h

maintainer
#11 lore raw
Quoted 9 lines · Greg Kroah-Hartman
Quoted 5 lines · Alice Ryhl

On Mon, Jul 13, 2026 at 12:35:22PM +0000, Jahnavi MN via B4 Relay wrote:
As for my other series, the netlink series [1] applies cleanly on char-misc-next both before and after applying the debug mask changes (or at least, they cherry-pick cleanly after 'git am'ing on the original base commit).

Are they ready to be applied? If so, I'll pick them up.

Yes netlink is ready.

Alice