Як я читаю з / proc / $ pid / mem в Linux?


142

Сторінка man Linuxproc(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)man або в розділі Розуміння 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--------) є більш ліберальними, ніж те, що має бути у випадку. Наприклад, ви не повинні мати можливість читати пам'ять процесу налаштування. Крім того, спроба прочитати пам'ять процесу, коли процес змінюється, може дати читачеві непослідовність уявлення про пам'ять, і ще гірше, були гоночні умови, які могли простежити старіші версії ядра 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.

Ось декілька зразків коду С, який потрібно приєднати до процесу та прочитати фрагмент його 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-приєднання / від'єднання чи зупинки / запуску процесу. Працює, якщо процес працює під тим самим користувачем і для кореневого користувача. Тобто я не можу помилитися ESRCHв цьому сценарії.
maxschlepzig

1
@maxschlepzig Я думаю, що це зміна, яку згадував ysdx у коментарі вище.
Жиль

28

Ця команда (від gdb) надійно скидає пам'ять:

gcore pid

Дамп може бути великим, використовуйте, -o outfileякщо у вашому поточному каталозі недостатньо місця.


12

При виконанні cat /proc/$$/memзмінна $$оцінюється за допомогою bash, який вставляє власний pid. Потім він виконує catякий має інший під. Ви закінчуєте 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не вдається прочитати простір пам'яті моєї оболонки.


Ви в кінцевому підсумку намагаєтеся прочитати пам'ять, що б там $pidне було. Як я пояснюю у своїй відповіді, читання пам'яті іншого процесу вимагає, щоб ви її простежили.
Жиль

Який буде баш. Я не казав, що ваша відповідь була неправильною. Я просто відповідав більш простим висловом "чому це не працює".
bahamat

@bahamat: Ви думаєте, $$коли пишете (і читаєте) $pid?
Жиль

Так ... він почав просити посилання $$і поставити $pidв кінці. Я переніс це в голові, не усвідомлюючи цього. Вся моя відповідь повинна стосуватися $$, а не $pid.
bahamat

@bahamat: Чи питання зараз зрозуміліше? (BTW Я не бачу ваших коментарів, якщо ви не використовуєте "@Gilles", я випадково побачив вашу редагування і прийшов. ")
Gilles

7

Ось невеличка програма, яку я написав на C:

Використання:

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

Програма використовує / proc / $ pid / maps для пошуку всіх відображених областей пам'яті процесу, а потім читає ці регіони з / proc / $ pid / mem, по одній сторінці. ці сторінки записуються в stdout або 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). Дивіться програмісти.stackexchange.com
questions/119600/…

Ви сказали, що ви протестували його лише на 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 Aloni
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.