Davide Beatrici Davide Beatrici - 4 months ago 34
Linux Question

Linux: is there a way to use ptrace without stopping/pausing the process (SIGSTOP)?

I'm trying to port a program from Windows to Linux.

I encountered a problem when I found out that there isn't a "real"

ReadProcessMemory
counterpart on Linux; I searched for an alternative and I found
ptrace
, a powerful process debugger.

I quickly coded two small console applications in C++ to test
ptrace
, before using it in the program.

TestApp


This is the tracee; it keeps printing two integers every 50 milliseconds while increasing their value by 1 every time.


#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();
}


MemoryTest

This is the tracer; it asks for the process name and retrieves the PID using the command
pidof -s
, then
ptrace
attaches to the process and retrieves the memory address' value every 500 milliseconds, for 10 times.



#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();
}


Everything works fine, but TestApp is paused (SIGSTOP) until
ptrace
detaches from it.

Also, when it attaches to the process, the status is 0 and the error is 2; the first time it tries to retrieve the memory address value it fails with status -1 and error 3. Is it normal?

Is there a way to prevent ptrace from sending the SIGSTOP signal to the process?
I already tried using
PTRACE_SEIZE
instead of
PTRACE_ATTACH
, but it doesn't work: status -1 and error 3.

Update: Using
Sleeper
in MemoryTest before the "do-while" loop fixes the problem of the first memory address value retrieval, even if the value of seconds, milliseconds or microseconds is 0. Why?

Answer

After a lot of research I'm pretty sure that there isn't a way to use ptrace without stopping the process.
I found a real ReadProcessMemory counterpart, called process_vm_readv, which is much more simple.

I'm posting the code in the hope of helping someone who is in my (previous) situation.

Many thanks to mkrautz for his help coding MemoryTest with this beautiful function.

#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();
}