use std;
use failure::{Error, ResultExt};
use remoteprocess::ProcessMemory;
use crate::python_interpreters::{InterpreterState, ThreadState, FrameObject, CodeObject, StringObject, BytesObject};
#[derive(Debug, Clone, Serialize)]
pub struct StackTrace {
pub thread_id: u64,
pub os_thread_id: Option<u64>,
pub active: bool,
pub owns_gil: bool,
pub frames: Vec<Frame>
}
#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize)]
pub struct Frame {
pub name: String,
pub filename: String,
pub module: Option<String>,
pub short_filename: Option<String>,
pub line: i32,
}
pub fn get_stack_traces<I, P>(interpreter: &I, process: &P) -> Result<(Vec<StackTrace>), Error>
where I: InterpreterState, P: ProcessMemory {
let mut ret = Vec::new();
let mut threads = interpreter.head();
while !threads.is_null() {
let thread = process.copy_pointer(threads).context("Failed to copy PyThreadState")?;
ret.push(get_stack_trace(&thread, process)?);
if ret.len() > 4096 {
return Err(format_err!("Max thread recursion depth reached"));
}
threads = thread.next();
}
Ok(ret)
}
pub fn get_stack_trace<T, P >(thread: &T, process: &P) -> Result<StackTrace, Error>
where T: ThreadState, P: ProcessMemory {
let mut frames = Vec::new();
let mut frame_ptr = thread.frame();
while !frame_ptr.is_null() {
let frame = process.copy_pointer(frame_ptr).context("Failed to copy PyFrameObject")?;
let code = process.copy_pointer(frame.code()).context("Failed to copy PyCodeObject")?;
let filename = copy_string(code.filename(), process).context("Failed to copy filename")?;
let name = copy_string(code.name(), process).context("Failed to copy function name")?;
let line = match get_line_number(&code, frame.lasti(), process) {
Ok(line) => line,
Err(e) => {
warn!("Failed to get line number from {}.{}: {}", filename, name, e);
0
}
};
frames.push(Frame{name, filename, line, short_filename: None, module: None});
if frames.len() > 4096 {
return Err(format_err!("Max frame recursion depth reached"));
}
frame_ptr = frame.back();
}
Ok(StackTrace{frames, thread_id: thread.thread_id(), owns_gil: false, active: true, os_thread_id: None})
}
impl StackTrace {
pub fn status_str(&self) -> &str {
match (self.owns_gil, self.active) {
(_, false) => "idle",
(true, true) => "active+gil",
(false, true) => "active",
}
}
}
fn get_line_number<C: CodeObject, P: ProcessMemory>(code: &C, lasti: i32, process: &P) -> Result<i32, Error> {
let table = copy_bytes(code.lnotab(), process).context("Failed to copy line number table")?;
let size = table.len();
let mut i = 0;
let mut line_number: i32 = code.first_lineno();
let mut bytecode_address: i32 = 0;
while (i + 1) < size {
bytecode_address += i32::from(table[i]);
if bytecode_address > lasti {
break;
}
line_number += i32::from(table[i + 1]);
i += 2;
}
Ok(line_number)
}
pub fn copy_string<T: StringObject, P: ProcessMemory>(ptr: * const T, process: &P) -> Result<String, Error> {
let obj = process.copy_pointer(ptr)?;
if obj.size() >= 4096 {
return Err(format_err!("Refusing to copy {} chars of a string", obj.size()));
}
let kind = obj.kind();
let bytes = process.copy(obj.address(ptr as usize), obj.size() * kind as usize)?;
match (kind, obj.ascii()) {
(4, _) => {
#[allow(clippy::cast_ptr_alignment)]
let chars = unsafe { std::slice::from_raw_parts(bytes.as_ptr() as * const char, bytes.len() / 4) };
Ok(chars.iter().collect())
},
(2, _) => {
Err(format_err!("ucs2 strings aren't supported yet!"))
},
(1, true) => Ok(String::from_utf8(bytes)?),
(1, false) => Ok(bytes.iter().map(|&b| { b as char }).collect()),
_ => Err(format_err!("Unknown string kind {}", kind))
}
}
pub fn copy_bytes<T: BytesObject, P: ProcessMemory>(ptr: * const T, process: &P) -> Result<Vec<u8>, Error> {
let obj = process.copy_pointer(ptr)?;
let size = obj.size();
if size >= 65536 {
return Err(format_err!("Refusing to copy {} bytes", size));
}
Ok(process.copy(obj.address(ptr as usize), size as usize)?)
}
#[cfg(test)]
mod tests {
use super::*;
use remoteprocess::LocalProcess;
use python_bindings::v3_7_0::{PyCodeObject, PyBytesObject, PyVarObject, PyUnicodeObject, PyASCIIObject};
use std::ptr::copy_nonoverlapping;
#[allow(dead_code)]
struct AllocatedPyByteObject {
base: PyBytesObject,
storage: [u8; 4096]
}
#[allow(dead_code)]
struct AllocatedPyASCIIObject {
base: PyASCIIObject,
storage: [u8; 4096]
}
fn to_byteobject(bytes: &[u8]) -> AllocatedPyByteObject {
let ob_size = bytes.len() as isize;
let base = PyBytesObject{ob_base: PyVarObject{ob_size, ..Default::default()}, ..Default::default()};
let mut ret = AllocatedPyByteObject{base, storage: [0 as u8; 4096]};
unsafe { copy_nonoverlapping(bytes.as_ptr(), ret.base.ob_sval.as_mut_ptr() as *mut u8, bytes.len()); }
ret
}
fn to_asciiobject(input: &str) -> AllocatedPyASCIIObject {
let bytes: Vec<u8> = input.bytes().collect();
let mut base = PyASCIIObject{length: bytes.len() as isize, ..Default::default()};
base.state.set_compact(1);
base.state.set_kind(1);
base.state.set_ascii(1);
let mut ret = AllocatedPyASCIIObject{base, storage: [0 as u8; 4096]};
unsafe {
let ptr = &mut ret as *mut AllocatedPyASCIIObject as *mut u8;
let dst = ptr.offset(std::mem::size_of::<PyASCIIObject>() as isize);
copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len());
}
ret
}
#[test]
fn test_get_line_number() {
let mut lnotab = to_byteobject(&[0u8, 1, 10, 1, 8, 1, 4, 1]);
let code = PyCodeObject{co_firstlineno: 3,
co_lnotab: &mut lnotab.base.ob_base.ob_base,
..Default::default()};
let lineno = get_line_number(&code, 30, &LocalProcess).unwrap();
assert_eq!(lineno, 7);
}
#[test]
fn test_copy_string() {
let original = "function_name";
let obj = to_asciiobject(original);
let unicode: &PyUnicodeObject = unsafe{ std::mem::transmute(&obj.base) };
let copied = copy_string(unicode, &LocalProcess).unwrap();
assert_eq!(copied, original);
}
#[test]
fn test_copy_bytes() {
let original = [10_u8, 20, 30, 40, 50, 70, 80];
let bytes = to_byteobject(&original);
let copied = copy_bytes(&bytes.base, &LocalProcess).unwrap();
assert_eq!(copied, original);
}
}