运行时链接程序可执行许多操作,包括将目标文件映射到内存中以及绑定符号。调试程序通常需要在分析应用程序的过程中访问说明这些运行时链接程序操作的信息。这些调试器作为不同于其所分析的应用程序的进程运行。
本节介绍了用于监视和修改其他进程中的动态链接应用程序的 rtld-debugger 接口。此接口的体系结构采用 libc_db(3LIB) 中所使用的模型。
使用 rtld-debugger 接口时,至少涉及两个进程:
控制进程与 rtld-debugger 接口库链接,并使用该接口来检查目标进程的动态方面。64 位控制进程可以调试 64 位目标和 32 位目标。但是,32 位控制进程只能调试 32 位目标。
当控制进程为调试器并且其目标为动态可执行文件时,最需要使用 rtld-debugger 接口。
rtld-debugger 接口可启用目标进程的以下活动:
与运行时链接程序初次会合。
通知装入和卸载动态目标文件。
检索与任何装入的目标文件相关的信息。
跳过过程链接表项。
启用目标文件填充。
要检查和处理目标进程,rtld-debugger 接口需要使用导出接口、导入接口以及代理在这些接口之间进行通信。
控制进程与 librtld_db.so.1 所提供的 rtld-debugger 接口链接,并会请求从该库导出的接口。此接口在 /usr/include/rtld_db.h 中定义。与此相反,librtld_db.so.1 会请求从控制进程导入的接口。通过此交互,rtld-debugger 接口可以执行以下操作:
在目标进程中查找符号。
在目标进程中读写内存。
导入接口由许多 proc_service 例程组成,大多数调试器已经使用这些例程来分析进程。这些例程将在调试器导入接口中进行介绍。
rtld-debugger 接口假定请求 rtld-debugger 接口时会停止进程分析。如果未停止分析,则目标进程的运行时链接程序内的数据结构在检查时可能处于不一致状态。
下图中显示了 librtld_db.so.1、控制进程(调试器)和目标进程(动态可执行文件)之间的信息流程。
图 6-1 rtld-debugger 信息流程
注 - rtld-debugger 接口依赖于 proc_service 接口 /usr/include/proc_service.h,后者被视为实验接口。rtld-debugger 接口可能必须跟踪 proc_service 接口在发展中的变化。
/usr/demo/librtld_db 下的 SUNWosdem 软件包中提供了使用 rtld-debugger 接口的控制进程的实现样例。此调试器 rdb 提供了使用 proc_service 导入接口的示例,并说明了所有 librtld_db.so.1 导出接口所需的调用顺序。以下各节介绍 rtld-debugger 接口。可以查看调试器样例,获取更多详细信息。
代理提供了可以描述内部接口结构的不透明处理方式,还提供了导出接口与导入接口之间的通信机制。rtld-debugger 接口旨在供可以同时处理多个进程的调试器使用,这些代理用于标识进程。
控制进程创建的不透明结构,用于标识在导出接口与导入接口之间传递的目标进程。
rtld-debugger 接口创建的不透明结构,用于标识在导出接口与导入接口之间传递的目标进程。
本节介绍 /usr/lib/librtld_db.so.1 审计库所导出的各种接口。可以将这些接口分为不同的功能组。
此函数可确定 rtld-debugger 的版本要求。基本 version 会定义为 RD_VERSION1。当前 version 始终由 RD_VERSION 定义。
rd_err_e rd_init(int version);
Solaris 8 10/00 发行版中添加的版本 RD_VERSION2 扩展了 rd_loadobj_t 结构。请参见扫描可装入目标文件中的 rl_flags、rl_bend 和 rl_dynamic 字段。
Solaris 8 01/01 发行版中添加的版本 RD_VERSION3 扩展了 rd_plt_info_t 结构。请参见跳过过程链接表中的 pi_baddr 和 pi_flags 字段。
如果控制进程要求的版本高于可用的 rtld-debugger 接口版本,则会返回 RD_NOCAPAB。
此函数可创建新的导出接口代理。
rd_agent_t *rd_new(struct ps_prochandle *php);
php 是控制进程所创建的 cookie,用于标识目标进程。此 cookie 供控制进程提供的导入接口用于维护上下文,并且对于 rtld-debugger 接口是不透明的。
此函数可基于为 rd_new() 提供的相同 ps_prochandle 结构重置代理内的信息。
rd_err_e rd_reset(struct rd_agent *rdap);
void rd_delete(struct rd_agent *rdap);
rtld-debugger 接口(在 rtld_db.h 中定义)会返回以下错误状态:
typedef enum { RD_ERR, RD_OK, RD_NOCAPAB, RD_DBERR, RD_NOBASE, RD_NODYNAM, RD_NOMAPS } rd_err_e;
以下接口可用于收集错误信息。
char *rd_errstr(rd_err_e rderr);
void rd_log(const int onoff);
启用日志记录时,会使用更多详细诊断信息来调用控制进程所提供的导入接口函数 ps_plog()。
可以获取运行时链接程序中维护的每个目标文件的信息。通过使用 rtld_db.h 中定义的以下结构,可实现链接映射:
typedef struct rd_loadobj { psaddr_t rl_nameaddr; unsigned rl_flags; psaddr_t rl_base; psaddr_t rl_data_base; unsigned rl_lmident; psaddr_t rl_refnameaddr; psaddr_t rl_plt_base; unsigned rl_plt_size; psaddr_t rl_bend; psaddr_t rl_padstart; psaddr_t rl_padend; psaddt_t rl_dynamic; } rd_loadobj_t;
请注意,在此结构中提供的所有地址(包括字符串指针)都是目标进程中的地址,而不是控制进程本身的地址空间中的地址。
指向包含动态目标文件名称的字符串的指针。
在修订版 RD_VERSION2 中,使用 RD_FLG_MEM_OBJECT 标识动态装入的可重定位目标文件。
动态目标文件的基本地址。
动态目标文件数据段的基本地址。
链接映射标识符(请参见建立名称空间)。
如果动态目标文件是标准过滤器,则指向 filtee 的名称。
提供这些元素是为了向下兼容,当前未使用。
目标文件的结束地址 (text + data + bss)。在修订版 RD_VERSION2 中,动态装入的可重定位目标文件将导致此元素指向创建的目标文件(包括其节头)的结尾。
动态目标文件之前填充的基本地址(请参阅动态目标文件填充)。
动态目标文件之后填充的基本地址(请参阅动态目标文件填充)。
添加了 RD_VERSION2 的此字段可提供目标文件动态节的基本地址,从而可允许引用 DT_CHECKSUM 之类的项(请参见表 7-32)。
rd_loadobj_iter() 例程使用此目标文件数据结构来访问运行时链接程序的链接映射列表中的信息。
对当前在目标进程中装入的所有动态目标文件重复执行此函数。
typedef int rl_iter_f(const rd_loadobj_t *, void *); rd_err_e rd_loadobj_iter(rd_agent_t *rap, rl_iter_f *cb, void *clnt_data);
每次重复时都会调用 cb 指定的导入函数。可以使用 clnt_data 将数据传递给 cb 调用。通过指向可变(已分配的栈)rd_loadobj_t 结构的指针可返回有关每个目标文件的信息。
cb 例程中的返回代码通过 rd_loadobj_iter() 进行检查,并具有以下含义:
1-继续处理链接映射。
0-停止处理链接映射并将控制权返回给控制进程。
rd_loadobj_iter() 运行成功时会返回 RD_OK。返回 RD_NOMAPS 表示运行时链接程序尚未装入初始链接映射。
控制进程可以跟踪运行时链接程序作用域内发生的特定事件。这些事件包括:
运行时链接程序已经装入并重定位所有动态目标文件,并且即将开始调用每个装入的目标文件的 .init 节。
运行时链接程序已经完成调用所有的 .init 节,并且即将会将控制权转交给主可执行文件。
已经调用运行时链接程序来装入或卸载动态目标文件。
可以使用 sys/link.h 和 rtld_db.h 中定义的以下接口来监视这些事件:
typedef enum { RD_NONE = 0, RD_PREINIT, RD_POSTINIT, RD_DLACTIVITY } rd_event_e; /* * Ways that the event notification can take place: */ typedef enum { RD_NOTIFY_BPT, RD_NOTIFY_AUTOBPT, RD_NOTIFY_SYSCALL } rd_notify_e; /* * Information on ways that the event notification can take place: */ typedef struct rd_notify { rd_notify_e type; union { psaddr_t bptaddr; long syscallno; } u; } rd_notify_t;
以下函数可跟踪事件:
rd_err_e rd_event_enable(struct rd_agent *rdap, int onoff);
注 - 目前,由于性能原因,运行时链接程序会忽略事件禁用。控制进程应假定可以访问指定的断点,因为最后调用了此例程。
rd_err_e rd_event_addr(rd_agent_t *rdap, rd_event_e event, rd_notify_t *notify);
根据事件类型,通过调用 notify->u.syscallno 标识的运行正常的低成本系统调用或者在 notify->u.bptaddr 指定的地址执行断点可实现控制进程通知。控制进程负责跟踪系统调用或定位实际断点。
事件发生后,可以通过 rtld_db.h 中定义的此接口获取其他信息:
typedef enum { RD_NOSTATE = 0, RD_CONSISTENT, RD_ADD, RD_DELETE } rd_state_e; typedef struct rd_event_msg { rd_event_e type; union { rd_state_e state; } u; } rd_event_msg_t;
rd_state_e 值包括:
没有其他可用的状态信息。
链接映射处于稳定状态,可以对其进行检查。
正在装入动态目标文件,链接映射未处于稳定状态。应该在达到 RD_CONSISTANT 状态之后再检查这些链接映射。
正在删除动态目标文件,链接映射未处于稳定状态。应该在达到 RD_CONSISTANT 状态之后再检查这些链接映射。
rd_event_getmsg() 函数用于获取此事件状态信息。
rd_err_e rd_event_getmsg(struct rd_agent *rdap, rd_event_msg_t *msg);
下表显示了各种不同事件类型的可能状态。
|
通过使用 rtld-debugger 接口,控制进程可以跳过过程链接表项。第一次要求控制进程(如调试器)步入函数时,通过过程链接表处理可将控制权传递给运行时链接程序以搜索函数定义。
通过使用以下接口,控制进程可以跳过运行时链接程序的过程链接表处理。控制进程可以基于 ELF 文件中提供的外部信息来确定何时遇到过程链接表项。
目标进程步入过程链接表项之后,便会调用 rd_plt_resolution() 接口。
此函数可返回当前过程链接表项的解析状态以及有关如何跳过此状态的信息。
rd_err_e rd_plt_resolution(rd_agent_t *rdap, paddr_t pc, lwpid_t lwpid, paddr_t plt_base, rd_plt_info_t *rpi);
pc 表示过程链接表项的第一条指令。lwpid 提供 lwp 标识符,plt_base 提供过程链接表的基本地址。这三个变量提供的信息足以供多个体系结构用于处理过程链接表。
rpi 提供有关以下数据结构(在 rtld_db.h 中定义)中定义的过程链接表项的详细信息:
typedef enum { RD_RESOLVE_NONE, RD_RESOLVE_STEP, RD_RESOLVE_TARGET, RD_RESOLVE_TARGET_STEP } rd_skip_e; typedef struct rd_plt_info { rd_skip_e pi_skip_method; long pi_nstep; psaddr_t pi_target; psaddr_t pi_baddr; unsigned int pi_flags; } rd_plt_info_t; #define RD_FLG_PI_PLTBOUND 0x0001
rd_plt_info_t 结构的元素包括:
标识遍历过程链接表项的方法。此方法可设置为 rd_skip_e 值之一。
标识返回 RD_RESOLVE_STEP 或 RD_RESOLVE_TARGET_STEP 时跳过的指令数。
指定返回 RD_RESOLVE_TARGET_STEP 或 RD_RESOLVE_TARGET 时设置断点的地址。
添加了 RD_VERSION3 的过程链接表的目标地址。设置 pi_flags 字段的 RD_FLG_PI_PLTBOUND 标志之后,此元素可标识已解析(绑定)的目标地址。
添加了 RD_VERSION3 的标志字段。标志 RD_FLG_PI_PLTBOUND 可将过程链接项标识为已解析(绑定)到其目标地址,此地址可用于 pi_baddr 字段。
rd_plt_info_t 返回值表明了以下可能的情况:
必须由运行时链接程序解析通过此过程链接表进行的首次调用。在这种情况下,rd_plt_info_t 包含以下内容:
{RD_RESOLVE_TARGET_STEP, M, <BREAK>, 0, 0}
控制进程会在 BREAK 处设置断点,从而使目标进程继续运行。到达断点时,即会完成过程链接表项处理。然后,控制进程可以将 M 条指令转到目标函数。请注意,由于这是通过过程链接表项进行的首次调用,因此尚未设置绑定地址 (pi_baddr)。
通过此过程链接表第 N 次进行调用时,rd_plt_info_t 会包含以下内容:
{RD_RESOLVE_STEP, M, 0, <BoundAddr>, RD_FLG_PI_PLTBOUND}
过程链接表项已经过解析,并且控制进程可以将 M 条指令转到目标函数。过程链接表项绑定到的地址为 <BoundAddr>,并且已在标志字段中设置了 RD_FLG_PI_PLTBOUND 位。
运行时链接程序的缺省行为取决于要装入动态目标文件的操作系统(可以在其中最有效地引用这些目标文件)。如果能够对装入目标进程内存的目标文件执行填充,有些控制进程会从中受益。控制进程可以使用此接口请求此填充。
此函数可启用或禁用对目标进程的任何随后装入的目标文件的填充。可以在装入目标文件的两端进行填充。
rd_err_e rd_objpad_enable(struct rd_agent *rdap, size_t padsize);
padsize 指定将任何目标文件装入内存前后要保留的填充大小(以字节为单位)。使用具有 PROT_NONE 权限和 MAP_NORESERVE 标志的 mmap(2) 可将填充保留为内存映射。实际上,运行时链接程序可保留与任何装入目标文件相邻的目标进程虚拟地址空间区域。控制进程随后可以利用这些空间区域。
如果 padsize 为 0,则对于后续目标文件将禁用目标文件填充。
注 - 通过使用 proc(1) 工具并引用 rd_loadobj_t 中提供的链接映射信息,可报告使用 mmap(2) 从具有 MAP_NORESERVE 标志的 /dev/zero 中获取的预留空间。
控制进程必须提供给 librtld_db.so.1 的导入接口在 /usr/include/proc_service.h 中定义。可以在 rdb 演示调试器中找到这些 proc_service 函数的实现样例。rtld-debugger 接口仅使用一部分可用的 proc_service 接口。将来版本的 rtld-debugger 接口可能会利用其他 proc_service 接口,而不会创建不兼容的更改。
当前 rtld-debugger 接口会使用以下接口:
此函数可返回指向 auxv 向量副本的指针。
ps_err_e ps_pauxv(const struct ps_prochandle *ph, auxv_t **aux);
此函数可从目标进程中读取数据。
ps_err_e ps_pread(const struct ps_prochandle *ph, paddr_t addr, char *buf, int size);
此函数可将数据写入目标进程。
ps_err_e ps_pwrite(const struct ps_prochandle *ph, paddr_t addr, char *buf, int size);
此函数通过 rtld-debugger 接口中的其他诊断信息调用。
void ps_plog(const char *fmt, ...);
控制进程会确定在何处或者是否记录此诊断信息。ps_plog() 的参数采用 printf(3C) 格式。
此函数可在目标进程中搜索符号。
ps_err_e ps_pglobal_lookup(const struct ps_prochandle *ph, const char *obj, const char *name, ulong_t *sym_addr);
在目标进程 ph 中的名为 obj 的目标文件中搜索名为 name 的符号。如果找到此符号,则将符号地址存储在 sym_addr 中。
此函数可在目标进程中搜索符号。
ps_err_e ps_pglobal_sym(const struct ps_prochandle *ph, const char *obj, const char *name, ps_sym_t *sym_desc);
在目标进程 ph 中的名为 obj 的目标文件中搜索名为 name 的符号。如果找到此符号,则将符号描述符存储在 sym_desc 中。
如果在创建任何链接映射之前,rtld-debugger 接口需要在应用程序或运行时链接程序内查找符号,则可以使用 obj 的以下保留值:
#define PS_OBJ_EXEC ((const char *)0x0) /* application id */ #define PS_OBJ_LDSO ((const char *)0x1) /* runtime linker id */
控制进程可以使用以下伪代码将 procfs 文件系统用于这些目标文件:
ioctl(.., PIOCNAUXV, ...) - obtain AUX vectors ldsoaddr = auxv[AT_BASE]; ldsofd = ioctl(..., PIOCOPENM, &ldsoaddr); /* process elf information found in ldsofd ... */ execfd = ioctl(.., PIOCOPENM, 0); /* process elf information found in execfd ... */
找到文件描述符之后,控制程序即可检查 ELF 文件来查找其符号信息。