借助/proc/pid/mem优化Android native crash的信息捕获

背景

在实现Android native crash捕获功能的时候,需要读取crash进程的内存数据,比如在计算elf的 load bias、Build-Id时,需要读取一块内存区域的数据,比如在获取以某个寄存器值为地址的附近内存数据时,也需要读取一块内存数据。之前我实现的时候是使用ptrace系统调用来读取crash进程数据的,当时查看了下,似乎ptrace不支持一次读取一块内存数据,所以当时我是多次PTRACE_PEEKDATA来实现的,也就是多次调用下面的方法来获取crash进程的一块内存数据的:

c++ 复制代码
std::optional<long> readData(pid_t pid, void* addr) {
    errno = 0;
    long data = ptrace(PTRACE_PEEKDATA, pid, addr, nullptr);
    if (errno != 0) {
        return {};
    }
    return data;
}

上周在写相关文章:Android native crash sdk实现之crash捕获&tombstone信息的生成的时候,又想起来这个事情,感觉应该是有方法能够一次获取一个 memory block的。

/proc/pid/mem

通过搜索发现 Linux 上有个文件:/proc/pid/mem,我们可以借助openlseekread 来访问目标进程的内存数据:

  1. 通过 open 打开 /proc/pid/mem,便可借助打开的这个fd访问目标进程的虚拟内存
  2. 通过 lseek 可以定位到指定的虚拟地址处
  3. 通过 read 可以读取一个内存块

当然打开/proc/pid/mem肯定是需要权限的,否则就可以获取任意进程的内存数据了。不过我们的dumper进程是crash进程的子进程,可以PTRACE_ATTACH到crash进程,也有权限open/proc/pid/mem

一次读取一个memory block

可以按如下方式打开crash进程的 mem fd:

c++ 复制代码
int openMemFd(pid_t targetPid) {
    char path[32];
    snprintf(path, sizeof(path), "/proc/%d/mem", targetPid);
    return open(path, O_RDONLY);
}

可以按如下方式一次读取一个 memory block:

c++ 复制代码
ssize_t readMemoryBlock(int memfd, uint64_t addr, void* buf, size_t len) {
    if (!buf) {
        return -1;
    }

    if (lseek(memfd, addr, SEEK_SET) == -1) {
        LOGE("lseek failed: %s", strerror(errno));
        return -1;
    }

    return read(memfd, buf, len);
}

以计算elf build-id 为例

上面提供了一个readMemoryBlock方法可以一次读取一个memory block,我们以计算elf build-id 为例看下使用:

  1. 读取 elf header:
c++ 复制代码
ElfW(Ehdr) ehdr;
if (readMemoryBlock(memfd, mapAddr, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) {
    LOGE("failed read elf header through memfd");
    return;
}
  1. 读取 program header table:
c++ 复制代码
auto phdrAddr = mapAddr + ehdr.e_phoff;
auto phdrSize = ehdr.e_phnum * ehdr.e_phentsize;
auto phdrData = malloc(phdrSize);
if (!phdrData) {
    return;
}
if (readMemoryBlock(memfd, phdrAddr, phdrData, phdrSize) != phdrSize) {
    LOGE("failed read phdr through memfd");
    free(phdrData);
    return;
}
  1. 读取 PT_NOTE 并计算 build-id:
c++ 复制代码
for (int i = 0; i < ehdr.e_phnum; ++i) {
    auto phdr = *(ElfW(Phdr)*)((uint64_t)phdrData + i * ehdr.e_phentsize);
    switch (phdr.p_type) {
        case PT_NOTE: {
            auto notePtr = (const char*)(loadBias_ + phdr.p_vaddr);
            auto noteData = malloc(phdr.p_memsz);
            if (readMemoryBlock(memfd, (uint64_t)notePtr, noteData, phdr.p_memsz) != phdr.p_memsz) {
                LOGE("failed read note through memfd");
            } else {
                auto noteStart = (const char*)noteData;
                auto noteEnd = noteStart + phdr.p_memsz;
                do {
                    auto note = *(ElfW(Nhdr)*) noteStart;
                    if (strncmp(noteStart + sizeof(ElfW(Nhdr)), "GNU", sizeof("GNU")) == 0) {
                        auto descStart = noteStart + sizeof(ElfW(Nhdr)) + note.n_namesz;
                        auto descEnd = descStart + note.n_descsz;

                        std::stringstream buf;
                        buf.fill('0');
                        buf.setf(std::ios_base::hex, std::ios_base::basefield);
                        for (; descStart < descEnd; ++descStart) {
                            buf.width(2);
                            buf << (uint32_t) *descStart;
                        }
                        buildId_ = buf.str();
                        break;
                    }

                    noteStart += sizeof(ElfW(Nhdr)) + note.n_namesz + note.n_descsz;
                } while (noteStart < noteEnd);
            }
            free(noteData);
            break;
        }
    }
}

优势

相比多次调用ptrace系统调用,性能上应该会有优化,另外多次的系统调用在错误处理方面也会比较麻烦

相关推荐
肖田变强不变秃1 小时前
C++实现有限元计算 矩阵装配Assembly类
开发语言·c++·矩阵·有限元·ansys
c++初学者ABC2 小时前
学生管理系统C++版(简单版)详解
c++·结构体·学生管理系统
kucupung2 小时前
【C++基础】多线程并发场景下的同步方法
开发语言·c++
L73S372 小时前
C++入门(1)
c++·程序人生·考研·蓝桥杯·学习方法
五味香2 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
迂幵myself2 小时前
14-6-1C++的list
开发语言·c++·list
十二测试录2 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
w(゚Д゚)w吓洗宝宝了2 小时前
观察者模式 - 观察者模式的应用场景
c++·观察者模式
Couvrir洪荒猛兽4 小时前
Android实训九 数据存储和访问
android
捕鲸叉4 小时前
Linux/C/C++下怎样进行软件性能分析(CPU/GPU/Memory)
c++·软件调试·软件验证