171

The Linux proc(5) man page tells me that /proc/$pid/mem “can be used to access the pages of a process's memory”. But a straightforward attempt to use it only gives me

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

Why isn't cat able to print its own memory (/proc/self/mem)? And what is this strange “no such process” error when I try to print the shell's memory (/proc/$$/mem, obviously the process exists)? How can I read from /proc/$pid/mem, then?

6 Answers6

187

/proc/$pid/maps

/proc/$pid/mem shows the contents of $pid's memory mapped the same way as in the process, i.e., the byte at offset x in the pseudo-file is the same as the byte at address x in the process. If an address is unmapped in the process, reading from the corresponding offset in the file returns EIO (Input/output error). For example, since the first page in a process is never mapped (so that dereferencing a NULL pointer fails cleanly rather than unintendedly accessing actual memory), reading the first byte of /proc/$pid/mem always yield an I/O error.

The way to find out what parts of the process memory are mapped is to read /proc/$pid/maps. This file contains one line per mapped region, looking like this:

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

The first two numbers are the boundaries of the region (addresses of the first byte and the byte after last, in hexa). The next column contain the permissions, then there's some information about the file (offset, device, inode and name) if this is a file mapping. See the proc(5) man page or Understanding Linux /proc/id/maps for more information.

Here's a proof-of-concept script that dumps the contents of its own memory.

#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'rb', 0)
output_file = open("self.dump", 'wb')
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
        output_file.write(chunk)  # dump contents to standard output
maps_file.close()
mem_file.close()
output_file.close()

/proc/$pid/mem

[The following is for historical interest. It does not apply to current kernels.]

Since version 3.3 of the kernel, you can access /proc/$pid/mem normally as long as you access only access it at mapped offsets and you have permission to trace it (same permissions as ptrace for read-only access). But in older kernels, there were some additional complications.

If you try to read from the mem pseudo-file of another process, it doesn't work: you get an ESRCH (No such process) error.

The permissions on /proc/$pid/mem (r--------) are more liberal than what should be the case. For example, you shouldn't be able to read a setuid process's memory. Furthermore, trying to read a process's memory while the process is modifying it could give the reader an inconsistent view of the memory, and worse, there were race conditions that could trace older versions of the Linux kernel (according to this lkml thread, though I don't know the details). So additional checks are needed:

  • The process that wants to read from /proc/$pid/mem must attach to the process using ptrace with the PTRACE_ATTACH flag. This is what debuggers do when they start debugging a process; it's also what strace does to a process's system calls. Once the reader has finished reading from /proc/$pid/mem, it should detach by calling ptrace with the PTRACE_DETACH flag.
  • The observed process must not be running. Normally calling ptrace(PTRACE_ATTACH, …) will stop the target process (it sends a STOP signal), but there is a race condition (signal delivery is asynchronous), so the tracer should call wait (as documented in ptrace(2)).

A process running as root can read any process's memory, without needing to call ptrace, but the observed process must be stopped, or the read will still return ESRCH.

In the Linux kernel source, the code providing per-process entries in /proc is in fs/proc/base.c, and the function to read from /proc/$pid/mem is mem_read. The additional check is performed by check_mem_permission.

Here's some sample C code to attach to a process and read a chunk its of mem file (error checking omitted):

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

I've already posted a proof-of-concept script for dumping /proc/$pid/mem on another thread.

  • Or you can just use dd to read from /proc/pid/mem – Ankur Agarwal Mar 04 '12 at 06:12
  • 2
    @abc No, reading from /proc/$pid/mem directly (whether with cat or dd or anything else) doesn't work. Read my answer. – Gilles 'SO- stop being evil' Mar 04 '12 at 17:55
  • You are right. Please check this article. http://www.trilithium.com/johan/2005/08/linux-gate/ Here the author is using dd to read from /proc/self/mem. I have asked him how was he able to accomplish that? – Ankur Agarwal Mar 04 '12 at 20:32
  • 6
    @abc He's reading from /proc/self/mem. A process can read its own memory space just fine, it's reading another process's memory space that requires PTRACE_ATTACH. – Gilles 'SO- stop being evil' Mar 04 '12 at 20:49
  • Somehow this hits the 2GB fseek limit, I didn't get it to work in Python 3 to see if that uses fseeko. Also it needs a SIGCONT somewhere in the crash case. I know it's quick and dirty, sorry. – Tobu Mar 19 '13 at 11:30
  • (I'm referring to the linked script) – Tobu Mar 19 '13 at 11:39
  • Even as root, I cannot peek at another process' /proc/pid/mem without ptrace-ing. kill -SIGSTOP pid alone doesn't suffice. This is on RHEL6.1 – sahilsuneja Jun 19 '13 at 21:03
  • Updated to work with 64-bit virtual memory https://gist.github.com/ntrrgc/9309999 – Alicia Mar 02 '14 at 17:22
  • @ntrrgc What was the problem? As far as I remember, I'd tested that on amd64. – Gilles 'SO- stop being evil' Mar 02 '14 at 18:46
  • file.seek will fail with 64-bit positions if the most-significant bit is 1, because offset in lseek() is signed and Python will signal 'could not convert Python int to C (signed) long'. – Alicia Mar 02 '14 at 22:03
  • Hi, from what I've seen the linux-memory-dumper (https://github.com/fuhry/linux-memory-dumper) uses /dev/$pid/maps to dump memory of arbitrary process. Can you elaborate how is it able to do that, since such a thing is not allowed as you've specified? Thank you – eleanor Aug 06 '14 at 20:46
  • About the high bit, I'm not aware of any solution even in C. All lseek functions expect a signed offset. Apparently, this is the case even at the kernel level. AFAIK, the only solution left is to use process_vm_readv with a recent kernel (3.2). – ysdx Jul 16 '15 at 14:28
  • My previous comment is wrong: lseek works correctly even if the high bit it set: you can use off_t as an unsigned value. – ysdx Sep 24 '15 at 10:10
  • 2
    Note that with recent Linux kernels, you do not need to PTRACE_ATTACH. This change comes with the process_vm_readv() system call (Linux 3.2). – ysdx Sep 24 '15 at 10:11
  • "If you try to read from the mem pseudo-file of another process, it doesn't work: you get an ESRCH (No such process) error." contradicts "The permissions on /proc/$pid/mem (r--------) are more liberal than what should be the case.". How can the permissions be too liberal when accessing mem gives an error? – Melab May 14 '16 at 16:02
  • @Melab The owner has read permission, but in general the owner can't read the file: the reading process has to be tracing $pid. – Gilles 'SO- stop being evil' May 14 '16 at 23:16
  • 2
    Hm, with Linux 4.14.8 this does work for me: start a long running process that is busy writing output to /dev/null. Then another process is able to open, seek and read some bytes from /proc/$otherpid/mem (i.e. at some offsets referenced via the auxiliary vector) - without having to ptrace-attach/detach or stopping/starting the process. Works if the process runs under the same user and for the root user. I.e. I can't yield a ESRCH error in this scenario. – maxschlepzig Dec 30 '17 at 15:57
  • 1
    @maxschlepzig I guess that's the change mentioned by ysdx in the comment above. – Gilles 'SO- stop being evil' Dec 31 '17 at 12:01
  • 1
    This answer is outdated, as already mentioned in the comments by ysdx and maxschlepzig above. Moreover, the /proc//mem of a setuid process is owned by root, neither the real nor the effective user have access to it. –  Apr 10 '19 at 16:43
32

This command (from gdb) dumps memory reliably:

gcore pid

Dumps can be large, use -o outfile if your current directory doesn't have enough room.

Tobu
  • 6,593
12

When you execute cat /proc/$$/mem the variable $$ is evaluated by by bash which inserts its own pid. It then executes cat which has a different pid. You end up with cat trying to read the memory of bash, its parent process. Since non-privileged processes can only read their own memory space this gets denied by the kernel.

Here's an example:

$ echo $$
17823

Note that $$ evaluates to 17823. Let's see which process that is.

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

It's my current shell.

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

Here again $$ evaluates to 17823, which is my shell. cat can't read my shell's memory space.

bahamat
  • 39,666
  • 4
  • 75
  • 104
  • You end up trying to read the memory of whatever $pid is. As I explain in my answer, reading the memory of a different process requires you to ptrace it. – Gilles 'SO- stop being evil' Jan 24 '11 at 19:35
  • 1
    Which is going to be bash. I wasn't saying your answer was wrong. I was just answering in more layman's terms "why doesn't this work". – bahamat Jan 24 '11 at 21:07
  • @bahamat: Are you thinking of $$ when you write (and read) $pid? – Gilles 'SO- stop being evil' Jan 31 '11 at 22:22
  • Yes...he started out asking referring to $$ and put $pid at the end. I transposed it in my head without realizing it. My entire answer should refer to $$, not $pid. – bahamat Jan 31 '11 at 22:44
  • @bahamat: Is the question clearer now? (BTW I don't see your comments unless you use “@Gilles”, I just happened to see your edit and came to see.) – Gilles 'SO- stop being evil' Jan 31 '11 at 23:23
  • @Gilles it was clear before. He specifically mentions using cat /proc/$$/mem. I was explaining why that specific command doesn't work as expected. I've updated my answer to include a specific example. – bahamat Jan 31 '11 at 23:35
  • @bahamat: I assure you that when I wrote cat /proc/$$/mem, I expected to see the memory of the shell. (Er, did you realize that I asked the question?) – Gilles 'SO- stop being evil' Jan 31 '11 at 23:45
  • 1
    @Giles: That's why I put "Since non-privileged processes can only read their own memory space this gets denied by the kernel". (And nope, I didn't realize that until just now when you pointed it out. I thought this whole time I was trying to make it a little more clear to someone else. I don't usually look at the poster's name or rep...then again I'm new here. It makes more sense to me now why the conversation went the way it did.) – bahamat Feb 01 '11 at 00:00
8

Here is a small program I wrote in C:

Usage:

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

The program uses /proc/$pid/maps to find all of the mapped memory regions of the process, and then read those regions from /proc/$pid/mem, one page at a time. those pages are written to stdout or the IP address and TCP port you specified.

Code (tested on Android, requires superuser permissions):

#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
  • 181
  • 7
    Add some explanation of your code. Your only comment is kinda pointless: write to stdout immediately above fwrite(..., stdout). See http://programmers.stackexchange.com/questions/119600/beginners-guide-to-writing-comments – muru Dec 27 '15 at 14:56
  • You said you only tested it on Android, so I just wanted to confirm, it works well on Linux 4.4.0-28 x86_64, as you'd expect – apricot boy Jul 14 '16 at 00:57
  • i get bunch of data like �/ ����� @8�l�/ ����� @��l on stdout that never ends any idea why ? compiled on Linux 4.9.0-3-amd64 #1 SMP Debian 4.9.25-1 (2017-05-02) x86_64 GNU/Linux Thread model: posix gcc version 6.3.0 20170516 (Debian 6.3.0-18) – ceph3us Jun 04 '17 at 22:26
  • ceph3us, the common usage is to pipe the data to a file (e.g. memdump > /sdcard/memdump.bin) – Tal Aloni Jun 06 '17 at 04:08
  • You could probably use Linux sendfile(2) or splice(2) to send the data to the output without copying it into user-space. (And again from the stdio buffer to your local array, since you're using stdio to hopefully reduce the number of read system calls, to use chunks larger than 1 page without having to make that explicit in the code.) /proc/<PID>/maps is already Linux-specific. – Peter Cordes Oct 14 '22 at 13:12
7

Perform the read using bash can be done also with dd(1)

If you're on a limited and basic Unix system which doesn't have the some of the commands mentioned above (from python all the way to memdump and stuff like that)

You can use dd(1), which should be available in the most limited Unix environment.

Example for dumping the first few bytes from the process:

$ dd if=/proc/1337/mem of=/tmp/dump bs=1 skip=$((0x400000)) count=128

and then you can read it with

hexdump -Cv ./tmp/dump
shaqed
  • 881
3

you can read the mem file with the xxd program

in this example i'm going to read the heap of a program

$ cd /proc/<the-pid>
$ cat maps | grep heap
55fe7eec6000-55fe7eee7000 rw-p 00000000 00:00 0                         [heap]
$
$ sudo xxd -s 0x55fe7eec6000 -l $((0x55fe7eee7000 - 0x55fe7eec6000)) mem  | less
  • flag s: seek offset position 0x05..6000
  • flag l: is the length. The last position (0x5..7000) minus the start position (0x5..6000)
  • mem: file
  • | less: visualize the output with less