blob: 7a70af98ab17a1a734856ebbc53af743ba3b2590 [file]
//! Utils for dealing with processes
use crate::{bpf_map::bytes_as_str, prefix_bpf, Timer};
use anyhow::{anyhow, bail, Result};
use dynamic_instrumentation_manager::{
ExecutableMethodFileOffsets, MethodDescriptor, TargetProcess,
};
use log::debug;
use std::fs::{read, read_dir};
use std::time::Duration;
use uprobestats_bpf::{bpf_perf_event_open, poll_ring_buf};
use uprobestats_bpf_bindgen::ProcessChange;
use uprobestats_proto::config::uprobestats_config::task::TargetProcessSelection;
pub(crate) struct ResolvedProcess {
pub(crate) pid: i32,
pub(crate) uid: i32,
pub(crate) name: String,
}
pub(crate) fn resolve_process(
target_process_name: &str,
target_process_selection: TargetProcessSelection,
duration: Duration,
) -> Result<ResolvedProcess> {
debug!(
"resolve_process: process_name: {} process_selection: {:?}",
target_process_name, target_process_selection
);
match target_process_selection {
TargetProcessSelection::SPECIFIC_APP_PROCESS_ON_START => {
wait_for_app_start(Some(target_process_name), duration)
}
TargetProcessSelection::ANY_APP_PROCESS_ON_START => wait_for_app_start(None, duration),
TargetProcessSelection::SPECIFIC_PROCESS_NAME | TargetProcessSelection::UNKNOWN => {
let pid = get_pid(target_process_name)
.ok_or(anyhow!("Can't find pid for {}", target_process_name))?;
Ok(ResolvedProcess { pid, uid: 0, name: target_process_name.to_string() })
}
}
}
fn wait_for_app_start(process_name: Option<&str>, duration: Duration) -> Result<ResolvedProcess> {
let system_server_pid =
get_pid("system_server").ok_or(anyhow!("failed to get system server pid"))?;
let (offsets, bpf_prog_name) = match get_ProcessRecord_makeActive_offsets() {
Ok(offsets) => (offsets, BPF_PROG_PROCESS_MANAGEMENT_MAKE_ACTIVE),
Err(e) => {
debug!(
"Could not find offsets for ProcessRecord#makeActive, trying onProcessActive: {e}"
);
(get_onProcessActive_offsets()?, BPF_PROG_PROCESS_MANAGEMENT_ON_PROCESS_ACTIVE)
}
};
debug!("attaching process management bpf for app start");
bpf_perf_event_open(
offsets.get_container_path(),
offsets.get_method_offset().try_into()?,
system_server_pid,
prefix_bpf(bpf_prog_name),
)?;
let timer = Timer::new(duration);
while let Some(remaining_millis) = timer.remaining_millis() {
debug!("polling {} for {} seconds", BPF_MAP_PROCESS_MANAGEMENT, remaining_millis / 1000);
// SAFETY: hard coded `const BPF_MAP_PROCESS_MANAGEMENT` writes the `ProcessChange` struct.
let result: Result<Vec<ProcessChange>> = unsafe {
poll_ring_buf(&prefix_bpf(BPF_MAP_PROCESS_MANAGEMENT), remaining_millis.try_into()?)
};
let result = result?;
for process_change in result {
let result_process_name = bytes_as_str(&process_change.process_name)?;
if process_name.is_none() || process_name.unwrap() == result_process_name {
if process_change.pid <= 0 {
continue;
}
debug!(
"detected process start: pid: {} uid: {}",
process_change.pid, process_change.uid
);
return Ok(ResolvedProcess {
pid: process_change.pid,
uid: process_change.uid,
name: result_process_name.to_string(),
});
}
}
}
bail!("Timeout waiting duration {:?} for process_name {:?}", duration, process_name)
}
fn get_pid(process_name: &str) -> Option<i32> {
for entry in read_dir("/proc").ok()? {
let entry = entry.ok()?;
let path = entry.path();
if path.is_dir() {
let cmdline_path = path.join("cmdline");
if let Ok(cmdline_bytes) = read(cmdline_path) {
let cmdline = String::from_utf8_lossy(&cmdline_bytes);
if cmdline == process_name || cmdline.starts_with(process_name) {
if let Some(pid_str) = path.file_name().and_then(|s| s.to_str()) {
if let Ok(pid) = pid_str.parse::<i32>() {
return Some(pid);
}
}
}
}
}
}
None
}
#[allow(non_snake_case)]
fn get_ProcessRecord_makeActive_offsets() -> Result<ExecutableMethodFileOffsets> {
let offsets = ExecutableMethodFileOffsets::get(
&TargetProcess::system_server()?,
&MethodDescriptor::new(
CLASS_PROCESS_RECORD,
METHOD_MAKE_ACTIVE,
METHOD_MAKE_ACTIVE_PARAMS.into_iter().map(String::from),
)?,
)?;
offsets.ok_or(anyhow!("Could not find offsets for ProcessRecord#makeActive"))
}
#[allow(non_snake_case)]
fn get_onProcessActive_offsets() -> Result<ExecutableMethodFileOffsets> {
let offsets = ExecutableMethodFileOffsets::get(
&TargetProcess::system_server()?,
&MethodDescriptor::new(
CLASS_PROCESS_PROFILE_RECORD,
METHOD_ON_PROCESS_ACTIVE,
METHOD_ON_PROCESS_ACTIVE_PARAMS.into_iter().map(String::from),
)?,
)?;
offsets.ok_or(anyhow!("Could not find offsets for ProcessProfileRecord#onProcessActive"))
}
const CLASS_PROCESS_RECORD: &str = "com.android.server.am.ProcessRecord";
const METHOD_MAKE_ACTIVE: &str = "makeActive";
const METHOD_MAKE_ACTIVE_PARAMS: [&str; 2] = [
"com.android.server.am.ApplicationThreadDeferred",
"com.android.server.am.ProcessStatsService",
];
const CLASS_PROCESS_PROFILE_RECORD: &str = "com.android.server.am.ProcessProfileRecord";
const METHOD_ON_PROCESS_ACTIVE: &str = "onProcessActive";
const METHOD_ON_PROCESS_ACTIVE_PARAMS: [&str; 2] =
["android.app.IApplicationThread", "com.android.server.am.ProcessStatsService"];
const BPF_PROG_PROCESS_MANAGEMENT_MAKE_ACTIVE: &str = "prog_ProcessManagement_uprobe_make_active";
const BPF_PROG_PROCESS_MANAGEMENT_ON_PROCESS_ACTIVE: &str =
"prog_ProcessManagement_uprobe_on_process_active";
const BPF_MAP_PROCESS_MANAGEMENT: &str = "map_ProcessManagement_process_change_output_buf";