Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add android support for api version 21+ #65

Merged
merged 4 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ readme = "./README.md"
repository = "https://github.com/gimli-rs/findshlibs"

[dependencies]
libc = "0.2.65"
libc = "0.2.98"

[build-dependencies]
# Only needed for Android, but cannot be target dependent
# https://github.com/rust-lang/cargo/issues/4932
cc = "1.0.67"

[target.'cfg(target_os = "macos")'.dependencies]
lazy_static = "1.4"
Expand Down
41 changes: 41 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
extern crate cc;

use std::env;

fn main() {
match env::var("CARGO_CFG_TARGET_OS").unwrap_or_default().as_str() {
"android" => build_android(),
_ => {}
}
}

fn build_android() {
let expansion = match cc::Build::new().file("src/android-api.c").try_expand() {
Ok(result) => result,
Err(e) => {
println!("cargo:warning=failed to run C compiler: {}", e);
return;
}
};

let expansion = match std::str::from_utf8(&expansion) {
Ok(s) => s,
Err(_) => return,
};

let marker = "APIVERSION";
let i = expansion.find(marker).unwrap_or_default();

let version = expansion[i + marker.len() + 1..]
.split_whitespace()
.next()
.unwrap_or("");
let version = version.parse::<u32>().unwrap_or_else(|_| {
println!("cargo:warning=failed to get android api version.");
0
});

if version >= 21 {
println!("cargo:rustc-cfg=feature=\"dl_iterate_phdr\"");
}
}
4 changes: 4 additions & 0 deletions src/android-api.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Used from the build script to detect the value of the `__ANDROID_API__`
// builtin #define

APIVERSION __ANDROID_API__
18 changes: 15 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@
#[cfg(target_os = "macos")]
pub mod macos;

#[cfg(target_os = "linux")]
#[cfg(any(
target_os = "linux",
all(target_os = "android", feature = "dl_iterate_phdr")
))]
pub mod linux;

#[cfg(target_os = "windows")]
Expand All @@ -110,7 +113,10 @@ use std::usize;

pub mod unsupported;

#[cfg(target_os = "linux")]
#[cfg(any(
target_os = "linux",
all(target_os = "android", feature = "dl_iterate_phdr")
))]
use crate::linux as native_mod;

#[cfg(target_os = "macos")]
Expand All @@ -119,7 +125,12 @@ use crate::macos as native_mod;
#[cfg(target_os = "windows")]
use crate::windows as native_mod;

#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
#[cfg(not(any(
target_os = "macos",
target_os = "linux",
all(target_os = "android", feature = "dl_iterate_phdr"),
target_os = "windows"
)))]
use unsupported as native_mod;

/// The [`SharedLibrary` trait](./trait.SharedLibrary.html)
Expand All @@ -130,6 +141,7 @@ pub type TargetSharedLibrary<'a> = native_mod::SharedLibrary<'a>;
pub const TARGET_SUPPORTED: bool = cfg!(any(
target_os = "macos",
target_os = "linux",
all(target_os = "android", feature = "dl_iterate_phdr"),
target_os = "windows"
));

Expand Down
52 changes: 35 additions & 17 deletions src/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ type Phdr = libc::Elf32_Phdr;
type Phdr = libc::Elf64_Phdr;

const NT_GNU_BUILD_ID: u32 = 3;
const PT_NULL: u32 = 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly libc crate does not add these constants to android, we need to manually copy them here for now.

const PT_LOAD: u32 = 1;
const PT_DYNAMIC: u32 = 2;
const PT_INTERP: u32 = 3;
const PT_NOTE: u32 = 4;
const PT_SHLIB: u32 = 5;
const PT_PHDR: u32 = 6;
const PT_TLS: u32 = 7;
const PT_NUM: u32 = 8;
const PT_LOOS: u32 = 0x60000000;
const PT_GNU_EH_FRAME: u32 = 0x6474e550;
const PT_GNU_STACK: u32 = 0x6474e551;
const PT_GNU_RELRO: u32 = 0x6474e552;

// Normally we would use `Elf32_Nhdr` on 32-bit platforms and `Elf64_Nhdr` on
// 64-bit platforms. However, in practice it seems that only `Elf32_Nhdr` is
Expand Down Expand Up @@ -59,7 +72,7 @@ impl<'a> Segment<'a> {
}

fn is_note(&self) -> bool {
self.phdr().p_type == libc::PT_NOTE
self.phdr().p_type == PT_NOTE
}

/// Parse the contents of a `PT_NOTE` segment.
Expand Down Expand Up @@ -144,19 +157,19 @@ impl<'a> SegmentTrait for Segment<'a> {
fn name(&self) -> &str {
unsafe {
match self.phdr.as_ref().unwrap().p_type {
libc::PT_NULL => "NULL",
libc::PT_LOAD => "LOAD",
libc::PT_DYNAMIC => "DYNAMIC",
libc::PT_INTERP => "INTERP",
libc::PT_NOTE => "NOTE",
libc::PT_SHLIB => "SHLI",
libc::PT_PHDR => "PHDR",
libc::PT_TLS => "TLS",
libc::PT_NUM => "NUM",
libc::PT_LOOS => "LOOS",
libc::PT_GNU_EH_FRAME => "GNU_EH_FRAME",
libc::PT_GNU_STACK => "GNU_STACK",
libc::PT_GNU_RELRO => "GNU_RELRO",
PT_NULL => "NULL",
PT_LOAD => "LOAD",
PT_DYNAMIC => "DYNAMIC",
PT_INTERP => "INTERP",
PT_NOTE => "NOTE",
PT_SHLIB => "SHLI",
PT_PHDR => "PHDR",
PT_TLS => "TLS",
PT_NUM => "NUM",
PT_LOOS => "LOOS",
PT_GNU_EH_FRAME => "GNU_EH_FRAME",
PT_GNU_STACK => "GNU_STACK",
PT_GNU_RELRO => "GNU_RELRO",
_ => "(unknown segment type)",
}
}
Expand All @@ -166,12 +179,12 @@ impl<'a> SegmentTrait for Segment<'a> {
fn is_code(&self) -> bool {
let hdr = self.phdr();
// 0x1 is PT_X for executable
hdr.p_type == libc::PT_LOAD && (hdr.p_flags & 0x1) != 0
hdr.p_type == PT_LOAD && (hdr.p_flags & 0x1) != 0
}

#[inline]
fn is_load(&self) -> bool {
self.phdr().p_type == libc::PT_LOAD
self.phdr().p_type == PT_LOAD
}

#[inline]
Expand Down Expand Up @@ -273,6 +286,10 @@ impl<'a> SharedLibrary<'a> {
F: FnMut(&Self) -> C,
C: Into<IterationControl>,
{
if (*info).dlpi_phdr.is_null() {
return CONTINUE;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these null entries have valid values for the other fields (name and addr)? If so, what are they?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result differs by android api version:

  • on android 8.0 and older the null entries does not contain any useful fields,

截屏2021-10-10 下午4 42 56

- on android 8.1 and android 9, the entry with a null dlpi_phdr contains the name of '/system/bin/linker' and its load address

截屏2021-10-10 下午4 51 00

- android 10+ won't return any null entries

截屏2021-10-10 下午4 52 15

The screenshots are the result of running example program at https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html on devices of different android versions.

Copy link
Contributor Author

@name1e5s name1e5s Oct 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS:
'/system/bin/linker' with base address on android 8.1:
截屏2021-10-11 上午11 00 02
May be we should create a SharedLibrary with no headers for this situation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but I'm happy to wait until someone needs it, since I don't see what use it is without the program headers.


let state = &mut *(state as *mut IterState<F>);
state.idx += 1;

Expand Down Expand Up @@ -451,11 +468,12 @@ mod tests {
}
});

assert!(names[0].contains("/findshlibs"));
assert!(names.iter().any(|x| x.contains("findshlibs")));
philipc marked this conversation as resolved.
Show resolved Hide resolved
assert!(names.iter().any(|x| x.contains("libc.so")));
}

#[test]
#[cfg(target_os = "linux")]
fn get_id() {
use std::path::Path;
use std::process::Command;
Expand Down