/proc/$pid/maps
/proc/$pid/mem
показывает содержимое памяти $ pid, отображенной так же, как и в процессе, т. е. байт со смещением x в псевдофайле совпадает с байтом по адресу x в процессе. Если адрес не отображается в процессе, чтение из соответствующего смещения в файле возвращает EIO
(Ошибка ввода / вывода). Например, поскольку первая страница в процессе никогда не отображается (так что разыменование NULL
указателя завершается ошибкой, а не непреднамеренно обращается к фактической памяти), чтение первого байта /proc/$pid/mem
всегда приводит к ошибке ввода-вывода.
Чтобы узнать, какие части памяти процесса отображаются, нужно прочитать /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", '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/mem
( r--------
) более либеральны, чем должно быть. Например, вы не должны иметь возможность читать память процесса 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 код, предоставляющий записи для каждого процесса, /proc
находится внутри fs/proc/base.c
, а функция для чтения /proc/$pid/mem
- mem_read
. Дополнительная проверка выполняется check_mem_permission
.
Вот некоторый пример кода C для присоединения к процессу и чтения фрагмента его mem
файла (проверка ошибок пропущена):
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
в другом потоке .