/proc/[pid]/pagemaps y /proc/[pid]/mapas | linux

10 minutos de lectura

avatar de usuario
janjust

Estoy tratando de entender los dos archivos mencionados en el título. He buscado cuáles son los bits; sin embargo, no entiendo cómo extraer información útil de ellos (o simplemente lo estoy abordando de manera incorrecta).

Me explico: los mapas de página son un pseudoarchivo de “características” bastante nuevo que contiene la información del marco físico de las páginas virtuales asignadas a un [pid]. Es decir, dada una página virtual que comienza en la dirección x, diga ‘vas’ para el inicio de la dirección virtual, puedo indexar el archivo de mapa de página usando vas para obtener los 64 bits del marco de la página física asignada. Estos bits contienen información sobre esa página virtual. Sin embargo, cuando extraigo los bits y cambio un poco, me pierdo con lo que veo.

Los bits se representan de la siguiente manera: 0-54 es el número de marco de página, 55-60 es el cambio de página, el bit 63 es el bit actual, hay otros bits de poco interés para mí. Después de hacer un poco de mapeo usando direcciones vas de /proc/[pid]/maps, parece que casi todas las páginas de los procesos se intercambian, es decir, el bit 63 siempre es un cero. 🙁

Supongo que la pregunta sería, ¿cómo debo usar mapas de página de manera efectiva para obtener la dirección física equivalente de la dirección proporcionada por /proc/?[pid]/mapas

Para ser justos, publiqué una pregunta similar pero el enfoque fue un poco diferente unos días antes.

Si alguien puede arrojar algo de luz sobre este asunto se lo agradecería mucho.

===EDITAR===

Para abordar el comentario a continuación: estoy leyendo una línea de /proc/[pid]/maps y las líneas se ven así:

00400000-00401000 r-xp 00000000 08:01 8915461 /home/janjust/my_programs/shared_mem 7ffffef1b000-7ffffef3c000 rw-p 00000000 00:00 0 [stack]

Luego estoy extrayendo la cantidad de páginas virtuales que toca e indexando un archivo binario /proc/[pid]/pagemaps , y para cada página virtual puedo extraer la página física a la que está asignada.

La salida se parece a:

00400000-00401000 r-xp 00000000 08:01 8915461 /home/janjust/my_programs/shared_mem num_pages: 1 : 86000000001464C6

Una dirección física para cada página virtual en el rango virtual.

El código para leer la línea y extraer la dirección física es:

74     /* process /proc/pid/maps, by line*/
75     while(fgets(line, 256, in_map) != NULL){
76         unsigned long vas;
77         unsigned long vae;
78         int num_pages;
79 
80         //print line
81         printf("%s", line);
82 
83         /*scan for the virtual addresses*/
84         n = sscanf(line, "%lX-%lX", &vas, &vae);
85         if(n != 2){
86             printf("Involid line read from %s\n",maps);
87             continue;
88         }
89 
90         num_pages = (vae - vas) / PAGE_SIZE;
91         printf("num_pages: %d\n", num_pages);
92 
93         if(num_pages > 0){
94             long index  = (vas / PAGE_SIZE) * sizeof(unsigned long long);
95             off64_t o;
96             ssize_t t;
97 
98             /* seek to index in pagemaps */
99             o = lseek64(pm, index, SEEK_SET);
100             if (o != index){
101                 printf("Error seeking to o:%ld, index:%ld.\n", o, index);
102             }
103 
104             /* map the virtual to physical page */
105             while(num_pages > 0){
106                 unsigned long long pa;
107 
108                 /* Read a 64-bit word from each pagemap file... */
109                 t = read(pm, &pa, sizeof(unsigned long long));
110                 if(t < 0){
111                     printf("Error reading file \"%s\" \n", page_map);
112                     goto next_line;
113                 }
114                 printf(": %016llX\n", pa);

Sin embargo, aunque creo que estoy obteniendo el resultado correcto, el índice parece ser una falta de coincidencia de tipo o algo más está sucediendo: el resultado dice, por ejemplo, para el [shared mem] la línea en los mapas da un índice incorrecto; sin embargo, todavía puedo escanear el archivo binario y obtener la dirección de la página física.

El ejemplo de esa salida es el siguiente:

969 7f7f08d58000-7f7f08d59000 rw-s 00000000 00:04 0    /SYSV00003039 (deleted)
970 num_pages: 1
971 Error seeking to o:-1081840960, index:273796065984.
972 : 8600000000148267

Ok, ahora, por último, debo decir que esto está bajo un sistema operativo de 64 bits, y este problema no persiste en un sistema operativo de 32 bits.

  • Interesante. De hecho, estoy tratando de hacer lo mismo, pero no obtengo resultados razonables. Lo que realmente me pregunto es el índice en /proc/[pid]/pagemap. En su código (y el mío, para el caso) tiene esto: long index = (vas / PAGE_SIZE) * sizeof(unsigned long long); Lo que me pregunto es PAGE_SIZE. La mayoría de las arquitecturas permiten páginas de gran tamaño; en x86, por ejemplo, las páginas pueden ser de 4 kB o 4 MB, según recuerdo. ¿No haría esto la indexación en /proc/[pid]/pagemap con un uniforme PAGE_SIZE ¿impracticable?

    –Ted Middleton

    23 de noviembre de 2011 a las 19:11


  • Relacionado: Comprender Linux /proc/pid/maps o /proc/self/maps.

    – codeforester

    30 ago 2021 a las 20:10


avatar de usuario
Ciro Santilli Путлер Капут 六四事

F**/proc/<pid>/pagemap + /proc/<pid>/maps programa de ejemplo de volcado**

Aquí hay un pagemap ejemplo que convierte direcciones virtuales en físicas: ¿Existe alguna API para determinar la dirección física de la dirección virtual en Linux?

El siguiente programa utiliza ambos /proc/<pid>/pagemap + /proc/<pid>/maps para volcar la información de la tabla de páginas para mostrar cómo se pueden usar juntos. Uso:

sudo ./pagemap_dump.out <pid>

Salida de muestra:

addr pfn soft-dirty file/shared swapped present library
400000 12845d 0 1 0 1 /bin/bash
401000 12845e 0 1 0 1 /bin/bash
402000 12845f 0 1 0 1 /bin/bash

Esto nos dice por ejemplo que la dirección virtual 0x400000 mapas a la dirección física
0x12845d000.

Por qué sudo es requerido: https://unix.stackexchange.com/questions/345915/how-to-change-permission-of-proc-self-pagemap-file/383838#383838

Este programa funciona en dos pasos:

  • analizar las líneas legibles por humanos de /proc/<pid>/maps. Este archivo contiene líneas de formulario:

    7ffff7b6d000-7ffff7bdd000 r-xp 00000000 fe:00 658                        /lib/libuClibc-1.0.22.so
    

    lo que nos da:

    • 7f8af99f8000-7f8af99ff000: un rango de direcciones virtuales que pertenecen al proceso, posiblemente conteniendo varias páginas.
    • /lib/libuClibc-1.0.22.so el nombre de la biblioteca que posee esa memoria.
  • recorra cada página de cada rango de direcciones y pregunte /proc/<pid>/pagemap para obtener más información sobre esa página, incluida la dirección física.

pagemap_dump.c

#define _XOPEN_SOURCE 700
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct {
    uint64_t pfn : 55;
    unsigned int soft_dirty : 1;
    unsigned int file_page : 1;
    unsigned int swapped : 1;
    unsigned int present : 1;
} PagemapEntry;

/* Parse the pagemap entry for the given virtual address.
 *
 * @param[out] entry      the parsed entry
 * @param[in]  pagemap_fd file descriptor to an open /proc/pid/pagemap file
 * @param[in]  vaddr      virtual address to get entry for
 * @return 0 for success, 1 for failure
 */
int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr)
{
    size_t nread;
    ssize_t ret;
    uint64_t data;

    nread = 0;
    while (nread < sizeof(data)) {
        ret = pread(pagemap_fd, ((uint8_t*)&data) + nread, sizeof(data) - nread,
                (vaddr / sysconf(_SC_PAGE_SIZE)) * sizeof(data) + nread);
        nread += ret;
        if (ret <= 0) {
            return 1;
        }
    }
    entry->pfn = data & (((uint64_t)1 << 55) - 1);
    entry->soft_dirty = (data >> 55) & 1;
    entry->file_page = (data >> 61) & 1;
    entry->swapped = (data >> 62) & 1;
    entry->present = (data >> 63) & 1;
    return 0;
}

/* Convert the given virtual address to physical using /proc/PID/pagemap.
 *
 * @param[out] paddr physical address
 * @param[in]  pid   process to convert for
 * @param[in] vaddr virtual address to get entry for
 * @return 0 for success, 1 for failure
 */
int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr)
{
    char pagemap_file[BUFSIZ];
    int pagemap_fd;

    snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);
    pagemap_fd = open(pagemap_file, O_RDONLY);
    if (pagemap_fd < 0) {
        return 1;
    }
    PagemapEntry entry;
    if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) {
        return 1;
    }
    close(pagemap_fd);
    *paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE));
    return 0;
}

int main(int argc, char **argv)
{
    char buffer[BUFSIZ];
    char maps_file[BUFSIZ];
    char pagemap_file[BUFSIZ];
    int maps_fd;
    int offset = 0;
    int pagemap_fd;
    pid_t pid;

    if (argc < 2) {
        printf("Usage: %s pid\n", argv[0]);
        return EXIT_FAILURE;
    }
    pid = strtoull(argv[1], NULL, 0);
    snprintf(maps_file, sizeof(maps_file), "/proc/%ju/maps", (uintmax_t)pid);
    snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);
    maps_fd = open(maps_file, O_RDONLY);
    if (maps_fd < 0) {
        perror("open maps");
        return EXIT_FAILURE;
    }
    pagemap_fd = open(pagemap_file, O_RDONLY);
    if (pagemap_fd < 0) {
        perror("open pagemap");
        return EXIT_FAILURE;
    }
    printf("addr pfn soft-dirty file/shared swapped present library\n");
    for (;;) {
        ssize_t length = read(maps_fd, buffer + offset, sizeof buffer - offset);
        if (length <= 0) break;
        length += offset;
        for (size_t i = offset; i < (size_t)length; i++) {
            uintptr_t low = 0, high = 0;
            if (buffer[i] == '\n' && i) {
                const char *lib_name;
                size_t y;
                /* Parse a line from maps. Each line contains a range that contains many pages. */
                {
                    size_t x = i - 1;
                    while (x && buffer[x] != '\n') x--;
                    if (buffer[x] == '\n') x++;
                    while (buffer[x] != '-' && x < sizeof buffer) {
                        char c = buffer[x++];
                        low *= 16;
                        if (c >= '0' && c <= '9') {
                            low += c - '0';
                        } else if (c >= 'a' && c <= 'f') {
                            low += c - 'a' + 10;
                        } else {
                            break;
                        }
                    }
                    while (buffer[x] != '-' && x < sizeof buffer) x++;
                    if (buffer[x] == '-') x++;
                    while (buffer[x] != ' ' && x < sizeof buffer) {
                        char c = buffer[x++];
                        high *= 16;
                        if (c >= '0' && c <= '9') {
                            high += c - '0';
                        } else if (c >= 'a' && c <= 'f') {
                            high += c - 'a' + 10;
                        } else {
                            break;
                        }
                    }
                    lib_name = 0;
                    for (int field = 0; field < 4; field++) {
                        x++;
                        while(buffer[x] != ' ' && x < sizeof buffer) x++;
                    }
                    while (buffer[x] == ' ' && x < sizeof buffer) x++;
                    y = x;
                    while (buffer[y] != '\n' && y < sizeof buffer) y++;
                    buffer[y] = 0;
                    lib_name = buffer + x;
                }
                /* Get info about all pages in this page range with pagemap. */
                {
                    PagemapEntry entry;
                    for (uintptr_t addr = low; addr < high; addr += sysconf(_SC_PAGE_SIZE)) {
                        /* TODO always fails for the last page (vsyscall), why? pread returns 0. */
                        if (!pagemap_get_entry(&entry, pagemap_fd, addr)) {
                            printf("%jx %jx %u %u %u %u %s\n",
                                (uintmax_t)addr,
                                (uintmax_t)entry.pfn,
                                entry.soft_dirty,
                                entry.file_page,
                                entry.swapped,
                                entry.present,
                                lib_name
                            );
                        }
                    }
                }
                buffer[y] = '\n';
            }
        }
    }
    close(maps_fd);
    close(pagemap_fd);
    return EXIT_SUCCESS;
}

  • Creo que hay un error al leer las entradas del mapa de página. Si por alguna razón solo se leen dos bytes en primer lugar, la siguiente iteración leerá 8 bytes de original_offset + 2. Entonces, elimine el nread += ret y el + nread del cuarto parámetro de pread o cambiarlo así: ret = pread(pagemap_fd, ((char*)&data) + nread, sizeof(data) - nread, (vaddr / sysconf(_SC_PAGE_SIZE)) * sizeof(data) + nread);

    – Phidelux

    6 de mayo de 2020 a las 9:03

  • Y parece haber otro error al enmascarar 54 bits en lugar de 55 (como dice el documento: “Bits 0-54 número de marco de página (PFN) si está presente”). asi que deberia ser entry->pfn = data & (((uint64_t)1 << 54) - 1);.

    – Phidelux

    6 mayo 2020 a las 14:57

  • @Phidelux gracias por esas correcciones. Intentaré revisarlos pronto.

    – Ciro Santilli Путлер Капут 六四事

    6 mayo 2020 a las 16:09

  • @Phidelux Bien, el - nread tuve ya lo arreglé en mi upstream pero olvidé aplicar aquí 🙂 El 54 vs 55 tienes toda la razón, arreglado ahora. Avísame si ves más errores.

    – Ciro Santilli Путлер Капут 六四事

    9 de mayo de 2020 a las 18:06

Oooh K, el índice era correcto, pero comparar off64_to (8 bytes) con el índice largo estaba interpretando mal, por lo que recibí ese error. ¡Decir ah! esto fue un error estúpido. Así que agregar el encabezado apropiado se encargó de eso.

Falta encabezado :-/ suspiro soluciona el problema de comparar off64_t con un largo sin firmar.

Use page-types.c como guía para lo que está buscando, analiza el contenido tanto del mapa de página como de los mapas:
https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/plain/Documentation/vm/page-types.c?h=linux-2.6.32.y

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad