Oracle® Solaris 11.2 链接程序和库指南

退出打印视图

更新时间: 2014 年 7 月
 
 

调试器导出接口

本节介绍 /usr/lib/librtld_db.so.1 审计库所导出的各种接口。可以将这些接口分为不同的功能组。

代理处理接口

rd_init()

此函数可确定 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_new()

此函数可创建新的导出接口代理。

rd_agent_t *rd_new(struct ps_prochandle *php);

php 是控制进程所创建的 cookie,用于标识目标进程。此 cookie 供控制进程提供的导入接口用于维护上下文,并且对于 rtld-debugger 接口是不透明的。

rd_reset()

此函数可基于为 rd_new() 提供的相同 ps_prochandle 结构重置代理内的信息。

rd_err_e rd_reset(struct rd_agent *rdap);

此函数在重新启动目标进程时调用。

rd_delete()

此函数可删除代理并释放与其关联的任何状态。

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;

以下接口可用于收集错误信息。

rd_errstr()

此函数可返回说明错误代码 rderr 的描述性错误字符串。

char *rd_errstr(rd_err_e rderr);
rd_log()

此函数可启用 (1) 或禁用 (0) 日志记录。

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;
        unsigned long   rl_tlsmodid;
} rd_loadobj_t;

请注意,在此结构中提供的所有地址(包括字符串指针)都是目标进程中的地址,而不是控制进程本身的地址空间中的地址。

rl_nameaddr

指向包含动态目标文件名称的字符串的指针。

rl_flags

在修订版 RD_VERSION2 中,使用 RD_FLG_MEM_OBJECT 标识动态装入的可重定位目标文件。

rl_base

动态目标文件的基本地址。

rl_data_base

动态目标文件数据段的基本地址。

rl_lmident

链接映射标识符(请参见建立名称空间)。

rl_refnameaddr

如果动态目标文件是标准过滤器,则指向 filtee 的名称。

rl_plt_baserl_plt_size

提供这些元素是为了向下兼容,当前未使用。

rl_bend

目标文件的结束地址 (text + data + bss)。在修订版 RD_VERSION2 中,动态装入的可重定位目标文件将导致此元素指向创建的目标文件(包括其节头)的结尾。

rl_padstart

动态目标文件之前填充的基本地址(请参阅动态目标文件填充)。

rl_padend

动态目标文件之后填充的基本地址(请参阅动态目标文件填充)。

rl_dynamic

添加了 RD_VERSION2 的此字段可提供目标文件动态节的基本地址,从而可允许引用 DT_CHECKSUM 之类的项(请参见Table 13–8)。

rl_tlsmodid

RD_VERSION4 添加的此字段为线程局部存储 TLS 引用提供了模块标识符。模块标识符是对目标文件唯一的一个小整数。可以将该标识符传递到 libc_db 函数 td_thr_tlsbase(),来为相关目标文件获取线程的 TLS 块的基本地址。请参见td_thr_tlsbase(3C_DB)

rd_loadobj_iter() 例程使用此目标文件数据结构来访问运行时链接程序的链接映射列表中的信息。

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 表示运行时链接程序尚未装入初始链接映射。

事件通知

控制进程可以跟踪运行时链接程序作用域内发生的特定事件。这些事件包括:

RD_PREINIT

运行时链接程序已经装入并重定位所有动态目标文件,并且即将开始调用每个装入的目标文件的 .init 节。

RD_POSTINIT

运行时链接程序已经完成调用所有的 .init 节,并且即将会将控制权转交给主可执行文件。

RD_DLACTIVITY

已经调用运行时链接程序来装入或卸载动态目标文件。

可以使用 sys/link.hrtld_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_event_enable()

此函数可启用 (1) 或禁用 (0) 事件监视。

rd_err_e rd_event_enable(struct rd_agent *rdap, int onoff);

注 - 目前,由于性能原因,运行时链接程序会忽略事件禁用。控制进程应假定可以访问指定的断点,因为最后调用了此例程。
rd_event_addr()

此函数可指定如何通知控制程序指定的事件。

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_NOSTATE

没有其他可用的状态信息。

RD_CONSISTANT

链接映射处于稳定状态,可以对其进行检查。

RD_ADD

正在装入动态目标文件,链接映射未处于稳定状态。应该在达到 RD_CONSISTANT 状态之后再检查这些链接映射。

RD_DELETE

正在删除动态目标文件,链接映射未处于稳定状态。应该在达到 RD_CONSISTANT 状态之后再检查这些链接映射。

rd_event_getmsg() 函数用于获取此事件状态信息。

rd_event_getmsg()

此函数可提供有关事件的其他信息。

rd_err_e rd_event_getmsg(struct rd_agent *rdap, rd_event_msg_t *msg);

下表显示了各种不同事件类型的可能状态。

RD_PREINIT
RD_POSTINIT
RD_DLACTIVITY
RD_NOSTATE
RD_NOSTATE
RD_CONSISTANT
RD_ADD
RD_DELETE

跳过过程链接表

通过使用 rtld-debugger 接口,控制进程可以跳过过程链接表项。第一次要求控制进程(如调试器)步入函数时,通过过程链接表处理可将控制权传递给运行时链接程序以搜索函数定义。

通过使用以下接口,控制进程可以跳过运行时链接程序的过程链接表处理。控制进程可以基于 ELF 文件中提供的外部信息来确定何时遇到过程链接表项。

目标进程步入过程链接表项之后,便会调用 rd_plt_resolution() 接口。

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 结构的元素包括:

pi_skip_method

标识遍历过程链接表项的方法。此方法可设置为 rd_skip_e 值之一。

pi_nstep

标识返回 RD_RESOLVE_STEPRD_RESOLVE_TARGET_STEP 时跳过的指令数。

pi_target

指定返回 RD_RESOLVE_TARGET_STEPRD_RESOLVE_TARGET 时设置断点的地址。

pi_baddr

添加了 RD_VERSION3 的过程链接表的目标地址。设置 pi_flags 字段的 RD_FLG_PI_PLTBOUND 标志之后,此元素可标识已解析(绑定)的目标地址。

pi_flags

添加了 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_objpad_enable()

此函数可启用或禁用对目标进程的任何随后装入的目标文件的填充。可以在装入目标文件的两端进行填充。

rd_err_e rd_objpad_enable(struct rd_agent *rdap, size_t padsize);

padsize 指定将任何目标文件装入内存前后要保留的填充大小(以字节为单位)。该填充保留为 mmapobj(2) 请求中的内存映射。实际上,运行时链接程序可保留与任何装入目标文件相邻的目标进程虚拟地址空间区域。控制进程随后可以利用这些空间区域。

如果 padsize 为 0,则对于后续目标文件将禁用目标文件填充。


注 - 通过使用 proc(1) 工具并引用 rd_loadobj_t 中提供的链接映射信息,可报告使用 mmapobj(2) 获取的预留空间。