Solaris 动态跟踪指南

copyin()copyinstr() 子例程

与大多数传统调试器或观察工具相比,DTrace 与进程之间的交互略有不同。这类工具多数可能会在进程范围内执行,从而使用户可直接取消对指向程序变量的指针的引用。DTrace 探测器在 Solaris 内核中执行,而不是在进程内或作为进程的一部分执行。为访问进程数据,探测器需要使用 copyin()copyinstr() 子例程,以便将用户进程数据复制到该内核的地址空间中。

例如,可以考虑下面的 write(2) 系统调用:

ssize_t write(int fd, const void *buf, size_t nbytes);

以下 D 程序说明试图列显传递给 write(2) 系统调用的字符串内容的尝试不正确:

syscall::write:entry
{
	printf("%s", stringof(arg1)); /* incorrect use of arg1 */
}

如果尝试运行此脚本,DTrace 将生成与以下示例类似的错误消息:


dtrace: error on enabled probe ID 1 (ID 37: syscall::write:entry): \
    invalid address (0x10038a000) in action #1

arg1 变量(该变量包含 buf 参数的值)是一个引用执行系统调用的进程内存的地址。要读取该地址中的字符串,请使用 copyinstr() 子例程,并借助 printf() 操作记录其结果:

syscall::write:entry
{
	printf("%s", copyinstr(arg1)); /* correct use of arg1 */

此脚本输出将显示传递给 write(2) 系统调用的所有字符串。不过,有时也可能会显示类似以下的异常输出:


  0     37                      write:entry mada���

copyinstr() 子例程对输入参数进行操作,该参数是以 Null 结尾的 ASCII 字符串的用户地址。但是,传递给 write(2) 系统调用的缓冲区可能会引用二进制数据,而非 ASCII 字符串。要仅列显调用方指定数量的字符串,请使用 copyin() 子例程,该子例程将大小作为其第二个参数:

syscall::write:entry
{
	printf("%s", stringof(copyin(arg1, arg2)));
}

请注意,为使 DTrace 将通过 copyin() 检索的用户数据正确转换为字符串,必须使用 stringof 运算符。使用 copyinstr() 时,无需使用 stringof,因为此函数返回的类型始终为 string

避免错误

copyin()copyinstr() 子例程无法从尚未访问的用户地址读取,因此,如果包含某个地址的页面没有得到访问,则即使该地址有效也可能会产生错误。以下面的示例为例:


# dtrace -n syscall::open:entry'{ trace(copyinstr(arg0)); }'
dtrace: description 'syscall::open:entry' matched 1 probe
CPU     ID                    FUNCTION:NAME
dtrace: error on enabled probe ID 2 (ID 50: syscall::open:entry): invalid address
(0x9af1b) in action #1 at DIF offset 52

在以上示例输出中,该应用程序运行正常,且 arg0 的地址是有效的,但其引用了尚未被对应进程访问的页面。为解决此问题,请在跟踪前等待内核或应用程序使用相应数据。例如,您可以等到系统调用返回时再应用 copyinstr(),如以下示例所示:


# dtrace -n syscall::open:entry'{ self->file = arg0; }' \
-n syscall::open:return'{ trace(copyinstr(self->file)); self->file = 0; }'
dtrace: description 'syscall::open:entry' matched 1 probe
CPU     ID                    FUNCTION:NAME
  2     51                      open:return   /dev/null