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

142

A página de manual do Linuxproc(5) 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 é catpossível imprimir sua própria memória ( /proc/self/mem)? E qual é esse erro estranho de "não existe esse processo" quando tento imprimir a memória do shell ( /proc/$$/memobviamente o processo existe)? Como posso ler /proc/$pid/mem, então?

Gilles
fonte
1
Existem vários outros métodos que mostram como fazer isso em SF neste Q & A intitulado: despejo de memória de um processo de linux para arquivo
SLM
resposta
pizdelect 07/06

Respostas:

140

/proc/$pid/maps

/proc/$pid/memmostra o conteúdo da memória do $ pid mapeado da mesma maneira que no processo, ou seja, o byte no deslocamento x no pseudo-arquivo é o mesmo que o byte no endereço x no processo. Se um endereço não estiver mapeado no processo, a leitura do deslocamento correspondente no arquivo retornará EIO(erro de entrada / saída). Por exemplo, como a primeira página de um processo nunca é mapeada (para que o cancelamento de referência de um NULLponteiro falhe de maneira limpa em vez de acessar a memória real sem intenção), a leitura do primeiro byte /proc/$pid/memsempre gera um erro de E / S.

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

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 o byte depois do último, em hexa). A próxima coluna contém as permissões, e há algumas informações sobre o arquivo (deslocamento, dispositivo, inode e nome) se este for um mapeamento de arquivo. Consulte a proc(5)página do manual ou Noções básicas sobre 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 mempseudo-arquivo de outro processo, ele não funcionará: você receberá um ESRCHerro (sem esse processo).

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

  • O processo que deseja ler /proc/$pid/memdeve ser anexado ao processo usando ptracecom o PTRACE_ATTACHsinalizador. É isso que os depuradores fazem quando começam a depurar um processo; é também o straceque as chamadas de sistema de um processo fazem. Depois que o leitor terminar de ler /proc/$pid/mem, ele deve se desconectar chamando ptracecom o PTRACE_DETACHsinalizador.
  • O processo observado não deve estar em execução. Normalmente, a chamada ptrace(PTRACE_ATTACH, …)interrompe o processo de destino (envia um STOPsinal), 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 em execução como root pode ler a memória de qualquer processo, sem precisar chamar ptrace, mas o processo observado deve ser parado ou a leitura ainda retornará ESRCH.

Na fonte do kernel Linux, o código que fornece entradas por processo /procestá em fs/proc/base.c, e a função para ler /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 pedaço do memarquivo (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);

Já publiquei um script de prova de conceito para despejo /proc/$pid/memem outro segmento .

Gilles
fonte
2
@abc Não, ler /proc/$pid/memdiretamente (seja com catou com ddqualquer outra coisa) não funciona. Leia minha resposta.
Gilles
4
@abc Ele está lendo /proc/self/mem. Um processo pode ler seu próprio espaço de memória muito bem, está lendo o espaço de memória de outro processo que requer PTRACE_ATTACH.
Gilles
2
Observe que, com os kernels recentes do Linux, você não precisa PTRACE_ATTACH. Essa mudança vem com a process_vm_readv()chamada do sistema (Linux 3.2).
ysdx 24/09
2
Hum, com o Linux 4.14.8, isso funciona para mim: inicie um processo de execução demorado que está ocupado gravando a saída em / dev / null. Em seguida, outro processo é capaz de abrir, buscar e ler alguns bytes de / proc / $ otherpid / mem (ou seja, em algumas compensações referenciadas pelo vetor auxiliar) - sem ter que rastrear / anexar / desanexar ou parar / iniciar o processo. Funciona se o processo for executado no mesmo usuário e para o usuário root. Ou seja, não posso produzir um ESRCHerro neste cenário.
maxschlepzig
1
@maxschlepzig Acho que essa é a alteração mencionada por ysdx no comentário acima.
Gilles
28

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

gcore pid

Os despejos podem ser grandes, use -o outfilese o diretório atual não tiver espaço suficiente.

Tobu
fonte
12

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

Aqui está 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

É o meu shell atual.

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

Aqui, novamente, $$avalio para 17823, que é meu shell. catnão consigo ler o espaço de memória do meu shell.

bahamat
fonte
Você acaba tentando ler a memória do que quer que $pidseja. Como explico na minha resposta, a leitura da memória de um processo diferente exige que você a rastreie.
Gilles
O que será uma festa. Eu não estava dizendo que sua resposta estava errada. Eu estava apenas respondendo em termos mais leigos "por que isso não funciona"?
bahamat
@bahamat: Você está pensando $$quando escreve (e lê) $pid?
Gilles
Sim ... ele começou perguntando se referindo $$e colocando $pidno final. Transpus na minha cabeça sem perceber. Minha resposta inteira deve se referir $$, não $pid.
bahamat
@bahamat: A pergunta está mais clara agora? (BTW eu não vejo os seus comentários menos que você use “@Gilles”, eu só aconteceu para ver a sua edição e veio para ver.)
Gilles
7

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, em seguida, lê essas regiões em / 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);
    }
}
Tal Aloni
fonte
5
Adicione alguma explicação do seu código. Seu único comentário é meio inútil: write to stdoutimediatamente acima fwrite(..., stdout). Veja programmers.stackexchange.com/questions/119600/…
muru
Você disse que só o testou no Android, então eu só queria confirmar, ele funciona bem no Linux 4.4.0-28 x86_64, como seria de esperar #
Apricot boy
Eu recebo um monte de dados como / @ 8 l / @ l no stdout que nunca acaba com nenhuma ideia de por que? compilado no Linux 4.9.0-3-amd64 # 1 SMP Debian 4.9.25-1 (02-05-2017) x86_64 Modelo de encadeamento GNU / Linux: posix gcc versão 6.3.0 20170516 (Debian 6.3.0-18)
ceph3us
ceph3us, o uso comum é para canalizar os dados para um arquivo (por exemplo memdump <pid>> /sdcard/memdump.bin)
Tal Aloni