From 22acdffa4fa068d22187b5f38a79c47fd575aad1 Mon Sep 17 00:00:00 2001 From: Ben Frederickson Date: Thu, 31 Oct 2024 14:09:09 -0700 Subject: [PATCH] Fix profiling when python symbols aren't available. Since python 3.10 - we haven't been able to profile python interpreters that have been compiled without symbols. This is because cpython changed where the 'PyRuntime' global is stored in python 3.10, from being in the BSS section into being in its own named section in the binary. This especially affected profiling on windows, where you'd have to install python symbols to be able to use py-spy. Fix by reading in the address/size of the the PyRuntime section from the elf/mach/pe binaries and using that to scan python interpreters when symbols aren't available. --- src/binary_parser.rs | 96 ++++++++++++++++++++++++++++---------- src/python_process_info.rs | 15 ++++++ 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/binary_parser.rs b/src/binary_parser.rs index e9bd52a4..a0b3f688 100644 --- a/src/binary_parser.rs +++ b/src/binary_parser.rs @@ -12,6 +12,8 @@ pub struct BinaryInfo { pub symbols: HashMap, pub bss_addr: u64, pub bss_size: u64, + pub pyruntime_addr: u64, + pub pyruntime_size: u64, #[allow(dead_code)] pub addr: u64, #[allow(dead_code)] @@ -65,11 +67,23 @@ pub fn parse_binary(filename: &Path, addr: u64, size: u64) -> Result Result Result Result Result Err(format_err!("Unhandled binary type")), } diff --git a/src/python_process_info.rs b/src/python_process_info.rs index 65befeb1..55b8cbef 100644 --- a/src/python_process_info.rs +++ b/src/python_process_info.rs @@ -437,6 +437,21 @@ fn get_interpreter_address_from_binary

( where P: ProcessMemory, { + // First check the pyruntime section it was found + if binary.pyruntime_addr != 0 { + let bss = process.copy( + binary.pyruntime_addr as usize, + binary.pyruntime_size as usize, + )?; + #[allow(clippy::cast_ptr_alignment)] + let addrs = unsafe { + slice::from_raw_parts(bss.as_ptr() as *const usize, bss.len() / size_of::()) + }; + if let Ok(addr) = check_interpreter_addresses(addrs, maps, process, version) { + return Ok(addr); + } + } + // We're going to scan the BSS/data section for things, and try to narrowly scan things that // look like pointers to PyinterpreterState let bss = process.copy(binary.bss_addr as usize, binary.bss_size as usize)?;