Как найти физический адрес переменной из пространства пользователя в Linux? - PullRequest
14 голосов
/ 14 марта 2010

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

Ответы [ 4 ]

17 голосов
/ 19 декабря 2012

Как уже частично отвечали ранее, обычным программам не нужно беспокоиться о физических адресах, поскольку они работают в виртуальном адресном пространстве со всеми его удобствами. Кроме того, не каждый виртуальный адрес имеет физический адрес, он может принадлежать сопоставленным файлам или обменным страницам. Однако иногда может быть интересно увидеть это отображение даже в пользовательском пространстве.

Для этой цели ядро ​​Linux предоставляет доступ к пользовательскому интерфейсу через набор файлов в /proc. Документацию можно найти здесь . Краткое резюме:

  1. /proc/$pid/maps предоставляет список сопоставлений виртуальных адресов вместе с дополнительной информацией, такой как соответствующий файл для сопоставленных файлов.
  2. /proc/$pid/pagemap предоставляет дополнительную информацию о каждой отображаемой странице, включая физический адрес, если он существует.

Этот веб-сайт предоставляет программу на C, которая выводит сопоставления всех запущенных процессов с использованием этого интерфейса, и объясняет, что он делает.

15 голосов
/ 11 марта 2015
#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;
}   
2 голосов
/ 14 марта 2010

Во-первых, почему вы хотите это сделать? Цель современных систем виртуальных машин состоит в том, чтобы избавить прикладного программиста от сложности физической памяти. Предоставьте им каждому свое единое адресное пространство, чтобы облегчить им жизнь.

Если вы хотите это сделать, вам почти наверняка понадобится модуль ядра. Получите виртуальный адрес переменной обычным способом, используйте его для индексации таблиц страниц процессов и считывания найденного значения (физического адреса фрейма). Затем добавьте смещение страницы, чтобы получить полный физический адрес. Обратите внимание, что вы не сможете использовать этот адрес, пока включена подкачка.

(Если вам повезет, вы сможете получить адрес кадра области VM из файловой системы / proc и, следовательно, не потребует написания модуля ядра.)

0 голосов
/ 14 марта 2010

(редактировать: если под «физическим адресом» вы подразумеваете уровень «в котором хранятся мои модули в ОЗУ», то следующий ответ неуместен.)

Вам не нужны привилегии 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 <scanf@plt+372>
0x0000000000400574 :     mov    0x2003fe(%rip),%edx        # 0x600978 <scanf@plt+2098528>
0x000000000040057a :     mov    0x2003fc(%rip),%esi        # 0x60097c <scanf@plt+2098532>
0x0000000000400580 :     mov    $0x40068f,%edi
0x0000000000400585 :     xor    %eax,%eax
0x0000000000400587 :     callq  0x4003f8 <printf@plt>
...

Теперь мы знаем, что функция printf получит три параметра, и два из них находятся всего в четырех байтах друг от друга. Это хороший признак того, что эти две переменные являются нашими a и b. Таким образом, адрес a является 0x600978 или 0x60097c. Давайте узнаем, попробовав:

(gdb) x/w 0x60097c        
0x60097c <scanf@plt+2098532>:   0x0000007b
(gdb) x/w 0x600978
0x600978 <scanf@plt+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
$
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...