1use crate::output::{Frame, Output};
2use crate::pipe_io::Record;
3use crate::resolver::Resolver;
4use crate::{executor, resolver};
5use indexmap::{IndexMap, IndexSet};
6use std::ffi::OsStr;
7use std::fs::OpenOptions;
8use std::io;
9use std::path::Path;
10use thiserror::Error;
11
12#[derive(Error, Debug)]
13pub enum Error {
14 #[error("Execution failed")]
15 Exec(#[from] executor::Error),
16 #[error("IO error")]
17 Io(#[from] io::Error),
18 #[error("Resolver")]
19 Resolver(#[from] resolver::Error),
20 #[error("Custom error: {0}")]
21 Custom(String),
22}
23
24#[derive(Default)]
25struct MemStats {
26 allocations: u64,
27 leaked_allocations: u64,
28 tmp_allocations: u64,
29}
30
31#[derive(Hash, PartialEq, Eq)]
32struct AllocationInfo {
33 size: u64,
34 trace_idx: u64,
35}
36
37const PAGE_SIZE: u64 = u16::MAX as u64 / 4;
38
39struct SplitPointer {
40 big: u64,
41 small: u16,
42}
43
44impl SplitPointer {
45 pub fn new(ptr: u64) -> Self {
46 Self {
47 big: ptr / PAGE_SIZE,
48 small: (ptr % PAGE_SIZE) as u16,
49 }
50 }
51}
52
53#[derive(Default)]
54struct Indices {
55 small_ptr_parts: Vec<u16>,
56 allocation_indices: Vec<usize>,
57}
58
59pub struct Interpreter {
60 output: Output,
61 strings: IndexSet<String>,
62 frames: IndexSet<u64>,
63 pointers: IndexMap<u64, Indices>,
64 allocation_info: IndexSet<AllocationInfo>,
65 resolver: Resolver,
66 stats: MemStats,
67 last_ptr: usize,
68}
69
70impl Interpreter {
71 pub fn new(out_filepath: impl AsRef<Path>) -> io::Result<Self> {
72 let file = OpenOptions::new()
73 .write(true)
74 .truncate(true)
75 .create(true)
76 .open(out_filepath)?;
77
78 Ok(Self {
79 output: Output::new(file),
80 strings: IndexSet::new(),
81 frames: IndexSet::new(),
82 pointers: IndexMap::new(),
83 allocation_info: IndexSet::new(),
84 resolver: Resolver::new(),
85 stats: MemStats::default(),
86 last_ptr: 0,
87 })
88 }
89
90 pub fn exec<S, P>(
91 &mut self,
92 program: S,
93 args: impl IntoIterator<Item = S>,
94 cwd: P,
95 lib_path: &str,
96 ) -> Result<(), Error>
97 where
98 S: AsRef<OsStr>,
99 P: AsRef<Path>,
100 {
101 let mut exec = executor::exec_cmd(program, args, cwd, lib_path);
102
103 while let Some(item) = exec.next() {
104 let record = item?;
105
106 self.handle_record(record)?;
107 }
108
109 self.write_comments()?;
110
111 self.output.flush()?;
112
113 Ok(())
114 }
115
116 fn handle_record(&mut self, record: Record) -> Result<(), Error> {
117 match record {
118 Record::Version(version) => {
119 self.output.write_version(version, 3)?;
120 }
121 Record::Exec(cmd) => {
122 self.output.write_exec(&cmd)?;
123 }
124 Record::Image {
125 name,
126 start_address,
127 size,
128 } => {
129 let module_id = self.write_string(&name)?;
130 _ = self.resolver.add_module(
131 module_id,
132 &name,
133 start_address as u64,
134 start_address as u64 + size as u64,
135 );
136 }
137 Record::PageInfo { size, pages } => {
138 self.output.write_page_info(size, pages as u64)?;
139 }
140 Record::Trace { ip, parent_idx } => {
141 let ip_id = self.add_frame(ip as u64)?;
142 self.output.write_trace(ip_id, parent_idx as u64)?;
143 }
144 Record::Alloc {
145 ptr,
146 size,
147 parent_idx,
148 } => {
149 self.stats.allocations += 1;
150 self.stats.leaked_allocations += 1;
151
152 let idx = self.add_alloc(size as u64, parent_idx as u64)?;
153
154 self.add_pointer(ptr as u64, idx as u64);
155 self.last_ptr = ptr;
156 self.output.write_alloc(idx)?;
157 }
158 Record::Free { ptr } => {
159 let temporary = self.last_ptr == ptr;
160 self.last_ptr = 0;
161
162 let Some(allocation_idx) = self.take_pointer(ptr as u64) else {
163 return Ok(());
164 };
165
166 self.output.write_free(allocation_idx)?;
167
168 if temporary {
169 self.stats.tmp_allocations += 1;
170 }
171 self.stats.leaked_allocations -= 1;
172 }
173 Record::Duration(duration) => {
174 self.output.write_duration(duration)?;
175 }
176 Record::RSS(rss) => {
177 self.output.write_rss(rss)?;
178 }
179 }
180
181 Ok(())
182 }
183
184 fn add_frame(&mut self, ip: u64) -> Result<usize, Error> {
185 match self.frames.get_full(&ip) {
186 None => {
187 let (id, _) = self.frames.insert_full(ip);
188
189 let Some(result) = self.resolver.lookup(ip) else {
190 return Err(Error::Custom("ip locations not found".to_string()));
191 };
192
193 let mut frames = Vec::with_capacity(result.locations.len());
194
195 for location in result.locations {
196 let function_idx = self.write_string(&location.function_name)?;
197
198 let frame = if location.file_name.is_some() {
199 let file_idx = self.write_string(
200 &location
201 .file_name
202 .ok_or_else(|| Error::Custom("empty file name".into()))?,
203 )?;
204
205 let line_number = location
206 .line_number
207 .ok_or_else(|| Error::Custom("empty line number".into()))?;
208
209 Frame::Multiple {
210 function_idx,
211 file_idx,
212 line_number,
213 }
214 } else {
215 Frame::Single { function_idx }
216 };
217
218 frames.push(frame);
219 }
220
221 self.output
222 .write_instruction(ip, result.module_id, &frames)?;
223
224 Ok(id + 1)
225 }
226 Some((id, _)) => Ok(id + 1),
227 }
228 }
229
230 fn add_alloc(&mut self, size: u64, parent_idx: u64) -> Result<usize, Error> {
231 let info = AllocationInfo {
232 size,
233 trace_idx: parent_idx,
234 };
235
236 match self.allocation_info.get_full(&info) {
237 None => {
238 let (idx, _) = self.allocation_info.insert_full(info);
239
240 self.output.write_trace_alloc(size, parent_idx as usize)?;
241
242 Ok(idx)
243 }
244 Some((idx, _)) => Ok(idx),
245 }
246 }
247
248 fn add_pointer(&mut self, ptr: u64, allocation_idx: u64) {
249 let pointer = SplitPointer::new(ptr);
250
251 let indices = self.pointers.entry(pointer.big).or_default();
252
253 match indices
254 .small_ptr_parts
255 .iter()
256 .position(|&i| i == pointer.small)
257 {
258 None => {
259 indices.small_ptr_parts.push(pointer.small);
260 indices.allocation_indices.push(allocation_idx as usize);
261 }
262 Some(idx) => {
263 indices.allocation_indices[idx] = allocation_idx as usize;
264 }
265 }
266 }
267
268 fn take_pointer(&mut self, ptr: u64) -> Option<usize> {
269 let pointer = SplitPointer::new(ptr);
270 let indices = self.pointers.get_mut(&pointer.big)?;
271
272 let idx = indices
273 .small_ptr_parts
274 .iter()
275 .position(|&i| i == pointer.small)?;
276 let allocation_idx = indices.allocation_indices[idx];
277
278 indices.small_ptr_parts.swap_remove(idx);
279 indices.allocation_indices.swap_remove(idx);
280 if indices.allocation_indices.is_empty() {
281 self.pointers.swap_remove(&pointer.big);
282 }
283
284 Some(allocation_idx)
285 }
286
287 fn write_string(&mut self, value: &str) -> Result<usize, Error> {
288 match self.strings.get_full(value) {
289 None => {
290 let (id, _) = self.strings.insert_full(value.to_string());
291 self.output.write_string(value)?;
292
293 Ok(id + 1)
294 }
295 Some((id, _)) => Ok(id + 1),
296 }
297 }
298
299 fn write_comments(&mut self) -> Result<(), Error> {
300 self.output.write("")?;
301
302 self.output
303 .write_comment(&format!("strings: {}", self.strings.len()))?;
304 self.output
305 .write_comment(&format!("ips: {}", self.frames.len()))?;
306
307 Ok(())
308 }
309}