本节介绍 /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_PREINIT |
RD_POSTINIT |
RD_DLACTIVITY |
---|---|---|
RD_NOSTATE |
RD_NOSTATE |
RD_CONSISTANT |
|
|
RD_ADD |
|
|
RD_DELETE |
通过使用 rtld-debugger 接口,控制进程可以跳过过程链接表项。第一次要求控制进程(如调试器)步入 (step into) 函数时,通过过程链接表处理可将控制权传递给运行时链接程序以搜索函数定义。
通过使用以下接口,控制进程可以跳过运行时链接程序的过程链接表处理。控制进程可以基于 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 中获取的预留空间。