← Gestión de Memoria Bloque 1 · El espacio de direcciones · Tema 4 de 52

Tema 4

El espacio de direcciones de un proceso

Cada proceso tiene su propio espacio de direcciones virtuales, y dentro de él las distintas partes del programa ocupan regiones bien definidas: el código, los datos, el heap, la pila y las zonas mapeadas. En este tema verás cómo se organiza ese espacio en Linux, qué contiene cada región, cómo leerlo en /proc/PID/maps y qué efecto tiene la aleatorización (ASLR). Es el mapa que usarás para entender cualquier programa.

1. Qué es el espacio de direcciones de un proceso

Cuando el sistema crea un proceso, le entrega un espacio de direcciones virtuales propio: el conjunto de todas las direcciones que ese proceso puede nombrar. Dentro de ese espacio, el cargador del programa y el propio programa colocan distintas cosas en distintas regiones, según su función y sus permisos.

El espacio de direcciones no es una lista de bytes indiferenciados: es una colección de regiones, cada una con una dirección inicial, un tamaño, unos permisos y un origen (un archivo del disco, memoria anónima, la pila...). El núcleo mantiene esa lista y la expone en /proc/PID/maps.

Espacio de direcciones de un proceso es el conjunto de regiones virtuales que el proceso puede usar, cada una con su rango, permisos y origen, administradas por el núcleo.

2. Las regiones típicas

Un programa en C compilado y ejecutado en Linux tiene, como mínimo, estas regiones:

Organización típica del espacio de direcciones de un proceso en Linux x86-64 Código (text) + datos (data, BSS) Heap (crece hacia arriba) Regiones mapeadas (mmap, bibliotecas) Pila (stack, crece hacia abajo) vdso / vvar / vsyscall (núcleo) bajas altas
Las direcciones crecen de arriba abajo en este diagrama: código y datos en las bajas, pila en las altas. El hueco central es espacio libre para heap y mmap.

3. El orden en un proceso Linux x86-64

En Linux sobre x86-64, el espacio virtual de un proceso de 64 bits se divide aproximadamente así (las direcciones son virtuales):

RegiónRango típico (con ASLR)Contenido
Código + datos0x55... (aleatorio)El binario y sus segmentos
Heapjusto después del códigoBloques de malloc
Bibliotecas (mmap)0x7f... (aleatorio)libc, libm, etc.
Pila0x7fff... (aleatorio)Variables locales, marcos
vdso / vvarcerca de la pilaTrampolín al núcleo

Los rangos exactos cambian en cada ejecución por ASLR, pero el orden relativo se mantiene: código y heap en la parte baja, bibliotecas y pila en la alta, y un gran hueco libre en medio (espacio no asignado).

4. El formato de /proc/PID/maps

Cada línea de /proc/PID/maps describe una región. El formato es:

inicio-fin permisos desplazamiento mayor:menor inodo ruta

5. Experimento: imprimir regiones y leer el mapa

El programa siguiente imprime las direcciones de distintas variables y luego muestra su propio mapa, destacando las regiones de código, datos, heap, pila y mmap:

espacio.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

static void funcion(void) { }

int global_init = 99;          /* data */
int global_bss;                /* BSS */

int main(void)
{
    int local = 42;             /* pila */
    void *heap = malloc(1024);  /* heap */
    void *mapa = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);  /* mmap anónimo */

    printf("código  : %p\n", (void *)&funcion);
    printf("data    : %p\n", (void *)&global_init);
    printf("BSS     : %p\n", (void *)&global_bss);
    printf("heap    : %p\n", heap);
    printf("mmap    : %p\n", mapa);
    printf("pila    : %p\n", (void *)&local);

    printf("\n/proc/self/maps:\n");
    FILE *f = fopen("/proc/self/maps", "r");
    char linea[256];
    while (fgets(linea, sizeof linea, f)) fputs(linea, stdout);
    fclose(f);

    munmap(mapa, 4096);
    free(heap);
    return 0;
}
terminal · el espacio de direcciones de un proceso
$ gcc -std=c17 -Wall -Wextra -o espacio espacio.c
$ ./espacio
código  : 0x55c3a1b01149
data    : 0x55c3a1b04010
BSS     : 0x55c3a1b04014
heap    : 0x55c3a1b062a0
mmap    : 0x7f8b4c000000
pila    : 0x7ffe2c3a1f2c

/proc/self/maps:
55c3a1b01000-55c3a1b02000 r-xp 00000000 08:01 1310734 /home/ana/espacio
55c3a1b02000-55c3a1b03000 r--p 00001000 08:01 1310734 /home/ana/espacio
55c3a1b03000-55c3a1b04000 rw-p 00002000 08:01 1310734 /home/ana/espacio
55c3a1b04000-55c3a1b05000 rw-p 00000000 00:00 0
55c3a1b06000-55c3a1b07000 rw-p 00000000 00:00 0       [heap]
7f8b4be00000-7f8b4be21000 rw-p 00000000 00:00 0
7f8b4be21000-7f8b4c000000 ---p 00000000 00:00 0
7f8b4c000000-7f8b4c001000 rw-p 00000000 00:00 0       (anon)
7f8b4c001000-7f8b4c200000 ---p 00000000 00:00 0
7f8b4c200000-7f8b4c3e7000 r-xp 00000000 08:01 1310735 /lib/x86_64-linux-gnu/libc.so.6
7f8b4c3e7000-7f8b4c3e8000 r--p 001e7000 08:01 1310735 /lib/x86_64-linux-gnu/libc.so.6
...
7ffe2c380000-7ffe2c3a2000 rw-p 00000000 00:00 0       [stack]
7ffe2c3f6000-7ffe2c3f8000 r-xp 00000000 00:00 0       [vdso]

Cada dirección impresa cae dentro de una región del mapa: código en la región ejecutable del binario, data y BSS en la región de datos, heap en [heap], el mmap en una región anónima, y la variable local en [stack]. Las bibliotecas (libc) aparecen como varias regiones mapeadas con distintos permisos.

6. Permisos por región

La MMU comprueba los permisos de cada región en cada acceso. Esto es lo que hace que un programa no pueda escribir en su código ni ejecutar datos:

RegiónPermisos típicosPor qué
Código (text)r-xpSe lee y se ejecuta, pero no se escribe (protección W^X).
Datos (data, BSS)rw-pSe leen y se escriben, pero no se ejecutan.
Heaprw-pMemoria dinámica de lectura/escritura.
Pilarw-pVariables locales y marcos de activación.
Bibliotecas (código)r-xpCompartido entre procesos, solo lectura/ejecución.
Regiones anónimas guardia---pPáginas sin permisos, para detectar desbordes.

La política W^X (escribir o ejecutar, pero no ambas) es una defensa contra ataques: impide inyectar código en datos y ejecutarlo. Por eso un programa que intenta ejecutar la pila o el heap recibe SIGSEGV.

7. Regiones mapeadas: archivos y anónimas

Las regiones mapeadas con mmap son la forma más flexible de crear memoria. Pueden ser:

Las bibliotecas compartidas (.so) se cargan con mmap, por eso aparecen como regiones con archivo de origen en el mapa. Esto permite compartir el código de libc entre todos los procesos que la usan.

8. ASLR: aleatorizar las direcciones

Para dificultar los ataques, Linux aleatoriza la posición de las regiones en cada ejecución: código, heap, pila, mmap y bibliotecas se cargan en direcciones distintas. Es el ASLR (Address Space Layout Randomization).

9. Probar con un simulador

Este simulador dibuja el espacio de direcciones de un proceso con sus regiones principales, y permite activar o desactivar ASLR para ver cómo cambian las direcciones base. También muestra el tamaño de cada región.

Simulador · espacio de direcciones de un proceso

RegiónRango virtualPermisos

10. Mirar el espacio en Linux

terminal · inspeccionar el espacio de direcciones
$ cat /proc/self/maps
$ pmap -x $$
Address           Kbytes     RSS   Dirty Mode  Mapping
000055...-...       4       4       0 r-x-- bash
...
total kB          ...       ...       ...
$ cat /proc/sys/kernel/randomize_va_space
2
$ setarch -R ./espacio   # desactiva ASLR para este programa
$ cat /proc/PID/smaps_rollup   # resumen de memoria del proceso

11. Resumen

12. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué regiones tiene un proceso típico en Linux?

Código (text), datos inicializados (data), BSS, heap, regiones mapeadas (mmap, bibliotecas), pila (stack) y regiones del núcleo (vdso, vvar).

2. ¿Qué significa el permiso r-xp en /proc/PID/maps?

Lectura y ejecución, región privada (copy-on-write). Es típico del código: se puede leer y ejecutar, pero no escribir.

3. ¿En qué dirección crece el heap y en cuál la pila?

El heap crece hacia direcciones crecientes (hacia arriba); la pila crece hacia direcciones decrecientes (hacia abajo).

4. ¿Qué es ASLR y para qué sirve?

Address Space Layout Randomization: aleatoriza las direcciones base de las regiones en cada ejecución para dificultar los ataques que dependen de direcciones fijas.

5. ¿Qué diferencia hay entre una región anónima y una mapeada de archivo?

La anónima no tiene archivo de respaldo (el núcleo la pone a cero); la de archivo corresponde a un archivo del disco y acceder a ella lee o escribe el archivo.

6. ¿Por qué el código tiene permiso de ejecución pero no de escritura?

Por la política W^X (escribir o ejecutar, pero no ambas), que impide inyectar código en datos y ejecutarlo, una defensa contra ataques.

7. ¿Qué muestra pmap que no muestra /proc/PID/maps?

Un resumen con el tamaño en KiB y el RSS de cada región, además del total, en un formato más legible.

8. ¿Qué es una página guardia (---p)?

Una región sin permisos de ningún tipo, colocada entre regiones para detectar desbordes de búfer: cualquier acceso provoca SIGSEGV.

Para practicar

  1. Compila y ejecuta espacio.c varias veces. ¿Cambia la dirección del heap entre ejecuciones? ¿Y la pila? Relaciona con ASLR.
  2. Ejecuta setarch -R ./espacio y compara las direcciones con las de una ejecución normal.
  3. Usa pmap -x sobre el proceso de espacio mientras está parado (añade un getchar() antes de terminar) y localiza cada región.
  4. Modifica espacio.c para pedir 1 MiB con malloc y observa si aparece una nueva región en el mapa o si se amplía el heap.
  5. Crea un programa que mapee un archivo de texto con mmap y modifique un byte. Comprueba que el archivo cambia.
  6. Consulta /proc/self/smaps_rollup y anota Rss, Pss y Dirty. ¿Qué diferencia hay entre Rss y Pss?
  7. Desactiva ASLR con echo 0 | sudo tee /proc/sys/kernel/randomize_va_space, ejecuta espacio y vuelve a activarlo. (Restaura el valor 2 al terminar.)