zx_libc/sanitizer.rs
1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::ffi::{CStr, c_char, c_size_t};
6use zx::sys::{ZX_LOG_RECORD_DATA_MAX, zx_handle_t};
7use zx::{NullableHandle, Vmo};
8
9// <zircon/sanitizer.h>
10unsafe extern "C" {
11 fn __sanitizer_log_write(string: *const c_char, len: c_size_t) -> ();
12
13 fn __sanitizer_publish_data(sink_name: *const c_char, vmo: zx_handle_t) -> zx_handle_t;
14
15 fn __sanitizer_fast_backtrace(pc_buffer: *mut usize, max_frames: c_size_t) -> c_size_t;
16}
17
18/// Write logging information from the sanitizer runtime. The string is
19/// expected to be printable text with '\n' ending each line. Timestamps and
20/// globally unique identifiers of the calling process and thread (zx::Koid)
21/// are attached to all messages, so there is no need to include those details
22/// in the text. The log of messages written with this call automatically
23/// includes address and ELF build ID details of the program and all shared
24/// libraries sufficient to translate raw address values into program symbols
25/// or source locations via a post-processor that has access to the original
26/// ELF files and their debugging information. The text can contain markup
27/// around address values that should be resolved symbolically.
28pub fn log(string: &str) {
29 // SAFETY: Basic ffi call.
30 unsafe { __sanitizer_log_write(string.as_ptr() as *const c_char, string.len() as c_size_t) }
31}
32
33/// Runtimes that have binary data to publish (e.g. coverage) use this
34/// interface. The name describes the data sink that will receive this blob of
35/// data; the string is not used after this call returns. The caller creates a
36/// VMO and passes it in. Each particular data sink has its own conventions
37/// about both the format of the data in the VMO and the protocol for when data
38/// must be written there. For some sinks, the VMO's data is used immediately.
39/// For other sinks, the caller is expected to have the VMO mapped in and be
40/// writing more data there throughout the life of the process, to be analyzed
41/// only after the process terminates. Yet others might use an asynchronous
42/// shared memory protocol between producer and consumer. The return value is
43/// either the null handle or a Zircon handle whose lifetime is used to signal
44/// the readiness of the data in the VMO. This handle can be dropped to
45/// indicate the data is ready to be consumed. Or the handle can safely be
46/// leaked; the data will be ready when the process exits. Note there is no
47/// indication of success or failure returned here (though it may be logged).
48/// A null handle return value merely indicates there is no way to communicate
49/// data readiness before process exit.
50pub fn publish_data(sink_name: &CStr, vmo: Vmo) -> NullableHandle {
51 // SAFETY: Basic ffi call.
52 unsafe {
53 let h = __sanitizer_publish_data(sink_name.as_ptr(), vmo.into_raw());
54 NullableHandle::from_raw(h)
55 }
56}
57
58/// This does a fast, best-effort attempt to collect a backtrace. It writes PC
59/// values (return addresses) into the pc_buffer, and returns the subslice of
60/// frames collected. The first frame (pc_buffer[0]) will be fast_backtrace()
61/// itself (and that's the only frame guaranteed to be collected), the second
62/// will be that frame's caller, and so on. This is safe even if register and
63/// memory state is bogus. It's best-effort; results will be imprecise in the
64/// face of code that doesn't use either shadow-call-stack or frame pointers.
65pub fn fast_backtrace(pc_buffer: &mut [usize]) -> &mut [usize] {
66 // SAFETY: Basic ffi call.
67 unsafe {
68 let n = __sanitizer_fast_backtrace(pc_buffer.as_mut_ptr(), pc_buffer.len());
69 &mut pc_buffer[0..n]
70 }
71}
72
73/// This is an ephemeral object that implements the core::fmt::Write trait.
74/// It's used as `write!(&zx_libc::sanitizer::Log::new(), "...", ...)` to send
75/// a single logging line. The object holds a fixed buffer that is used to
76/// collect the multiple fragments from formatters; it's written using
77/// `zx_libc::sanitizer::log()` when the buffer fills or the object is dropped.
78#[derive(Debug)]
79pub struct Log {
80 buffer: [u8; ZX_LOG_RECORD_DATA_MAX],
81 used: usize,
82}
83
84impl Log {
85 pub fn new() -> Log {
86 Log { buffer: [0; _], used: 0 }
87 }
88
89 fn space(&self) -> usize {
90 self.buffer.len() - self.used
91 }
92
93 fn flush(&mut self) {
94 let buf = &self.buffer[0..self.used];
95 self.used = 0;
96
97 // SAFETY: The string was vetted on the way into the buffer.
98 let s = unsafe { str::from_utf8_unchecked(buf) };
99 if !s.is_empty() {
100 log(s)
101 }
102 }
103}
104
105impl Drop for Log {
106 fn drop(&mut self) {
107 self.flush()
108 }
109}
110
111impl core::fmt::Write for Log {
112 fn write_str(&mut self, s: &str) -> core::fmt::Result {
113 let mut left = s;
114 while !left.is_empty() {
115 // Find a newline in `left` that fits within the remaining space in
116 // the buffer. If found, chunk up to the newline. Otherwise, take
117 // as many full UTF-8 code points as can fit in the remaining space.
118 let (to_copy, has_newline) = match left.find('\n') {
119 Some(pos) if pos <= self.space() => (pos, true),
120 _ => (left.floor_char_boundary(self.space()), false),
121 };
122
123 // If no characters fit and there is no newline to complete a line,
124 // flush the buffer to make room.
125 if to_copy == 0 && !has_newline {
126 if self.used == 0 {
127 // Pathological case with no valid UTF-8 chars at all.
128 return Err(core::fmt::Error);
129 }
130 self.flush();
131 continue;
132 }
133
134 // Copy the chunk into the buffer.
135 let (chunk, rest) = left.split_at(to_copy);
136 let buf = &mut self.buffer[self.used..self.used + to_copy];
137 buf.copy_from_slice(chunk.as_bytes());
138 self.used += to_copy;
139
140 if has_newline {
141 // Skip the newline character and flush the completed log line.
142 left = &rest[1..];
143 self.flush();
144 } else {
145 left = rest;
146 // If the buffer is now full, flush it to free up space for
147 // subsequent writes.
148 if self.space() == 0 {
149 self.flush();
150 }
151 }
152 }
153 Ok(())
154 }
155}