我该如何让dtrace以非root权限运行被跟踪的命令?

64

OS X没有像linux中的strace一样的功能,但是它有dtrace,被认为更好。

然而,我想要的是能够对单个命令进行简单跟踪的能力。例如,在linux上,我可以编写strace -f gcc hello.c来捕获所有系统调用,这给我编译程序所需的所有文件名列表(优秀的memoize脚本就是基于这个技巧构建的)

我想在mac上移植memoize,所以我需要某种形式的strace。实际上,我需要的是gcc读取和写入的文件列表,所以我更需要的是truss。当然,我可以使用dtruss -f gcc hello.c并获得相同的功能,但是编译器是以root特权运行的,这显然是不可取的(除了巨大的安全风险外,还有一个问题是a.out文件现在归root所有:-)

然后我尝试了dtruss -f sudo -u myusername gcc hello.c,但这感觉有点不对,并且无效(这次我根本没有得到a.out文件,不知道为什么)

所有这些长篇故事试图激发我的原始问题: 如何让dtrace以普通用户权限运行我的命令,就像在linux中strace一样?

编辑:看起来我不是唯一一个想知道如何做到这一点的人:问题#1204256基本上和我的问题一样(并且有相同的次优sudo答案:-)

9个回答

59

最简单的方法是使用sudo:

sudo dtruss -f sudo -u $USER whoami
另一种解决方案是先运行调试器并监视新的特定进程。 例如:
sudo dtruss -fn whoami

然后在另一个终端运行:

whoami

就是这么简单。

更多棘手的参数可以在手册中找到:man dtruss


或者你可以将dtruss附加到正在运行的用户进程上,例如在Mac上:

sudo dtruss -fp PID

可以使用strace在Linux/Unix上执行类似操作:

sudo strace -fp PID

另一个巧妙的技巧是在执行命令后立即附加到进程中。以下是一些示例:

sudo true; (./Pages &); sudo dtruss -fp `pgrep -n -x Pages`
sudo true; (sleep 1 &); sudo dtruss -fp `pgrep -n -x sleep`
sudo true; (tail -f /var/log/system.log &); sudo dtruss -fp `pgrep -n -x tail`

注意:

  • 第一个 sudo 命令只是为了在第一次运行时缓存密码,

  • 对于像 ls、date 这样的快速命令行,这个技巧不起作用,因为需要一些时间调试器才能附加到进程上,

  • 你必须在两个地方键入你的命令,

  • 如果进程已经在后台运行,可以忽略 & 将其运行到后台,

  • 结束调试后,你需要手动杀死后台进程(例如:killall -v tail)。


6
您指出使用dtruss的“-n”选项是正确的答案,真是太棒了!这完全正确。 - wfaulk
使用macOS SIP,如果追踪任何系统二进制文件(包括sudo),dtruss将会失败。错误信息为:"dtrace: failed to execute sudo: Operation not permitted"。 - Joe

9
-n参数会让dtruss等待并检查与-n参数匹配的进程。使用-f选项仍然可以跟踪从与-n匹配的进程派生出的进程。
这意味着,如果您想要dtruss一个以非特权用户身份运行的进程(假设它是whoami),请按照以下步骤操作:
  1. 打开root shell
  2. 运行dtruss -fn whoami
    • 这将等待名为"whoami"的进程存在
  3. 打开非特权shell
  4. 运行whoami
    • 这将正常执行并退出
  5. 观察dtruss窗口中的系统调用跟踪
    • dtruss不会自动退出-它将继续等待匹配的进程-因此在完成后请退出

本答案重复了@kenorb响应的后半部分,但它值得成为一流的答案。

5

我不知道你是否能使dtruss像strace一样无侵入性。

在我的一些快速测试中,一个变体的“sudo [切换到root] dtruss sudo [返回到非root] cmd”似乎效果更好:

sudo dtruss -f su -l `whoami` cd `pwd` && cmd....

外部的sudo当然会以root身份运行dtruss。

内部的su回到我这里,带有-l选项可以正确地重新创建环境,在这一点上,我们需要返回到我们开始的位置。

如果您想要环境是该用户通常获得的,则认为"su -l user"比"sudo -u user"更好。那将是他们的登录环境; 我不知道是否有一个很好的方法让环境在两个用户更改之间继承。

在您的问题中,除了丑陋之外,您对"sudo dtruss sudo"解决方法的另一个抱怨是"这一次我根本没有a.out文件,不确定为什么"。我也不知道为什么,但在我的小测试脚本中,"sudo dtruss sudo"变体也无法写入测试输出文件,而上面的"sudo dtruss su"变体确实创建了输出文件。


确实,感谢您的尝试。不过看起来我得放弃这个了。在Linux上,我曾经使用strace命令从一个替代makefile的脚本中跟踪哪些文件被这些命令所触及。我想将这些脚本移植到Mac OS X上,但最终我意识到,我不喜欢为了监视自己的命令而让我的所有构建都涉及几个级别的sudo的想法。 - Gyom
是的。直接尝试让dtruss作为非侵入式strace替代品似乎是错误的选择,因为你所描述的原因。也许需要采用另一种方法。我想到两件可能的事情:1)获取dtrace源代码,修改用户级前端(需要以root身份运行),使其仅启动新进程并仅监视这些进程,添加一个选项告诉它要将这些进程启动为谁,然后将其设置为root的setuid所有者。 - metamatt
继续上一条评论,第二个建议是:要回答你想要的问题,请直接使用dtrace,按照它设计的方式进行。而不是尝试将其作为strace的替代品用于makefiles中来监视子命令的操作——你能否编写dtrace探针以外的东西,识别并注释make过程正在做什么? - metamatt
非常感谢这些评论。然而对于#1,我恐怕既没有足够的时间也没有足够的技能(远远不够!)去尝试这种方法。选项#2听起来更现实,我一直在努力熟悉DTrace以尝试它。如果我可以编写一个作为守护进程运行并从外部监视所有构建的DTrace脚本,那么我确实可以获得所需的信息。但是目前还不清楚如何通知此守护程序要跟踪新进程。如果我取得了一些进展,我会发布更新。再次感谢。 - Gyom

5

这并不是回答你问题的答案,但有些需要了解。OpenSolaris使用“权限”(privileges)来部分解决了这个问题 - 参见此页面。即使在OpenSolaris中,也不可能允许一个没有任何额外特权的用户跟踪自己的进程。 原因是dtrace的工作方式 - 它在内核中启用探针。因此,允许非特权用户探测内核意味着该用户可以做很多不必要的事情,例如通过启用键盘驱动程序中的探针来嗅探其他用户的密码!


你可能是对的。虽然在我的笔记本电脑上获取root权限不是问题,因为我可以(而且已经这样做了)chmod a+s dtrace,但dtrace并不是一个“Unix高级用户”工具,而是一个“Unix管理员”工具。这就是为什么尝试从用户程序中使用它会导致如此牵强的情况。非常感谢您的回答。 - Gyom
它是否可以有一个“受限模式”,其中只有一些探测器(例如系统调用探测器或用户空间探测器)会触发,在一些进程中(那些属于相关用户的进程),并且只有一些功能可用:那些可以轻松地被制成仅检查用户自己的进程或仅提供已经以其他方式对用户可用的信息的功能? - SamB

3
似乎 OS X 不支持使用 dtrace 复制所有需要的 strace 功能。然而,我建议尝试创建一个适当系统调用的包装器。看起来 DYLD_INSERT_LIBRARIES 是你想要稍微修改一下的环境变量。这基本上和 Linux 的 LD_PRELOAD 一样。
更简单的办法是使用 DYLD_INSERT_LIBRARIES 环境变量来进行库函数覆盖(类似于 Linux 上的 LD_PRELOAD)。概念很简单:在加载时,动态链接器(dyld)将会在可执行文件想要加载任何库之前,加载 DYLD_INSERT_LIBRARIES 中指定的任何动态库。通过将函数命名为库函数中的某个函数,它将覆盖对原始函数的任何调用。原始函数也会被加载,并且可以使用 dlsym(RTLD_NEXT, “function_name”) 函数检索它。这允许一种简单的方法来包装现有的库函数。
根据示例,由Tom Robinson提供的信息,您可能还需要设置DYLD_FORCE_FLAT_NAMESPACE=1
覆盖仅fopen的原始示例副本(lib_overrides.c):
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

// for caching the original fopen implementation
FILE * (*original_fopen) (const char *, const char *) = NULL;

// our fopen override implmentation
FILE * fopen(const char * filename, const char * mode)
{
    // if we haven’t already, retrieve the original fopen implementation
    if (!original_fopen)
        original_fopen = dlsym(RTLD_NEXT, "fopen");

    // do our own processing; in this case just print the parameters
    printf("== fopen: {%s,%s} ==\n", filename, mode);

    // call the original fopen with the same arugments
    FILE* f = original_fopen(filename, mode);

    // return the result
    return f;
}

使用方法:

$ gcc -Wall -o lib_overrides.dylib -dynamiclib lib_overrides.c
$ DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=lib_overrides.dylib command-to-test

2
免责声明:这是从 @kenorb 的 answer 衍生出来的。它有一些优点:PID 比 execname 更具体。而且我们可以让短暂的进程在开始之前等待 DTrace。
这有点竞争条件,但是...
假设我们想跟踪 `cat /etc/hosts`:
sudo true && \
(sleep 1; cat /etc/hosts) &; \
sudo dtrace -n 'syscall:::entry /pid == $1/ {@[probefunc] = count();}' $!; \
kill $!

我们使用 sudo true 确保在运行任何时间敏感的东西之前清除 sudo 的密码提示。
我们启动一个后台进程(“等待 1 秒,然后做一些有趣的事情”)。同时,我们启动 DTrace。我们将后台进程的 PID 捕获到 $! 中,因此我们可以将其作为参数传递给 DTrace。 kill $! 在关闭 DTrace 后运行。对于我们的 cat 示例(进程自己关闭),这不是必要的,但它有助于结束长时间运行的后台进程,如 ping。将 -p $! 传递给 DTrace 是首选方法,但在 macOS 上显然需要一个代码签名的可执行文件。
另外一件事情是在一个单独的 shell 中运行命令,并监视该 shell。请参阅我的 答案

1

我不知道有什么方法可以让普通用户运行您想要的内容,因为似乎使用dtrace的dtruss需要su权限。

然而,我相信您寻找的命令应该是

sudo dtruss -f gcc hello.c

而不是

dtruss -f sudo -u myusername gcc hello.c

在输入密码后,dtruss将以sudo权限运行dtrace,并且您将获得跟踪和a.out文件。

很抱歉我无法提供更多帮助。


4
实际以root身份运行命令的问题(除了明显的巨大安全风险之外)在于sudo的运行环境(PATH、环境变量、权限)与正常环境非常不同,这会严重影响被跟踪程序的行为。相反地,Linux的strace几乎在跟踪命令的功能行为方面是透明的。 - Gyom

0

由于声望不足,我无法对kenorb的答案进行评论。但是我想补充一下,-W选项会导致dtruss等待命名进程启动,以便输出不包含大量不相关的系统调用。因此,

sudo dtruss -fW whoami

让我找到了我的问题,而使用-fn选项则会使事情变得混乱。


-1
我使用终端应用程序(Terminal.app)上的bash提示符中的常规方法诊断和修复崩溃应用程序,解决了我的破碎的预览问题: $ sudo dtruss -fn Preview 2>&1 | grep '/Users/' 输入密码后,我启动了预览,dtruss立即崩溃,

70256/0x19cd898: chdir("/Users/devon/Library/Containers/com.apple.Preview/Data\0", 0x0, 0x7FFF5EAC4BC8) = 0 0

而不是删除这个可能有问题的文件夹,我将其重命名 $ (cd ~/Library/Containers && mv -i com.apple.Preview com.apple.Preview.~NOT~) 现在,预览正常工作,并创建了一个新的、无害版本的目录。
你可以使用grep '"/'代替在整个系统上搜索来寻找问题,但会显示数百个无辜的项目,如/usr/System等。
从MacOSX 11.6 "El Capitan" 开始,dtrace/dtruss默认已被损坏 - 可参阅如何修复
我使用此bash函数,例如,$ d /Applications/Preview.app/Contents/MacOS/Preview
d () 
{ 
    case "$*" in 
        [0-9] | [0-9][0-9] | [0-9][0-9][0-9] | [0-9][0-9][0-9][0-9] | [0-9][0-9][0-9][0-9][0-9])
            dtruss -f -p "$*"
        ;;
        *)
            bash -c 'echo -en "\t <<< press return in this window to run after launching trace in root window like this >>> \t # d $$" >/dev/tty; (read -u3 3</dev/tty); exec "$0" "$@"' "$@"
        ;;
    esac
}

很久以前我们曾经使用苹果电脑来推动数据和李维斯牛仔裤来驾驶拖拉机 - 现在它们都已经沦为纯粹的时尚公司。S-F末世浪潮的电影《愚人节》似乎很相关。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接