从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库 ,直接影响就是在 7.0 以后 dlopen 和dlsym 函数将受到限制。如果直接调用,logcat 可能会生成一个警告或运行时错误。
void *handler = dlopen("/apex/com.android.art/lib/libart.so", RTLD_LAZY);
if (nullptr != handler) {
LOGD("handler: %u", (uintptr_t) handler);
} else {
LOGD("can't find libart.so");
}
}
E/linker: library "/apex/com.android.art/lib/libart.so"
("/apex/com.android.art/lib/libart.so") needed or dlopened by
"/data/app/~~3RWpnGAvU22Gu6D55CM6wQ==/com.youngtr.jnievner-NnG3_CcHIYdUNtIcmKG5zA==/base.apk!/lib/armeabi-v7a/libjnievner.so"
is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="/data/app/~~3RWpnGAvU22Gu6D55CM6wQ==/com.youngtr.jnievner-NnG3_CcHIYdUNtIcmKG5zA==/lib/arm:/data/app/~~3RWpnGAvU22Gu6D55CM6wQ==/com.youngtr.jnievner-NnG3_CcHIYdUNtIcmKG5zA==/base.apk!/lib/armeabi-v7a", permitted_paths="/data:/mnt/expand:/data/data/com.youngtr.jnievner"]
这里主要介绍通过 dl_iterate_phdr
获取符号地址的方式
#include <link.h>
int dl_iterate_phdr(
int (*callback)(struct dl_phdr_info *info,
size_t size, void *data),
void *data);
dl_iterate_phdr
函数允许应用程序在运行时查询它已经加载的共享对象,以及它们的加载顺序。
dl_iterate_phdr
函数遍历应用程序的共享对象列表并为每个对象回调一次 callback
,直到所有共享对象都已处理或 callback
返回非零值。
#include <link.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
char *type;
int p_type;
printf("Name: \\"%s\\" (%d segments)\\n", info->dlpi_name,
info->dlpi_phnum);
for (int j = 0; j < info->dlpi_phnum; j++) {
p_type = info->dlpi_phdr[j].p_type;
type = (p_type == PT_LOAD) ? "PT_LOAD" :
(p_type == PT_DYNAMIC) ? "PT_DYNAMIC" :
(p_type == PT_INTERP) ? "PT_INTERP" :
(p_type == PT_NOTE) ? "PT_NOTE" :
(p_type == PT_INTERP) ? "PT_INTERP" :
(p_type == PT_PHDR) ? "PT_PHDR" :
(p_type == PT_TLS) ? "PT_TLS" :
(p_type == PT_GNU_EH_FRAME) ? "PT_GNU_EH_FRAME" :
(p_type == PT_GNU_STACK) ? "PT_GNU_STACK" :
(p_type == PT_GNU_RELRO) ? "PT_GNU_RELRO" : NULL;
printf(" %2d: [%14p; memsz:%7jx] flags: %#jx; ", j,
(void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr),
(uintmax_t) info->dlpi_phdr[j].p_memsz,
(uintmax_t) info->dlpi_phdr[j].p_flags);
if (type != NULL)
printf("%s\\n", type);
else
printf("[other (%#x)]\\n", p_type);
}
return 0;
}
int
main(int argc, char *argv[])
{
dl_iterate_phdr(callback, NULL);
exit(EXIT_SUCCESS);
}
PHT
typedef struct elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;
字段 | 占位 | 示意 |
---|---|---|
p_type | 4 | 类型,关注“LOAD”类型 |
p_flags | 4 | 权限属性,R W X |
p_offset | 8 | 在文件中偏移 |
p_vaddr | 8 | “Segment” 的第一个字节在进程虚拟地址空间的起始位置。 |
p_paddr | 8 | “Segment” 物理装载地址,一般情况下跟 p_vaddr 一样 |
p_filesz | 8 | ”Segment“ 在 ELF 文件中所占空间的长度 |
p_memsz | 8 | “Segment” 在进程虚拟地址空间所占的长度 |
p_align | 8 | “Segment” 的对齐属性 |
在操作系统里,VMA 用来映射可执行文件中的各个 “Segment”,也使用 VMA 来对进程的地址空间进行管理。
Linux 中,可以通过查看 “/proc” 来查看进程的虚拟看空间分别:
00400000-00401000 r--p 00000000 08:02 6949080 ./sectionmapping.elf
00401000-00495000 r-xp 00001000 08:02 6949080 ./sectionmapping.elf
00495000-004bc000 r--p 00095000 08:02 6949080 ./sectionmapping.elf
004bd000-004c0000 r--p 000bc000 08:02 6949080 ./sectionmapping.elf
004c0000-004c3000 rw-p 000bf000 08:02 6949080 ./sectionmapping.elf
004c3000-004c4000 rw-p 00000000 00:00 0
00e5c000-00e7f000 rw-p 00000000 00:00 0 [heap]
7ffee7ab1000-7ffee7ad2000 rw-p 00000000 00:00 0 [stack]
7ffee7b29000-7ffee7b2d000 r--p 00000000 00:00 0 [vvar]
7ffee7b2d000-7ffee7b2f000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]