├── LICENSE ├── README.md ├── TODO.md ├── __init__.py ├── libdlmalloc_26x.py ├── libdlmalloc_28x.py ├── libdlmalloc_gdb.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, NCC Group 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libdlmalloc 2 | 3 | **libdlmalloc** is a python script designed for use with GDB that can be used to 4 | analyse the Doug Lea's allocator, aka dlmalloc. It currently supports dlmalloc 5 | 2.8.x versions. Note that some parts can also be used independently GDB, for 6 | instance to do offline analysis of some snapshotted heap memory. 7 | 8 | libdlmalloc was inspired by other gdb python scripts for analyzing heaps like 9 | [libtalloc](https://github.com/nccgroup/libtalloc), 10 | [unmask_jemalloc](https://github.com/argp/unmask_jemalloc) and 11 | [libheap](https://github.com/cloudburst/libheap). Some basic functionality is 12 | almost identical to these projects. 13 | 14 | ## Supported versions 15 | 16 | libdlmalloc has been tested predominately on 32-bit and 64-bit Cisco ASA 17 | devices which use dlmalloc 2.8.3. It should work with other 2.8.x versions, 18 | however due to significant differences it will not work on earlier releases, 19 | such as <= 2.7.x. 20 | 21 | If you successfully test libdlmalloc on some specific 2.8.x release or some 22 | specific device, please let the authors know and we will update the documents. 23 | 24 | ## Installation 25 | 26 | The script just requires a relatively modern version of GDB with python3 27 | support. We have primarily tested on python3, so we expect it will break on 28 | python2.7 atm. 29 | 30 | If you want to use the gdb commands you can use: 31 | 32 | ``` 33 | (gdb) source libdlmalloc_28x.py 34 | ``` 35 | 36 | A bunch of the core logic is broken out into the `dl_helper` class, which allows 37 | you to directly import libdlmalloc and access certain important structures 38 | outside of a GDB session. This is useful if you want to analyze offline 39 | chunk/heap snapshots. 40 | 41 | # Usage 42 | 43 | Most of the functionality is modelled after the approach in unmask_jemalloc and 44 | libtalloc where a separate GDB command is provided. Though we do also use a 45 | fair number of switches. 46 | 47 | To see a full list of currently supported commands you can use the `dlhelp` 48 | command: 49 | 50 | ## dlhelp 51 | 52 | This is the main function to view the available commands. Each of the commands 53 | supports the `-h` option which allows you to obtain more detailed usage 54 | instructions. 55 | 56 | ``` 57 | (gdb) dlhelp 58 | [libdlmalloc] dlmalloc commands for gdb 59 | [libdlmalloc] dlchunk : show one or more chunks metadata and contents 60 | [libdlmalloc] dlmstate : print mstate structure information. caches address after first use 61 | [libdlmalloc] dlcallback : register a callback or query/modify callback status 62 | [libdlmalloc] dlhelp : this help message 63 | [libdlmalloc] NOTE: Pass -h to any of these commands for more extensive usage. Eg: dlchunk -h 64 | ``` 65 | 66 | ## Chunk analysis 67 | 68 | `dlchunk` can provide you with a summary of a chunk, or more verbose 69 | information of every field. You can also use it to list information about 70 | multiple chunks, search chunks, etc. Usage for dlchunk can be seen below: 71 | 72 | ``` 73 | (gdb) dlchunk -h 74 | [libdlmalloc] usage: dlchunk [-v] [-f] [-x] [-c ] 75 | [libdlmalloc] a dlmalloc chunk header 76 | [libdlmalloc] -v use verbose output (multiples for more verbosity) 77 | [libdlmalloc] -f use explicitly, rather than be smart 78 | [libdlmalloc] -x hexdump the chunk contents 79 | [libdlmalloc] -m max bytes to dump with -x 80 | [libdlmalloc] -c number of chunks to print 81 | [libdlmalloc] -s search pattern when print chunks 82 | [libdlmalloc] --depth depth to search inside chunk 83 | [libdlmalloc] -d debug and force printing stuff 84 | [libdlmalloc] Flag legend: C=CINUSE, P=PINUSE 85 | ``` 86 | 87 | Basic output looks like this: 88 | 89 | ``` 90 | (gdb) dlchunk 0xacff59d0 91 | 0xacff59d0 M sz:0x000f8 fl:CP 92 | ``` 93 | 94 | As you can see you want to give it the address of the actual dlmalloc metadata 95 | itself. To get more verbose output you can use `-v`. 96 | 97 | ``` 98 | (gdb) dlchunk -v 0xacff59d0 99 | struct malloc_chunk @ 0xacff59d0 { 100 | prev_foot = 0x8140d4d0 101 | size = 0xf8 (CINUSE|PINUSE) 102 | ``` 103 | 104 | You can also list multiple adjacent chunks by using the `-c ` switch. 105 | 106 | ``` 107 | (gdb) dlchunk -c 2 0xacff59d0 108 | 0xacff59d0 M sz:0x000f8 fl:CP 109 | 0xacff5ac8 M sz:0x00270 fl:CP 110 | (gdb) dlchunk -v -c 2 0xacff59d0 111 | struct malloc_chunk @ 0xacff59d0 { 112 | prev_foot = 0x8140d4d0 113 | size = 0xf8 (CINUSE|PINUSE) 114 | -- 115 | struct malloc_chunk @ 0xacff5ac8 { 116 | prev_foot = 0x8140d4d0 117 | size = 0x270 (CINUSE|PINUSE) 118 | ``` 119 | 120 | You can dump the hex contents of a chunk with `-x` and control how many bytes 121 | you want to dump with `-m`. 122 | 123 | 124 | ``` 125 | (gdb) dlchunk -v -x -m 16 -c 2 0xacff59d0 126 | struct malloc_chunk @ 0xacff59d0 { 127 | prev_foot = 0x8140d4d0 128 | size = 0xf8 (CINUSE|PINUSE) 129 | 0x10 bytes of chunk data: 130 | 0xacff59d8: 0xa11c0123 0x000000cc 0x00000000 0x00000000 131 | -- 132 | struct malloc_chunk @ 0xacff5ac8 { 133 | prev_foot = 0x8140d4d0 134 | size = 0x270 (CINUSE|PINUSE) 135 | 0x10 bytes of chunk data: 136 | 0xacff5ad0: 0xa11c0123 0x00000244 0x00000000 0x00000000 137 | ``` 138 | 139 | You can also search inside the chunks. Let's search 2 chunks for the value 140 | `0x00000244`, which we see above is only in the second chunk. 141 | 142 | ``` 143 | (gdb) dlchunk -s 0x00000244 -c 2 0xacff59d0 144 | 0xacff59d0 M sz:0x000f8 fl:CP [NO MATCH] 145 | 0xacff5ac8 M sz:0x00270 fl:CP [MATCH] 146 | ``` 147 | 148 | All matches inside the number of chunks searched will be shown. Let's search 149 | for `0xa11c01123` which we saw above is present in both chunks: 150 | 151 | ``` 152 | (gdb) dlchunk -s 0xa11c0123 -c 2 0xacff59d0 153 | 0xacff59d0 M sz:0x000f8 fl:CP [MATCH] 154 | 0xacff5ac8 M sz:0x00270 fl:CP [MATCH] 155 | ``` 156 | 157 | ## dlmstate 158 | 159 | The dlmstate command can be used for analyzing the `mstate` structure used to 160 | manage a discrete dlmalloc heap (aka mspace if compiled with `MSPACES`). You 161 | can see the usage of the command with the `-h` switch. 162 | 163 | 164 | ``` 165 | (gdb) dlmstate -h 166 | [libdlmalloc] usage: dlmstate [-v] [-f] [-x] [-c ] 167 | [libdlmalloc] a mstate struct addr. Optional if mstate cached 168 | [libdlmalloc] -v use verbose output (multiples for more verbosity) 169 | [libdlmalloc] -c print bin counts 170 | [libdlmalloc] --depth how deep to count each bin (default 10) 171 | [libdlmalloc] NOTE: Last defined mstate will be cached for future use 172 | ``` 173 | 174 | If you know the address holding the mstate, which is usually the first chunk 175 | inside of the first malloc asegment, you can pass it to dlmstate: 176 | 177 | ``` 178 | (gdb) dlmstate 0xa8400008 179 | struct dl_mstate @ 0xa8400008 { 180 | smallmap = 0b000000000000010000011111111100 181 | treemap = 0b000000000000000000000000000111 182 | dvsize = 0x0 183 | topsize = 0x2ebdf040 184 | least_addr = 0xa8400000 185 | dv = 0x0 186 | top = 0xad020f90 187 | trim_check = 0x200000 188 | magic = 0x2900d4d8 189 | smallbin[00] (sz 0x0) = 0xa840002c, 0xa840002c [EMPTY] 190 | smallbin[01] (sz 0x8) = 0xa8400034, 0xa8400034 [EMPTY] 191 | smallbin[02] (sz 0x10) = 0xacbf7ad0, 0xa88647f0 192 | smallbin[03] (sz 0x18) = 0xa95059b8, 0xa9689a20 193 | smallbin[04] (sz 0x20) = 0xac79a028, 0xa87206f8 194 | smallbin[05] (sz 0x28) = 0xacff0120, 0xa948a0f8 195 | smallbin[06] (sz 0x30) = 0xac4e4af8, 0xacb56878 196 | smallbin[07] (sz 0x38) = 0xacfe3880, 0xacfe0df0 197 | smallbin[08] (sz 0x40) = 0xa9509b28, 0xa9509b28 198 | smallbin[09] (sz 0x48) = 0xa8a1dc80, 0xa8a1dc80 199 | smallbin[10] (sz 0x50) = 0xac782cb0, 0xac782cb0 200 | smallbin[11] (sz 0x58) = 0xacbf7a88, 0xacbf7a88 [EMPTY] 201 | smallbin[12] (sz 0x60) = 0xac782c00, 0xac782c00 [EMPTY] 202 | smallbin[13] (sz 0x68) = 0xacbf7a78, 0xacbf7a78 [EMPTY] 203 | smallbin[14] (sz 0x70) = 0xa89b9650, 0xa89b9650 [EMPTY] 204 | smallbin[15] (sz 0x78) = 0xac789828, 0xac789828 [EMPTY] 205 | smallbin[16] (sz 0x80) = 0xa89b9738, 0xa94af740 206 | smallbin[17] (sz 0x88) = 0xac4e5700, 0xac4e5700 [EMPTY] 207 | smallbin[18] (sz 0x90) = 0xac788030, 0xac788030 [EMPTY] 208 | smallbin[19] (sz 0x98) = 0xac782bc8, 0xac782bc8 [EMPTY] 209 | smallbin[20] (sz 0xa0) = 0xa89b9718, 0xa89b9718 [EMPTY] 210 | smallbin[21] (sz 0xa8) = 0xa8a1dc20, 0xa8a1dc20 [EMPTY] 211 | smallbin[22] (sz 0xb0) = 0xac782af8, 0xac782af8 [EMPTY] 212 | smallbin[23] (sz 0xb8) = 0xac789ed0, 0xac789ed0 [EMPTY] 213 | smallbin[24] (sz 0xc0) = 0xacbf7a20, 0xacbf7a20 [EMPTY] 214 | smallbin[25] (sz 0xc8) = 0xac789940, 0xac789940 [EMPTY] 215 | smallbin[26] (sz 0xd0) = 0xac789eb8, 0xac789eb8 [EMPTY] 216 | smallbin[27] (sz 0xd8) = 0xa94af6e8, 0xa94af6e8 [EMPTY] 217 | smallbin[28] (sz 0xe0) = 0xacbf78e8, 0xacbf78e8 [EMPTY] 218 | smallbin[29] (sz 0xe8) = 0xac4e4e68, 0xac4e4e68 [EMPTY] 219 | smallbin[30] (sz 0xf0) = 0xac4e5780, 0xac4e5780 [EMPTY] 220 | smallbin[31] (sz 0xf8) = 0xac7880b0, 0xac7880b0 [EMPTY] 221 | treebin[00] (sz 0x180) = 0xac783cb0 222 | treebin[01] (sz 0x200) = 0xac789dc0 223 | treebin[02] (sz 0x300) = 0xa883db48 224 | treebin[03] (sz 0x400) = 0x0 [EMPTY] 225 | treebin[04] (sz 0x600) = 0x0 [EMPTY] 226 | treebin[05] (sz 0x800) = 0x0 [EMPTY] 227 | treebin[06] (sz 0xc00) = 0x0 [EMPTY] 228 | treebin[07] (sz 0x1000) = 0x0 [EMPTY] 229 | treebin[08] (sz 0x1800) = 0x0 [EMPTY] 230 | treebin[09] (sz 0x2000) = 0x0 [EMPTY] 231 | treebin[10] (sz 0x3000) = 0x0 [EMPTY] 232 | treebin[11] (sz 0x4000) = 0x0 [EMPTY] 233 | treebin[12] (sz 0x6000) = 0x0 [EMPTY] 234 | treebin[13] (sz 0x8000) = 0x0 [EMPTY] 235 | treebin[14] (sz 0xc000) = 0x0 [EMPTY] 236 | treebin[15] (sz 0x10000) = 0x0 [EMPTY] 237 | treebin[16] (sz 0x18000) = 0x0 [EMPTY] 238 | treebin[17] (sz 0x20000) = 0x0 [EMPTY] 239 | treebin[18] (sz 0x30000) = 0x0 [EMPTY] 240 | treebin[19] (sz 0x40000) = 0x0 [EMPTY] 241 | treebin[20] (sz 0x60000) = 0x0 [EMPTY] 242 | treebin[21] (sz 0x80000) = 0x0 [EMPTY] 243 | treebin[22] (sz 0xc0000) = 0x0 [EMPTY] 244 | treebin[23] (sz 0x100000) = 0x0 [EMPTY] 245 | treebin[24] (sz 0x180000) = 0x0 [EMPTY] 246 | treebin[25] (sz 0x200000) = 0x0 [EMPTY] 247 | treebin[26] (sz 0x300000) = 0x0 [EMPTY] 248 | treebin[27] (sz 0x400000) = 0x0 [EMPTY] 249 | treebin[28] (sz 0x600000) = 0x0 [EMPTY] 250 | treebin[29] (sz 0x800000) = 0x0 [EMPTY] 251 | treebin[30] (sz 0xc00000) = 0x0 [EMPTY] 252 | treebin[31] (sz 0xffffffff) = 0x0 [EMPTY] 253 | footprint = 0x33800000 254 | max_footprint = 0x33800000 255 | mflags = 0x7 256 | mutex = 0x0,0x0,0x0,0x0,0xa8400000, 257 | seg = struct malloc_segment @ 0xa84001d4 { 258 | base = 0xa8400000 259 | size = 0x33800000 260 | next = 0x0 261 | sflags = 0x8 262 | ``` 263 | 264 | To speed up output on slower devices we cache the last mstate data that we 265 | read. So if you just run dlmstate again, you will see the previously dumped 266 | output (which of course could be stale). 267 | 268 | ``` 269 | (gdb) dlmstate 270 | [libdlmalloc] Using cached mstate 271 | struct dl_mstate @ 0xa8400008 { 272 | smallmap = 0b000000000000010000011111111100 273 | treemap = 0b000000000000000000000000000111 274 | dvsize = 0x0 275 | topsize = 0x2ebdf040 276 | least_addr = 0xa8400000 277 | dv = 0x0 278 | top = 0xad020f90 279 | trim_check = 0x200000 280 | magic = 0x2900d4d8 281 | smallbin[00] (sz 0x0) = 0xa840002c, 0xa840002c [EMPTY] 282 | smallbin[01] (sz 0x8) = 0xa8400034, 0xa8400034 [EMPTY] 283 | smallbin[02] (sz 0x10) = 0xacbf7ad0, 0xa88647f0 284 | smallbin[03] (sz 0x18) = 0xa95059b8, 0xa9689a20 285 | [...] 286 | ``` 287 | 288 | The `-c` switch can be used to count the number of chunks in a given bin. Note 289 | that this can be quite slow if you're debugging over a serial line, so we also 290 | provide the `--depth` option to limit how many bin entries will be counted. By 291 | default the depth is set to 10: 292 | 293 | ``` 294 | (gdb) dlmstate -c 295 | [libdlmalloc] Using cached mstate 296 | 297 | smallbin[00] (sz 0x0) = 0xa840002c, 0xa840002c [EMPTY] 298 | smallbin[01] (sz 0x8) = 0xa8400034, 0xa8400034 [EMPTY] 299 | smallbin[02] (sz 0x10) = 0xacbf7ad0, 0xa88647f0 [10+] 300 | smallbin[03] (sz 0x18) = 0xa95059b8, 0xa9689a20 [10+] 301 | smallbin[04] (sz 0x20) = 0xac79a028, 0xa87206f8 [10+] 302 | smallbin[05] (sz 0x28) = 0xacff0120, 0xa948a0f8 [10+] 303 | smallbin[06] (sz 0x30) = 0xac4e4af8, 0xacb56878 [10+] 304 | smallbin[07] (sz 0x38) = 0xacfe3880, 0xacfe0df0 [10] 305 | smallbin[08] (sz 0x40) = 0xa9509b28, 0xa9509b28 [2] 306 | smallbin[09] (sz 0x48) = 0xa8a1dc80, 0xa8a1dc80 [2] 307 | smallbin[10] (sz 0x50) = 0xac782cb0, 0xac782cb0 [2] 308 | [...] 309 | ``` 310 | 311 | As shown the count is displayed in brackets to the right of the bin contents. 312 | We use the mstate bitmap to first test if a bin is empty, so you can expect to 313 | occasionally see bin entries that have valid pointers that to the heap, but 314 | that are marked `[EMPTY]`. These pointers are just stale at this point. 315 | 316 | ## dlcallback 317 | 318 | We support the concept of inter-plugin callbacks. You can register a callback 319 | function in a specified module and that function will be called with a dict 320 | holding a bunch of information about the state of whatever is being looked at. 321 | The callback is called when both dlchunk and dlmstate area finished doing 322 | whatever they do with their arguments. 323 | 324 | The usage can be seen with the `-h` switch: 325 | 326 | ``` 327 | (gdb) dlcallback -h 328 | [libdlmalloc] usage: dlcallback