1use std::sync::Arc;
2
3use anyhow::{Context, Error, Result};
4
5use remoteprocess::{Pid, ProcessMemory};
6use serde_derive::Serialize;
7
8use crate::config::{Config, LineNo};
9use crate::python_data_access::{copy_bytes, copy_string};
10use crate::python_interpreters::{
11 CodeObject, FrameObject, InterpreterState, ThreadState, TupleObject,
12};
13
14#[derive(Debug, Clone, Serialize)]
16pub struct StackTrace {
17 pub pid: Pid,
19 pub thread_id: u64,
21 pub thread_name: Option<String>,
23 pub os_thread_id: Option<u64>,
25 pub active: bool,
27 pub owns_gil: bool,
29 pub frames: Vec<Frame>,
31 pub process_info: Option<Arc<ProcessInfo>>,
33}
34
35#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize)]
37pub struct Frame {
38 pub name: String,
40 pub filename: String,
42 pub module: Option<String>,
44 pub short_filename: Option<String>,
46 pub line: i32,
48 pub locals: Option<Vec<LocalVariable>>,
50 pub is_entry: bool,
52 pub is_shim_entry: bool,
54}
55
56#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize)]
57pub struct LocalVariable {
58 pub name: String,
59 pub addr: usize,
60 pub arg: bool,
61 pub repr: Option<String>,
62}
63
64#[derive(Debug, Clone, Serialize)]
65pub struct ProcessInfo {
66 pub pid: Pid,
67 pub command_line: String,
68 pub parent: Option<Box<ProcessInfo>>,
69}
70
71pub fn get_stack_traces<I, P>(
73 interpreter_address: usize,
74 process: &P,
75 threadstate_address: usize,
76 config: Option<&Config>,
77) -> Result<Vec<StackTrace>, Error>
78where
79 I: InterpreterState,
80 P: ProcessMemory,
81{
82 let gil_thread_id = get_gil_threadid::<I, P>(threadstate_address, process)?;
83
84 let threadstate_ptr_ptr = I::threadstate_ptr_ptr(interpreter_address);
85 let mut threads: *const I::ThreadState = process
86 .copy_struct(threadstate_ptr_ptr as usize)
87 .context("Failed to copy PyThreadState head pointer")?;
88
89 let mut ret = Vec::new();
90
91 let lineno = config.map(|c| c.lineno).unwrap_or(LineNo::NoLine);
92 let dump_locals = config.map(|c| c.dump_locals).unwrap_or(0);
93
94 while !threads.is_null() {
95 let thread = process
96 .copy_pointer(threads)
97 .context("Failed to copy PyThreadState")?;
98
99 let mut trace = get_stack_trace(&thread, process, dump_locals > 0, lineno)?;
100 trace.owns_gil = trace.thread_id == gil_thread_id;
101
102 ret.push(trace);
103 if ret.len() > 4096 {
105 return Err(format_err!("Max thread recursion depth reached"));
106 }
107 threads = thread.next();
108 }
109 Ok(ret)
110}
111
112pub fn get_stack_trace<T, P>(
114 thread: &T,
115 process: &P,
116 copy_locals: bool,
117 lineno: LineNo,
118) -> Result<StackTrace, Error>
119where
120 T: ThreadState,
121 P: ProcessMemory,
122{
123 let mut frames = Vec::new();
125
126 let mut frame_address = thread.frame_address();
128 if let Some(addr) = frame_address {
129 frame_address = Some(process.copy_struct(addr)?);
130 }
131
132 let mut frame_ptr = thread.frame(frame_address);
133
134 let set_last_frame_as_shim_entry = &mut |frames: &mut Vec<Frame>| {
138 if let Some(frame) = frames.last_mut() {
139 frame.is_shim_entry = true;
140 }
141 };
142
143 while !frame_ptr.is_null() {
144 let frame = process
145 .copy_pointer(frame_ptr)
146 .context("Failed to copy PyFrameObject")?;
147
148 let code = process
149 .copy_pointer(frame.code())
150 .context("Failed to copy PyCodeObject")?;
151
152 let filename = copy_string(code.filename(), process).context("Failed to copy filename");
153 let name = copy_string(code.name(), process).context("Failed to copy function name");
154
155 if filename.is_err() || name.is_err() {
163 frame_ptr = frame.back();
164 set_last_frame_as_shim_entry(&mut frames);
165 continue;
166 }
167 let filename = filename?;
168 let name = name?;
169
170 if filename.is_empty() || filename == "<shim>" {
173 frame_ptr = frame.back();
174 set_last_frame_as_shim_entry(&mut frames);
175 continue;
176 }
177
178 let line = match lineno {
179 LineNo::NoLine => 0,
180 LineNo::First => code.first_lineno(),
181 LineNo::LastInstruction => match get_line_number(&code, frame.lasti(), process) {
182 Ok(line) => line,
183 Err(e) => {
184 warn!(
189 "Failed to get line number from {}.{}: {}",
190 filename, name, e
191 );
192 0
193 }
194 },
195 };
196
197 let locals = if copy_locals {
198 Some(get_locals(&code, frame_ptr, &frame, process)?)
199 } else {
200 None
201 };
202
203 let is_entry = frame.is_entry();
204
205 frames.push(Frame {
206 name,
207 filename,
208 line,
209 short_filename: None,
210 module: None,
211 locals,
212 is_entry,
213 is_shim_entry: false,
214 });
215 if frames.len() > 4096 {
216 return Err(format_err!("Max frame recursion depth reached"));
217 }
218
219 frame_ptr = frame.back();
220 }
221
222 set_last_frame_as_shim_entry(&mut frames);
224
225 Ok(StackTrace {
226 pid: 0,
227 frames,
228 thread_id: thread.thread_id(),
229 thread_name: None,
230 owns_gil: false,
231 active: true,
232 os_thread_id: thread.native_thread_id(),
233 process_info: None,
234 })
235}
236
237impl StackTrace {
238 pub fn status_str(&self) -> &str {
239 match (self.owns_gil, self.active) {
240 (_, false) => "idle",
241 (true, true) => "active+gil",
242 (false, true) => "active",
243 }
244 }
245
246 pub fn format_threadid(&self) -> String {
247 #[cfg(target_os = "macos")]
249 return format!("{:#X}", self.thread_id);
250
251 #[cfg(not(target_os = "macos"))]
253 match self.os_thread_id {
254 Some(tid) => format!("{}", tid),
255 None => format!("{:#X}", self.thread_id),
256 }
257 }
258}
259
260fn get_line_number<C: CodeObject, P: ProcessMemory>(
262 code: &C,
263 lasti: i32,
264 process: &P,
265) -> Result<i32, Error> {
266 let table =
267 copy_bytes(code.line_table(), process).context("Failed to copy line number table")?;
268 Ok(code.get_line_number(lasti, &table))
269}
270
271fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(
272 code: &C,
273 frameptr: *const F,
274 frame: &F,
275 process: &P,
276) -> Result<Vec<LocalVariable>, Error> {
277 let local_count = code.nlocals() as usize;
278 let argcount = code.argcount() as usize;
279 let varnames = process.copy_pointer(code.varnames())?;
280
281 let ptr_size = std::mem::size_of::<*const i32>();
282 let locals_addr = frameptr as usize + std::mem::size_of_val(frame) - ptr_size;
283
284 let mut ret = Vec::new();
285
286 for i in 0..local_count {
287 let nameptr: *const C::StringObject =
288 process.copy_struct(varnames.address(code.varnames() as usize, i))?;
289 let name = copy_string(nameptr, process)?;
290 let addr: usize = process.copy_struct(locals_addr + i * ptr_size)?;
291 if addr == 0 {
292 continue;
293 }
294 ret.push(LocalVariable {
295 name,
296 addr,
297 arg: i < argcount,
298 repr: None,
299 });
300 }
301 Ok(ret)
302}
303
304pub fn get_gil_threadid<I: InterpreterState, P: ProcessMemory>(
305 threadstate_address: usize,
306 process: &P,
307) -> Result<u64, Error> {
308 if threadstate_address == 0 {
310 return Ok(0);
311 }
312
313 let addr = if I::HAS_GIL_RUNTIME_STATE {
314 let gil_state: crate::python_bindings::v3_13_0::_gil_runtime_state =
316 process.copy_struct(threadstate_address)?;
317 if gil_state.locked != 0 {
319 gil_state.last_holder as usize
320 } else {
321 0
322 }
323 } else {
324 process.copy_struct::<usize>(threadstate_address)?
325 };
326
327 let threadid = if addr != 0 {
329 let threadstate: I::ThreadState = process.copy_struct(addr)?;
330 threadstate.thread_id()
331 } else {
332 0
333 };
334
335 Ok(threadid)
336}
337
338impl ProcessInfo {
339 pub fn to_frame(&self) -> Frame {
340 Frame {
341 name: format!("process {}:\"{}\"", self.pid, self.command_line),
342 filename: String::from(""),
343 module: None,
344 short_filename: None,
345 line: 0,
346 locals: None,
347 is_entry: true,
348 is_shim_entry: true,
349 }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use crate::python_bindings::v3_7_0::PyCodeObject;
357 use crate::python_data_access::tests::to_byteobject;
358 use remoteprocess::LocalProcess;
359
360 #[test]
361 fn test_get_line_number() {
362 let mut lnotab = to_byteobject(&[0u8, 1, 10, 1, 8, 1, 4, 1]);
363 let code = PyCodeObject {
364 co_firstlineno: 3,
365 co_lnotab: &mut lnotab.base.ob_base.ob_base,
366 ..Default::default()
367 };
368 let lineno = get_line_number(&code, 30, &LocalProcess).unwrap();
369 assert_eq!(lineno, 7);
370 }
371}