rftrace_frontend/
frontend.rs

1use std::fs::File;
2use std::io::prelude::*;
3use std::io::{self};
4
5use byteorder::{LittleEndian, WriteBytesExt};
6
7use crate::interface::*;
8
9extern "C" {
10    fn rftrace_backend_enable();
11    fn rftrace_backend_disable();
12    fn rftrace_backend_init(bufptr: *mut Event, len: usize, overwriting: bool);
13    fn rftrace_backend_get_events() -> *const Event;
14    fn rftrace_backend_get_events_index() -> usize;
15}
16
17/// Enables tracing in the backend.
18pub fn enable() {
19    unsafe { rftrace_backend_enable() }
20}
21
22/// Disables tracing in the backend.
23pub fn disable() {
24    unsafe { rftrace_backend_disable() }
25}
26
27/// Used to keep track of event buffer given to the staticlib
28#[derive(Copy, Clone, Debug)]
29pub struct Events {
30    ptr: *mut Event,
31    len: usize,
32    cap: usize,
33}
34
35fn get_events(events: &mut Events) -> (Vec<Event>, usize) {
36    // Tell backend to not use the current buffer anymore.
37    let ptr = unsafe { rftrace_backend_get_events() };
38    println!("{:?}, {:?}", ptr, events);
39    assert!(ptr == events.ptr, "Event buffer pointer mismatch!");
40
41    let eventvec = unsafe { Vec::from_raw_parts(events.ptr, events.len, events.cap) };
42
43    let idx = unsafe { rftrace_backend_get_events_index() };
44    (eventvec, idx)
45}
46
47/// Initializes a new event buffer.
48///
49/// Allocs a new buffer of size `max_event_count` and passes it to the backend.
50/// If `overwriting`, treats it as a ring-buffer, keeping only the most-recent entries, otherwise it stopps logging once it is full.
51/// `max_event_count` will not be filled completely, since space is left for the returns of hooked functions.
52/// Currently, the maximum stack-depth is 1000. Consequently, `max_event_count` has to be greater than 1000.
53pub fn init(max_event_count: usize, overwriting: bool) -> &'static mut Events {
54    assert!(
55        max_event_count > MAX_STACK_HEIGHT,
56        "Event buffer has to be larger than maximum stack height!"
57    );
58    let buf = vec![Event::Empty; max_event_count];
59    unsafe {
60        // intentionally leak here! stacks have to live until end of application.
61        let (ptr, len, cap) = buf.into_raw_parts();
62        rftrace_backend_init(ptr, cap, overwriting);
63        // TODO: free this leaked box somewhere. Create a drop() function or similar?
64        Box::leak(Box::new(Events { ptr, len, cap }))
65    }
66}
67
68/// Dumps the traces with some faked metadata into the given folder. Uses the same format as uftrace, which should be used to parse them.
69///
70/// Will NOT generate symbols! You can generate them with `nm -n $BINARY > binary_name.sym`
71///
72/// # Arguments
73///
74/// * `events` - Events buffer to write, returned by `init()`
75/// * `out_dir` - folder into which the resulting trace is dumped. Has to exist.
76/// * `binary_name` - only relevant for this symbol file. Generated metadata instructs uftrace where to look for it.
77///
78pub fn dump_full_uftrace(events: &mut Events, out_dir: &str, binary_name: &str) -> io::Result<()> {
79    // arbitrary values for pid and sid
80    let pid = 42;
81    let sid = "00";
82
83    // First lets create all traces.
84    let tids = dump_traces(events, out_dir, false)?;
85
86    if tids.is_empty() {
87        println!("Trace is empty!");
88        return Ok(());
89    }
90
91    println!("Creating fake uftrace data dir at {}..", out_dir);
92    println!("  Creating ./info");
93    let mut info: Vec<u8> = Vec::new();
94
95    // /info HEADER
96    // magic
97    info.extend("Ftrace!\x00".as_bytes());
98    // version. we are using version 4 of fileformat
99    info.write_u32::<LittleEndian>(4)
100        .expect("Write interrupted");
101    // header size. 0x28 == 40 bytes
102    info.write_u16::<LittleEndian>(40)
103        .expect("Write interrupted");
104    // endinaness = 1
105    info.push(1);
106    // elf_ident[EI_CLASS]. always 2 for 64bit
107    info.push(2);
108    // feature flags
109    println!("    feats = TASK_SESSION | SYM_REL_ADDR");
110    const TASK_SESSION: u64 = 1 << 1; // needed.
111    const SYM_REL_ADDR: u64 = 1 << 5; // enable symbol relocation (important for ASLR on linux)
112    info.write_u64::<LittleEndian>(TASK_SESSION | SYM_REL_ADDR)
113        .expect("Write interrupted");
114    // info flags
115    println!("    info = CMDLINE | TASKINFO");
116    const CMDLINE: u64 = 1 << 3; // needed, else --dump chrome outputs invalid json.
117    const TASKINFO: u64 = 1 << 7; // needed, since uftrace uses this to determine how to interpret task.txt
118    info.write_u64::<LittleEndian>(CMDLINE | TASKINFO)
119        .expect("Write interrupted");
120    // mstack. disable in feature flags, so 0
121    info.write_u16::<LittleEndian>(0)
122        .expect("Write interrupted");
123    // reserved
124    info.write_u16::<LittleEndian>(0)
125        .expect("Write interrupted");
126    info.write_u16::<LittleEndian>(0)
127        .expect("Write interrupted");
128    info.write_u16::<LittleEndian>(0)
129        .expect("Write interrupted");
130    // /info END OF HEADER
131
132    // cmdline
133    println!("    cmdline = 'fakeuftrace'");
134    writeln!(info, "cmdline:fakeuftrace")?;
135    // taskinfo
136    println!("    tid = {:?}", tids);
137    writeln!(info, "taskinfo:lines=2")?;
138    writeln!(info, "taskinfo:nr_tid={}", tids.len())?;
139    write!(info, "taskinfo:tids={}", tids[0])?;
140    for tid in &tids[1..] {
141        write!(info, ",{}", tid)?;
142    }
143    writeln!(info)?;
144
145    let infofile = format!("{}/info", out_dir);
146    let mut infofile = File::create(infofile)?;
147    infofile.write_all(&info[..])?;
148    drop(infofile);
149
150    println!("  Creating ./task.txt");
151    let taskfile = format!("{}/task.txt", out_dir);
152    let mut taskfile = File::create(taskfile)?;
153    println!("    pid = {}", pid);
154    println!("    sid = {}", sid);
155    println!("    exe = {}", binary_name);
156    writeln!(
157        taskfile,
158        "SESS timestamp=0.0 pid={} sid={} exename=\"{}\"",
159        pid, sid, binary_name
160    )?;
161    for tid in tids {
162        writeln!(taskfile, "TASK timestamp=0.0 tid={} pid={}", tid, pid)?;
163    }
164    drop(taskfile);
165
166    let mapfilename = format!("{}/sid-{}.map", out_dir, sid);
167    let mut mapfile = File::create(mapfilename)?;
168    cfg_if::cfg_if! {
169        if #[cfg(target_os = "linux")] {
170            // see uftrace's record_proc_maps(..)
171            // TODO: implement section-merging
172            println!(
173                "  Creating (incorrect) ./sid-{}.map by copying /proc/self/maps",
174                sid
175            );
176            let mut procfile = File::open("/proc/self/maps")?;
177            io::copy(&mut procfile, &mut mapfile)?;
178        } else if #[cfg(target_os = "hermit")] {
179            extern "C" {
180                fn sys_image_start_addr() -> usize;
181            }
182
183            let addr = unsafe { sys_image_start_addr() };
184
185            writeln!(mapfile, "{addr:0>12x}-ffffffffffff r-xp 00000000 00:00 0                          {binary_name}")?;
186            writeln!(mapfile, "ffffffffffff-ffffffffffff rw-p 00000000 00:00 0                          [stack]")?;
187        } else {
188            println!("  Creating ./sid-{sid}.map fake memory map file");
189
190            writeln!(mapfile, "000000000000-ffffffffffff r-xp 00000000 00:00 0                          {binary_name}")?;
191            writeln!(mapfile, "ffffffffffff-ffffffffffff rw-p 00000000 00:00 0                          [stack]")?;
192        }
193    }
194
195    if cfg!(target_os = "linux") {
196        println!(
197            "\nYou should generate symbols with `nm -n $BINARY > {}/$BINARY.sym`",
198            out_dir
199        );
200        println!(
201            "INFO: Linux mode is NOT fully supported yet! To get symbols working, you have to"
202        );
203        println!("      edit the sid-00.map and merge the section for each binary, so that it only occurs once.");
204        println!("      Needs to contain at least [stack] and the binaries you want symbols of.");
205    } else {
206        println!(
207            "\nYou should generate symbols with `nm -n $BINARY > {}/{}.sym`",
208            out_dir, binary_name
209        );
210    }
211
212    Ok(())
213}
214
215/// Dumps only the trace file to disk, without additional metadata.
216///
217/// `events` is the Events buffer as returned by `init`.
218/// outfile is the file into which the output trace is written.
219/// The trace itself has the same format as uftrace, but is not directly parsable due to the missing metadata.
220///
221/// # Format
222/// Packed array of uftrace_record structs
223/// ```c
224/// struct uftrace_record {
225///     uint64_t time;
226///     uint64_t type:   2;
227///     uint64_t more:   1;
228///     uint64_t magic:  3;
229///     uint64_t depth:  10;
230///     uint64_t addr:   48; /* child ip or uftrace_event_id */
231/// };
232pub fn dump_trace(events: &mut Events, outfile: &str) -> io::Result<()> {
233    dump_traces(events, outfile, true)?;
234    Ok(())
235}
236
237fn dump_traces(events: &mut Events, outpath: &str, singlefile: bool) -> io::Result<Vec<u64>> {
238    // Uftraces trace format: a bunch of 64-bit fields, See https://github.com/namhyung/uftrace/wiki/Data-Format
239    //
240    // Array of 2x64 bit unsigned long: `[{time: u64, address: u64}, ...]`
241    // Since addresses are (currently) only using the low 48 bits, metadata (mainly funciton entry/exit) is saved in the remaining 16 bits.
242
243    /* struct uftrace_record {
244        uint64_t time;
245        uint64_t type:   2;
246        uint64_t more:   1;
247        uint64_t magic:  3;
248        uint64_t depth:  10;
249        uint64_t addr:   48; /* child ip or uftrace_event_id */
250    }; */
251
252    // TODO: create enable lock, to ensure no mcount() happens while we read the events array.
253    disable();
254    println!("Saving traces to disk...!");
255
256    let (events, cidx) = get_events(events);
257    let cidx = cidx % events.len();
258
259    // The following is somewhat inefficient, but is intended to solve two constraints:
260    // - don't use too much memory. Here we have ~2x trace array.
261    // - don't have multiple files open at once
262
263    // To avoid to many reallocs, use array with maximum size for all traces.
264    let mut out = Vec::<u8>::with_capacity(16 * events.len());
265
266    // Gather all tids so we can assemble metadata
267    let mut tids: Vec<Option<core::num::NonZeroU64>> = Vec::new();
268    for e in events[cidx..].iter().chain(events[..cidx].iter()) {
269        match e {
270            Event::Exit(e) => {
271                if !tids.contains(&e.tid) {
272                    tids.push(e.tid);
273                }
274            }
275            Event::Entry(e) => {
276                if !tids.contains(&e.tid) {
277                    tids.push(e.tid);
278                }
279            }
280            Event::Empty => {}
281        }
282    }
283
284    // For each TID, loop through the events array and save only the relevant items to disk
285    for current_tid in &tids {
286        // clear out vec in case it contains entries from previous tid
287        out.clear();
288
289        let tid = current_tid.map_or(0, |tid| tid.get());
290
291        println!("  Parsing TID {:?}...!", tid);
292        for e in events[cidx..].iter().chain(events[..cidx].iter()) {
293            match e {
294                Event::Exit(e) => {
295                    if !singlefile && current_tid != &e.tid {
296                        continue;
297                    };
298                    write_event(&mut out, e.time, e.from, 1);
299                }
300                Event::Entry(e) => {
301                    if !singlefile && current_tid != &e.tid {
302                        continue;
303                    };
304                    write_event(&mut out, e.time, e.to, 0);
305                }
306                Event::Empty => {
307                    continue;
308                }
309            }
310        }
311
312        if !out.is_empty() {
313            let filename = if singlefile {
314                outpath.into()
315            } else {
316                let file = format!("{}.dat", tid);
317                format!("{}/{}", outpath, file)
318            };
319
320            println!(
321                "  Writing to disk: {} events, {} bytes ({})",
322                out.len() / 16,
323                out.len(),
324                filename
325            );
326            let mut file = File::create(filename)?;
327            file.write_all(&out[..])?;
328        }
329    }
330    println!("  Parsed all events!");
331
332    // Remove the options from the tids, using 0 for None
333    Ok(tids
334        .iter()
335        .map(|tid| tid.map_or(0, |tid| tid.get()))
336        .collect())
337}
338
339#[allow(clippy::identity_op)]
340#[allow(clippy::erasing_op)]
341fn write_event(out: &mut Vec<u8>, time: u64, addr: *const usize, kind: u64) {
342    out.write_u64::<LittleEndian>(time)
343        .expect("Write interrupted");
344
345    let mut merged: u64 = 0;
346    merged |= (kind & 0b11) << 0; // type = UFTRACE_EXIT / UFTRACE_ENTRY
347    merged |= 0 << 2; // more, always 0
348    merged |= 0b101 << 3; // magic, always 0b101
349    merged |= (0 & ((1 << 10) - 1)) << 6; // depth
350    merged |= (addr as u64 & ((1 << 48) - 1)) << 16; // actual address, limited to 48 bit.
351    out.write_u64::<LittleEndian>(merged)
352        .expect("Write interrupted");
353}