@ -3,6 +3,7 @@ use libc::pid_t;
use object ::{ Object , ObjectSection , ObjectSymbol } ;
use std ::{
borrow ::Cow ,
collections ::HashMap ,
error ::Error ,
ffi ::CStr ,
fs ,
@ -10,6 +11,7 @@ use std::{
mem ,
os ::{ fd ::AsFd as _ , raw ::c_char } ,
path ::{ Path , PathBuf } ,
str ::FromStr ,
sync ::Arc ,
} ;
use thiserror ::Error ;
@ -136,12 +138,17 @@ fn resolve_attach_path<T: AsRef<Path>>(
} ;
let target_str = target . to_str ( ) . ok_or_else ( invalid_target ) ? ;
pid . and_then ( | pid | {
find_lib_in_proc_maps ( pid , target_str )
. map_err ( | io_error | UProbeError ::FileError {
filename : format ! ( "/proc/{pid}/maps" ) ,
io_error ,
ProcMap ::new ( pid )
. map_err ( | source | UProbeError ::ProcMapError { pid , source } )
. and_then ( | proc_map_libs | {
proc_map_libs
. find_library_path_by_name ( target_str )
. map_err ( | io_error | UProbeError ::FileError {
filename : format ! ( "/proc/{pid}/maps" ) ,
io_error ,
} )
. map ( | v | v . map ( Cow ::Owned ) )
} )
. map ( | v | v . map ( Cow ::Owned ) )
. transpose ( )
} )
. or_else ( | | target . is_absolute ( ) . then ( | | Ok ( Cow ::Borrowed ( target_str ) ) ) )
@ -247,43 +254,17 @@ pub enum UProbeError {
#[ source ]
io_error : io ::Error ,
} ,
}
fn proc_maps_libs ( pid : pid_t ) -> Result < Vec < ( String , String ) > , io ::Error > {
let maps_file = format! ( "/proc/{pid}/maps" ) ;
let data = fs ::read_to_string ( maps_file ) ? ;
Ok ( data
. lines ( )
. filter_map ( | line | {
let line = line . split_whitespace ( ) . last ( ) ? ;
if line . starts_with ( '/' ) {
let path = PathBuf ::from ( line ) ;
let key = path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) . into_owned ( ) ;
Some ( ( key , path . to_string_lossy ( ) . to_string ( ) ) )
} else {
None
}
} )
. collect ( ) )
}
fn find_lib_in_proc_maps ( pid : pid_t , lib : & str ) -> Result < Option < String > , io ::Error > {
let libs = proc_maps_libs ( pid ) ? ;
let ret = if lib . contains ( ".so" ) {
libs . into_iter ( ) . find ( | ( k , _ ) | k . as_str ( ) . starts_with ( lib ) )
} else {
libs . into_iter ( ) . find ( | ( k , _ ) | {
k . strip_prefix ( lib )
. map ( | k | k . starts_with ( ".so" ) | | k . starts_with ( '-' ) )
. unwrap_or_default ( )
} )
} ;
Ok ( ret . map ( | ( _ , v ) | v ) )
/// There was en error resolving a path
#[ error( " error fetching libs for {pid} " ) ]
ProcMapError {
/// The pid
pid : i32 ,
/// The [`ProcMapError`] that caused the error
#[ source ]
source : ProcMapError ,
} ,
}
#[ derive(Debug) ]
pub ( crate ) struct CacheEntry {
key : String ,
@ -450,3 +431,228 @@ fn resolve_symbol(path: &str, symbol: &str) -> Result<u64, ResolveSymbolError> {
Ok ( sym . address ( ) - section . address ( ) + offset )
}
}
/// Error reading from /proc/pid/maps
#[ derive(Debug, Error) ]
pub enum ProcMapError {
/// An [`io::Error`]
#[ error(transparent) ]
IoError ( io ::Error ) ,
/// Error parsing a line of /proc/pid/maps
#[ error( " proc map entry parse error " ) ]
ParseError ,
}
/// The memory maps of a process.
///
/// This is read from /proc/`pid`/maps.
/// The information here may be used to resolve addresses to paths
pub struct ProcMap {
entries : Vec < ProcMapEntry > ,
libraries : HashMap < String , String > ,
}
impl ProcMap {
/// Create a new `ProcMap` from a given pid.
pub fn new ( pid : pid_t ) -> Result < Self , ProcMapError > {
let maps_file = format! ( "/proc/{}/maps" , pid ) ;
let data = fs ::read_to_string ( maps_file ) . map_err ( ProcMapError ::IoError ) ? ;
let mut entries = vec! [ ] ;
let mut libraries = HashMap ::new ( ) ;
for line in data . lines ( ) {
let entry = ProcMapEntry ::from_str ( line ) ? ;
if let Some ( path ) = & entry . path {
let p = PathBuf ::from ( path ) ;
let filename = p . file_name ( ) . unwrap ( ) . to_string_lossy ( ) . into_owned ( ) ;
let library_path = p . to_string_lossy ( ) . to_string ( ) ;
libraries . entry ( filename ) . or_insert ( library_path ) ;
}
entries . push ( entry ) ;
}
Ok ( ProcMap { entries , libraries } )
}
// Find the full path of a library by its name.
// This isn't part of the public API since it's really only useful for
// attaching uprobes.
fn find_library_path_by_name ( & self , lib : & str ) -> Result < Option < String > , io ::Error > {
let ret = if lib . contains ( ".so" ) {
self . libraries
. iter ( )
. find ( | ( k , _ ) | k . as_str ( ) . starts_with ( lib ) )
} else {
self . libraries . iter ( ) . find ( | ( k , _ ) | {
k . strip_prefix ( lib )
. map ( | k | k . starts_with ( ".so" ) | | k . starts_with ( '-' ) )
. unwrap_or_default ( )
} )
} ;
Ok ( ret . map ( | ( _ , v ) | v . clone ( ) ) )
}
/// Provides an iterator over all parsed memory map entries
/// for the process. This is useful to resolve
/// instruction pointers to a the shared object they belong to.
pub fn entries ( & self ) -> impl Iterator < Item = & ProcMapEntry > {
self . entries . iter ( )
}
}
/// A entry that has been parsed from /proc/`pid`/maps.
///
/// This contains information about a mapped portion of memory
/// for the process, ranging from address to address_end.
pub struct ProcMapEntry {
address : u64 ,
address_end : u64 ,
perms : String ,
offset : u64 ,
dev : String ,
inode : u32 ,
path : Option < String > ,
}
impl ProcMapEntry {
/// The start address of the mapped memory
pub fn address ( & self ) -> u64 {
self . address
}
/// The end address of the mapped memory
pub fn address_end ( & self ) -> u64 {
self . address_end
}
/// The permissions of the mapped memory
pub fn perms ( & self ) -> & str {
& self . perms
}
/// The offset of the mapped memory
pub fn offset ( & self ) -> u64 {
self . offset
}
/// The device of the mapped memory
pub fn dev ( & self ) -> & str {
& self . dev
}
/// The inode of the mapped memory
pub fn inode ( & self ) -> u32 {
self . inode
}
/// The destination path of the mapped memory
pub fn path ( & self ) -> Option < & str > {
self . path . as_deref ( )
}
}
impl FromStr for ProcMapEntry {
type Err = ProcMapError ;
fn from_str ( line : & str ) -> Result < Self , Self ::Err > {
let mut parts = line . split_whitespace ( ) ;
let mut next = | | parts . next ( ) . ok_or ( ProcMapError ::ParseError ) ;
let ( address , address_end ) = next ( ) ?
. split_once ( '-' )
. and_then ( | ( a , b ) | {
Some ( (
u64 ::from_str_radix ( a , 16 ) . ok ( ) ? ,
u64 ::from_str_radix ( b , 16 ) . ok ( ) ? ,
) )
} )
. ok_or ( ProcMapError ::ParseError ) ? ;
let perms = next ( ) ? . to_string ( ) ;
let offset = u64 ::from_str_radix ( next ( ) ? , 16 ) . map_err ( | _ | ProcMapError ::ParseError ) ? ;
let dev = next ( ) ? . to_string ( ) ;
let inode = next ( ) ? . parse ( ) . map_err ( | _ | ProcMapError ::ParseError ) ? ;
let path = next ( ) . ok ( ) . and_then ( | s | {
if s . starts_with ( '/' ) {
Some ( s . to_string ( ) )
} else {
None
}
} ) ;
Ok ( ProcMapEntry {
address ,
address_end ,
perms ,
offset ,
dev ,
inode ,
path ,
} )
}
}
#[ cfg(test) ]
mod test {
use super ::* ;
#[ test ]
fn test_parse_proc_map_entry_shared_lib ( ) {
let s = "7ffd6fbea000-7ffd6fbec000 r-xp 00000000 00:00 0 [vdso]" ;
let proc_map = ProcMapEntry ::from_str ( s ) . unwrap ( ) ;
assert_eq! ( proc_map . address , 0x7ffd6fbea000 ) ;
assert_eq! ( proc_map . address_end , 0x7ffd6fbec000 ) ;
assert_eq! ( proc_map . perms , "r-xp" ) ;
assert_eq! ( proc_map . offset , 0x0 ) ;
assert_eq! ( proc_map . dev , "00:00" ) ;
assert_eq! ( proc_map . inode , 0 ) ;
assert_eq! ( proc_map . path , None ) ;
}
#[ test ]
fn test_parse_proc_map_entry_absolute_path ( ) {
let s = "7f1bca83a000-7f1bca83c000 rw-p 00036000 fd:01 2895508 /usr/lib64/ld-linux-x86-64.so.2" ;
let proc_map = ProcMapEntry ::from_str ( s ) . unwrap ( ) ;
assert_eq! ( proc_map . address , 0x7f1bca83a000 ) ;
assert_eq! ( proc_map . address_end , 0x7f1bca83c000 ) ;
assert_eq! ( proc_map . perms , "rw-p" ) ;
assert_eq! ( proc_map . offset , 0x00036000 ) ;
assert_eq! ( proc_map . dev , "fd:01" ) ;
assert_eq! ( proc_map . inode , 2895508 ) ;
assert_eq! (
proc_map . path ,
Some ( "/usr/lib64/ld-linux-x86-64.so.2" . to_string ( ) )
) ;
}
#[ test ]
fn test_parse_proc_map_entry_all_zeros ( ) {
let s = "7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0" ;
let proc_map = ProcMapEntry ::from_str ( s ) . unwrap ( ) ;
assert_eq! ( proc_map . address , 0x7f1bca5f9000 ) ;
assert_eq! ( proc_map . address_end , 0x7f1bca601000 ) ;
assert_eq! ( proc_map . perms , "rw-p" ) ;
assert_eq! ( proc_map . offset , 0x0 ) ;
assert_eq! ( proc_map . dev , "00:00" ) ;
assert_eq! ( proc_map . inode , 0 ) ;
assert_eq! ( proc_map . path , None ) ;
}
#[ test ]
fn test_proc_map_find_lib_by_name ( ) {
let entry = ProcMapEntry ::from_str (
"7fc4a9800000-7fc4a98ad000 r--p 00000000 00:24 18147308 /usr/lib64/libcrypto.so.3.0.9" ,
) . unwrap ( ) ;
let proc_map_libs = ProcMap {
entries : vec ! [ entry ] ,
libraries : HashMap ::from ( [ (
"libcrypto.so.3.0.9" . to_owned ( ) ,
"/usr/lib64/libcrypto.so.3.0.9" . to_owned ( ) ,
) ] ) ,
} ;
assert_eq! (
proc_map_libs
. find_library_path_by_name ( "libcrypto.so.3.0.9" )
. unwrap ( ) ,
Some ( "/usr/lib64/libcrypto.so.3.0.9" . to_owned ( ) )
) ;
}
}