
Ferramenta de análise de heap para mempool
Nota preliminar: recomendamos que você o use como parte do asatools mas ele também pode ser usado de forma independente.
libmempool é um script Python e uma ferramenta de análise para GDB que auxilia na análise
de estruturas de dados relacionadas a mempool encontradas em vários heaps em dispositivos
Cisco ASA. Normalmente essas informações estão embutidas em um chunk do heap, ou são uma
extensão personalizada de alguma outra estrutura comum de heap, como uma estrutura
mstate do dlmalloc.
A Cisco usa o termo mempool para descrever regiões de memória mapeadas para diversos propósitos, como alocações gerais, DMA, etc. Essas regiões normalmente contêm seu próprio heap, como dlmalloc. Rotinas de alocação nesses mempools são tipicamente feitas usando wrappers em torno do alocador de heap subjacente, e esses wrappers injetam metadados específicos de mempool nas alocações resultantes. Nós nos referimos a esses metadados como cabeçalho de mempool ou mh, abreviadamente. Da mesma forma, com frequência nos referimos a um mempool como mp. O uso de mh para descrever um cabeçalho de mempool também é consistente com várias strings encontradas nos binários lina do Cisco ASA.
Embora o libmempool possa ser usado como uma ferramenta autônoma para analisar algumas coisas relacionadas a cabeçalhos e estruturas de dados de mempool, seu maior valor vem de ser usado como callback de outras bibliotecas, como libdlmalloc ou libptmalloc.
Vale notar que certos aspectos do mempool, como os bins usados para rastrear chunks em uso, estão embutidos dentro de uma estrutura mstate do dlmalloc 2.8.x e seguem o mesmo dimensionamento de bins. Isso significa que, quase inevitavelmente, você pode precisar examinar pelo menos o mstate que encapsula os dados do mempool usando o libdlmalloc.
O libmempool foi testado com versões 32-bit / 64-bit do Cisco ASA (tanto as séries ASA5500-X quanto GNS3) que usam dlmalloc2.8 ou o alocador baseado em ptmalloc2 da glibc. Foi testado em várias versões do ASA, incluindo diversos ramos 8.x.y e 9.x.y. No entanto, é inteiramente possível que ele quebre em alguma versão.
Para usar o libmempool de forma autônoma, basta importar o arquivo libmempool.py
para o seu projeto. Isso permite que você execute algumas ações limitadas, como
registrar o objeto mpcallback, etc. Isso pode ser útil se você estiver fazendo
análise offline da funcionalidade de heap registrada em log.
Para importar no GDB, o script apenas requer GDB com suporte a Python. Embora a maioria das versões modernas do GDB tenha migrado para Python 3, algumas ainda esperam 2.7. O script foi testado em ambos, mas o desenvolvimento e os testes são feitos principalmente com Python 3.
(gdb) source libmempool_gdb.py
Separamos a maior parte da lógica relacionada ao GDB do libmempool.py no libmempool_gdb.py apenas para testar a abstração das coisas e para que você possa usar facilmente o libmempool.py fora do GDB. Isso provavelmente mudará no futuro, pois eventualmente queremos implementar abstrações de engine de debug semelhantes às usadas por outras ferramentas de análise de heap, como libheap e shadow.
Embora grande parte do valor do libmempool venha da função de callback mpcallback que ele expõe, há vários comandos GDB integrados que podemos analisar.
(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
Supondo que saibamos o endereço de algum cabeçalho de mempool, podemos analisar seus dados. Observe que este deve ser o endereço do próprio cabeçalho de mempool, e não o endereço dos metadados do chunk do alocador principal. Assim, podemos despejar o conteúdo da seguinte forma:
(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 (-)
Também podemos despejar o conteúdo hexadecimal do 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
Os mempools têm o conceito de bins, que são listas duplamente encadeadas com o mesmo
dimensionamento dos bins do dlmalloc, mas que são usadas para rastrear chunks em uso em vez de
chunks livres. Isso é feito para fins de contabilidade de uso de memória em dispositivos Cisco.
Muitas vezes você pode encontrar um chunk que contém um cabeçalho de mempool, mas
ainda não sabe onde a estrutura mstate está no heap. Para se orientar,
você pode usar o comando mpbin, que informa o endereço do
bin do mempool em que um chunk em uso reside atualmente. Por exemplo:
(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
Isso não apenas encontrou o início da porção de mempool do mstate, mas também o
armazenou em cache e lista o bin específico em que o chunk existe. Opcionalmente,
poderíamos usar o comando mpbinwalk para listar todos os 0xd3 chunks deste bin,
ou apenas até um 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
[...]
Agora, se quisermos despejar toda a porção de mempool da estrutura mstate (ou seja,
todos os bins e estatísticas relacionadas), podemos usar o comando mpmstate: