如何在不以root权限运行的情况下跟踪程序从一开始的过程

14

我正在编写一个工具,通过调用DTrace跟踪用户指定的程序。

如果我的工具使用dtrace -c运行程序作为DTrace的子进程,则既无法向程序传递任何参数,而且该程序将以DTrace的所有特权(即root身份)运行(我在Mac OS X上)。这会导致某些应该工作的功能出现问题,并且显然使许多不应该工作的东西成为可能。

我知道的另一种解决方案是自己启动程序,通过发送SIGSTOP暂停它,将其PID传递给dtrace -p,然后通过发送SIGCONT继续它。问题在于,要么程序在未被跟踪的情况下运行几秒钟,而DTrace收集符号信息,要么如果我在继续进程之前睡眠几秒钟,DTrace会抱怨objc<pid>:<class>:<method>:entry没有匹配探针。

有没有办法以用户的帐户身份运行程序,而不是作为root,但仍然可以让DTrace从一开始就能够跟踪它?

8个回答

6

类似这样的命令:sudo dtruss -f sudo -u <原始用户名> <命令> 对我来说是有效的,但之后我感到很内疚。

我提交了一个Radar bug,并将其关闭为#5108629的重复。


5
这个脚本需要将你想要监控的可执行文件的名称(对于应用程序来说,这是info.plist中的CFBundleExecutable)作为参数传递给DTrace(在此脚本运行后,你可以启动目标应用程序)。
string gTarget;     /* the name of the target executable */

dtrace:::BEGIN
{
    gTarget = $$1;  /* get the target execname from 1st DTrace parameter */

    /*
    * Note: DTrace's execname is limited to 15 characters so if $$1 has more
    * than 15 characters the simple string comparison "($$1 == execname)"
    * will fail. We work around this by copying the parameter passed in $$1
    * to gTarget and truncating that to 15 characters.
    */

    gTarget[15] = 0;        /* truncate to 15 bytes */
    gTargetPID = -1;        /* invalidate target pid */
}

/*
* capture target launch (success)
*/
proc:::exec-success
/
    gTarget == execname
/
{
    gTargetPID = pid;
}

/*
*   detect when our target exits
*/
syscall::*exit:entry
/
    pid == gTargetPID
/
{
    gTargetPID = -1;        /* invalidate target pid */
}

/*
* capture open arguments
*/
syscall::open*:entry
/
    ((pid == gTargetPID) || progenyof(gTargetPID))
/
{
    self->arg0 = arg0;
    self->arg1 = arg1;
}

/*
* track opens
*/
syscall::open*:return
/
    ((pid == gTargetPID) || progenyof(gTargetPID))
/
{
    this->op_kind = ((self->arg1 & O_ACCMODE) == O_RDONLY) ? "READ" : "WRITE";
    this->path0 = self->arg0 ? copyinstr(self->arg0) : "<nil>";

    printf("open for %s: <%s> #%d",
        this->op_kind,
        this->path0,
        arg0);
}

这很棒,但是在执行此操作时,dtrace找不到任何pid提供程序跟踪点。例如,我会收到一个错误:“探测描述符pid*::confstr:return与任何探测器都不匹配”。 - Droopycom

4

好的,这可能有点旧了,但为什么不试一下:-)。

我认为没有简单的命令行方法来做到这一点,但正如建议的那样,一个简单的启动器应用程序,比如以下内容,就可以实现。当然,手动附加也可以用几个对libdtrace的调用来代替。

int main(int argc, char *argv[]) {
    pid_t pid = fork();
    if(pid == 0) {
        setuid(123);
        seteuid(123);
        ptrace(PT_TRACE_ME, 0, NULL, 0);
        execl("/bin/ls", "/bin/ls", NULL);
    } else if(pid > 0) {
        int status;
        wait(&status);

        printf("Process %d started. Attach now, and click enter.\n", pid);
        getchar();

        ptrace(PT_CONTINUE, pid, (caddr_t) 1, 0);
    }

    return 0;
}

3
如果其他答案对您不起作用,您可以在gdb中运行程序,在main函数(甚至更早的位置)中断,获取pid,然后启动脚本?我以前尝试过这种方法,似乎可行。

2
创建一个启动程序,等待某种信号(不一定是文字信号,只要有指示就可以),然后执行你的目标程序。现在使用dtrace -p命令跟踪这个启动程序,在dtrace启动后,让启动程序继续运行。

1
更好的方法是将所有内容合并到一个管道中。创建一个管道,分叉,让子进程等待管道,使用dtrace -p CHILD_PID命令,向管道写入数据,然后子进程唤醒并调用exec。 - bstpierre
听起来很有前途。但我不确定它是否有效:我的工具是用Python编写的,旨在跟踪Cocoa程序。如果我的工具中没有任何Cocoa,我认为我仍然会收到错误消息,即objc提供程序不匹配任何探针。但我明天会尝试一下。 - Peter Hosey

1

dtruss有-n选项,您可以在不启动进程的情况下指定要跟踪的进程名称(感谢@kenorb在https://dev59.com/lXA75IYBdhLWcg3w9-Ox#11706251中提供的后半部分答案)。因此,类似以下内容的语句应该可以实现:

sudo dtruss -n "$program"
$program

1

苹果的CLT LLDB.framework中有一个名为darwin-debug的工具,它可以在程序执行任何操作之前启动并暂停程序。然后,您需要从作为参数传递的Unix套接字中读取pid,并在附加调试器/ dtrace后继续该进程。

darwin-debug will exec itself into a child process <PROGRAM> that is
halted for debugging. It does this by using posix_spawn() along with
darwin specific posix_spawn flags that allows exec only (no fork), and
stop at the program entry point. Any program arguments <PROGRAM-ARG> are
passed on to the exec as the arguments for the new process. The current
environment will be passed to the new process unless the "--no-env"
option is used. A unix socket must be supplied using the
--unix-socket=<SOCKET> option so the calling program can handshake with
this process and get its process id.

0

请参考相关问题“如何让dtrace以非root权限运行被跟踪的命令?”[sic]上我的回答my answerquestion

基本上,您可以启动一个(非root)后台进程,等待1秒钟以便DTrace启动(对于竞争条件我们很抱歉),并窃听该进程的PID。

sudo true && \
(sleep 1; cat /etc/hosts) &; \
sudo dtrace -n 'syscall:::entry /pid == $1/ {@[probefunc] = count();}' $! \
&& kill $!

请参阅链接的回答以获得完整解释。


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