Como leio / proc / $ pid / mem no Linux?

131

A página Linux proc(5) man me diz que /proc/$pid/mem “pode ser usado para acessar as páginas da memória de um processo”. Mas uma tentativa direta de usá-lo só me dá

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

Por que não é possível cat imprimir sua própria memória ( /proc/self/mem )? E o que é esse estranho erro de “nenhum processo desse tipo” quando tento imprimir a memória do shell ( /proc/$$/mem , obviamente, o processo existe)? Como posso ler /proc/$pid/mem ?

    
por Gilles 23.01.2011 / 00:21

4 respostas

128

/proc/$pid/maps

/proc/$pid/mem mostra o conteúdo da memória de $ pid mapeado da mesma forma que no processo, ou seja, o byte no deslocamento x no pseudo-arquivo é o mesmo que o byte no endereço < em> x no processo. Se um endereço não for mapeado no processo, a leitura do deslocamento correspondente no arquivo retornará EIO (erro de entrada / saída). Por exemplo, como a primeira página em um processo nunca é mapeada (para que a referência a um ponteiro NULL falhe de forma limpa, em vez de acessar a memória real acidentalmente), ler o primeiro byte de /proc/$pid/mem sempre gera um erro de E / S. p>

A maneira de descobrir quais partes da memória do processo estão mapeadas é ler /proc/$pid/maps . Este arquivo contém uma linha por região mapeada, com esta aparência:

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

Os dois primeiros números são os limites da região (endereços do primeiro byte e byte após o último, em hexa). A próxima coluna contém as permissões, então há algumas informações sobre o arquivo (offset, device, inode e name) se este for um mapeamento de arquivo. Veja a página do manual proc(5) ou Compreendendo o Linux / proc / id / maps para obter mais informações.

Aqui está um script de prova de conceito que despeja o conteúdo de sua própria memória.

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

Se você tentar ler o pseudo arquivo mem de outro processo, ele não funcionará: você receberá um erro ESRCH (Nenhum processo desse tipo).

As permissões em /proc/$pid/mem ( r-------- ) são mais liberais do que deveria ser o caso. Por exemplo, você não deveria poder ler a memória de um processo setuid. Além disso, tentar ler a memória de um processo enquanto o processo está modificando isso poderia dar ao leitor uma visão inconsistente da memória, e pior, havia condições de corrida que poderiam rastrear versões mais antigas do kernel Linux (de acordo com este encadeamento lkml , embora eu não saiba os detalhes). Por isso, são necessárias verificações adicionais:

  • O processo que deseja ler de /proc/$pid/mem deve anexar ao processo usando ptrace com o sinalizador PTRACE_ATTACH . Isso é o que os depuradores fazem quando começam a depurar um processo; é também o que strace faz com as chamadas do sistema de um processo. Depois que o leitor terminar de ler /proc/$pid/mem , ele deverá ser desanexado chamando ptrace com o sinalizador PTRACE_DETACH .
  • O processo observado não deve estar em execução. Normalmente, chamar ptrace(PTRACE_ATTACH, …) interromperá o processo de destino (envia um sinal STOP ), mas há uma condição de corrida (a entrega do sinal é assíncrona), portanto o rastreador deve chamar wait (conforme documentado em ptrace(2) ).

Um processo executado como root pode ler a memória de qualquer processo, sem precisar chamar ptrace , mas o processo observado deve ser interrompido ou a leitura ainda retornará ESRCH .

Na origem do kernel do Linux, o código que fornece entradas por processo em /proc está em fs/proc/base.c , e a função para ler a partir de /proc/$pid/mem é mem_read . A verificação adicional é realizada por check_mem_permission .

Aqui está um exemplo de código C para anexar a um processo e ler um fragmento do arquivo mem (verificação de erro omitida):

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

Eu já publiquei um script de prova de conceito para jogando /proc/$pid/mem em outro thread .

    
por 23.01.2011 / 00:21
24

Este comando (do gdb) descarta a memória de forma confiável:

gcore pid

Dumps podem ser grandes, use -o outfile se o diretório atual não tiver espaço suficiente.

    
por 19.03.2013 / 12:17
10

Quando você executa cat /proc/$$/mem , a variável $$ é avaliada pelo bash, que insere seu próprio pid. Em seguida, ele executa cat , que possui um pid diferente. Você acaba com cat tentando ler a memória de bash , seu processo pai. Como os processos não privilegiados só podem ler seu próprio espaço de memória, isso é negado pelo kernel.

Veja um exemplo:

$ echo $$
17823

Observe que $$ é avaliado como 17823. Vamos ver qual processo é esse.

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

É meu shell atual.

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

Aqui novamente $$ avalia para 17823, que é meu shell. cat não pode ler o espaço de memória do meu shell.

    
por 24.01.2011 / 18:02
6

Aqui está um pequeno programa que escrevi em C:

Uso:

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

O programa usa / proc / $ pid / maps para encontrar todas as regiões de memória mapeadas do processo, e depois ler essas regiões de / proc / $ pid / mem, uma página de cada vez. essas páginas são gravadas no stdout ou no endereço IP e na porta TCP que você especificou.

Código

(testado no Android, requer permissões de superusuário):

#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);
    }
}
    
por 27.12.2015 / 15:51