与大多数传统调试器或观察工具相比,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 |