Я хочу найти физический адрес переменной, определенной в процессе пользовательского пространства? Есть ли способ сделать это, используя привилегии root?
Как найти физический адрес переменной из пользовательского пространства в Linux?
Ответ 1
Во-первых, зачем вам это делать? Цель современных систем VM - удалить программиста приложений из сложности компоновки физической памяти. Gving их каждое их собственное единообразное адресное пространство, чтобы сделать их жизнь более легкой.
Если вы действительно хотите сделать это, вы почти наверняка должны будете использовать модуль ядра. Получить виртуальный адрес переменной обычным способом, использовать это, чтобы индексировать в таблицы страниц процессов и читать значение, которое вы найдете (физический адрес кадра). Затем добавьте смещение страницы, чтобы получить полный физический адрес. Обратите внимание, что вы не сможете использовать этот адрес во время работы пейджинга.
(Если вам повезет, вы сможете получить адрес фрейма области VM из файловой системы /proc и, следовательно, не потребуется писать модуль ядра.)
Ответ 2
Как частично ответили ранее, обычным программам не нужно беспокоиться о физических адресах, поскольку они работают в виртуальном адресном пространстве со всеми его удобствами. Кроме того, не каждый виртуальный адрес имеет физический адрес, он может принадлежать отображаемым файлам или замененным страницам. Однако иногда это может быть интересно увидеть это отображение, даже в userland.
С этой целью ядро Linux предоставляет свое сопоставление с пользовательской средой через набор файлов в /proc
. Документацию можно найти здесь. Краткое описание:
-
/proc/$pid/maps
предоставляет список сопоставлений виртуальных адресов вместе с дополнительной информацией, такой как соответствующий файл для сопоставленных файлов. -
/proc/$pid/pagemap
предоставляет дополнительную информацию о каждой отображаемой странице, включая физический адрес, если он существует.
Этот веб-сайт предоставляет программу на C, которая сбрасывает сопоставления всех запущенных процессов с использованием этого интерфейса и объясняет, что он делает.
Ответ 3
#include "stdio.h"
#include "unistd.h"
#include "inttypes.h"
uintptr_t vtop(uintptr_t vaddr) {
FILE *pagemap;
intptr_t paddr = 0;
int offset = (vaddr / sysconf(_SC_PAGESIZE)) * sizeof(uint64_t);
uint64_t e;
// https://www.kernel.org/doc/Documentation/vm/pagemap.txt
if ((pagemap = fopen("/proc/self/pagemap", "r"))) {
if (lseek(fileno(pagemap), offset, SEEK_SET) == offset) {
if (fread(&e, sizeof(uint64_t), 1, pagemap)) {
if (e & (1ULL << 63)) { // page present ?
paddr = e & ((1ULL << 54) - 1); // pfn mask
paddr = paddr * sysconf(_SC_PAGESIZE);
// add offset within page
paddr = paddr | (vaddr & (sysconf(_SC_PAGESIZE) - 1));
}
}
}
fclose(pagemap);
}
return paddr;
}
Ответ 4
(edit: Если по "физическому адресу" вы имеете в виду уровень "в котором RAM-модуль - мои биты, хранящиеся", тогда следующий ответ неуместен.)
Для этого вам не нужны привилегии root. Вместо этого вам нужен отладчик. И вот мы идем (используя Linux-систему на x86_64):
Сначала нам нужна небольшая программа для игры. Этот пользователь получает глобальную переменную и печатает ее два раза подряд. Он имеет две глобальные переменные, которые мы найдем в памяти позже.
#include <stdio.h> int a, b = 0; int main(void) { printf("a: "); if (fscanf("%d", &a) < 1) return 0; printf("a = %d\n", myglobal); printf("b: "); if (fscanf("%d", &b) < 1) return 0; printf("a = %d, b = %d\n", a, b); return 0; }
Шаг 1: Скомпилируйте программу и удалите с нее всю отладочную информацию, поэтому мы не получаем никаких намеков от отладчика, которые мы не получили бы в реальной жизненной ситуации.
$ gcc -s -W -Wall -Os -o ab ab.c
Шаг 2: Запустите программу и введите один из двух чисел.
$ ./ab a: 123 a = 123 b: _
Шаг 3: Найдите процесс.
$ ps aux | grep ab roland 21601 0.0 0.0 3648 456 pts/11 S+ 15:17 0:00 ./ab roland 21665 0.0 0.0 5132 672 pts/12 S+ 15:18 0:00 grep ab
Шаг 4. Присоедините отладчик к процессу (21601).
$ gdb ... (gdb) attach 21601 ... (gdb) where #0 0x00007fdecfdd2970 in read () from /lib/libc.so.6 #1 0x00007fdecfd80b40 in _IO_file_underflow () from /lib/libc.so.6 #2 0x00007fdecfd8230e in _IO_default_uflow () from /lib/libc.so.6 #3 0x00007fdecfd66903 in _IO_vfscanf () from /lib/libc.so.6 #4 0x00007fdecfd7245c in scanf () from /lib/libc.so.6 #5 0x0000000000400570 in ?? () #6 0x00007fdecfd2f1a6 in __libc_start_main () from /lib/libc.so.6 #7 0x0000000000400459 in ?? () #8 0x00007fffd827da48 in ?? () #9 0x000000000000001c in ?? () #10 0x0000000000000001 in ?? () #11 0x00007fffd827f9a2 in ?? () #12 0x0000000000000000 in ?? ()
Интересный кадр - номер 5, так как он находится между некоторым кодом, вызывающим функцию main
и функцией scanf
, поэтому это должна быть наша функция main
. Продолжение сеанса отладки:
(gdb) frame 5 ... (gdb) disassemble $pc $pc+50 ... 0x0000000000400570 : test %eax,%eax 0x0000000000400572 : jle 0x40058c <[email protected]+372> 0x0000000000400574 : mov 0x2003fe(%rip),%edx # 0x600978 <[email protected]+2098528> 0x000000000040057a : mov 0x2003fc(%rip),%esi # 0x60097c <[email protected]+2098532> 0x0000000000400580 : mov $0x40068f,%edi 0x0000000000400585 : xor %eax,%eax 0x0000000000400587 : callq 0x4003f8 <[email protected]> ...
Теперь мы знаем, что функция printf
получит три параметра, а две из них находятся всего в четырех байтах друг от друга. Это хороший признак того, что эти два являются нашими переменными a
и b
. Таким образом, адрес a
равен 0x600978 или 0x60097c. Давайте узнаем, попробовав:
(gdb) x/w 0x60097c 0x60097c <[email protected]+2098532>: 0x0000007b (gdb) x/w 0x600978 0x600978 <[email protected]+2098528>: 0x00000000
Итак, a
, переменная, которая считывается сначала, находится по адресу 0x60097c (потому что 0x0000007b - это шестнадцатеричное представление для 123, которое мы ввели), а b
- 0x600978.
В отладчике все еще можно изменить переменную a
, а затем продолжить программу.
(gdb) set *(int *)0x60097c = 1234567 (gdb) continue
Вернувшись в программу, которая попросила нас ввести два номера:
$ ./ab a: 123 a = 123 b: 5 a = 1234567, b = 5 $