
Herramienta de análisis de heap para mempool
Nota preliminar: recomendamos usar esto como parte de asatools pero también se puede usar de forma independiente.
libmempool es un script de Python y una herramienta de análisis para GDB
que ayuda en el análisis de las estructuras de datos relacionadas con mempool
que se encuentran dentro de varios heaps en dispositivos Cisco ASA.
Normalmente esta información está incrustada en un chunk de heap, o es una
extensión personalizada de alguna otra estructura de heap común, como una
estructura mstate de dlmalloc.
Cisco usa el término mempool para describir regiones de memoria mapeadas con diversos fines, como asignaciones generales, DMA, etc. Estas regiones suelen contener su propio heap, como dlmalloc. Las rutinas de asignación en estos mempools se hacen típicamente mediante wrappers alrededor del asignador del heap subyacente, y estos wrappers inyectan metadatos específicos del mempool en las asignaciones resultantes. Nos referimos a estos metadatos como una cabecera de mempool o mh para abreviar. De igual manera, nos referiremos a menudo a un mempool como mp. El uso de mh para describir una cabecera de mempool también es consistente con varias cadenas encontradas en los binarios lina de Cisco ASA.
Aunque libmempool puede usarse como una herramienta independiente para analizar algunas cosas relacionadas con las cabeceras de mempool y las estructuras de datos, su mayor valor proviene de ser utilizado como callback desde otras librerías como libdlmalloc o libptmalloc.
Vale la pena señalar que ciertos aspectos del mempool, como los bins utilizados
para rastrear los chunks en uso, están incrustados dentro de una estructura
mstate de dlmalloc 2.8.x y siguen el mismo dimensionado de bins. Esto
significa que casi inevitablemente tendrás que explorar al menos el mstate
que encapsula los datos del mempool usando libdlmalloc.
libmempool ha sido probado con versiones de Cisco ASA de 32 y 64 bits (tanto la serie ASA5500-X como GNS3) que usan dlmalloc2.8 o el asignador basado en ptmalloc2 de glibc. Ha sido probado en numerosas versiones de ASA, incluyendo numerosas ramas 8.x.y y 9.x.y. Sin embargo, es totalmente posible que falle en alguna versión.
Para usar libmempool de forma independiente, simplemente necesitas importar el
archivo libmempool.py en tu proyecto. Esto te permite hacer algunas acciones
limitadas, como registrar el objeto mpcallback, etc. Esto puede ser útil si
estás haciendo análisis fuera de línea de la funcionalidad de heap registrada.
Para importarlo en GDB, el script solo requiere GDB con soporte de Python. Aunque la mayoría de las versiones modernas de GDB han migrado a Python 3, algunas todavía esperan 2.7. El script ha sido probado en ambas, pero principalmente el desarrollo y las pruebas se hacen con Python 3.
(gdb) source libmempool_gdb.py
Separamos la mayor parte de la lógica relacionada con GDB de libmempool.py en libmempool_gdb.py solo para probar la abstracción de cosas y para que puedas usar libmempool.py fácilmente fuera de GDB. Esto probablemente cambiará en el futuro, ya que eventualmente querríamos implementar abstracciones de motor de depuración similares a las que usan otras herramientas de análisis de heap como libheap y shadow.
Aunque gran parte del valor de libmempool proviene de la función de callback
mpcallback que expone, hay una serie de comandos GDB integrados que podemos
ver.
(gdb) mphelp
[libmempool] mempool commands for gdb
[libmempool] mpheader -v -x <addr> : show chunk contents (-v for verbose, -x for data dump)
[libmempool] mpbinwalk [-v] [-p <addr>] <sz> : walk an mpbin and operate on each chunk in a bin
[libmempool] mpbin <addr> : determine to which bin an mp_header is associated to
[libmempool] mpmstate <addr> : display and cache a mempool mstate address
[libmempool] mphelp
Suponiendo que conocemos la dirección de alguna cabecera de mempool, podemos analizar sus datos. Ten en cuenta que debe ser la dirección de la propia cabecera de mempool, y no la dirección de los metadatos del chunk del asignador central. Así, podemos volcar el contenido de la siguiente manera:
(gdb) mpheader 0x7fffbc1c1ca0
struct mp_header @ 0x7fffbc1c1ca0 {
mh_magic = 0xa11c0123
mh_len = 0x3
mh_refcount = 0x10000
mh_unused = 0x0
mh_fd_link = 0x7fffbc1c19e0 (OK)
mh_bk_link = 0x7ffff7ff7540 (-)
alloc_pc = 0x55555849e260 (-)
free_pc = 0x0 (-)
}
También podemos volcar el contenido hexadecimal del chunk usando -x.
(gdb) mpheader -x 0x7fffbc1c1ca0
struct mp_header @ 0x7fffbc1c1ca0 {
mh_magic = 0xa11c0123
mh_len = 0x3
mh_refcount = 0x10000
mh_unused = 0x0
mh_fd_link = 0x7fffbc1c19e0 (OK)
mh_bk_link = 0x7ffff7ff7540 (-)
alloc_pc = 0x55555849e260 (-)
free_pc = 0x0 (-)
0x3 bytes of chunk data:
0x7fffbc1c1cd0: 0x55 0x04 0x03
}
Los mempools tienen el concepto de bins, que son listas doblemente enlazadas
con el mismo tamaño que los bins de dlmalloc, pero que se usan para rastrear
chunks en uso en lugar de chunks libres. Esto se hace para llevar la
contabilidad del uso de memoria en los dispositivos Cisco. A menudo puedes
encontrar un chunk que contiene una cabecera de mempool, pero aún no sabes
dónde vive la estructura mstate en el heap. Para orientarte, puedes usar el
comando mpbin, que te dará la dirección del bin de mempool en el que vive
actualmente un chunk en uso. Por ejemplo:
(gdb) mpbin 0x7fffbc1c1ca0
[libmempool] Found bin start at 0x7ffff7ff7540
[libmempool] Cached new mp_mstate @ 0x7ffff7ff73c0
[libmempool] mp_smallbin[08] - sz: 0x00000040 cnt: 0x00d3, mh_fd_link: 0x7fffbc1c1ca0
Esto no solo encontró el inicio de la porción de mempool del mstate, sino que
también lo almacenó en caché, y lista el bin específico en el que existe el
chunk. Opcionalmente podríamos usar el comando mpbinwalk para listar todos
los 0xd3 chunks de este bin, o solo hasta un número específico:
(gdb) mpbinwalk 0x40
[libmempool] mp_header @ 0x7ffff7ff7540 - mh_len: 0x00000000, alloc_pc: 0x00000000 [BIN HEAD]
[libmempool] mp_header @ 0x7fffbc1c1ca0 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1c19e0 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1c1750 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1bffa0 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[libmempool] mp_header @ 0x7fffbc1bff60 - mh_len: 0x00000003, alloc_pc: 0x5555584a1288
[libmempool] mp_header @ 0x7fffbc1c0050 - mh_len: 0x00000003, alloc_pc: 0x55555849e260
[...]
Ahora, si queremos volcar toda la porción de mempool de la estructura mstate
(es decir, todos los bins y estadísticas relacionadas), podemos usar el comando
mpmstate: