linux proc pid maps,/proc/[pid]/pagemaps and /proc/[pid]/maps | linux

本文介绍了一个实用程序,该程序使用/proc/pid/pagemap 和 /proc/pid/maps 文件来将进程的虚拟地址映射到物理地址,并提供了源代码示例。

F**/proc//pagemap + /proc//maps dump example program**

Here is a pagemap example that converts virtual addresses to physical: Is there any API for determining the physical address from virtual address in Linux?

The following program uses both /proc//pagemap + /proc//maps to dump page table information to show how they can be used together. Usage:

sudo ./pagemap_dump.out

Sample output:

addr pfn soft-dirty file/shared swapped present library

400000 12845d 0 1 0 1 /bin/bash

401000 12845e 0 1 0 1 /bin/bash

402000 12845f 0 1 0 1 /bin/bash

This tells us for example that the virtual address 0x400000 maps to the physical address

0x12845d000.

Why sudo is required: https://unix.stackexchange.com/questions/345915/how-to-change-permission-of-proc-self-pagemap-file/383838#383838

This program works in two steps:

parse the human readable lines lines from /proc//maps. This files contains lines of form:

7ffff7b6d000-7ffff7bdd000 r-xp 00000000 fe:00 658 /lib/libuClibc-1.0.22.so

which gives us:

7f8af99f8000-7f8af99ff000: a virtual address range that belong to the process, possibly containing multiple pages.

/lib/libuClibc-1.0.22.so the name of the library that owns that memory.

loop over each page of each address range, and ask /proc//pagemap for more information about that page, including the physical address.

pagemap_dump.c

#define _XOPEN_SOURCE 700

#include

#include

#include

#include

#include

#include

#include

typedef struct {

uint64_t pfn : 55;

unsigned int soft_dirty : 1;

unsigned int file_page : 1;

unsigned int swapped : 1;

unsigned int present : 1;

} PagemapEntry;

/* Parse the pagemap entry for the given virtual address.

*

* @param[out] entry the parsed entry

* @param[in] pagemap_fd file descriptor to an open /proc/pid/pagemap file

* @param[in] vaddr virtual address to get entry for

* @return 0 for success, 1 for failure

*/

int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr)

{

size_t nread;

ssize_t ret;

uint64_t data;

nread = 0;

while (nread < sizeof(data)) {

ret = pread(pagemap_fd, ((uint8_t*)&data) + nread, sizeof(data) - nread,

(vaddr / sysconf(_SC_PAGE_SIZE)) * sizeof(data) + nread);

nread += ret;

if (ret <= 0) {

return 1;

}

}

entry->pfn = data & (((uint64_t)1 << 55) - 1);

entry->soft_dirty = (data >> 55) & 1;

entry->file_page = (data >> 61) & 1;

entry->swapped = (data >> 62) & 1;

entry->present = (data >> 63) & 1;

return 0;

}

/* Convert the given virtual address to physical using /proc/PID/pagemap.

*

* @param[out] paddr physical address

* @param[in] pid process to convert for

* @param[in] vaddr virtual address to get entry for

* @return 0 for success, 1 for failure

*/

int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr)

{

char pagemap_file[BUFSIZ];

int pagemap_fd;

snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);

pagemap_fd = open(pagemap_file, O_RDONLY);

if (pagemap_fd < 0) {

return 1;

}

PagemapEntry entry;

if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) {

return 1;

}

close(pagemap_fd);

*paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE));

return 0;

}

int main(int argc, char **argv)

{

char buffer[BUFSIZ];

char maps_file[BUFSIZ];

char pagemap_file[BUFSIZ];

int maps_fd;

int offset = 0;

int pagemap_fd;

pid_t pid;

if (argc < 2) {

printf("Usage: %s pid\n", argv[0]);

return EXIT_FAILURE;

}

pid = strtoull(argv[1], NULL, 0);

snprintf(maps_file, sizeof(maps_file), "/proc/%ju/maps", (uintmax_t)pid);

snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);

maps_fd = open(maps_file, O_RDONLY);

if (maps_fd < 0) {

perror("open maps");

return EXIT_FAILURE;

}

pagemap_fd = open(pagemap_file, O_RDONLY);

if (pagemap_fd < 0) {

perror("open pagemap");

return EXIT_FAILURE;

}

printf("addr pfn soft-dirty file/shared swapped present library\n");

for (;;) {

ssize_t length = read(maps_fd, buffer + offset, sizeof buffer - offset);

if (length <= 0) break;

length += offset;

for (size_t i = offset; i < (size_t)length; i++) {

uintptr_t low = 0, high = 0;

if (buffer[i] == '\n' && i) {

const char *lib_name;

size_t y;

/* Parse a line from maps. Each line contains a range that contains many pages. */

{

size_t x = i - 1;

while (x && buffer[x] != '\n') x--;

if (buffer[x] == '\n') x++;

while (buffer[x] != '-' && x < sizeof buffer) {

char c = buffer[x++];

low *= 16;

if (c >= '0' && c <= '9') {

low += c - '0';

} else if (c >= 'a' && c <= 'f') {

low += c - 'a' + 10;

} else {

break;

}

}

while (buffer[x] != '-' && x < sizeof buffer) x++;

if (buffer[x] == '-') x++;

while (buffer[x] != ' ' && x < sizeof buffer) {

char c = buffer[x++];

high *= 16;

if (c >= '0' && c <= '9') {

high += c - '0';

} else if (c >= 'a' && c <= 'f') {

high += c - 'a' + 10;

} else {

break;

}

}

lib_name = 0;

for (int field = 0; field < 4; field++) {

x++;

while(buffer[x] != ' ' && x < sizeof buffer) x++;

}

while (buffer[x] == ' ' && x < sizeof buffer) x++;

y = x;

while (buffer[y] != '\n' && y < sizeof buffer) y++;

buffer[y] = 0;

lib_name = buffer + x;

}

/* Get info about all pages in this page range with pagemap. */

{

PagemapEntry entry;

for (uintptr_t addr = low; addr < high; addr += sysconf(_SC_PAGE_SIZE)) {

/* TODO always fails for the last page (vsyscall), why? pread returns 0. */

if (!pagemap_get_entry(&entry, pagemap_fd, addr)) {

printf("%jx %jx %u %u %u %u %s\n",

(uintmax_t)addr,

(uintmax_t)entry.pfn,

entry.soft_dirty,

entry.file_page,

entry.swapped,

entry.present,

lib_name

);

}

}

}

buffer[y] = '\n';

}

}

}

close(maps_fd);

close(pagemap_fd);

return EXIT_SUCCESS;

}

### `/proc/pid/maps` 文件详解 Linux 中的 `/proc/pid/maps` 文件用于展示指定进程(由 `pid` 标识)的虚拟内存映射信息。该文件包含了进程的虚拟地址空间分布,包括起始和结束地址、权限、偏移量、设备和 inode 信息,以及映射的文件名(如果有的话)[^1]。 #### 文件格式解析 `/proc/pid/maps` 文件中的每一行代表一个内存映射区域(VMA,Virtual Memory Area)。每一行的内容由多个字段组成,具体如下: 1. **地址范围**:表示该内存区域的起始地址和结束地址,例如 `55f4e7a6a000-55f4e7a6c000`。 2. **权限**:表示该内存区域的访问权限,例如 `r--p` 表示可读但不可写和不可执行。 - `r`:可读 - `w`:可写 - `x`:可执行 - `s`:共享内存 - `p`:私有内存 3. **偏移量**:表示该内存区域在文件中的偏移量,例如 `00000000`。 4. **设备号**:表示映射文件所在的设备,例如 `08:01`。 5. **inode 号**:表示映射文件的 inode 号,例如 `123456`。 6. **文件路径**:表示该内存区域映射的文件路径,例如 `/usr/bin/ls`。如果该区域是匿名内存(如堆、栈),则会显示 `[heap]` 或 `[stack]`。 #### 示例文件内容解析 ```plaintext 55f4e7a6a000-55f4e7a6c000 r--p 00000000 08:01 123456 /usr/bin/ls 55f4e7a6c000-55f4e7a71000 r-xp 00002000 08:01 123456 /usr/bin/ls 55f4e7a71000-55f4e7a73000 r--p 00007000 08:01 123456 /usr/bin/ls 55f4e7a73000-55f4e7a74000 rw-p 00009000 08:01 123456 /usr/bin/ls 7f8d4b2a0000-7f8d4b4c0000 rw-p 00000000 00:00 0 [heap] 7f8d4b4c0000-7f8d4b4e5000 r--p 00000000 08:01 789012 /usr/lib/libc-2.33.so 7f8d4b4e5000-7f8d4b63d000 r-xp 00025000 08:01 789012 /usr/lib/libc-2.33.so 7f8d4b63d000-7f8d4b68b000 r--p 0017d000 08:01 789012 /usr/lib/libc-2.33.so 7f8d4b68b000-7f8d4b68f000 rw-p 001cb000 08:01 789012 /usr/lib/libc-2.33.so 7f8d4b68f000-7f8d4b69b000 rw-p 00000000 00:00 0 7ffd3c3e0000-7ffd3c401000 rw-p 00000000 00:00 0 [stack] 7ffd3c5f0000-7ffd3c5f4000 r--p 00000000 00:00 0 [vvar] 7ffd3c5f4000-7ffd3c5f6000 r-xp 00000000 00:00 0 [vdso] fffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] ``` ##### 内存区域类型解析 - **代码段**:例如 `r-xp` 权限的区域,表示可执行的代码段,通常映射到可执行文件的代码部分。 - **数据段**:例如 `r--p` 和 `rw-p` 权限的区域,表示只读和可写的全局变量区域。 - **堆(Heap)**:例如 `[heap]` 区域,表示动态分配的内存区域,通常由 `malloc` 等函数分配。 - **栈(Stack)**:例如 `[stack]` 区域,表示进程的临时数据区,栈空间的增长方向是从高地址到低地址。 - **共享库**:例如 `/usr/lib/libc-2.33.so` 的映射区域,表示共享库的代码段和数据段。 - **特殊区域**: - `[vvar]`:表示虚拟动态变量(VVAR),用于加速系统调用。 - `[vdso]`:表示虚拟动态共享对象(VDSO),用于加速某些系统调用。 - `[vsyscall]`:表示虚拟系统调用(VSYSCALL),用于早期版本的 Linux 系统调用加速。 #### 如何查看 `/proc/pid/maps` 文件 可以通过以下命令查看某个进程的内存映射信息: ```bash cat /proc/<pid>/maps ``` 其中 `<pid>` 是目标进程的进程 ID。 #### 内核实现解析 在 Linux 内核中,`/proc/pid/maps` 文件的生成是通过 `seq_operations` 结构体实现的。`seq_operations` 是一种用于生成序列化输出的机制,通常用于 `/proc` 文件系统的文件生成。具体实现如下: ```c static const struct seq_operations proc_pid_maps_op = { .start = m_start, .next = m_next, .stop = m_stop, .show = show_map }; ``` - `m_start`、`m_next`、`m_stop`:用于控制遍历进程的虚拟内存区域(VMA)。 - `show_map`:用于显示每个 VMA 的信息。 `show_map` 函数进一步调用 `show_map_vma` 函数,最终通过 `seq_file` 操作遍历 VMA 的信息,并将其格式化输出到用户空间。 #### 内存映射的作用 1. **进程调试**:通过分析 `/proc/pid/maps` 文件,可以了解进程的内存布局,帮助调试内存相关的问题。 2. **性能优化**:通过查看内存映射,可以识别频繁访问的内存区域,优化内存使用。 3. **安全分析**:通过分析内存映射,可以检测潜在的安全漏洞,例如栈溢出、堆溢出等问题。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值