Skip to content

Commit 2dac6c4

Browse files
committed
Improve Linux process implementation
- Implement Process::state() via /proc/<pid>/stat: zombie/dead states map to ProcessState::Dead (with exit_code when readable), a vanished PID maps to Dead, and permission errors stay Unknown. - Use the module base address as the opaque module handle instead of an index into a freshly parsed maps list, eliminating the race between module_address_list_callback and module_by_address. - Resolve primary_module_address() through /proc/<pid>/exe instead of assuming the 0th mapping, falling back to the first mapping. - Report real in-process addresses for environment variables using env_start from /proc/<pid>/stat, and return the env block address from environment_block_address() (plugin API 2).
1 parent 937b57c commit 2dac6c4

1 file changed

Lines changed: 62 additions & 18 deletions

File tree

src/linux/process.rs

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,32 @@ impl LinuxProcess {
115115
let data = std::fs::read(path)
116116
.map_err(|_| Error(ErrorOrigin::OsLayer, ErrorKind::EnvarNotFound))?;
117117

118+
// /proc/<pid>/environ is exactly the memory range env_start..env_end,
119+
// so in-process addresses can be derived from byte offsets within it.
120+
let env_start = self
121+
.proc_handle()
122+
.ok()
123+
.and_then(|p| p.stat().ok())
124+
.and_then(|stat| stat.env_start);
125+
118126
let mut out = Vec::new();
119-
for entry in data.split(|b| *b == 0).filter(|entry| !entry.is_empty()) {
120-
let entry = String::from_utf8_lossy(entry);
121-
if let Some((name, value)) = entry.split_once('=') {
122-
out.push(EnvVarInfo {
123-
name: ReprCString::from(name),
124-
value: ReprCString::from(value),
125-
address: Address::NULL,
126-
arch: self.info.proc_arch,
127-
});
127+
let mut offset = 0u64;
128+
for entry in data.split(|b| *b == 0) {
129+
let entry_len = entry.len() as u64;
130+
if !entry.is_empty() {
131+
let entry = String::from_utf8_lossy(entry);
132+
if let Some((name, value)) = entry.split_once('=') {
133+
out.push(EnvVarInfo {
134+
name: ReprCString::from(name),
135+
value: ReprCString::from(value),
136+
address: env_start
137+
.map(|start| Address::from(start + offset))
138+
.unwrap_or(Address::NULL),
139+
arch: self.info.proc_arch,
140+
});
141+
}
128142
}
143+
offset += entry_len + 1;
129144
}
130145

131146
Ok(out)
@@ -152,11 +167,10 @@ impl Process for LinuxProcess {
152167

153168
module_maps
154169
.iter()
155-
.enumerate()
156170
.filter(|_| target_arch.is_none() || Some(&self.info().sys_arch) == target_arch)
157-
.take_while(|(i, _)| {
171+
.take_while(|map| {
158172
callback.call(ModuleAddressInfo {
159-
address: Address::from(*i as u64),
173+
address: Address::from(map.address.0),
160174
arch: self.info.proc_arch,
161175
})
162176
})
@@ -182,7 +196,8 @@ impl Process for LinuxProcess {
182196
let module_maps = self.module_maps()?;
183197

184198
module_maps
185-
.get(address.to_umem() as usize)
199+
.iter()
200+
.find(|map| Address::from(map.address.0) == address)
186201
.map(|map| ModuleInfo {
187202
address,
188203
parent_process: self.info.address,
@@ -223,8 +238,23 @@ impl Process for LinuxProcess {
223238
///
224239
/// This will generally be for the initial executable that was run
225240
fn primary_module_address(&mut self) -> Result<Address> {
226-
// TODO: Is it always 0th mod?
227-
Ok(Address::from(0))
241+
let exe = self.proc_handle()?.exe().ok();
242+
let module_maps = self.module_maps()?;
243+
244+
if let Some(exe) = exe {
245+
if let Some(map) = module_maps
246+
.iter()
247+
.find(|m| matches!(&m.pathname, MMapPath::Path(p) if *p == exe))
248+
{
249+
return Ok(Address::from(map.address.0));
250+
}
251+
}
252+
253+
// exe link unreadable or unmatched (e.g. deleted binary) - fall back to first mapping
254+
module_maps
255+
.first()
256+
.map(|m| Address::from(m.address.0))
257+
.ok_or(Error(ErrorOrigin::OsLayer, ErrorKind::NotFound))
228258
}
229259

230260
/// Retrieves the process info
@@ -234,7 +264,16 @@ impl Process for LinuxProcess {
234264

235265
/// Retrieves the state of the process
236266
fn state(&mut self) -> ProcessState {
237-
ProcessState::Unknown
267+
match procfs::process::Process::new(self.pid).and_then(|p| p.stat()) {
268+
Ok(stat) => match stat.state {
269+
// Z = zombie (exited, unreaped), X/x = dead
270+
'Z' | 'X' | 'x' => ProcessState::Dead(stat.exit_code.unwrap_or(0)),
271+
_ => ProcessState::Alive,
272+
},
273+
// /proc/<pid> vanished -> the process was reaped; exit code is no longer recoverable
274+
Err(procfs::ProcError::NotFound(_)) => ProcessState::Dead(0),
275+
Err(_) => ProcessState::Unknown,
276+
}
238277
}
239278

240279
/// Changes the dtb this process uses for memory translations.
@@ -321,8 +360,13 @@ impl Process for LinuxProcess {
321360

322361
#[cfg(memflow_plugin_api = "2")]
323362
fn environment_block_address(&mut self, _architecture: ArchitectureIdent) -> Result<Address> {
324-
// Linux does not expose a stable public env-block pointer through procfs.
325-
Ok(Address::NULL)
363+
// env_start is only exposed on kernel >= 3.5 and may be hidden by permission checks.
364+
self.proc_handle()?
365+
.stat()
366+
.map_err(|_| Error(ErrorOrigin::OsLayer, ErrorKind::UnableToReadFile))?
367+
.env_start
368+
.map(Address::from)
369+
.ok_or(Error(ErrorOrigin::OsLayer, ErrorKind::NotSupported))
326370
}
327371

328372
#[cfg(memflow_plugin_api = "2")]

0 commit comments

Comments
 (0)