尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从ELF到DWARF:memprof如何定位Ruby函数地址并动态插桩系统调用

从ELF到DWARF:memprof如何定位Ruby函数地址并动态插桩系统调用 从ELF到DWARFmemprof如何定位Ruby函数地址并动态插桩系统调用【免费下载链接】memprofA Ruby gem for memory profiling项目地址: https://gitcode.com/gh_mirrors/me/memprofmemprof 是一款运行在 Ruby 层的内存剖析工具Ruby gem for memory profiling它能追踪每个对象在哪个文件、哪一行代码被创建并通过动态插桩统计 GC、文件描述符、MySQL、Memcached、malloc 等系统调用与库调用。它的核心难题在于不修改 Ruby 源码就能在运行时劫持 Ruby 内部的函数调用。而要做到这一点memprof 必须先读懂 Linux 可执行文件——从 ELF 格式到 DWARF 调试信息本文带你完整理解这套机制。为什么必须读懂 ELF 可执行文件每个 Ruby 进程在 Linux 上都是一个 ELF 格式的可执行文件或共享库。ELF 文件内部被划分为多个段和节其中和 memprof 最相关的有.text机器指令代码段插桩需要改写它.plt/.got.plt过程链接表动态链接的跳板是拦截外部函数调用的最佳落点.dynsym/.symtab动态符号表与普通符号表记录函数名与地址的对应关系。memprof 用 libelf 库逐节解析当前 Ruby 二进制把上述信息存进一个elf_info结构。核心解析逻辑在 ext/elf.c 的dissect_elf()中它遍历所有节头识别.dynamic、.dynsym、.text、.plt、.got.plt等节并记录它们的地址。源码注释里有一句关键提示memprof only works on binaries that are not stripped——剥离符号表的二进制无法被解析。 这就是官方文档强调Ruby 必须带调试符号编译的原因见 README.md 的 Compatibility 章节。DWARF 调试信息补齐 C 结构体的地图ELF 的符号表只能告诉你函数在哪但 memprof 还需要知道C 结构体的内存布局。例如要 dump 一个 RubyBlock对象它得知道BLOCK.body字段偏移多少字节、Method结构的mid在什么位置。这些信息来自DWARF——编译器用-g选项生成的调试格式。memprof 用 libdwarf 库在 ext/elf.c 中实现了两个关键函数函数作用典型用途bin_type_size()查结构体总大小计算gc_sweep等函数的扫描范围bin_type_member_offset()查成员字段偏移定位BLOCK、Method的各成员地址实现方式是递归遍历 DWARF 的 DIE调试信息项树按DW_TAG_structure_type、DW_TAG_member标签匹配名字再读出DW_AT_byte_size和DW_AT_data_member_location属性见 ext/bin_api.h 的接口说明。另外memprof 还处理了一个常见的工程难题发布版二进制常把调试符号剥离到独立文件。它通过 ELF 的.gnu_debuglink节找到调试文件名用 CRC32 校验后从/usr/lib/debug目录加载find_debug_syms()保证在瘦身版Ruby 上也能工作。定位函数地址两条并行的路径拿到地址后就要动手改代码。memprof 的bin_update_image()ext/elf.c按顺序尝试两条路径路径一改写 PLT/GOT拦截动态调用通过动态链接调用的函数调用指令会先跳到 PLT 条目PLT 再跳转到 GOT 里记录的真实地址。memprof 的find_plt_addr()遍历.rela.plt重定位条目按符号名如rb_newobj匹配找到对应 GOT 槽位然后用overwrite_got()把槽位里的地址偷换成自己的代理函数——原地址会被保存下来供代理函数在统计完事后继续调用原逻辑。路径二扫描 .text 改写调用指令拦截内部调用如果符号不在 PLT 中Ruby 内部函数大多如此memprof 会沿.text段逐字节扫描用arch_insert_st1_tramp()识别所有call rb_newobj形式的指令把它们改写为跳转到第一阶段跳板。同时memprof 通过 Linux 的link_map链表walk_linkmap()遍历进程里所有已加载的共享库——包括libmysqlclient、libmemcached等第三方库对每个库重复上述 PLT 钩子流程这正是它能把 MySQL 查询、Memcached 命令也统计进来的原因。两级跳板动态插桩的精髓直接原地改写代码会破坏原始指令memprof 采用了经典的**两级跳板trampoline**方案实现在 ext/tramp.c一级跳板Stage 1把目标位置 5 字节左右的call/jmp指令改写为一条jmp指向跳板表中的对应条目二级跳板Stage 2跳板表里存放统计逻辑 跳转回原函数的代码。有个容易忽略的细节x86-64 的相对跳转指令范围只有±2GB。所以 memprof 的bin_allocate_page()会在.text段附近或低 4GB 地址空间专门mmap一块可执行内存来存放二级跳板确保跳转距离在范围内。以对象追踪为例Memprof.start触发insert_tramp(rb_newobj, newobj_tramp)后Ruby VM 每次创建对象都会先经过 memprof 的newobj_tramp()ext/memprof.c——它记下当前源码文件与行号再放行给真正的rb_newobj()。这就是文件:行号:类名统计报表的由来。追踪器框架一次插桩多路统计插桩层之上memprof 抽象出一个通用追踪器Tracer框架ext/tracer.h每个追踪器提供start/stop/reset/dump四个回调Memprof.trace统一调度它们结果汇总成 JSON 报告。内置追踪器位于 ext/tracers/ 目录fd.c钩住connect/read/write/select统计网络与文件 I/O 次数和耗时mysql.c/postgres.c统计 SQL 查询memcache.c统计 Memcached 命令与响应memory.c统计 malloc/realloc/free 的字节数。上手须知三把钥匙缺一不可想让 memprof 完整工作你的 Ruby 环境需要满足64 位 Linux下编译的 Ruby 1.8.xMRI 或 REE带调试符号编译加-g或使用发行版的-dbg包如libruby1.8-dbg未被 strip或至少保留.gnu_debuglink指向独立的调试符号文件。小结memprof 的巧妙之处在于把三件底层硬活组合成一条流水线libelf 解析 ELF 结构 → libdwarf 读取 DWARF 补齐布局信息 → PLT 改写与两级跳板完成无源码插桩。理解了这条流水线你不仅能看懂 memprof 的Memprof.trace为什么能统计到 C 层的系统调用也能掌握给任意 Linux 程序做运行时插桩的通用套路。【免费下载链接】memprofA Ruby gem for memory profiling项目地址: https://gitcode.com/gh_mirrors/me/memprof创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表