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_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}