diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index 8c42ced0..9c018233 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -82,36 +82,7 @@ impl UProbe { target: T, pid: Option, ) -> Result { - let target = target.as_ref(); - let target_str = &*target.as_os_str().to_string_lossy(); - - let mut path = if let Some(pid) = pid { - find_lib_in_proc_maps(pid, target_str).map_err(|io_error| UProbeError::FileError { - filename: format!("/proc/{pid}/maps"), - io_error, - })? - } else { - None - }; - - if path.is_none() { - path = if target.is_absolute() { - Some(target_str) - } else { - let cache = - LD_SO_CACHE - .as_ref() - .map_err(|error| UProbeError::InvalidLdSoCache { - io_error: error.clone(), - })?; - cache.resolve(target_str) - } - .map(String::from) - }; - - let path = path.ok_or(UProbeError::InvalidTarget { - path: target.to_owned(), - })?; + let path = resolve_attach_path(target, pid)?; let sym_offset = if let Some(fn_name) = fn_name { resolve_symbol(&path, fn_name).map_err(|error| UProbeError::SymbolError { @@ -152,6 +123,64 @@ impl UProbe { } } +fn resolve_attach_path>( + target: T, + pid: Option, +) -> Result { + // Look up the path for the target. If it there is a pid, and the target is a library name that + // is in the process's memory map, use the path of that library. Otherwise, use the target + // as-is. + let target = target.as_ref(); + let target_str = &*target.as_os_str().to_string_lossy(); + + let mut path = if let Some(pid) = pid { + find_lib_in_proc_maps(pid, target_str).map_err(|io_error| UProbeError::FileError { + filename: format!("/proc/{pid}/maps"), + io_error, + })? + } else { + None + }; + + if path.is_none() { + path = if target.is_absolute() { + Some(target_str) + } else { + let cache = LD_SO_CACHE + .as_ref() + .map_err(|error| UProbeError::InvalidLdSoCache { + io_error: error.clone(), + })?; + cache.resolve(target_str) + } + .map(String::from) + }; + + path.ok_or(UProbeError::InvalidTarget { + path: target.to_owned(), + }) +} + +// Only run this test on linux with glibc because only in that configuration do we know that we'll +// be dynamically linked to libc and can exercise resolving the path to libc via the current +// process's memory map. +#[test] +#[cfg_attr( + any(miri, not(all(target_os = "linux", target_env = "gnu"))), + ignore = "requires glibc, doesn't work in miri" +)] +fn test_resolve_attach_path() { + // Look up the current process's pid. + let pid = std::process::id().try_into().unwrap(); + + // Now let's resolve the path to libc. It should exist in the current process's memory map and + // then in the ld.so.cache. + let libc_path = resolve_attach_path("libc", Some(pid)).unwrap(); + + // Make sure we got a path that contains libc. + assert!(libc_path.contains("libc"), "libc_path: {}", libc_path); +} + define_link_wrapper!( /// The link used by [UProbe] programs. UProbeLink, diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index ece72daf..0b0d1532 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -201,7 +201,9 @@ fn basic_uprobe() { prog.load().unwrap(); assert_loaded("test_uprobe"); - let link = prog.attach(Some("sleep"), 0, "libc", None).unwrap(); + let link = prog + .attach(Some("uprobe_function"), 0, "/proc/self/exe", None) + .unwrap(); { let _link_owned = prog.take_link(link).unwrap(); @@ -213,7 +215,8 @@ fn basic_uprobe() { prog.load().unwrap(); assert_loaded("test_uprobe"); - prog.attach(Some("sleep"), 0, "libc", None).unwrap(); + prog.attach(Some("uprobe_function"), 0, "/proc/self/exe", None) + .unwrap(); assert_loaded("test_uprobe"); prog.unload().unwrap(); @@ -424,14 +427,23 @@ fn pin_lifecycle_kprobe() { assert_unloaded("test_kprobe"); } +#[no_mangle] +#[inline(never)] +extern "C" fn uprobe_function() { + core::hint::black_box(uprobe_function); +} + #[test] fn pin_lifecycle_uprobe() { + const FIRST_PIN_PATH: &str = "/sys/fs/bpf/aya-uprobe-test-prog-1"; + const SECOND_PIN_PATH: &str = "/sys/fs/bpf/aya-uprobe-test-prog-2"; + // 1. Load Program and Pin { let mut bpf = Bpf::load(crate::TEST).unwrap(); let prog: &mut UProbe = bpf.program_mut("test_uprobe").unwrap().try_into().unwrap(); prog.load().unwrap(); - prog.pin("/sys/fs/bpf/aya-uprobe-test-prog").unwrap(); + prog.pin(FIRST_PIN_PATH).unwrap(); } // should still be loaded since prog was pinned @@ -439,11 +451,7 @@ fn pin_lifecycle_uprobe() { // 2. Load program from bpffs but don't attach it { - let _ = UProbe::from_pin( - "/sys/fs/bpf/aya-uprobe-test-prog", - aya::programs::ProbeKind::UProbe, - ) - .unwrap(); + let _ = UProbe::from_pin(FIRST_PIN_PATH, aya::programs::ProbeKind::UProbe).unwrap(); } // should still be loaded since prog was pinned @@ -451,17 +459,13 @@ fn pin_lifecycle_uprobe() { // 3. Load program from bpffs and attach { - let mut prog = UProbe::from_pin( - "/sys/fs/bpf/aya-uprobe-test-prog", - aya::programs::ProbeKind::UProbe, - ) - .unwrap(); - let link_id = prog.attach(Some("sleep"), 0, "libc", None).unwrap(); + let mut prog = UProbe::from_pin(FIRST_PIN_PATH, aya::programs::ProbeKind::UProbe).unwrap(); + let link_id = prog + .attach(Some("uprobe_function"), 0, "/proc/self/exe", None) + .unwrap(); let link = prog.take_link(link_id).unwrap(); let fd_link: FdLink = link.try_into().unwrap(); - fd_link - .pin("/sys/fs/bpf/aya-uprobe-test-bash-sleep") - .unwrap(); + fd_link.pin(SECOND_PIN_PATH).unwrap(); // Unpin the program. It will stay attached since its links were pinned. prog.unpin().unwrap(); @@ -472,7 +476,7 @@ fn pin_lifecycle_uprobe() { // 4. unpin link, and make sure everything is unloaded { - PinnedLink::from_pin("/sys/fs/bpf/aya-uprobe-test-bash-sleep") + PinnedLink::from_pin(SECOND_PIN_PATH) .unwrap() .unpin() .unwrap(); @@ -480,4 +484,7 @@ fn pin_lifecycle_uprobe() { // program should be unloaded assert_unloaded("test_uprobe"); + + // Make sure the function isn't optimized out. + uprobe_function(); }