La memoria de un proceso en Linux
🎯 Al terminar podrás
- Ubicar en el espacio virtual de un proceso el text, data, heap, librerías y stack, y leer
/proc/PID/maps. - Explicar con la MMU por qué escribir en una dirección de kernel da SIGSEGV y no corrompe la RAM.
- Predecir qué devuelve
mallocen casos límite (overflow desize_t,short, 0, más que la RAM) y por qué funciona el overcommit. - Reconocer un buffer overflow que «no falla» y saber qué herramienta lo detecta.
Antes necesitas: Paginación por demanda · Protección con la MMU
- Detective escribe tu hipótesis del bug antes de abrir el diagnóstico
- Visual mira el diagrama o la tabla y explica qué cambia en cada paso
El espacio de direcciones virtual (VAS)
Sección titulada «El espacio de direcciones virtual (VAS)»Así se suele representar un programa en memoria (lab 10, diap. 12):
Cómo leerlo: de abajo hacia arriba suben las direcciones. A la derecha de cada zona van sus permisos (r = leer, w = escribir, x = ejecutar). El stack crece hacia abajo y el heap hacia arriba, hacia el hueco del medio (rayado), que no tiene páginas mapeadas: tocarlo da SIGSEGV. brk marca dónde termina el heap.
Cada segmento es un mapeo (mapping) de páginas con sus permisos. Se ven en /proc/PID/maps o, desde GDB, con info proc mappings. Este es el mapa real de stack.c mientras está detenido en pause() (ejecución de prueba):
5fd878169000-5fd87816a000 r--p /tmp/fin/stack ← cabecera ELF5fd87816a000-5fd87816b000 r-xp /tmp/fin/stack ← text: lectura + ejecución5fd87816b000-5fd87816c000 r--p /tmp/fin/stack ← constantes (rodata)5fd87816d000-5fd87816e000 rw-p /tmp/fin/stack ← data/bss: lectura + escritura5fd8a932b000-5fd8a934c000 rw-p [heap]705ecde28000-705ecdfb1000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.6705ece17d000-705ece1a8000 r-xp /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27ffea5870000-7ffea5891000 rw-p [stack]7ffea58ae000-7ffea58b2000 r--p [vvar]7ffea58b2000-7ffea58b4000 r-xp [vdso]Cómo se lee:
- Las columnas son el rango virtual, los permisos (
r,w,x, yp= privado) y el archivo de respaldo. Cada rango es un múltiplo de 4 KB (una página). - text no tiene
w(escribir en él daSIGSEGV), y data no tienex: protección por página. - Las direcciones son virtuales y cambian en cada ejecución por ASLR (aleatorización del espacio de direcciones).
[vdso]es código del kernel mapeado en el proceso para hacer syscalls rápidas (gettimeofday) sin cambiar a modo kernel.
El stack en acción (stack.c)
Sección titulada «El stack en acción (stack.c)»main llama a foo, que llama a bar, que llama a bar_is_now_closed, que se detiene en pause(). Cada función imprime la dirección de su variable local:
In function main; &localvar = 0x7ffdb32528d4In function foo; &localvar = 0x7ffdb32528a4In function bar; &localvar = 0x7ffdb3252884In function bar_is_now_closed; &localvar = 0x7ffdb3252864- Las direcciones bajan (…8d4 → …8a4 → …884 → …864): el stack crece hacia direcciones menores, y cada llamada apila un marco (frame) nuevo.
- La diferencia entre marcos (0x30, 0x20, 0x20 bytes) es la variable local más la dirección de retorno, el puntero de marco guardado y la alineación.
Cómo leerlo: cada caja es el marco de una función, con la dirección real de su localvar a la izquierda. main está arriba porque se llamó primero; cada llamada apila un marco debajo, en direcciones menores. A la derecha va cuánto bajó cada marco respecto del anterior.
El laboratorio pide conectarse con gdb attach PID y ejecutar bt. En sistemas con ptrace_scope = 1 (el valor por defecto de Ubuntu), adjuntarse a un proceso que no es hijo requiere sudo. La alternativa es lanzarlo desde GDB y detenerlo en pause:
$ gdb -q ./stack(gdb) break pause(gdb) run(gdb) bt#0 __libc_pause () at ../sysdeps/unix/sysv/linux/pause.c:27#1 0x0000555555555224 in bar_is_now_closed () at stack.c:17#2 0x0000555555555287 in bar () at stack.c:24#3 0x00005555555552ea in foo () at stack.c:31#4 0x0000555555555354 in main (argc=1, argv=0x7fffffffe108) at stack.c:39(gdb) frame 2(gdb) p localvar$1 = 2bt (backtrace) recorre los marcos del stack de arriba hacia abajo. Con frame k te paras en uno y puedes ver sus variables locales. Dentro de GDB las direcciones son «bonitas» (0x5555…, 0x7fff…) porque GDB desactiva ASLR.
memcpy y sus bugs
Sección titulada «memcpy y sus bugs»void *memcpy(void *dest, const void *src, size_t n); // copia n bytes; las zonas no deben solaparseLa idea de la diapositiva 9: «App1 empieza en el offset físico 0x0; copiar de 300 KB a 400 KB». Pero un programa no trabaja con direcciones físicas: en main.c, src = 0x300 * 1024 es solo un número, no un puntero válido del proceso.
malloc, size_t y sus límites
Sección titulada «malloc, size_t y sus límites»void *malloc(size_t size); // size_t = unsigned long en x86-64: 8 bytesmalloc_limits.c prueba casos extremos. Ejecución de prueba (x86-64):
sizeof size_t = 8; max value of the param to malloc = 18446744073709551616ld_num2alloc = -288225969623711744szt_num2alloc = 181585181040858398721. long int used: malloc(-288225969623711744) returns (nil)2. size_t used: malloc(18158518104085839872) returns (nil)3. short int used: malloc(6144) returns 0x625e5b4d22b04. short int used: malloc(-4096) returns (nil)5. size_t used: malloc(18446744073709547520) returns (nil)6. malloc(18446744073709551615) returns (nil)7. malloc(0) returns 0x625e5b4d22b0Lo que hay que entender de cada línea:
- 1 y 2:
28 000 000 × 1 PBdesborda el entero: comolong, da un número negativo; comosize_t, uno gigantesco.mallocrecibe siempre unsize_t, así que el negativo se convierte en un número enorme. Ninguno se puede satisfacer: NULL. - 3 y 4:
shorttiene 16 bits con signo, máximo 32 767.6·1024 = 6144cabe.60·1024 = 61 440no cabe y se convierte en −4096; pasado asize_tes casi : NULL.
Cómo leerlo: los mismos 16 bits significan 61 440 sin signo o −4096 con signo, según cómo se lean. Al pasar a size_t, el −4096 se extiende con unos a la izquierda y queda un número enorme, imposible de reservar.
- 5 y 6:
-1ULes elsize_tmáximo. Imposible: NULL. - 7:
malloc(0)devuelve un puntero válido (no NULL) que no se debe usar, pero sí se puede pasar afree. Fíjate que es la misma dirección que el caso 3: después delfree, el bloque se reutilizó. - Siempre hay que revisar si
mallocdevolvió NULL.
realloc
Sección titulada «realloc»void *realloc(void *ptr, size_t new_size);Agranda o achica un bloque. Puede moverlo a otra dirección (copiando el contenido) si no hay espacio contiguo. Por eso:
p = realloc(p, n); // ✘ si falla devuelve NULL, se pierde p y hay una fugaq = realloc(p, n); // ✔if (q) p = q; else { /* p sigue siendo válido */ }¿De dónde saca malloc la memoria? (diap. 23)
Sección titulada «¿De dónde saca malloc la memoria? (diap. 23)»«El consenso general es que malloc obtiene su memoria del heap. No siempre es así.»
glibc usa dos mecanismos:
- Bloques chicos: el heap, que se agranda moviendo el «break» con
brk/sbrk. - Bloques ≥ 128 KB (
M_MMAP_THRESHOLDpor defecto): un mapeo anónimo propio conmmap, quefreedevuelve al sistema conmunmap.
Ejecución de prueba (mallocmap.c):
fin del heap antes : 0x5d8f3f3e1000malloc(1000) : 0x5d8f3f3e12a0 ← heapmalloc(100 KB) : 0x5d8f3f3e1690 ← heapmalloc(200 KB) : 0x7ca57ffcd010 ← zona de mmap (cerca de las librerías)malloc(64 MB) : 0x7ca57bfcc010 ← zona de mmapfin del heap después : 0x5d8f3f402000Los dos bloques chicos quedan dentro del [heap]. Los de 200 KB y 64 MB aparecen en la zona alta, junto a libc.so.6, en un mapeo anónimo rw-p nuevo. Lo mismo explica por qué en la medición de page faults el comportamiento cambia desde N ≥ 32 páginas (128 KB).
Cómo leerlo: cada flecha une una llamada con el lugar donde quedó su bloque. Las dos de abajo (direcciones 0x5d8f…) caen dentro del heap; las dos de arriba (0x7ca5…), en la zona de mmap junto a libc. El corte es el umbral de 128 KB.
Autoevaluación
0/6 correctas · 0 respondidas📈 ¿Qué tan bien lo dominas?
Elige pensando en lo que hiciste, no en lo que leíste. Para marcar 3 tienes que haber resuelto un ejercicio nuevo sin pistas y haberlo justificado. Si solo lo leíste y sientes que «ya entendí», todavía es un 1.
Fuente ·sem_9/10-OSL-Memory.pdf (26 diap.) · Código del curso sem9/: stack.c, memcpy1.c, memcpy2.c, main.c, malloc.c, malloc_limits.c (ejecuciones de prueba) · Prueba propia: mallocmap.c · man 3 malloc, man 3 memcpy, proc(5)