Linux:是否有一种方法可以在不停止/暂停进程(SIGSTOP)的情况下使用ptrace?

5
我正在尝试将一个程序从Windows移植到Linux。
当我发现Linux上没有一个“真正的”ReadProcessMemory对应项时,我遇到了一个问题;我搜索了一个替代方案,找到了一个强大的进程调试器ptrace
在使用它之前,我快速编写了两个小的控制台应用程序来测试ptrace

TestApp

这是被跟踪的程序。它每50毫秒打印两个整数,并在每次打印时将它们的值增加1。

#include <QCoreApplication>
#include <QThread>
#include <iostream>

using namespace std;

class Sleeper : public QThread
{
public:
    static void usleep(unsigned long usecs){QThread::usleep(usecs);}
    static void msleep(unsigned long msecs){QThread::msleep(msecs);}
    static void sleep(unsigned long secs){QThread::sleep(secs);}
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int value = 145;
    int i = 0;

    do {
    cout << "i: " << i << " " << "Value: " << value << endl;
    value++;
    i++;
    Sleeper::msleep(50);
    } while (true);

    return a.exec();
}

内存测试

这是一个跟踪器;它会要求进程名称并使用命令pidof -s检索PID,然后ptrace附加到进程并每500毫秒检索一次内存地址的值,共10次。

#include <QCoreApplication>
#include <QThread>
#include <iostream>
#include <string>
#include <sys/ptrace.h>
#include <errno.h>

using namespace std;

class Sleeper : public QThread
{
public:
    static void usleep(unsigned long usecs){QThread::usleep(usecs);}
    static void msleep(unsigned long msecs){QThread::msleep(msecs);}
    static void sleep(unsigned long secs){QThread::sleep(secs);}
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    char process_name[50];
    cout << "Process name: ";
    cin >> process_name;

    char command[sizeof(process_name) + sizeof("pidof -s ")];
    snprintf(command, sizeof(command), "pidof -s %s", process_name);

    FILE* shell = popen(command, "r");
    char pidI[sizeof(shell)];
    fgets(pidI, sizeof(pidI), shell);
    pclose(shell);

    pid_t pid = atoi(pidI);
    cout << "The PID is " << pid << endl;

    long status = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
    cout << "Status: " << status << endl;
    cout << "Error: " << errno << endl;

    unsigned long addr = 0x012345; // Example address, not the true one
    int i = 0;
    do {
    status = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
    cout << "Status: " << status << endl;
    cout << "Error: " << errno << endl;
    i++;
    Sleeper::msleep(500);
    } while (i < 10);

    status = ptrace(PTRACE_DETACH, pid, NULL, NULL);
    cout << "Status: " << status << endl;
    cout << "Error: " << errno << endl;

    return a.exec();
}

一切正常,但是TestApp被暂停(SIGSTOP),直到ptrace从中分离出来。
此外,当它附加到进程时,状态为0,错误为2;第一次尝试检索内存地址值时,状态为-1,错误为3。这是正常的吗?
有没有办法防止ptrace向进程发送SIGSTOP信号? 我已经尝试使用PTRACE_SEIZE而不是PTRACE_ATTACH,但它不起作用:状态为-1,错误为3。

更新:在“do-while”循环之前在MemoryTest中使用Sleeper可以解决第一个内存地址值检索的问题,即使秒、毫秒或微秒的值为0。为什么?

2个回答

6
经过大量研究,我很确定没有办法在不停止进程的情况下使用ptrace
我找到了一个真正的ReadProcessMemory同等功能的替代方案,名为process_vm_readv,这个方法更加简单易用。
我希望通过发布这段代码来帮助那些处于我(以前)同样境地的人们。
感谢mkrautz编写了这个漂亮函数的MemoryTest,并提供了帮助。
#include <QCoreApplication>
#include <QThread>
#include <sys/uio.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <iostream>

using namespace std;

class Sleeper : public QThread
{
public:
    static void usleep(unsigned long usecs){QThread::usleep(usecs);}
    static void msleep(unsigned long msecs){QThread::msleep(msecs);}
    static void sleep(unsigned long secs){QThread::sleep(secs);}
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    char process_name[50];
    cout << "Process name: ";
    cin >> process_name;

    char command[sizeof(process_name) + sizeof("pidof -s ")];
    snprintf(command, sizeof(command), "pidof -s %s", process_name);

    FILE* shell = popen(command, "r");
    char pidI[sizeof(shell)];
    fgets(pidI, sizeof(pidI), shell);
    pclose(shell);

    pid_t pid = atoi(pidI);

    cout << "The PID is " << pid << endl;

    if (pid == 0)
        return false;

    struct iovec in;
    in.iov_base = (void *) 0x012345; // Example address, not the true one
    in.iov_len = 4;

    uint32_t foo;

    struct iovec out;
    out.iov_base = &foo;
    out.iov_len = sizeof(foo);

    do {
        ssize_t nread = process_vm_readv(pid, &out, 1, &in, 1, 0);
        if (nread == -1) {
            fprintf(stderr, "error: %s", strerror(errno));
        } else if (nread != in.iov_len) {
            fprintf(stderr, "error: short read of %li bytes", (ssize_t)nread);
        }
        cout << foo << endl;
        Sleeper::msleep(500);
    } while (true);

    return a.exec();
}

1
我一直在寻找类似的东西,但是为了不同的目的,我一直在寻找窥视堆栈的方法。现在Linux确实有/proc/{pid}/stack,但是我必须为其编写程序的Linux系统没有这个功能,而且我也不能停止该进程。 - Mohit Sharma
我会在 /proc/{pid}/maps 中查找 [stack] 。例如,该行可能是:7ffd44730000-7ffd44751000 rw-p 00000000 00:00 0 [stack]这两个地址(分别为开始和结束)定义了可用于指定模块(在此情况下为堆栈)的内存区域。您可以使用 process_vm_readv() 读取该区域。 - Davide Beatrici
1
谢谢。我正在尝试制作一个基本的调试应用程序,进程堆栈在我的情况下并没有太大帮助。我发现了strace,它也使用ptrace(),但只有在进行syscall时才会使用,它会使进程变慢一些,但由于我们正在调试,我想这是公平的。我现在正在尝试制作自己的版本的strace,以便它可以在嵌入式系统上工作,这将需要进行新的研究。 - Mohit Sharma

4

Davide,

你看过/proc文件系统了吗?它包含内存映射文件,可以用来窥视整个进程空间。你也可以在这个空间中写入以设置断点。/proc中还有大量其他信息。

PTRACE_CONT命令可用于继续进程。通常,在调试器附加时,目标会被暂停并使用PTRACE_ATTACH。

手册页面说PTRACE_SIEZE不应该暂停进程。您使用的是哪种Linux版本?PTRACE_SIEZE已经存在了很长时间,所以我不确定为什么您在那里遇到麻烦。

我注意到addr值设置为0x12345。这是目标空间中的有效地址吗?还是只是一个例子?感兴趣的堆栈地址(&value)如何在两个进程之间通信?

我对返回代码不太确定。通常,0表示一切正常,errno可能只是上一个错误的余波。

--Matt


非常感谢您的回答。我没有查看/proc,因为我想使用更简单的方法;如果不可能,我将尝试使用内存映射文件。我正在使用Debian 8 x64,这应该不是问题;我猜测PTRACE_SEIZE需要以与PTRACE_ATTACH不同的方式使用,但我找不到任何具体的文档。 - Davide Beatrici
抱歉,我在输入评论时按下了回车键,以为它可以换行;我编辑了评论,但在5分钟后就不能再更新了。addr值只是一个例子,正确的值可以正常工作。关于错误代码,它们应该不会是问题,因为程序正常运行。 - Davide Beatrici
不用担心,我在你的回答后面注明了这只是一个例子;我已经投票了。我没有尝试使用 PTRACE_CONT,因为它不能解决我的问题,它只会恢复进程而不需要使用 PTRACE_DETACH,而我需要在不停止进程的情况下附加 ptrace 到进程。 - Davide Beatrici

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