
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
Reading process memory is useful for troubleshooting performance issues, analyzing security vulnerabilities, and debugging software. However, the traditional approach of attaching a debugger to a running process and halting its execution can disrupt the system’s normal operation.
In this article, we’ll explore how to read living process memory without interrupting it.
The /proc filesystem is a virtual filesystem in Linux that exposes information about the system’s state and running processes in a hierarchical directory structure. Unlike traditional filesystems, the /proc filesystem doesn’t contain actual files on the disk but rather dynamically generated files and directories that show the system’s current state.
Each process on the system has a corresponding directory under /proc, with its process ID (PID) as the directory name. Inside the process directory, various files and directories provide information about the process, such as the process’s command line arguments, environment variables, open files, and memory usage.
The memory map of a process shows how the process’s virtual address space is organized. The process uses the virtual address space to access memory, and it isn’t necessarily the same as the physical address space used by the system’s memory hardware.
In the memory maps, we can find a list of memory regions allocated to the process, along with information about the attributes of each region, such as its starting address, ending address, and access permissions.
The memory map of a process can be found in the /proc/PID/maps file, where PID is the process ID of the process. Each line in the maps file represents a single memory region, and the format of each line is as follows:
start_address-end_address permissions offset device inode filename
Here’s what each field in the line means:
The /proc/PID/mem file in Linux provides direct access to the process’s memory with the given PID. We can use it to read or write arbitrary data in the process’s virtual address space, bypassing the normal system calls for memory access. To read the memory of a running process, we’ll read data from the memory map to navigate the mem file.
Let’s use knowledge about the structure of memory maps to write a script that will print memory content for each record in a memory map. We’ll get one parameter for our script – the process ID.
Let’s start with checking if that argument was provided:
if [ -z "$1" ]; then
echo "Usage: $0 <pid>"
exit 1
fi
We should also check if the process for the given PID exists:
if [ ! -d "/proc/$1" ]; then
echo "PID $1 does not exist"
exit 1
fi
Then, we’ll read lines from the memory map and extract memory ranges and permissions from them using awk:
while read -r line; do
mem_range="$(echo "$line" | awk '{print $1}')"
perms="$(echo "$line" | awk '{print $2}')"
# reading will happen here
done < "/proc/$1/maps"
After that, if the memory range is readable, we can print its content using the dd command. To do that, we’ll extract the start and end addresses of the range and transform them from hex to decimal base:
if [[ "$perms" == *"r"* ]]; then
start_addr="$(echo "$mem_range" | cut -d'-' -f1)"
end_addr="$(echo "$mem_range" | cut -d'-' -f2)"
echo "Reading memory range $mem_range..."
dd if="/proc/$1/mem" of="/dev/stdout" bs=1 skip="$(( 16#$start_addr ))" count="$((16#$end_addr - 16#$start_addr))" 2>/dev/null
fi
Finally, the whole script looks like that:
#!/bin/bash
if [ -z "$1" ]; then
echo "Usage: $0 <pid>"
exit 1
fi
if [ ! -d "/proc/$1" ]; then
echo "PID $1 does not exist"
exit 1
fi
while read -r line; do
mem_range="$(echo "$line" | awk '{print $1}')"
perms="$(echo "$line" | awk '{print $2}')"
if [[ "$perms" == *"r"* ]]; then
start_addr="$(echo "$mem_range" | cut -d'-' -f1)"
end_addr="$(echo "$mem_range" | cut -d'-' -f2)"
echo "Reading memory range $mem_range..."
dd if="/proc/$1/mem" of="/dev/stdout" bs=1 skip="$start_addr" count="$((16#$end_addr - 16#$start_addr))" 2>/dev/null
fi
done < "/proc/$1/maps"
Reading process memory without interrupting it’s a useful technique for troubleshooting, analyzing, and debugging Linux systems. In this article, we learned how to use the /proc filesystem to get an insight into the memory of a process.
Finally, we wrote a script that can fetch the contents of the memory with the help of memory maps.