我如何在Linux下从/ proc / $ pid / mem中读取?


142

Linux的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

Answers:


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]

前两个数字是区域的边界(第一个字节的地址和后一个字节的地址,以六进制表示)。下一列包含权限,如果这是文件映射,则包含有关文件的一些信息(偏移量,设备,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(无此类进程)错误。

/proc/$pid/memr--------)上的权限比实际情况要宽松。例如,您不应该能够读取setuid进程的内存。此外,尝试在修改进程时读取进程的内存可能会使读者对内存有不一致的看法,更糟糕的是,有些竞争条件可能会跟踪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内核源代码中,在中提供每个进程条目的代码在/procfs/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 2012年

4
@abc他正在阅读/proc/self/mem。一个进程可以读取自己的内存空间,它正在读取另一个需要的内存空间PTRACE_ATTACH
Gilles 2012年

2
请注意,对于最新的Linux内核,您不需要PTRACE_ATTACH。此更改随process_vm_readv()系统调用(Linux 3.2)一起提供。
ysdx

2
嗯,在Linux 4.14.8中,这确实对我有用:启动一个长时间运行的进程,忙于将输出写入/ dev / null。然后,另一个进程能够打开,查找和读取/ proc / $ otherpid / mem中的某些字节(即,通过辅助向量引用的某些偏移量),而无需ptrace-attach / detach或停止/启动该进程。如果该进程在同一用户下并针对root用户运行,则可以使用。即ESRCH在这种情况下我无法产生错误。
maxschlepzig

1
@maxschlepzig我想这就是ysdx在上面的评论中提到的更改。
吉尔斯

28

此命令(来自gdb)可靠地转储内存:

gcore pid

转储可能很大,-o outfile如果当前目录空间不足,请使用转储。


12

当您执行cat /proc/$$/mem变量时$$,bash会评估该变量,该变量会插入其自己的pid。然后执行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,这是我的shell。cat无法读取外壳程序的内存空间。


您最终尝试读取任何$pid内容。正如我在回答中解释的那样,读取不同进程的内存需要您追踪它。
吉尔斯

这将是重击。我并不是说你的答案是错误的。我只是用更多的外行回答“为什么不行”。
bahamat 2011年

@bahamat:您在想$$什么时候写(读)$pid
吉尔斯

是的...他开始询问$$$pid结束。我没意识到就把它换了个头。我的整个答案应该参考$$,而不是$pid
bahamat 2011年

@bahamat:问题现在更清楚了吗?(顺便说一句,除非您使用“ @Gilles”,否则我看不到您的评论,我只是碰巧看到您的修改而来。)
Gilles

7

这是我用C编写的一个小程序:

用法:

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

该程序使用/ proc / $ pid / maps查找进程的所有映射内存区域,然后一次从一页/ proc / $ pid / mem中读取这些区域。这些页面将写入标准输出或您指定的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/...
穆鲁

您说您只在Android上进行了测试,所以我只想确认一下,它可以在Linux 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)
塔尔阿洛尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.