Skip to main content

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}