mirror of
https://github.com/nxp-imx/linux-imx.git
synced 2025-07-06 17:35:20 +02:00
ANDROID: rust_binder: add binder_logs/state
The binderfs directory has four files intended for debugging the driver. This patch implements the state file so that you can use it to view the current state of the driver. Link: https://lore.kernel.org/rust-for-linux/20231101-rust-binder-v1-18-08ba9197f637@google.com/ Change-Id: I41d33ad5149b6f5cfb2569da1db02b36f2a8b99b Signed-off-by: Alice Ryhl <aliceryhl@google.com> Bug: 278052745
This commit is contained in:
parent
ec1855352b
commit
01ac18bd09
|
@ -8,6 +8,8 @@ use kernel::{
|
|||
TryNewListArc,
|
||||
},
|
||||
prelude::*,
|
||||
seq_file::SeqFile,
|
||||
seq_print,
|
||||
sync::lock::{spinlock::SpinLockBackend, Guard},
|
||||
sync::{Arc, LockedBy, SpinLock},
|
||||
uaccess::UserSliceWriter,
|
||||
|
@ -76,6 +78,7 @@ struct NodeInner {
|
|||
|
||||
#[pin_data]
|
||||
pub(crate) struct Node {
|
||||
pub(crate) debug_id: usize,
|
||||
ptr: u64,
|
||||
cookie: u64,
|
||||
pub(crate) flags: u32,
|
||||
|
@ -121,6 +124,7 @@ impl Node {
|
|||
refs: List::new(),
|
||||
},
|
||||
),
|
||||
debug_id: super::next_debug_id(),
|
||||
ptr,
|
||||
cookie,
|
||||
flags,
|
||||
|
@ -129,6 +133,40 @@ impl Node {
|
|||
})
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn full_debug_print(
|
||||
&self,
|
||||
m: &mut SeqFile,
|
||||
owner_inner: &mut ProcessInner,
|
||||
) -> Result<()> {
|
||||
let prio = self.node_prio();
|
||||
let inner = self.inner.access_mut(owner_inner);
|
||||
seq_print!(
|
||||
m,
|
||||
" node {}: u{:016x} c{:016x} pri {}:{} hs {} hw {} cs {} cw {}",
|
||||
self.debug_id,
|
||||
self.ptr,
|
||||
self.cookie,
|
||||
prio.sched_policy,
|
||||
prio.prio,
|
||||
inner.strong.has_count,
|
||||
inner.weak.has_count,
|
||||
inner.strong.count,
|
||||
inner.weak.count,
|
||||
);
|
||||
if !inner.refs.is_empty() {
|
||||
seq_print!(m, " proc");
|
||||
for node_ref in &inner.refs {
|
||||
seq_print!(m, " {}", node_ref.process.task.pid());
|
||||
}
|
||||
}
|
||||
seq_print!(m, "\n");
|
||||
for t in &inner.oneway_todo {
|
||||
t.debug_print_inner(m, " pending async transaction ");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert the `NodeRef` into this `refs` list.
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -447,6 +485,19 @@ impl DeliverToRead for Node {
|
|||
fn should_sync_wakeup(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn debug_print(&self, m: &mut SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
|
||||
seq_print!(
|
||||
m,
|
||||
"{}node work {}: u{:016x} c{:016x}\n",
|
||||
prefix,
|
||||
self.debug_id,
|
||||
self.ptr,
|
||||
self.cookie,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents something that holds one or more ref-counts to a `Node`.
|
||||
|
@ -494,6 +545,10 @@ impl NodeRef {
|
|||
other.weak_node_count = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn get_count(&self) -> (usize, usize) {
|
||||
(self.strong_count, self.weak_count)
|
||||
}
|
||||
|
||||
pub(crate) fn clone(&self, strong: bool) -> Result<NodeRef> {
|
||||
if strong && self.strong_count == 0 {
|
||||
return Err(EINVAL);
|
||||
|
@ -788,4 +843,23 @@ impl DeliverToRead for NodeDeath {
|
|||
fn should_sync_wakeup(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn debug_print(&self, m: &mut SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
|
||||
let inner = self.inner.lock();
|
||||
|
||||
let dead_binder = inner.dead && !inner.notification_done;
|
||||
|
||||
if dead_binder {
|
||||
if inner.cleared {
|
||||
seq_print!(m, "{}has cleared dead binder\n", prefix);
|
||||
} else {
|
||||
seq_print!(m, "{}has dead binder\n", prefix);
|
||||
}
|
||||
} else {
|
||||
seq_print!(m, "{}has cleared death notification\n", prefix);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ use kernel::{
|
|||
page_range::ShrinkablePageRange,
|
||||
prelude::*,
|
||||
rbtree::{self, RBTree},
|
||||
seq_file::SeqFile,
|
||||
seq_print,
|
||||
sync::poll::PollTable,
|
||||
sync::{
|
||||
lock::Guard, Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc,
|
||||
|
@ -300,6 +302,7 @@ impl ProcessInner {
|
|||
/// Used to keep track of a node that this process has a handle to.
|
||||
#[pin_data]
|
||||
pub(crate) struct NodeRefInfo {
|
||||
debug_id: usize,
|
||||
/// The refcount that this process owns to the node.
|
||||
node_ref: ListArcField<NodeRef, { Self::LIST_PROC }>,
|
||||
death: ListArcField<Option<DArc<NodeDeath>>, { Self::LIST_PROC }>,
|
||||
|
@ -309,7 +312,7 @@ pub(crate) struct NodeRefInfo {
|
|||
/// The handle for this `NodeRefInfo`.
|
||||
handle: u32,
|
||||
/// The process that has a handle to the node.
|
||||
process: Arc<Process>,
|
||||
pub(crate) process: Arc<Process>,
|
||||
}
|
||||
|
||||
impl NodeRefInfo {
|
||||
|
@ -320,6 +323,7 @@ impl NodeRefInfo {
|
|||
|
||||
fn new(node_ref: NodeRef, handle: u32, process: Arc<Process>) -> impl PinInit<Self> {
|
||||
pin_init!(Self {
|
||||
debug_id: super::next_debug_id(),
|
||||
node_ref: ListArcField::new(node_ref),
|
||||
death: ListArcField::new(None),
|
||||
links <- ListLinks::new(),
|
||||
|
@ -470,6 +474,80 @@ impl Process {
|
|||
Ok(process)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn debug_print(&self, m: &mut SeqFile, ctx: &Context) -> Result<()> {
|
||||
seq_print!(m, "proc {}\n", self.task.pid_in_current_ns());
|
||||
seq_print!(m, "context {}\n", &*ctx.name);
|
||||
|
||||
let mut all_threads = Vec::new();
|
||||
let mut all_nodes = Vec::new();
|
||||
loop {
|
||||
let inner = self.inner.lock();
|
||||
let num_threads = inner.threads.iter().count();
|
||||
let num_nodes = inner.nodes.iter().count();
|
||||
|
||||
if all_threads.capacity() < num_threads || all_nodes.capacity() < num_nodes {
|
||||
drop(inner);
|
||||
all_threads.try_reserve(num_threads)?;
|
||||
all_nodes.try_reserve(num_nodes)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
for thread in inner.threads.values() {
|
||||
assert!(all_threads.len() < all_threads.capacity());
|
||||
let _ = all_threads.try_push(thread.clone());
|
||||
}
|
||||
|
||||
for node in inner.nodes.values() {
|
||||
assert!(all_nodes.len() < all_nodes.capacity());
|
||||
let _ = all_nodes.try_push(node.clone());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
for thread in all_threads {
|
||||
thread.debug_print(m);
|
||||
}
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
for node in all_nodes {
|
||||
node.full_debug_print(m, &mut inner)?;
|
||||
}
|
||||
drop(inner);
|
||||
|
||||
let mut refs = self.node_refs.lock();
|
||||
for r in refs.by_handle.values_mut() {
|
||||
let node_ref = r.node_ref();
|
||||
let dead = node_ref.node.owner.inner.lock().is_dead;
|
||||
let (strong, weak) = node_ref.get_count();
|
||||
let debug_id = node_ref.node.debug_id;
|
||||
|
||||
seq_print!(
|
||||
m,
|
||||
" ref {}: desc {} {}node {debug_id} s {strong} w {weak}",
|
||||
r.debug_id,
|
||||
r.handle,
|
||||
if dead { "dead " } else { "" },
|
||||
);
|
||||
}
|
||||
drop(refs);
|
||||
|
||||
let inner = self.inner.lock();
|
||||
for work in &inner.work {
|
||||
work.debug_print(m, " ", " pending transaction")?;
|
||||
}
|
||||
for _death in &inner.delivered_deaths {
|
||||
seq_print!(m, " has delivered dead binder\n");
|
||||
}
|
||||
if let Some(mapping) = &inner.mapping {
|
||||
mapping.alloc.debug_print(m)?;
|
||||
}
|
||||
drop(inner);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempts to fetch a work item from the process queue.
|
||||
pub(crate) fn get_work(&self) -> Option<DLArc<dyn DeliverToRead>> {
|
||||
self.inner.lock().work.pop_front()
|
||||
|
|
|
@ -6,6 +6,8 @@ use kernel::{
|
|||
page::PAGE_SIZE,
|
||||
prelude::*,
|
||||
rbtree::{RBTree, RBTreeNode, RBTreeNodeReservation},
|
||||
seq_file::SeqFile,
|
||||
seq_print,
|
||||
task::Pid,
|
||||
};
|
||||
|
||||
|
@ -55,6 +57,33 @@ impl<T> RangeAllocator<T> {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn debug_print(&self, m: &mut SeqFile) -> Result<()> {
|
||||
for desc in self.tree.values() {
|
||||
let state = match &desc.state {
|
||||
Some(state) => state,
|
||||
None => continue,
|
||||
};
|
||||
seq_print!(
|
||||
m,
|
||||
" buffer {}: {} size {} pid {} oneway {}",
|
||||
0,
|
||||
desc.offset,
|
||||
desc.size,
|
||||
state.pid(),
|
||||
state.is_oneway()
|
||||
);
|
||||
match state {
|
||||
DescriptorState::Reserved(_res) => {
|
||||
seq_print!(m, "reserved\n");
|
||||
}
|
||||
DescriptorState::Allocated(_alloc) => {
|
||||
seq_print!(m, "allocated\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_best_match(&mut self, size: usize) -> Option<&mut Descriptor<T>> {
|
||||
let free_cursor = self.free_tree.cursor_lower_bound(&(size, 0))?;
|
||||
let ((_, offset), _) = free_cursor.current();
|
||||
|
|
|
@ -13,6 +13,8 @@ use kernel::{
|
|||
},
|
||||
page_range::Shrinker,
|
||||
prelude::*,
|
||||
seq_file::SeqFile,
|
||||
seq_print,
|
||||
sync::poll::PollTable,
|
||||
sync::Arc,
|
||||
types::ForeignOwnable,
|
||||
|
@ -21,7 +23,7 @@ use kernel::{
|
|||
|
||||
use crate::{context::Context, process::Process, thread::Thread};
|
||||
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
|
||||
mod allocation;
|
||||
mod context;
|
||||
|
@ -42,6 +44,12 @@ module! {
|
|||
license: "GPL",
|
||||
}
|
||||
|
||||
fn next_debug_id() -> usize {
|
||||
static NEXT_DEBUG_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
NEXT_DEBUG_ID.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Specifies how a type should be delivered to the read part of a BINDER_WRITE_READ ioctl.
|
||||
///
|
||||
/// When a value is pushed to the todo list for a process or thread, it is stored as a trait object
|
||||
|
@ -72,6 +80,8 @@ trait DeliverToRead: ListArcSafe + Send + Sync {
|
|||
fn debug_name(&self) -> &'static str {
|
||||
core::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn debug_print(&self, m: &mut SeqFile, prefix: &str, transaction_prefix: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
// Wrapper around a `DeliverToRead` with linked list links.
|
||||
|
@ -169,6 +179,19 @@ impl DeliverToRead for DeliverCode {
|
|||
fn should_sync_wakeup(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn debug_print(&self, m: &mut SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
|
||||
seq_print!(m, "{}", prefix);
|
||||
if self.skip.load(Ordering::Relaxed) {
|
||||
seq_print!(m, "(skipped) ");
|
||||
}
|
||||
if self.code == defs::BR_TRANSACTION_COMPLETE {
|
||||
seq_print!(m, "transaction complete\n");
|
||||
} else {
|
||||
seq_print!(m, "transaction error: {}\n", self.code);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const fn ptr_align(value: usize) -> usize {
|
||||
|
@ -360,7 +383,13 @@ unsafe extern "C" fn rust_binder_stats_show(_: *mut seq_file) -> core::ffi::c_in
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rust_binder_state_show(_: *mut seq_file) -> core::ffi::c_int {
|
||||
unsafe extern "C" fn rust_binder_state_show(ptr: *mut seq_file) -> core::ffi::c_int {
|
||||
// SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which
|
||||
// this method is called.
|
||||
let m = unsafe { SeqFile::from_raw(ptr) };
|
||||
if let Err(err) = rust_binder_state_show_impl(m) {
|
||||
seq_print!(m, "failed to generate state: {:?}\n", err);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
|
@ -373,3 +402,16 @@ unsafe extern "C" fn rust_binder_transactions_show(_: *mut seq_file) -> core::ff
|
|||
unsafe extern "C" fn rust_binder_transaction_log_show(_: *mut seq_file) -> core::ffi::c_int {
|
||||
0
|
||||
}
|
||||
|
||||
fn rust_binder_state_show_impl(m: &mut SeqFile) -> Result<()> {
|
||||
seq_print!(m, "binder state:\n");
|
||||
let contexts = context::get_all_contexts()?;
|
||||
for ctx in contexts {
|
||||
let procs = ctx.get_all_procs()?;
|
||||
for proc in procs {
|
||||
proc.debug_print(m, &ctx)?;
|
||||
seq_print!(m, "\n");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ use kernel::{
|
|||
},
|
||||
prelude::*,
|
||||
security,
|
||||
seq_file::SeqFile,
|
||||
seq_print,
|
||||
sync::poll::{PollCondVar, PollTable},
|
||||
sync::{Arc, SpinLock},
|
||||
task::Task,
|
||||
|
@ -466,6 +468,33 @@ impl Thread {
|
|||
}))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn debug_print(self: &Arc<Self>, m: &mut SeqFile) {
|
||||
let inner = self.inner.lock();
|
||||
|
||||
seq_print!(
|
||||
m,
|
||||
" thread {}: l {:02x} need_return {}\n",
|
||||
self.id,
|
||||
inner.looper_flags,
|
||||
inner.looper_need_return
|
||||
);
|
||||
|
||||
let mut t_opt = inner.current_transaction.clone();
|
||||
while let Some(t) = t_opt {
|
||||
if Arc::ptr_eq(&t.from, self) {
|
||||
t.debug_print_inner(m, " outgoing transaction ");
|
||||
t_opt = t.from_parent.clone();
|
||||
} else if Arc::ptr_eq(&t.to, &self.process) {
|
||||
t.debug_print_inner(m, " incoming transaction ");
|
||||
t_opt = t.find_from(self);
|
||||
} else {
|
||||
t.debug_print_inner(m, " bad transaction ");
|
||||
t_opt = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_extended_error(&self, data: UserSlice) -> Result {
|
||||
let mut writer = data.writer();
|
||||
let ee = self.inner.lock().extended_error;
|
||||
|
@ -1608,6 +1637,16 @@ impl DeliverToRead for ThreadError {
|
|||
fn should_sync_wakeup(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn debug_print(&self, m: &mut SeqFile, prefix: &str, _tprefix: &str) -> Result<()> {
|
||||
seq_print!(
|
||||
m,
|
||||
"{}transaction error: {}\n",
|
||||
prefix,
|
||||
self.error_code.load(Ordering::Relaxed)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
kernel::list::impl_list_arc_safe! {
|
||||
|
|
|
@ -6,6 +6,8 @@ use core::sync::atomic::{AtomicBool, Ordering};
|
|||
use kernel::{
|
||||
list::ListArcSafe,
|
||||
prelude::*,
|
||||
seq_file::SeqFile,
|
||||
seq_print,
|
||||
sync::{Arc, SpinLock},
|
||||
task::Kuid,
|
||||
types::ScopeGuard,
|
||||
|
@ -26,10 +28,11 @@ use crate::{
|
|||
|
||||
#[pin_data(PinnedDrop)]
|
||||
pub(crate) struct Transaction {
|
||||
debug_id: usize,
|
||||
target_node: Option<DArc<Node>>,
|
||||
from_parent: Option<DArc<Transaction>>,
|
||||
pub(crate) from_parent: Option<DArc<Transaction>>,
|
||||
pub(crate) from: Arc<Thread>,
|
||||
to: Arc<Process>,
|
||||
pub(crate) to: Arc<Process>,
|
||||
#[pin]
|
||||
allocation: SpinLock<Option<Allocation>>,
|
||||
is_outstanding: AtomicBool,
|
||||
|
@ -103,6 +106,7 @@ impl Transaction {
|
|||
};
|
||||
|
||||
Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
|
||||
debug_id: super::next_debug_id(),
|
||||
target_node: Some(target_node),
|
||||
from_parent,
|
||||
sender_euid: from.process.cred.euid(),
|
||||
|
@ -142,6 +146,7 @@ impl Transaction {
|
|||
alloc.set_info_clear_on_drop();
|
||||
}
|
||||
Ok(DTRWrap::arc_pin_init(pin_init!(Transaction {
|
||||
debug_id: super::next_debug_id(),
|
||||
target_node: None,
|
||||
from_parent: None,
|
||||
sender_euid: from.process.task.euid(),
|
||||
|
@ -162,6 +167,27 @@ impl Transaction {
|
|||
}))?)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn debug_print_inner(&self, m: &mut SeqFile, prefix: &str) {
|
||||
seq_print!(
|
||||
m,
|
||||
"{}{}: from {}:{} to {} code {:x} flags {:x} pri {}:{}",
|
||||
prefix,
|
||||
self.debug_id,
|
||||
self.from.process.task.pid(),
|
||||
self.from.id,
|
||||
self.to.task.pid(),
|
||||
self.code,
|
||||
self.flags,
|
||||
self.priority.sched_policy,
|
||||
self.priority.prio
|
||||
);
|
||||
if let Some(target_node) = &self.target_node {
|
||||
seq_print!(m, " node {}", target_node.debug_id);
|
||||
}
|
||||
seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size);
|
||||
}
|
||||
|
||||
pub(crate) fn saved_priority(&self) -> BinderPriority {
|
||||
*self.saved_priority.lock()
|
||||
}
|
||||
|
@ -474,6 +500,11 @@ impl DeliverToRead for Transaction {
|
|||
fn should_sync_wakeup(&self) -> bool {
|
||||
self.flags & TF_ONE_WAY == 0
|
||||
}
|
||||
|
||||
fn debug_print(&self, m: &mut SeqFile, _prefix: &str, tprefix: &str) -> Result<()> {
|
||||
self.debug_print_inner(m, tprefix);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pinned_drop]
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <linux/pid_namespace.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <linux/rust_binder.h>
|
||||
|
|
|
@ -49,6 +49,7 @@ pub mod prelude;
|
|||
pub mod print;
|
||||
pub mod rbtree;
|
||||
pub mod security;
|
||||
pub mod seq_file;
|
||||
mod static_assert;
|
||||
#[doc(hidden)]
|
||||
pub mod std_vendor;
|
||||
|
|
47
rust/kernel/seq_file.rs
Normal file
47
rust/kernel/seq_file.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Seq file bindings.
|
||||
//!
|
||||
//! C header: [`include/linux/seq_file.h`](../../../../include/linux/seq_file.h)
|
||||
|
||||
use crate::{bindings, c_str, types::Opaque};
|
||||
|
||||
/// A helper for implementing special files, where the complete contents can be generated on each
|
||||
/// access.
|
||||
pub struct SeqFile(Opaque<bindings::seq_file>);
|
||||
|
||||
impl SeqFile {
|
||||
/// Creates a new [`SeqFile`] from a raw pointer.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that, for the duration of 'a, the pointer must point at a valid
|
||||
/// `seq_file` and that it will not be accessed via anything other than the returned reference.
|
||||
pub unsafe fn from_raw<'a>(ptr: *mut bindings::seq_file) -> &'a mut SeqFile {
|
||||
// SAFETY: The safety requirements guarantee the validity of the dereference, while the
|
||||
// `Credential` type being transparent makes the cast ok.
|
||||
unsafe { &mut *ptr.cast() }
|
||||
}
|
||||
|
||||
/// Used by the [`seq_print`] macro.
|
||||
///
|
||||
/// [`seq_print`]: crate::seq_print
|
||||
pub fn call_printf(&mut self, args: core::fmt::Arguments<'_>) {
|
||||
// SAFETY: Passing a void pointer to `Arguments` is valid for `%pA`.
|
||||
unsafe {
|
||||
bindings::seq_printf(
|
||||
self.0.get(),
|
||||
c_str!("%pA").as_char_ptr(),
|
||||
&args as *const _ as *const core::ffi::c_void,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Use for writing to a [`SeqFile`] with the ordinary Rust formatting syntax.
|
||||
#[macro_export]
|
||||
macro_rules! seq_print {
|
||||
($m:expr, $($arg:tt)+) => (
|
||||
$m.call_printf(format_args!($($arg)+))
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user