【问题标题】:Linux: is there a way to use ptrace without stopping/pausing the process (SIGSTOP)?Linux:有没有办法在不停止/暂停进程(SIGSTOP)的情况下使用 ptrace?
【发布时间】:2023-04-01 06:37:01
【问题描述】:

我正在尝试将程序从 Windows 移植到 Linux。
当我发现 Linux 上没有“真正的”ReadProcessMemory 对应对象时,我遇到了一个问题;我搜索了一个替代方案,发现ptrace,一个强大的进程调试器。
我很快用 C++ 编写了两个小型控制台应用程序来测试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。为什么?

【问题讨论】:

    标签: c++ linux memory process ptrace


    【解决方案1】:

    经过大量研究,我很确定没有办法在不停止进程的情况下使用ptrace
    我找到了一个真正的ReadProcessMemory 对应物,称为@987654321@,它要简单得多。

    我发布代码是为了帮助处于我(以前)情况的人。

    非常感谢 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();
    }
    

    【讨论】:

    • 我一直在寻找类似的东西,但出于不同的目的,我一直在寻找查看堆栈的方法。现在 linux 确实有 /proc/{pid}/stack 但我必须为其制作程序的 linux 系统没有,我无法停止该进程。
    • 我会在 /proc/{pid}/maps 中寻找 [stack]。例如,该行可以是:7ffd44730000-7ffd44751000 rw-p 00000000 00:00 0 [stack] 这两个地址(分别为开始和结束)定义了指定模块(在本例中为堆栈)可用的内存区域。您可以使用process_vm_readv()阅读该区域。
    • 谢谢。我正在尝试制作一个基本的调试应用程序,进程堆栈对我来说没有多大帮助。我发现 strace 也使用了`ptrace()`,但只有当它生成syscall 时,它确实会减慢一点处理速度,但由于我们正在调试,我认为这是公平的。我现在正在尝试制作我自己的strace 版本,以便它在嵌入式系统上工作,现在需要进行新的研究。
    【解决方案2】:

    大卫,

    您查看过 /proc 文件系统吗?它包含可用于查看完整进程空间的内存映射文件。您也可以在空格中写入设置断点。 /proc 中还有大量其他信息。

    PTRACE_CONT 命令可用于继续进程。通常,当调试器附加时,目标将通过 PTRACE_ATTACH 暂停。

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

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

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

    --马特

    【讨论】:

    • 非常感谢您的回答。我没有看/proc,因为我想用更简单的方法;如果不可能,我将尝试使用内存映射文件。我使用的是 Debian 8 x64,这应该不是问题;我的猜测是PTRACE_SEIZE 需要以不同于PTRACE_ATTACH 的方式使用,但我找不到任何具体的文档。
    • 对不起这两个 cmets,我按 Enter 认为它打破了界限;我编辑了评论,但 5 分钟后无法再更新。 addr 值只是一个示例,正确的值可以正常工作。关于错误代码,它们应该不是问题,因为程序运行良好。
    • 别担心,我在你的回答之后指定了这是一个例子;我投票了。我没有尝试使用PTRACE_CONT,因为它不能解决我的问题,因为它只是在不需要PTRACE_DETACH 的情况下恢复进程,而我需要将ptrace 附加到进程而不停止它。跨度>
    最近更新 更多