Jak czytać z / proc / $ pid / mem w systemie Linux?


142

Linux proc(5)strona człowiek mówi mi, że /proc/$pid/mem„może być używany do uzyskania dostępu do stron pamięci procesu”. Ale prosta próba użycia go daje mi tylko

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

Dlaczego nie jest w catstanie wydrukować własnej pamięci ( /proc/self/mem)? A jaki jest ten dziwny błąd „brak takiego procesu”, gdy próbuję wydrukować pamięć powłoki ( /proc/$$/memoczywiście proces istnieje)? Jak zatem mogę czytać /proc/$pid/mem?


1
Istnieje kilka innych metod, które pokazują, jak to zrobić na SF w tym
slm

aktualna odpowiedź
pizdelect

Odpowiedzi:


140

/proc/$pid/maps

/proc/$pid/mempokazuje zawartość pamięci $ pid zmapowanej w taki sam sposób jak w procesie, tzn. bajt o przesunięciu x w pseudopliku jest taki sam jak bajt o adresie x w procesie. Jeśli adres nie jest odwzorowywany w procesie, odczyt z odpowiedniego przesunięcia w pliku zwraca EIO(błąd wejścia / wyjścia). Na przykład, ponieważ pierwsza strona w procesie nigdy nie jest odwzorowywana (tak, że wyłuskowanie NULLwskaźnika kończy się niepowodzeniem, zamiast niezamierzonego dostępu do rzeczywistej pamięci), odczytanie pierwszego bajtu /proc/$pid/memzawsze powoduje błąd we / wy.

Aby dowiedzieć się, które części pamięci procesu są mapowane, należy przeczytać /proc/$pid/maps. Ten plik zawiera jedną linię na region odwzorowany, wygląda następująco:

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

Pierwsze dwie liczby to granice regionu (adresy pierwszego bajtu i bajtu po ostatnim, w heksie). Następna kolumna zawiera uprawnienia, a następnie informacje o pliku (przesunięcie, urządzenie, i-węzeł i nazwa), jeśli jest to mapowanie pliku. Aby uzyskać więcej informacji, zobacz proc(5)stronę podręcznika man lub Understanding Linux / proc / id / maps .

Oto skrypt sprawdzający koncepcję, który zrzuca zawartość własnej pamięci.

#! /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

Jeśli spróbujesz odczytać z mempseudopliku innego procesu, to nie działa: pojawia się ESRCHbłąd (Brak takiego procesu).

Uprawnienia do /proc/$pid/mem( r--------) są bardziej liberalne niż powinno być. Na przykład nie powinieneś być w stanie odczytać pamięci procesu setuid. Co więcej, próba odczytania pamięci procesu, gdy proces się modyfikuje, może dać czytelnikowi niespójny obraz pamięci, a co gorsza, istnieją warunki wyścigu, które mogą śledzić starsze wersje jądra Linuxa (zgodnie z tym wątkiem lkml , chociaż ja nie znam szczegółów). Potrzebne są więc dodatkowe kontrole:

  • Proces, który chce czytać z /proc/$pid/memmusi dołączyć do procesu z wykorzystaniem ptracez PTRACE_ATTACHflagą. To właśnie robią debuggery, gdy zaczynają debugować proces; dotyczy to również stracewywołań systemowych procesu. Gdy czytnik zakończy czytanie /proc/$pid/mem, powinien się odłączyć, dzwoniąc ptracez PTRACE_DETACHflagą.
  • Obserwowany proces nie może być uruchomiony. Zwykle wywołanie ptrace(PTRACE_ATTACH, …)zatrzyma proces docelowy (wysyła STOPsygnał), ale występuje warunek wyścigu (dostarczanie sygnału jest asynchroniczne), więc znacznik powinien wywołać wait(zgodnie z dokumentacją ptrace(2)).

Proces działający jako root może odczytywać pamięć dowolnego procesu, bez konieczności wywoływania ptrace, ale obserwowany proces musi zostać zatrzymany, inaczej odczyt będzie nadal zwracany ESRCH.

W źródle jądra Linux, kod zapewniając wpisy na procesie w /procjest fs/proc/base.c, a funkcja odczytu z /proc/$pid/memjest mem_read. Dodatkowa kontrola jest wykonywana przez check_mem_permission.

Oto przykładowy kod C do dołączenia do procesu i odczytania fragmentu jego mempliku (pominięto sprawdzanie błędów):

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);

Opublikowałem już skrypt sprawdzający koncepcję, który pozwala zrzucić /proc/$pid/meminny wątek .


2
@abc Nie, czytanie /proc/$pid/membezpośrednio (bez względu na to, catczy jest to ddczy coś innego) nie działa. Przeczytaj moją odpowiedź.
Gilles

4
@abc Czyta z /proc/self/mem. Proces może dobrze odczytać swoje własne miejsce w pamięci, to wymaga miejsca w pamięci innego procesu PTRACE_ATTACH.
Gilles

2
Zauważ, że w najnowszych jądrach Linuksa nie potrzebujesz PTRACE_ATTACH. Ta zmiana pochodzi z process_vm_readv()wywołania systemowego (Linux 3.2).
ysdx

2
Hm, w Linuksie 4.14.8 to działa dla mnie: rozpocznij długi proces, który jest zajęty zapisywaniem danych wyjściowych do / dev / null. Następnie inny proces może otwierać, wyszukiwać i odczytywać niektóre bajty z / proc / $ otherpid / mem (tj. Przy niektórych przesunięciach, do których odwołuje się wektor pomocniczy) - bez konieczności ptrace-dołączania / odłączania lub zatrzymywania / uruchamiania procesu. Działa, jeśli proces działa w ramach tego samego użytkownika i użytkownika root. Tzn. Nie mogę podać ESRCHbłędu w tym scenariuszu.
maxschlepzig

1
@maxschlepzig Myślę, że to zmiana wspomniana przez ysdx w powyższym komentarzu.
Gilles

28

To polecenie (z gdb) niezawodnie zrzuca pamięć:

gcore pid

Zrzuty mogą być duże, użyj, -o outfilejeśli w bieżącym katalogu nie ma wystarczającej ilości miejsca.


12

Po uruchomieniu cat /proc/$$/memzmienna $$jest oceniana przez bash, który wstawia własny pid. Następnie wykonuje się, catktóry ma inny pid. Kończysz catpróbą odczytania pamięci bash, jej procesu nadrzędnego. Ponieważ procesy nieuprzywilejowane mogą odczytywać tylko własną pamięć, jądro odmawia jej.

Oto przykład:

$ echo $$
17823

Zauważ, że $$wartość wynosi 17823. Zobaczmy, który to proces.

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

To moja obecna skorupa.

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

Tutaj ponownie $$przyjmuje wartość 17823, która jest moją powłoką. catnie mogę odczytać pamięci mojej powłoki.


W końcu próbujesz odczytać pamięć tego, co $pidjest. Jak wyjaśniam w mojej odpowiedzi, czytanie pamięci innego procesu wymaga od ciebie śledzenia.
Gilles

Które będzie bash. Nie mówiłem, że twoja odpowiedź była zła. Właśnie odpowiadałem, mówiąc bardziej laikom, „dlaczego to nie działa”.
bahamat

@bahamat: Czy myślisz o tym, $$kiedy piszesz (i czytasz) $pid?
Gilles

Tak ... zaczął na początku, prosząc o odniesienie $$i $pidkoniec. Transponowałem to w mojej głowie, nie zdając sobie z tego sprawy. Cała moja odpowiedź powinna dotyczyć $$, a nie $pid.
bahamat

@bahamat: Czy pytanie jest teraz jaśniejsze? (BTW, nie widzę twoich komentarzy, chyba że użyjesz „@Gilles”, akurat zobaczyłem twoją edycję i przyszedłem, aby zobaczyć).
Gilles

7

Oto mały program, który napisałem w C:

Stosowanie:

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

Program używa / proc / $ pid / maps, aby znaleźć wszystkie zmapowane regiony pamięci procesu, a następnie odczytać te regiony z / proc / $ pid / mem, po jednej stronie na raz. strony te są zapisywane na standardowe wyjście lub podany adres IP i port TCP.

Kod (testowany na Androidzie, wymaga uprawnień administratora):

#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
Dodaj wyjaśnienie swojego kodu. Twój jedyny komentarz jest trochę bezcelowy: write to stdoutbezpośrednio powyżej fwrite(..., stdout). Zobacz programmers.stackexchange.com/questions/119600/…
muru

Powiedziałeś, że przetestowałeś go tylko na Androidzie, więc chciałem tylko potwierdzić, że działa dobrze na Linuksie 4.4.0-28 x86_64, jak można się spodziewać
morelowy chłopiec

dostaję mnóstwo danych takich jak / @ 8 l / @ l na stdout, który nigdy nie kończy żadnego pojęcia dlaczego? skompilowany na systemie Linux 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (2017-05-02) x86_64 GNU / Linux Model wątku: posix gcc wersja 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us

ceph3us, powszechnym zastosowaniem jest przesyłanie danych do pliku (np. memdump <pid>> /sdcard/memdump.bin)
Tal Aloni
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.