1use std;
2use std::sync::Arc;
3
4use anyhow::{Context, Error, Result};
5
6use remoteprocess::{ProcessMemory, Pid, Process};
7use serde_derive::Serialize;
8
9use crate::python_interpreters::{InterpreterState, ThreadState, FrameObject, CodeObject, TupleObject};
10use crate::python_data_access::{copy_string, copy_bytes};
11use crate::config::LineNo;
12
13#[derive(Debug, Clone, Serialize)]
15pub struct StackTrace {
16 pub pid: Pid,
18 pub thread_id: u64,
20 pub thread_name: Option<String>,
22 pub os_thread_id: Option<u64>,
24 pub active: bool,
26 pub owns_gil: bool,
28 pub frames: Vec<Frame>,
30 pub process_info: Option<Arc<ProcessInfo>>
32}
33
34#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize)]
36pub struct Frame {
37 pub name: String,
39 pub filename: String,
41 pub module: Option<String>,
43 pub short_filename: Option<String>,
45 pub line: i32,
47 pub locals: Option<Vec<LocalVariable>>,
49}
50
51#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize)]
52pub struct LocalVariable {
53 pub name: String,
54 pub addr: usize,
55 pub arg: bool,
56 pub repr: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize)]
60pub struct ProcessInfo {
61 pub pid: Pid,
62 pub command_line: String,
63 pub parent: Option<Box<ProcessInfo>>
64}
65
66pub fn get_stack_traces<I>(interpreter: &I, process: &Process, lineno: LineNo) -> Result<Vec<StackTrace>, Error>
68 where I: InterpreterState {
69 let mut ret = Vec::new();
71 let mut threads = interpreter.head();
72 while !threads.is_null() {
73 let thread = process.copy_pointer(threads).context("Failed to copy PyThreadState")?;
74 ret.push(get_stack_trace(&thread, process, false, lineno)?);
75 if ret.len() > 4096 {
77 return Err(format_err!("Max thread recursion depth reached"));
78 }
79 threads = thread.next();
80 }
81 Ok(ret)
82}
83
84pub fn get_stack_trace<T>(thread: &T, process: &Process, copy_locals: bool, lineno: LineNo) -> Result<StackTrace, Error>
86 where T: ThreadState {
87 let mut frames = Vec::new();
89
90 let mut frame_address = thread.frame_address();
92 if let Some(addr) = frame_address {
93 frame_address = Some(process.copy_struct(addr)?);
94 }
95
96 let mut frame_ptr = thread.frame(frame_address);
97 while !frame_ptr.is_null() {
98 let frame = process.copy_pointer(frame_ptr).context("Failed to copy PyFrameObject")?;
99 let code = process.copy_pointer(frame.code()).context("Failed to copy PyCodeObject")?;
100
101 let filename = copy_string(code.filename(), process).context("Failed to copy filename")?;
102 let name = copy_string(code.name(), process).context("Failed to copy function name")?;
103
104 let line = match lineno {
105 LineNo::NoLine => 0,
106 LineNo::FirstLineNo => code.first_lineno(),
107 LineNo::LastInstruction => match get_line_number(&code, frame.lasti(), process) {
108 Ok(line) => line,
109 Err(e) => {
110 warn!("Failed to get line number from {}.{}: {}", filename, name, e);
115 0
116 }
117 }
118 };
119
120 let locals = if copy_locals {
121 Some(get_locals(&code, frame_ptr, &frame, process)?)
122 } else {
123 None
124 };
125
126 frames.push(Frame{name, filename, line, short_filename: None, module: None, locals});
127 if frames.len() > 4096 {
128 return Err(format_err!("Max frame recursion depth reached"));
129 }
130
131 frame_ptr = frame.back();
132 }
133
134 Ok(StackTrace{pid: process.pid, frames, thread_id: thread.thread_id(), thread_name: None, owns_gil: false, active: true, os_thread_id: None, process_info: None})
135}
136
137impl StackTrace {
138 pub fn status_str(&self) -> &str {
139 match (self.owns_gil, self.active) {
140 (_, false) => "idle",
141 (true, true) => "active+gil",
142 (false, true) => "active",
143 }
144 }
145
146 pub fn format_threadid(&self) -> String {
147 #[cfg(target_os="macos")]
149 return format!("{:#X}", self.thread_id);
150
151 #[cfg(not(target_os="macos"))]
153 match self.os_thread_id {
154 Some(tid) => format!("{}", tid),
155 None => format!("{:#X}", self.thread_id)
156 }
157 }
158}
159
160fn get_line_number<C: CodeObject, P: ProcessMemory>(code: &C, lasti: i32, process: &P) -> Result<i32, Error> {
162 let table = copy_bytes(code.line_table(), process).context("Failed to copy line number table")?;
163 Ok(code.get_line_number(lasti, &table))
164}
165
166
167fn get_locals<C: CodeObject, F: FrameObject, P: ProcessMemory>(code: &C, frameptr: *const F, frame: &F, process: &P)
168 -> Result<Vec<LocalVariable>, Error> {
169 let local_count = code.nlocals() as usize;
170 let argcount = code.argcount() as usize;
171 let varnames = process.copy_pointer(code.varnames())?;
172
173 let ptr_size = std::mem::size_of::<*const i32>();
174 let locals_addr = frameptr as usize + std::mem::size_of_val(frame) - ptr_size;
175
176 let mut ret = Vec::new();
177
178 for i in 0..local_count {
179 let nameptr: *const C::StringObject = process.copy_struct(varnames.address(code.varnames() as usize, i))?;
180 let name = copy_string(nameptr, process)?;
181 let addr: usize = process.copy_struct(locals_addr + i * ptr_size)?;
182 if addr == 0 {
183 continue;
184 }
185 ret.push(LocalVariable{name, addr, arg: i < argcount, repr: None});
186 }
187 Ok(ret)
188}
189
190impl ProcessInfo {
191 pub fn to_frame(&self) -> Frame {
192 Frame{name: format!("process {}:\"{}\"", self.pid, self.command_line),
193 filename: String::from(""),
194 module: None, short_filename: None, line: 0, locals: None}
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use remoteprocess::LocalProcess;
202 use crate::python_bindings::v3_7_0::{PyCodeObject};
203 use crate::python_data_access::tests::to_byteobject;
204
205 #[test]
206 fn test_get_line_number() {
207 let mut lnotab = to_byteobject(&[0u8, 1, 10, 1, 8, 1, 4, 1]);
208 let code = PyCodeObject{co_firstlineno: 3,
209 co_lnotab: &mut lnotab.base.ob_base.ob_base,
210 ..Default::default()};
211 let lineno = get_line_number(&code, 30, &LocalProcess).unwrap();
212 assert_eq!(lineno, 7);
213 }
214}