rftrace_frontend/
frontend.rs

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