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 楼
/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]
前两个数字是区域的边界(第一个字节的地址和最后一个字节的地址,以六进制表示)。下一列包含权限,如果这是文件映射,则包含有关文件的一些信息(偏移量,设备,索引节点和名称)。有关更多信息,请参见
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", '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
[以下内容出于历史考虑。它不适用于当前内核。]
自内核版本3.3起,您可以正常访问
/proc/$pid/mem
,只要您仅以映射的偏移量访问它并且您有权跟踪它(与ptrace
的读取权限相同) -仅访问)。但是,在较旧的内核中,还存在一些其他复杂情况。 >mem
(ESRCH
)上的权限比实际情况要宽松。例如,您不应该能够读取setuid进程的内存。此外,尝试在修改进程时读取进程的内存可能会使读者对内存有不一致的看法,更糟糕的是,有些竞争条件可能会跟踪旧版本的Linux内核(根据该lkml线程,尽管我不知道细节)。因此,需要进行其他检查:要从
/proc/$pid/mem
读取的进程必须使用带有r--------
标志的/proc/$pid/mem
附加到该进程。这是调试器在开始调试进程时所做的工作。这也是ptrace
对进程的系统调用所做的。读取器完成对PTRACE_ATTACH
的读取后,应通过使用strace
标志调用/proc/$pid/mem
来断开连接。观察到的进程一定不能运行。通常,调用
ptrace
将停止目标进程(它发送PTRACE_DETACH
信号),但是存在竞争状态(信号传递是异步的),因此跟踪程序应调用ptrace(PTRACE_ATTACH, …)
(如STOP
中所述)。以root用户身份运行的进程可以读取任何进程的内存,而无需调用wait
,但是必须停止观察到的进程,否则读取仍将返回ptrace(2)
。在Linux内核源代码中,提供按进程条目的代码
ptrace
中的在ESRCH
中,而从/proc
读取的函数是fs/proc/base.c
。额外的检查由/proc/$pid/mem
执行。这里有一些示例C代码附加到进程中,并读取
mem_read
文件的一部分(省略了错误检查):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);
我已经发布了用于在另一个线程上转储
check_mem_permission
的概念验证脚本。评论
@abc否,无法直接从/ proc / $ pid / mem中读取(无论是cat还是dd或其他任何东西)。阅读我的答案。
–吉尔斯'所以-不再是邪恶的'
2012年3月4日在17:55
@abc他正在从/ proc / self / mem阅读。一个进程可以读取自己的内存空间,它正在读取另一个需要PTRACE_ATTACH的内存空间。
–吉尔斯'所以-不再是邪恶的'
2012年3月4日20:49
请注意,对于最新的Linux内核,您不需要PTRACE_ATTACH。此更改与process_vm_readv()系统调用(Linux 3.2)一起提供。
–ysdx
2015年9月24日上午10:11
嗯,在Linux 4.14.8中,这确实对我有用:启动一个长时间运行的进程,忙于将输出写入/ dev / null。然后另一个进程能够从/ proc / $ otherpid / mem中打开,查找和读取一些字节(即以通过辅助向量引用的某些偏移量)-无需ptrace-attach / detach或停止/启动该进程。如果该进程在同一用户下并且对于root用户运行,则可以使用。即在这种情况下,我无法产生ESRCH错误。
–maxschlepzig
17年12月30日15:57
@maxschlepzig我想这就是ysdx在上面的评论中提到的更改。
–吉尔斯'所以-不再是邪恶的'
17年12月31日在12:01
#2 楼
此命令(来自gdb)可靠地转储内存:gcore pid
转储可能很大,如果当前目录空间不足,请使用
-o outfile
。#3 楼
当执行cat /proc/$$/mem
时,bash会评估变量$$
,后者会插入其自己的pid。然后,它执行具有不同pid的cat
。您最终以cat
尝试读取其父进程bash
的内存。由于非特权进程只能读取自己的内存空间,因此内核会拒绝它。下面是一个示例:
$ echo $$
17823
请注意
$$
的计算结果为17823。让我们看看是哪个进程。$ ps -ef | awk '{if ( == "17823") print}'
bahamat 17823 17822 0 13:51 pts/0 00:00:00 -bash
这是我当前的shell。
$ cat /proc/$$/mem
cat: /proc/17823/mem: No such process
这里
$$
的计算结果为17823,这是我的shell。 cat
无法读取外壳程序的内存空间。评论
您最终试图读取$ pid的内存。正如我在回答中解释的那样,读取不同进程的内存需要您跟踪它。
–吉尔斯'所以-不再是邪恶的'
11年1月24日在19:35
这将是重击。我并不是说你的答案是错误的。我只是用更多的外行回答“为什么不行”。
– bahamat
2011年1月24日在21:07
@bahamat:您写(读)$ pid时会想到$$吗?
–吉尔斯'所以-不再是邪恶的'
2011年1月31日22:22
是的...他开始询问$$,最后加$ pid。我没有意识到就把它放在脑海中。我的整个答案应该是$$,而不是$ pid。
– bahamat
2011年1月31日22:44
@bahamat:问题现在更清楚了吗? (顺便说一句,除非您使用“ @Gilles”,否则我看不到您的评论,我只是碰巧看到您的修改而来。)
–吉尔斯'所以-不再是邪恶的'
2011年1月31日23:23
#4 楼
这是我用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);
}
}
评论
添加一些代码说明。您唯一的评论是毫无意义的:直接在fwrite(...,stdout)上方写入stdout。参见programmers.stackexchange.com/questions/119600/…
–muru
2015年12月27日14:56
您说您仅在Android上进行了测试,所以我只想确认一下,它可以在Linux 4.4.0-28 x86_64上正常运行,正如您所期望的那样
–杏子
16年7月14日在0:57
我在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
17年4月4日在22:26
ceph3us,常见用法是将数据通过管道传输到文件(例如memdump
–塔尔·阿罗尼(Tal Aloni)
17年6月6日在4:08
#5 楼
如果您使用的是有限的基本Unix系统,并且没有上述某些命令(从dd(1)
一直到python
以及类似的东西),则也可以使用memdump
完成bash的读取操作。 )可以使用
dd(1)
,它应该在最有限的Unix环境中可用。从进程中转储前几个字节的示例:
$ dd if=/proc/1337/mem of=/tmp/dump bs=1 skip=$((0x400000)) count=128
,然后您可以阅读与
hexdump -Cv ./tmp/dump
评论
在标题为“ Q&A”的问题解答中,还有几种其他方法显示了如何在SF上执行此操作:将linux进程的内存转储到文件中最新答案