借助/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系统调用,性能上应该会有优化,另外多次的系统调用在错误处理方面也会比较麻烦

相关推荐
zh_xuan1 小时前
c++ 类的语法3
开发语言·c++
奔跑吧 android1 小时前
【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】
android·bluetooth·bt·aosp13·storagemodule
一律清风2 小时前
【Opencv】canny边缘检测提取中心坐标
c++·opencv
田一一一5 小时前
Android framework 中间件开发(三)
android·中间件·framework·jni
a东方青6 小时前
蓝桥杯 2024 C++国 B最小字符串
c++·职场和发展·蓝桥杯
XiaoyaoCarter8 小时前
每日一道leetcode
c++·算法·leetcode·职场和发展·二分查找·深度优先·前缀树
galaxy_strive8 小时前
qtc++ qdebug日志生成
开发语言·c++·qt
Darkwanderor8 小时前
c++STL-list的模拟实现
c++·list
Humbunklung9 小时前
Visual Studio 2022 中添加“高级保存选项”及解决编码问题
前端·c++·webview·visual studio
小乌龟不会飞9 小时前
gflags 安装及使用
c++·mfc·gflags 库