Skip to content

Commit 0fe344d

Browse files
committed
standalone proc/self/maps parsing code to try instead of less reliable current_exe.
1 parent 8b83ba1 commit 0fe344d

File tree

4 files changed

+182
-1
lines changed

4 files changed

+182
-1
lines changed

src/symbolize/gimli.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ cfg_if::cfg_if! {
3030
if #[cfg(windows)] {
3131
#[path = "gimli/mmap_windows.rs"]
3232
mod mmap;
33+
#[path = "gimli/parse_running_mmaps_noop.rs"]
34+
mod parse_running_mmaps;
3335
} else if #[cfg(any(
3436
target_os = "android",
3537
target_os = "freebsd",
@@ -44,9 +46,13 @@ cfg_if::cfg_if! {
4446
))] {
4547
#[path = "gimli/mmap_unix.rs"]
4648
mod mmap;
49+
#[path = "gimli/parse_running_mmaps_unix.rs"]
50+
mod parse_running_mmaps;
4751
} else {
4852
#[path = "gimli/mmap_fake.rs"]
4953
mod mmap;
54+
#[path = "gimli/parse_running_mmaps_noop.rs"]
55+
mod parse_running_mmaps;
5056
}
5157
}
5258

src/symbolize/gimli/libs_dl_iterate_phdr.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@ pub(super) fn native_libraries() -> Vec<Library> {
1717
return ret;
1818
}
1919

20+
fn infer_current_exe(base_addr: usize) -> OsString {
21+
if let Ok(entries) = super::parse_running_mmaps::parse_maps() {
22+
let opt_path = entries.iter()
23+
.find(|e| { // first entry with matching address and useful path
24+
e.ip_matches(base_addr) &&
25+
e.pathname().map(|p|p.len() > 0).unwrap_or(false)}
26+
)
27+
.map(|e|e.pathname())
28+
.flatten()
29+
.cloned();
30+
if let Some(path) = opt_path {
31+
return path;
32+
}
33+
}
34+
env::current_exe().map(|e| e.into()).unwrap_or_default()
35+
}
36+
2037
// `info` should be a valid pointers.
2138
// `vec` should be a valid pointer to a `std::Vec`.
2239
unsafe extern "C" fn callback(
@@ -28,8 +45,12 @@ unsafe extern "C" fn callback(
2845
let libs = &mut *(vec as *mut Vec<Library>);
2946
let is_main_prog = info.dlpi_name.is_null() || *info.dlpi_name == 0;
3047
let name = if is_main_prog {
48+
// The man page for dl_iterate_phdr says that the first object visited by
49+
// callback is the main program; so the first time we encounter a
50+
// nameless entry, we can assume its the main program and try to infer its path.
51+
// After that, we cannot continue that assumption, and we use an empty string.
3152
if libs.is_empty() {
32-
env::current_exe().map(|e| e.into()).unwrap_or_default()
53+
infer_current_exe(info.dlpi_addr as usize)
3354
} else {
3455
OsString::new()
3556
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use super::{OsString, Vec};
2+
3+
pub(super) struct MapsEntry { }
4+
pub(super) fn parse_maps() -> Result<Vec<MapsEntry>, &'static str> { Err("this is a noop") }
5+
impl MapsEntry {
6+
pub(super) fn pathname(&self) -> Option<&OsString> { None }
7+
pub(super) fn ip_matches(&self, _ip: usize) -> bool { false }
8+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use super::mystd::io::BufRead;
2+
use super::{OsString, Vec};
3+
4+
#[derive(PartialEq, Eq, Debug)]
5+
pub(super) struct MapsEntry {
6+
/// start (inclusive) and limit (exclusive) of address range.
7+
address: (usize, usize),
8+
/// The perms field are the permissions for the entry
9+
///
10+
/// r = read
11+
/// w = write
12+
/// x = execute
13+
/// s = shared
14+
/// p = private (copy on write)
15+
perms: [char; 4],
16+
/// Offset into the file (or "whatever").
17+
offset: usize,
18+
/// device (major, minor)
19+
dev: (usize, usize),
20+
/// inode on the device. 0 indicates that no inode is associated with the memory region (e.g. uninitalized data aka BSS).
21+
inode: usize,
22+
/// Usually the file backing the mapping.
23+
///
24+
/// Note: The man page for proc includes a note about "coordination" by
25+
/// using readelf to see the Offset field in ELF program headers. pnkfelix
26+
/// is not yet sure if that is intended to be a comment on pathname, or what
27+
/// form/purpose such coordination is meant to have.
28+
///
29+
/// There are also some pseudo-paths:
30+
/// "[stack]": The initial process's (aka main thread's) stack.
31+
/// "[stack:<tid>]": a specific thread's stack. (This was only present for a limited range of Linux verisons; it was determined to be too expensive to provide.)
32+
/// "[vdso]": Virtual dynamically linked shared object
33+
/// "[heap]": The process's heap
34+
///
35+
/// The pathname can be blank, which means it is an anonymous mapping
36+
/// obtained via mmap.
37+
///
38+
/// Newlines in pathname are replaced with an octal escape sequence.
39+
///
40+
/// The pathname may have "(deleted)" appended onto it if the file-backed
41+
/// path has been deleted.
42+
///
43+
/// Note that modifications like the latter two indicated above imply that
44+
/// in general the pathname may be ambiguous. (I.e. you cannot tell if the
45+
/// denoted filename actually ended with the text "(deleted)", or if that
46+
/// was added by the maps rendering.
47+
pathname: OsString,
48+
}
49+
50+
pub(super) fn parse_maps() -> Result<Vec<MapsEntry>, &'static str> {
51+
let mut v = Vec::new();
52+
let proc_self_maps = std::fs::File::open("/proc/self/maps").map_err(|_| "couldnt open /proc/self/maps")?;
53+
let proc_self_maps = std::io::BufReader::new(proc_self_maps);
54+
for line in proc_self_maps.lines() {
55+
let line = line.map_err(|_io_error| "couldnt read line from /proc/self/maps")?;
56+
v.push(line.parse()?);
57+
}
58+
59+
Ok(v)
60+
}
61+
62+
impl MapsEntry {
63+
pub(super) fn pathname(&self) -> Option<&OsString> {
64+
Some(&self.pathname)
65+
}
66+
67+
pub(super) fn ip_matches(&self, ip: usize) -> bool {
68+
self.address.0 <= ip && ip < self.address.1
69+
}
70+
}
71+
72+
impl std::str::FromStr for MapsEntry {
73+
type Err = &'static str;
74+
75+
// Format: address perms offset dev inode pathname
76+
// e.g.: "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]"
77+
// e.g.: "7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"
78+
// e.g.: "35b1a21000-35b1a22000 rw-p 00000000 00:00 0"
79+
fn from_str(s: &str) -> Result<Self, Self::Err> {
80+
let mut parts = s
81+
.split(' ') // space-separated fields
82+
.filter(|s| s.len() > 0); // multiple spaces implies empty strings that need to be skipped.
83+
let range_str = parts.next().ok_or("Couldn't find address")?;
84+
let perms_str = parts.next().ok_or("Couldn't find permissions")?;
85+
let offset_str = parts.next().ok_or("Couldn't find offset")?;
86+
let dev_str = parts.next().ok_or("Couldn't find dev")?;
87+
let inode_str = parts.next().ok_or("Couldn't find inode")?;
88+
let pathname_str = parts.next().unwrap_or(""); // pathname may be omitted.
89+
90+
let hex = |s| usize::from_str_radix(s, 16).map_err(|_| "couldnt parse hex number");
91+
let address = {
92+
let (start, limit) = range_str.split_once('-').ok_or("Couldn't parse address range")?;
93+
(hex(start)?, hex(limit)?)
94+
};
95+
let perms: [char; 4] = {
96+
let mut chars = perms_str.chars();
97+
let mut c = || chars.next().ok_or("insufficient perms");
98+
let perms = [c()?, c()?, c()?, c()?];
99+
if chars.next().is_some() { return Err("too many perms"); }
100+
perms
101+
};
102+
let offset = hex(offset_str)?;
103+
let dev = {
104+
let (major, minor) = dev_str.split_once(':').ok_or("Couldn't parse dev")?;
105+
(hex(major)?, hex(minor)?)
106+
};
107+
let inode = hex(inode_str)?;
108+
let pathname = pathname_str.into();
109+
110+
Ok(MapsEntry { address, perms, offset, dev, inode, pathname })
111+
}
112+
}
113+
114+
#[test]
115+
fn check_maps_entry_parsing() {
116+
assert_eq!("ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 \
117+
[vsyscall]".parse::<MapsEntry>().unwrap(),
118+
MapsEntry {
119+
address: (0xffffffffff600000, 0xffffffffff601000),
120+
perms: ['-','-','x','p'],
121+
offset: 0x00000000,
122+
dev: (0x00, 0x00),
123+
inode: 0x0,
124+
pathname: "[vsyscall]".into(),
125+
});
126+
127+
assert_eq!("7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795 \
128+
/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2".parse::<MapsEntry>().unwrap(),
129+
MapsEntry {
130+
address: (0x7f5985f46000, 0x7f5985f48000),
131+
perms: ['r','w','-','p'],
132+
offset: 0x00039000,
133+
dev: (0x103, 0x06),
134+
inode: 0x76021795,
135+
pathname: "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2".into(),
136+
});
137+
assert_eq!("35b1a21000-35b1a22000 rw-p 00000000 00:00 0".parse::<MapsEntry>().unwrap(),
138+
MapsEntry {
139+
address: (0x35b1a21000, 0x35b1a22000),
140+
perms: ['r','w','-','p'],
141+
offset: 0x00000000,
142+
dev: (0x00,0x00),
143+
inode: 0x0,
144+
pathname: Default::default(),
145+
});
146+
}

0 commit comments

Comments
 (0)