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_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; 224],
81 n: u8,
82}
83
84impl Log {
85 pub fn new() -> Log {
86 Log { buffer: [0; _], n: 0 }
87 }
88
89 fn used(&self) -> usize {
90 self.n as usize
91 }
92
93 fn space(&self) -> usize {
94 self.buffer.len() - self.used()
95 }
96
97 fn flush(&mut self) {
98 let buf = &self.buffer[0..self.used()];
99 self.n = 0;
100
101 // SAFETY: The string was vetted on the way into the buffer.
102 let s = unsafe { str::from_utf8_unchecked(buf) };
103 if !s.is_empty() {
104 log(s)
105 }
106 }
107}
108
109impl Drop for Log {
110 fn drop(&mut self) {
111 self.flush()
112 }
113}
114
115impl core::fmt::Write for Log {
116 fn write_str(&mut self, s: &str) -> core::fmt::Result {
117 let mut left = s;
118 while !left.is_empty() {
119 let to_copy = left.floor_char_boundary(self.space());
120 let used = self.used();
121 let buf = &mut self.buffer[used..(used + to_copy)];
122 let (chunk, rest) = left.split_at(to_copy);
123 left = rest;
124 if chunk.is_empty() {
125 if buf.is_empty() {
126 // Pathological case with no valid UTF-8 chars at all.
127 return core::fmt::Result::Err(core::fmt::Error);
128 }
129 // No space because the buffer is full. Flush and iterate.
130 self.flush()
131 } else {
132 self.n += chunk.len() as u8;
133 buf.copy_from_slice(chunk.as_bytes())
134 }
135 }
136 core::fmt::Result::Ok(())
137 }
138}