Linux에서 / proc / $ pid / mem을 어떻게 읽습니까?


142

리눅스 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
이 Q & A의 SF에서이 작업을 수행하는 방법을 보여주는 몇 가지 다른 방법이 있습니다 . Linux 프로세스의 메모리를 파일로 덤프
slm

최신 답변
pizdelect

답변:


140

/proc/$pid/maps

/proc/$pid/mem프로세스에서와 동일한 방식으로 매핑 된 $ pid의 메모리 내용을 나타냅니다. 즉, 의사 파일의 오프셋 x 에있는 바이트 는 프로세스에있는 주소 x 에있는 바이트와 ​​같습니다 . 프로세스에서 주소가 매핑 해제되면 파일의 해당 오프셋에서 읽은 경우 EIO(입력 / 출력 오류)가 반환 됩니다. 예를 들어, 프로세스의 첫 번째 페이지는 매핑되지 않으므로 ( NULL실제로 메모리에 액세스하는 대신 포인터 역 참조가 제대로 실패하지 않으므로 ) 첫 번째 바이트를 읽으면 /proc/$pid/mem항상 I / O 오류가 발생합니다.

프로세스 메모리에서 매핑되는 부분을 찾는 방법은 읽는 것 /proc/$pid/maps입니다. 이 파일에는 다음과 같이 매핑 된 영역 당 한 줄이 포함됩니다.

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

처음 두 숫자는 영역의 경계입니다 (첫 번째 바이트와 마지막 바이트 이후의 16 진수). 다음 열에는 권한이 포함되어 있으며 파일 매핑 인 경우 파일에 대한 정보 (오프셋, 장치, inode 및 이름)가 있습니다. 자세한 내용은 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(No such process) 오류가 발생합니다.

/proc/$pid/mem( r--------) 에 대한 권한 은 상황보다 더 자유 롭습니다. 예를 들어 setuid 프로세스의 메모리를 읽을 수 없어야합니다. 또한, 공정 악화가 독자에게 메모리의 일관성보기를 줄 수있는 수정, 동안 프로세스의 메모리를 읽으려고, (에 따라 리눅스 커널의 이전 버전을 추적 할 수있는 경쟁 조건이 있었다 이 LKML 스레드 I 생각은, 세부 사항을 모른다). 따라서 추가 점검이 필요합니다.

  • 읽을 /proc/$pid/mem프로세스 ptracePTRACE_ATTACH플래그를 사용하여 프로세스에 첨부해야합니다 . 디버거가 프로세스 디버깅을 시작할 때 수행하는 작업입니다. 또한 strace프로세스의 시스템 호출에도 영향을줍니다. 리더가에서 읽기를 마치면 플래그 /proc/$pid/mem로 호출 ptrace하여 분리해야합니다 PTRACE_DETACH.
  • 관찰 된 프로세스가 실행되고 있지 않아야합니다. 일반적으로 호출 ptrace(PTRACE_ATTACH, …)하면 대상 프로세스가 중지 STOP되지만 (신호를 전송 함) 경쟁 조건 (신호 전달이 비동기 임)이 있으므로 추적 프로그램이 호출해야합니다 wait(에 설명 된대로 ptrace(2)).

루트로 실행중인 프로세스는을 호출 할 필요없이 프로세스의 메모리를 읽을 수 ptrace있지만 관찰 된 프로세스를 중지해야합니다. 그렇지 않으면 읽기는 여전히을 반환 ESRCH합니다.

Linux 커널 소스에서 프로세스 별 항목을 제공하는 코드 /proc는에 fs/proc/base.c있으며 읽을 함수 /proc/$pid/memmem_read입니다. 추가 확인은에 의해 수행됩니다 check_mem_permission.

다음은 프로세스에 첨부하고 mem파일 청크를 읽는 샘플 C 코드입니다 (오류 검사 생략).

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작동하지 않거나 다른 아무것도). 내 대답을 읽으십시오.
Gilles

4
@abc 그는에서 읽고 있습니다 /proc/self/mem. 프로세스는 자신의 메모리 공간을 잘 읽을 수 있으며 필요한 다른 프로세스의 메모리 공간을 읽습니다 PTRACE_ATTACH.
Gilles

2
최신 Linux 커널에서는 PTRACE_ATTACH 할 필요가 없습니다. 이 변경은 process_vm_readv()시스템 호출 (Linux 3.2) 과 함께 제공됩니다 .
ysdx

2
흠, 리눅스 4.14.8에서 이것은 나를 위해 작동합니다 : / dev / null에 출력을 작성하는 데 바쁜 장기 실행 프로세스를 시작하십시오. 그런 다음 다른 프로세스는 ptrace-attach / detach 또는 프로세스를 중지 / 시작할 필요없이 / proc / $ otherpid / mem (예 : 보조 벡터를 통해 참조되는 일부 오프셋에서)에서 일부 바이트를 열고 검색하고 읽을 수 있습니다. 프로세스가 동일한 사용자 및 루트 사용자에 대해 실행되는 경우 작동합니다. 즉 ESRCH,이 시나리오에서 오류를 생성 할 수 없습니다 .
maxschlepzig

1
@maxschlepzig 위 의견에서 ysdx가 언급 한 변경 사항이라고 생각합니다.
Gilles

28

이 명령 (gdb에서)은 메모리를 안정적으로 덤프합니다.

gcore pid

덤프가 클 수 있습니다. -o outfile현재 디렉토리에 충분한 공간이없는 경우 사용 하십시오.


12

실행할 cat /proc/$$/mem때 변수 $$는 자체 pid를 삽입하는 bash에 의해 평가됩니다. 그런 다음 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쉘의 메모리 공간을 읽을 수 없습니다.


당신은 무엇이든 기억을 읽으려고 노력하게됩니다 $pid. 내 대답에서 설명 하듯이 다른 프로세스의 메모리를 읽으려면 ptrace해야합니다.
Gilles

bash가 될 것입니다. 나는 당신의 대답이 잘못되었다고 말하지 않았습니다. 나는 더 많은 평신도의 용어로 "왜 이것이 효과가 없는가?"라고 대답했습니다.
bahamat

@bahamat : $$글을 읽고 읽을 때 생각하고 $pid있습니까?
Gilles

예 ... 그는 참조 묻는 시작 $$넣어 $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). programmers.stackexchange.com/questions/119600/…
muru

당신은 당신이 안드로이드에서만 테스트했다고 말했다. 그래서 나는 단지 확인하고 싶다. 그것은 예상대로 리눅스 4.4.0-28 x86_64에서 잘 작동한다.
살구 소년

나는 stdout에서 / @ 8 l / @ l와 같은 많은 데이터를 얻습니다. 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.