Skip to content

Commit 3344386

Browse files
committed
standalone proc/self/maps parsing code to try instead of less reliable current_exe.
(updated to only define the parse_running_mmaps module in cases where we are also defining libs_dl_iterate_phdr, since that is the only place it is currently needed.)
1 parent 8b83ba1 commit 3344386

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

src/symbolize/gimli.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ cfg_if::cfg_if! {
184184
))] {
185185
mod libs_dl_iterate_phdr;
186186
use libs_dl_iterate_phdr::native_libraries;
187+
#[path = "gimli/parse_running_mmaps_unix.rs"]
188+
mod parse_running_mmaps;
187189
} else if #[cfg(target_env = "libnx")] {
188190
mod libs_libnx;
189191
use libs_libnx::native_libraries;

src/symbolize/gimli/libs_dl_iterate_phdr.rs

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

0 commit comments

Comments
 (0)