JavaScript is required to for searching.
跳过导航链接
退出打印视图
链接程序和库指南     Oracle Solaris 11 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

第 1 部分使用链接编辑器和运行时链接程序

1.  Oracle Solaris 链接编辑器介绍

2.  链接编辑器

3.  运行时链接程序

4.  共享目标文件

5.  接口和版本控制

6.  使用动态字符串标记建立依赖性

第 2 部分快速参考

7.  链接编辑器快速参考

8.  版本控制快速参考

第 3 部分高级主题

9.  直接绑定

10.  Mapfile

11.  可扩展性机制

链接编辑器支持接口

调用支持接口

32 位环境和 64 位环境

支持接口函数

支持接口示例

运行时链接程序审计接口

建立名称空间

创建审计库

调用审计接口

记录局部审计程序

记录全局审计程序

审计接口交互

审计接口函数

审计接口示例

审计接口演示

审计接口限制

使用应用程序代码

la_pltexit() 的用法

直接检查栈的函数

运行时链接程序调试器接口

控制进程和目标进程之间的交互

调试器接口代理

调试器导出接口

代理处理接口

错误处理

扫描可装入目标文件

事件通知

跳过过程链接表

动态目标文件填充

调试器导入接口

第 4 部分ELF 应用程序二进制接口

12.  目标文件格式

13.  程序装入和动态链接

14.  线程局部存储

第 5 部分附录

A.  链接程序和库的更新及新增功能

B.  System V 发行版 4(版本 1)Mapfile

索引

运行时链接程序调试器接口

运行时链接程序可执行许多操作,包括将目标文件映射到内存中以及绑定符号。调试程序通常需要在分析应用程序的过程中访问说明这些运行时链接程序操作的信息。这些调试器作为不同于其所分析的应用程序的进程运行。

本节介绍了用于监视和修改其他进程中的动态链接应用程序的 rtld-debugger 接口。此接口的体系结构采用 libc_db(3LIB) 中所使用的模型。

使用 rtld-debugger 接口时,至少涉及两个进程:

当控制进程为调试器并且其目标为动态可执行文件时,最需要使用 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、控制进程(调试器)和目标进程(动态可执行文件)之间的信息流程。

图 11-1 rtld-debugger 信息流程

image:rtld-debugger 信息流程。

注 - rtld-debugger 接口依赖于 proc_service 接口 /usr/include/proc_service.h,后者被视为实验接口。rtld-debugger 接口可能必须跟踪 proc_service 接口在发展中的变化。


/usr/demo/librtld_db 下的 pkg:/solaris/source/demo/system 软件包中提供了使用 rtld-debugger 接口的控制进程的实现样例。此调试器 rdb 提供了使用 proc_service 导入接口的示例,并说明了所有 librtld_db.so.1 导出接口所需的调用顺序。以下各节介绍 rtld-debugger 接口。可以查看调试器样例,获取更多详细信息。

调试器接口代理

代理提供了可以描述内部接口结构的不透明处理方式,还提供了导出接口与导入接口之间的通信机制。rtld-debugger 接口旨在供可以同时处理多个进程的调试器使用,这些代理用于标识进程。

struct ps_prochandle

控制进程创建的不透明结构,用于标识在导出接口与导入接口之间传递的目标进程。

struct rd_agent

rtld-debugger 接口创建的不透明结构,用于标识在导出接口与导入接口之间传递的目标进程。

调试器导出接口

本节介绍 /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_flagsrl_bendrl_dynamic 字段。

Solaris 8 01/01 发行版中添加的版本 RD_VERSION3 扩展了 rd_plt_info_t 结构。请参见跳过过程链接表中的 pi_baddrpi_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;
} 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 之类的项(请参见表 13-8)。

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_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) 获取的预留空间。


调试器导入接口

控制进程必须提供给 librtld_db.so.1 的导入接口在 /usr/include/proc_service.h 中定义。可以在 rdb 演示调试器中找到这些 proc_service 函数的实现样例。rtld-debugger 接口仅使用一部分可用的 proc_service 接口。将来版本的 rtld-debugger 接口可能会利用其他 proc_service 接口,而不会创建不兼容的更改。

当前 rtld-debugger 接口会使用以下接口:

ps_pauxv()

此函数可返回指向 auxv 向量副本的指针。

ps_err_e ps_pauxv(const struct ps_prochandle *ph, auxv_t **aux);

由于 auxv 向量信息会复制到已分配的结构,因此只要 ps_prochandle 有效,便会保留指针。

ps_pread()

此函数可从目标进程中读取数据。

ps_err_e ps_pread(const struct ps_prochandle *ph, paddr_t addr,
        char *buf, int size);

size 字节从目标进程中的地址 addr 复制到 buf

ps_pwrite()

此函数可将数据写入目标进程。

ps_err_e ps_pwrite(const struct ps_prochandle *ph, paddr_t addr,
        char *buf, int size);

size 字节从 buf 复制到目标进程的地址 addr

ps_plog()

此函数通过 rtld-debugger 接口中的其他诊断信息调用。

void ps_plog(const char *fmt, ...);

控制进程会确定在何处或者是否记录此诊断信息。ps_plog() 的参数采用 printf(3C) 格式。

ps_pglobal_lookup()

此函数可在目标进程中搜索符号。

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_pglobal_sym()

此函数可在目标进程中搜索符号。

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 文件来查找其符号信息。