Как мне прочитать из / proc / $ pid / mem под Linux?

142

Linux proc(5)страница человека говорит мне , что /proc/$pid/mem«может быть использована для доступа к страницам памяти процесса». Но простая попытка использовать его только дает мне

$ cat /proc/$$/mem /proc/self/mem
cat: /proc/3065/mem: No such process
cat: /proc/self/mem: Input/output error

Почему не catможет распечатать свою собственную память ( /proc/self/mem)? И что это за странная ошибка «нет такого процесса», когда я пытаюсь распечатать память оболочки ( /proc/$$/memочевидно, процесс существует)? Как я могу читать с /proc/$pid/mem, тогда?

жилль
источник
1
Есть несколько других методов, которые показывают, как сделать это на SF в этом разделе вопросов и ответов, под названием: « Дамп памяти процесса linux в файл
slm
актуальный ответ
pizdelect

Ответы:

140

/proc/$pid/maps

/proc/$pid/memпоказывает содержимое памяти $ pid, отображенной так же, как и в процессе, т. е. байт со смещением x в псевдофайле совпадает с байтом по адресу x в процессе. Если адрес не отображается в процессе, чтение из соответствующего смещения в файле возвращает EIO(Ошибка ввода / вывода). Например, поскольку первая страница в процессе никогда не отображается (так что разыменование NULLуказателя завершается ошибкой, а не непреднамеренно обращается к фактической памяти), чтение первого байта /proc/$pid/memвсегда приводит к ошибке ввода-вывода.

Чтобы узнать, какие части памяти процесса отображаются, нужно прочитать /proc/$pid/maps. Этот файл содержит одну строку для каждой отображаемой области, выглядит так:

08048000-08054000 r-xp 00000000 08:01 828061     /bin/cat
08c9b000-08cbc000 rw-p 00000000 00:00 0          [heap]

Первые два числа являются границами области (адреса первого байта и байта после последнего в гекса). В следующем столбце содержатся разрешения, затем есть некоторая информация о файле (смещение, устройство, индекс и имя), если это сопоставление файла. См. proc(5)Справочную страницу или Понимание Linux / proc / id / maps для получения дополнительной информации.

Вот сценарий проверки концепции, который выгружает содержимое своей собственной памяти.

#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'r', 0)
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    if m.group(3) == 'r':  # if this is a readable region
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)  # seek to region start
        chunk = mem_file.read(end - start)  # read region contents
        print chunk,  # dump contents to standard output
maps_file.close()
mem_file.close()

/proc/$pid/mem

Если вы попытаетесь прочитать memпсевдофайл другого процесса, это не сработает: вы получите ESRCHошибку (Нет такого процесса).

Права на /proc/$pid/mem( r--------) более либеральны, чем должно быть. Например, вы не должны иметь возможность читать память процесса setuid. Кроме того, попытка прочитать память процесса во время его изменения может дать читателю непоследовательное представление о памяти, и, что еще хуже, были условия гонки, которые могли отследить более старые версии ядра Linux (согласно этому потоку lkml , хотя я не знаю деталей). Поэтому необходимы дополнительные проверки:

  • Процесс , который хочет читать /proc/$pid/memдолжен присоединиться к процессу , используя ptraceс PTRACE_ATTACHфлагом. Это то, что делают отладчики, когда начинают отлаживать процесс; это также то, что straceотносится к системным вызовам процесса. Как только читатель закончит чтение /proc/$pid/mem, он должен отключиться, позвонив ptraceс PTRACE_DETACHфлагом.
  • Наблюдаемый процесс не должен быть запущен. Обычно вызов ptrace(PTRACE_ATTACH, …)останавливает целевой процесс (он посылает STOPсигнал), но есть условие гонки (доставка сигнала асинхронная), поэтому трассировщик должен вызывать wait(как описано в ptrace(2)).

Процесс, выполняющийся от имени пользователя root, может читать память любого процесса без необходимости вызова ptrace, но наблюдаемый процесс должен быть остановлен, иначе чтение все равно вернется ESRCH.

В исходном коде ядра Linux код, предоставляющий записи для каждого процесса, /procнаходится внутри fs/proc/base.c, а функция для чтения /proc/$pid/mem- mem_read. Дополнительная проверка выполняется check_mem_permission.

Вот некоторый пример кода C для присоединения к процессу и чтения фрагмента его memфайла (проверка ошибок пропущена):

sprintf(mem_file_name, "/proc/%d/mem", pid);
mem_fd = open(mem_file_name, O_RDONLY);
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, NULL, 0);
lseek(mem_fd, offset, SEEK_SET);
read(mem_fd, buf, _SC_PAGE_SIZE);
ptrace(PTRACE_DETACH, pid, NULL, NULL);

Я уже разместил проверочный скрипт для создания дампа /proc/$pid/memв другом потоке .

жилль
источник
2
@abc Нет, чтение /proc/$pid/memнапрямую (с помощью catили с ddчем-либо еще) не работает. Прочитайте мой ответ.
Жиль
4
@abc Он читает с /proc/self/mem. Процесс может читать свое собственное пространство памяти просто отлично, он читает пространство памяти другого процесса, которое требует PTRACE_ATTACH.
Жиль
2
Обратите внимание, что с последними ядрами Linux вам не нужно PTRACE_ATTACH. Это изменение происходит с process_vm_readv()системным вызовом (Linux 3.2).
ysdx
2
Хм, с Linux 4.14.8 это работает для меня: запустить длительный процесс, который занят записью вывода в / dev / null. Затем другой процесс может открывать, искать и считывать некоторые байты из / proc / $ otherpid / mem (т.е. с некоторыми смещениями, на которые ссылается вспомогательный вектор) - без необходимости ptrace-attach / detach или остановки / запуска процесса. Работает, если процесс выполняется под тем же пользователем и для пользователя root. Т.е. я не могу выдать ESRCHошибку в этом сценарии.
maxschlepzig
1
@maxschlepzig Полагаю, что это изменение, упомянутое ysdx в комментарии выше.
Жиль
28

Эта команда (из gdb) надежно выгружает память:

gcore pid

Дампы могут быть большими, используйте, -o outfileесли в вашем текущем каталоге недостаточно места.

Tobu
источник
12

При выполнении cat /proc/$$/memпеременная $$оценивается с помощью bash, который вставляет свой собственный pid. Затем выполняется, catкоторый имеет другой pid. В конечном итоге вы catпытаетесь прочитать память о bashродительском процессе. Поскольку непривилегированные процессы могут только читать свое собственное пространство памяти, ядру это запрещается.

Вот пример:

$ echo $$
17823

Обратите внимание, что $$оценивается в 17823. Давайте посмотрим, что это за процесс.

$ ps -ef | awk '{if ($2 == "17823") print}'
bahamat  17823 17822  0 13:51 pts/0    00:00:00 -bash

Это моя текущая оболочка.

$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process

Здесь снова $$оценивается 17823, который является моей оболочкой. catне могу прочитать память моей оболочки

bahamat
источник
В итоге вы пытаетесь прочитать память о том, что $pidесть. Как я объясняю в своем ответе, чтение памяти другого процесса требует от вас его отслеживания.
Жиль
Который будет Баш. Я не говорил, что твой ответ был неправильным. Я просто отвечал более непрофессиональным выражением «почему это не работает».
Багамат
@bahamat: Ты думаешь о том, $$когда пишешь (и читаешь) $pid?
Жиль
Да ... он начал спрашивать, ссылаясь на $$и положить $pidв конце. Я перенес это в мою голову, не осознавая этого. Весь мой ответ должен касаться $$, а не $pid.
Багамат
@bahamat: Теперь вопрос понятнее? (Кстати, я не вижу ваши комментарии, если вы не используете «@Gilles», я просто случайно увидел ваше изменение и пришел посмотреть.)
Жиль
7

Вот небольшая программа, которую я написал на C:

Использование:

memdump <pid>
memdump <pid> <ip-address> <port>

Программа использует / proc / $ pid / maps, чтобы найти все отображенные области памяти процесса, а затем прочитать эти области из / proc / $ pid / mem, по одной странице за раз. эти страницы записываются на стандартный вывод или IP-адрес и порт TCP, которые вы указали.

Код (протестирован на Android, требуются права суперпользователя):

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/ptrace.h>
#include <sys/socket.h>
#include <arpa/inet.h>

void dump_memory_region(FILE* pMemFile, unsigned long start_address, long length, int serverSocket)
{
    unsigned long address;
    int pageLength = 4096;
    unsigned char page[pageLength];
    fseeko(pMemFile, start_address, SEEK_SET);

    for (address=start_address; address < start_address + length; address += pageLength)
    {
        fread(&page, 1, pageLength, pMemFile);
        if (serverSocket == -1)
        {
            // write to stdout
            fwrite(&page, 1, pageLength, stdout);
        }
        else
        {
            send(serverSocket, &page, pageLength, 0);
        }
    }
}

int main(int argc, char **argv) {

    if (argc == 2 || argc == 4)
    {
        int pid = atoi(argv[1]);
        long ptraceResult = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
        if (ptraceResult < 0)
        {
            printf("Unable to attach to the pid specified\n");
            return;
        }
        wait(NULL);

        char mapsFilename[1024];
        sprintf(mapsFilename, "/proc/%s/maps", argv[1]);
        FILE* pMapsFile = fopen(mapsFilename, "r");
        char memFilename[1024];
        sprintf(memFilename, "/proc/%s/mem", argv[1]);
        FILE* pMemFile = fopen(memFilename, "r");
        int serverSocket = -1;
        if (argc == 4)
        {   
            unsigned int port;
            int count = sscanf(argv[3], "%d", &port);
            if (count == 0)
            {
                printf("Invalid port specified\n");
                return;
            }
            serverSocket = socket(AF_INET, SOCK_STREAM, 0);
            if (serverSocket == -1)
            {
                printf("Could not create socket\n");
                return;
            }
            struct sockaddr_in serverSocketAddress;
            serverSocketAddress.sin_addr.s_addr = inet_addr(argv[2]);
            serverSocketAddress.sin_family = AF_INET;
            serverSocketAddress.sin_port = htons(port);
            if (connect(serverSocket, (struct sockaddr *) &serverSocketAddress, sizeof(serverSocketAddress)) < 0)
            {
                printf("Could not connect to server\n");
                return;
            }
        }
        char line[256];
        while (fgets(line, 256, pMapsFile) != NULL)
        {
            unsigned long start_address;
            unsigned long end_address;
            sscanf(line, "%08lx-%08lx\n", &start_address, &end_address);
            dump_memory_region(pMemFile, start_address, end_address - start_address, serverSocket);
        }
        fclose(pMapsFile);
        fclose(pMemFile);
        if (serverSocket != -1)
        {
            close(serverSocket);
        }

        ptrace(PTRACE_CONT, pid, NULL, NULL);
        ptrace(PTRACE_DETACH, pid, NULL, NULL);
    }
    else
    {
        printf("%s <pid>\n", argv[0]);
        printf("%s <pid> <ip-address> <port>\n", argv[0]);
        exit(0);
    }
}
Таль Алони
источник
5
Добавьте некоторое объяснение вашего кода. Ваш единственный комментарий бессмысленно: write to stdoutсразу выше fwrite(..., stdout). См. Programmers.stackexchange.com/questions/119600/…
muru
Вы сказали, что тестировали его только на Android, поэтому я просто хотел подтвердить, что он хорошо работает на Linux 4.4.0-28 x86_64, как и следовало ожидать
абрикосовый мальчик
я получаю кучу данных, таких как / @ 8 l / @ l на stdout, которые никогда не заканчивают, почему? Скомпилировано в Linux 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (2017-05-02) x86_64 Модель потока GNU / Linux: posix gcc версия 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us
ceph3us, обычно используется для передачи данных в файл (например, memdump <pid>> /sdcard/memdump.bin)
Tal