Подтвердить что ты не робот

Как найти физический адрес переменной из пользовательского пространства в Linux?

Я хочу найти физический адрес переменной, определенной в процессе пользовательского пространства? Есть ли способ сделать это, используя привилегии root?

4b9b3361

Ответ 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
$