├── .gitignore ├── README.md └── libtalloc.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | # Precompiled Headers 7 | *.gch 8 | *.pch 9 | # Libraries 10 | *.lib 11 | *.a 12 | *.la 13 | *.lo 14 | # Shared objects (inc. Windows DLLs) 15 | *.dll 16 | *.so 17 | *.so.* 18 | *.dylib 19 | # Executables 20 | *.exe 21 | *.out 22 | *.app 23 | *.i*86 24 | *.x86_64 25 | *.hex 26 | 27 | *.order 28 | *.mod.c 29 | *.symvers 30 | 31 | # VIM swap 32 | *.swp 33 | *.swo 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libtalloc 2 | 3 | libtalloc is a python script for use with GDB that can be used to analyse the 4 | "trivial allocator" (talloc). An introduction is about talloc can be found here: 5 | 6 | https://talloc.samba.org/talloc/doc/html/index.html 7 | 8 | libtalloc was inspired by other gdb python scripts for analyzing heaps like 9 | unmask_jemalloc and libheap. Some basic functionality is identical to these 10 | projects. 11 | 12 | https://github.com/cloudburst/libheap 13 | 14 | https://github.com/argp/unmask_jemalloc 15 | 16 | Please note that I am no python guru and the code quality reflects this. If you 17 | see something that disgusts you, feel free to send a patch or give me some 18 | suggestions. All feedback is welcome. 19 | 20 | # Testing 21 | 22 | libtalloc has been tested on a variety of 2.x releases of talloc and supports 23 | dynamic version detection in order to try to overcome various structural 24 | differences across the versions. It has been tested on 32-bit and 64-bit, 25 | however not exhaustively, so don't be surprised if it breaks from time to time. 26 | 27 | It has been tested to some degree on x86 and x64: 28 | * 2.0.7 29 | * 2.0.8 30 | * 2.1.0 31 | * 2.1.1 32 | 33 | If you test it on another version, please let me know if it worked, or what 34 | broke and I will try to update it and/or the docs accordingly. 35 | 36 | # Installation 37 | 38 | The script just requires a relatively modern version of GDB with python support. 39 | 40 | Some LTS distros, like Ubuntu 12.04, still use GDB with python 2.7, whereas 41 | newer versions like 14.04 use python 3.0. I tried to make this script work with 42 | both, so you should only need to: 43 | 44 | (gdb) source libtalloc.py 45 | 46 | # Usage 47 | 48 | Most of the functionality is modeled after the approach by unmask_jemalloc, 49 | where a separate GDB command is provided rather than a complex set of switches. 50 | 51 | A number of methods specifically designed to mimic the talloc library C 52 | functions are available, to help people trying to extend libtalloc if they're 53 | already familiar with the library. 54 | 55 | To see a full list of commands you can issue the tchelp command: 56 | 57 | (gdb) tchelp 58 | [libtalloc] talloc commands for gdb 59 | [libtalloc] tcchunk -v -x : show chunk contents (-v for verbose, -x for data dump) 60 | [libtalloc] tcvalidate -a : validate chunk (-a for whole heap) 61 | [libtalloc] tcsearch : search heap for hex value or address 62 | [libtalloc] tcwalk : walk whole heap calling func on every chunk 63 | [libtalloc] tcreport : give talloc_report_full() info on memory context 64 | [libtalloc] tcdump -s : dump chunks linked to memory context (-s for sorted by addr) 65 | [libtalloc] tcparents : show all parents of chunk 66 | [libtalloc] tcchildren : show all children of chunk 67 | [libtalloc] tcinfo : show information known about heap 68 | [libtalloc] tcprobe : try to collect information about talloc version 69 | [libtalloc] tchelp : this help message 70 | 71 | ## Dynamic Version Probing 72 | 73 | One of the most important commands is tcprobe. It needs to be run in order to 74 | figure out what version of talloc is actually installed. The structure layouts 75 | for different versions can vary significantly, so in order for most functions to 76 | work the version must be known. 77 | 78 | If the command works, it should tell you the detected version: 79 | 80 | (gdb) tcprobe 81 | Version: 2.1.1 82 | File: /usr/lib/libtalloc.so.2.1.1 83 | 84 | ## Meta information 85 | 86 | The tcinfo command is meant to show as much information collected about the heap 87 | as possible, such as the information from tcprobe, the null_context structure if 88 | it was found, and more. Atm it only shows the version and if the null_context is 89 | set. The null_context is required for most of the functions that walk the actual 90 | heirarchy, and in order to find it most other functionality, like tchunk, etc 91 | will try to auto-find it. 92 | 93 | After tcprobe is run but before tchunk is actually used: 94 | 95 | (gdb) tcinfo 96 | [libtalloc] null_context not yet found yet 97 | [libtalloc] Version: 2.0.7 98 | [libtalloc] File: /usr/lib/i386-linux-gnu/libtalloc.so.2.0.7 99 | 100 | Then after analyzing a chunk, like so: 101 | 102 | (gdb) tcchunk 0xb94a52b0 103 | WARNING: 0xb94a52b0 not a talloc_chunk. Assuming ptr to chunk data 104 | 0xb94a5280 sz:0x0000003c, flags:...., name:struct tevent_context 105 | 106 | You can confirm that it was found after the fact using tcinfo. 107 | 108 | (gdb) tcinfo 109 | [libtalloc] null_context: 0xb94a5028 110 | [libtalloc] Version: 2.0.7 111 | [libtalloc] File: /usr/lib/i386-linux-gnu/libtalloc.so.2.0.7 112 | 113 | Now that the null_context is set you could run other commands that would 114 | normally complain that it wasn't set, like the tcsearch command. 115 | 116 | ## Chunk analysis 117 | 118 | tcchunk can provide you with a summary of the chunk, a more verbose output of 119 | every field, or extremely verbose information about every surrounding chunk. 120 | 121 | NOTE: One important think to note about tcchunk is that internally it uses the 122 | tc_chunk() method, which attempts to correct errors made when passing in the 123 | chunk address. Specifically if you pass in the address of the chunk data 124 | itself, if it doesn't find the expected talloc magic, it will look for a 125 | legitimate chunk header slightly earlier in memory. This can mess with you in 126 | corrupted scenarios, so always be sure you're passing in the explicit address 127 | unless you're doing cursory analysis. 128 | 129 | Summary output: 130 | 131 | (gdb) tcchunk 0x80a13c88 132 | 0x80a13c88 sz:0x00000020, flags:..p., name:struct netr_ServerPasswordSet 133 | 134 | The following is a legend for chunks within the summary output: 135 | 136 | p - Member of a pool (POOLMEM flag) 137 | P - Chunk is a pool (POOL flag) 138 | F - Chunk is free (FREE flag) 139 | L - Chunk is looped (LOOP flag) 140 | 141 | Verbose output: 142 | 143 | (gdb) tcchunk -v 0x80a13c88 144 | struct talloc_chunk @ 0x80a13c88 { 145 | next = 0x0 146 | prev = 0x80a140c8 147 | parent = 0x0 148 | child = 0x80a14088 149 | refs = 0x0 150 | destructor = 0x0 151 | name = 0x807d9f2f (struct netr_ServerPasswordSet) 152 | size = 0x20 153 | flags = 0xe8150c78 (POOLMEM) 154 | limit = 0x0 155 | pool = 0x80a13248 156 | 157 | ### Validation 158 | 159 | talloc chunks contain some magic values that can be used to validate if they are 160 | sane. The tcvalidate command will analyze a chunk to ensure that the chunk magic 161 | is as expected. Additionally, it analyzes all other pointer members to ensure 162 | they actually fall into memory ranges (as known by gdb), if the size is valid, 163 | etc. 164 | 165 | (gdb) tcvalidate 0x80a13c88 166 | Chunk header is valid 167 | 168 | We'll use a built-in method to modify a value to show how it could fail: 169 | 170 | (gdb) python set_destructor(tc_chunk(0x80a13c88), 0x41414141) 171 | (gdb) tcchunk -v 0x80a13c88 172 | struct talloc_chunk @ 0x80a13c88 { 173 | next = 0x0 174 | prev = 0x80a140c8 175 | parent = 0x0 176 | child = 0x80a14088 177 | refs = 0x0 178 | destructor = 0x41414141 179 | name = 0x807d9f2f (struct netr_ServerPasswordSet) 180 | size = 0x20 181 | flags = 0xe8150c78 (POOLMEM) 182 | limit = 0x0 183 | pool = 0x80a13248 184 | (gdb) tcvalidate 0x80a13c88 185 | Chunk header is invalid: 186 | 0x80a13c88: Chunk has bad destructor pointer 0x41414141 187 | 188 | ### Finding parents 189 | 190 | tcparents can be used to view all parents of the provided chunk: 191 | 192 | (gdb) tcparents 0x80a13c88 193 | 0x809f8300: null_context 194 | 0x80a08660: TALLOC_CTX * 195 | 0x809f8370: talloc_new: ../lib/util/talloc_stack.c:147 196 | 0x809fb680: talloc_new: ../lib/util/talloc_stack.c:147 197 | 0x80a13258: UNNAMED 198 | 0x80a13c58: talloc_new: ../lib/util/talloc_stack.c:147 199 | 0x80a13c88: struct netr_ServerPasswordSet 200 | 201 | ### Finding children 202 | 203 | tchildren can be used to view all children (and grandchildren, etc) of the 204 | provided chunk: 205 | 206 | (gdb) tcchildren 0x80a13c88 207 | 0x80a14088: struct netr_Authenticator 208 | 0x80a14048: librpc/gen_ndr/ndr_netlogon.c:10964 209 | 0x80a14008: librpc/gen_ndr/ndr_netlogon.c:10958 210 | 0x80a13fc8: librpc/gen_ndr/ndr_netlogon.c:10951 211 | 0x80a13f88: lib/charcnv.c:506 212 | 0x80a13ec8: lib/charcnv.c:506 213 | 0x80a13d48: librpc/gen_ndr/ndr_netlogon.c:10913 214 | 0x80a13e08: 215 | 0x80a13cd8: struct ndr_pull 216 | 0x80a13f48: struct ndr_token_list 217 | 0x80a13f08: struct ndr_token_list 218 | 0x80a13e88: struct ndr_token_list 219 | 0x80a13e48: struct ndr_token_list 220 | 0x80a13dc8: struct ndr_token_list 221 | 0x80a13d88: struct ndr_token_list 222 | 223 | ## Pool analysis 224 | 225 | talloc has the concept of pool chunks. These are basically regular talloc chunks 226 | but that are used for allocating new chunks rather than falling back on the 227 | underlying malloc() implementation of the system. A pool chunk has slightly 228 | different headers depending on the version used, sometimes using padding, and 229 | sometimes using a prefix/suffix header. 230 | 231 | tcpool can be used to analyze a pool chunk header, similar to tcchunk: 232 | 233 | # First we find a pool to analyze 234 | (gdb) tcchunk -v 0x80a13c88 235 | struct talloc_chunk @ 0x80a13c88 { 236 | next = 0x0 237 | prev = 0x80a140c8 238 | parent = 0x0 239 | child = 0x80a14088 240 | refs = 0x0 241 | destructor = 0x0 242 | name = 0x807d9f2f (struct netr_ServerPasswordSet) 243 | size = 0x20 244 | flags = 0xe8150c78 (POOLMEM) 245 | limit = 0x0 246 | pool = 0x80a13248 247 | 248 | (gdb) tcpool -v 0x80a13248 249 | struct talloc_pool_hdr @ 0x80a13248 { 250 | end = 0x80a14108 251 | object_count = 0x19 252 | poolsize = 0x2000 253 | struct talloc_chunk @ 0x80a13258 { 254 | next = 0x0 255 | prev = 0x0 256 | parent = 0x809fb680 257 | child = 0x80a13c58 258 | refs = 0x0 259 | destructor = 0x80429aa0 260 | name = 0x0 (UNNAMED) 261 | size = 0x0 262 | flags = 0xe8150c74 (POOL) 263 | limit = 0x0 264 | pool = 0x0 265 | 266 | In the case above the pool had a prefixed talloc_pool_hdr, which is shown. The 267 | -l option can be passed to tcpool to list all of the allocated chunks within a 268 | pool: 269 | 270 | (gdb) tcpool -l 0x80a13248 271 | Pool summary -- objects: 0x19, total size: 0x2000, space left: 0x1180, next free: 0x80a14108 272 | 0x80a13258 sz:0x00000000, flags:.P.., name:UNNAMED 273 | 0x80a13288 sz:0x000007a3, flags:..p., name:char 274 | 0x80a13a68 sz:0x0000005c, flags:..p., name:struct smb_request 275 | 0x80a13af8 sz:0x00000008, flags:..p., name:struct pipe_write_andx_state 276 | 0x80a13b38 sz:0x00000038, flags:..p., name:struct tevent_req 277 | 0x80a13ba8 sz:0x00000028, flags:..p., name:struct tevent_immediate 278 | 0x80a13c08 sz:0x00000014, flags:..p., name:struct np_write_state 279 | 0x80a13c58 sz:0x00000000, flags:..p., name:talloc_new: ../lib/util/talloc_stack.c:147 280 | 0x80a13c88 sz:0x00000020, flags:..p., name:struct netr_ServerPasswordSet 281 | 0x80a13cd8 sz:0x00000038, flags:..p., name:struct ndr_pull 282 | 0x80a13d48 sz:0x00000001, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10913 283 | 0x80a13d88 sz:0x00000010, flags:..p., name:struct ndr_token_list 284 | 0x80a13dc8 sz:0x00000010, flags:..p., name:struct ndr_token_list 285 | 0x80a13e08 sz:0x00000001, flags:..p., name: 286 | 0x80a13e48 sz:0x00000010, flags:..p., name:struct ndr_token_list 287 | 0x80a13e88 sz:0x00000010, flags:..p., name:struct ndr_token_list 288 | 0x80a13ec8 sz:0x00000008, flags:..p., name:lib/charcnv.c:506 289 | 0x80a13f08 sz:0x00000010, flags:..p., name:struct ndr_token_list 290 | 0x80a13f48 sz:0x00000010, flags:..p., name:struct ndr_token_list 291 | 0x80a13f88 sz:0x00000008, flags:..p., name:lib/charcnv.c:506 292 | 0x80a13fc8 sz:0x0000000c, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10951 293 | 0x80a14008 sz:0x00000010, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10958 294 | 0x80a14048 sz:0x0000000c, flags:..p., name:librpc/gen_ndr/ndr_netlogon.c:10964 295 | 0x80a14088 sz:0x0000000c, flags:..p., name:struct netr_Authenticator 296 | 0x80a140c8 sz:0x0000000b, flags:..p., name:/etc/samba 297 | 298 | Note the P flag in the output above, the top chunk being the pool chunk that 299 | holds all of the chunks below. 300 | 301 | ## Heap dumping 302 | 303 | tcdump can be used to dump all chunks in the entire tree. By default they are 304 | shown in a heirarchical order, however the -s option can be used to sort the 305 | output by address. 306 | 307 | (gdb) tcdump -a 0x809f8300 308 | 0x809f8300 sz:0x00000000, flags:...., name:null_context 309 | 0x80a0b3f8 sz:0x0000000c, flags:...., name:struct handle_list 310 | 0x809ff178 sz:0x00000014, flags:...., name:struct security_token 311 | 0x80a089e8 sz:0x00000198, flags:...., name:lib/util_nttoken.c:50 312 | 0x80a07268 sz:0x00000188, flags:...., name:connection_struct 313 | 0x80a08bc8 sz:0x00000020, flags:...., name:struct fd_handle 314 | 0x80a00c30 sz:0x000000f0, flags:...., name:struct files_struct 315 | 0x80a083b8 sz:0x00000008, flags:...., name:struct fake_file_handle 316 | 0x80a08c20 sz:0x0000009c, flags:...., name:struct pipes_struct 317 | 0x80a07900 sz:0x00000760, flags:...., name:uint8_t 318 | 0x80a07428 sz:0x000000c0, flags:...., name:struct auth_serversupplied_info 319 | 0x80a06390 sz:0x00000001, flags:...., name: 320 | 0x80a06350 sz:0x00000007, flags:...., name:nobody 321 | 0x80a06158 sz:0x000000cc, flags:...., name:struct netr_SamInfo3 322 | 0x80a062d8 sz:0x00000044, flags:...., name:struct dom_sid 323 | [SNIP] 324 | 325 | tcreport is a command similar to tcdump but it prettifies the output somewhat 326 | and is meant to mimic the talloc_report_full() debug function provided by the 327 | talloc library itself. 328 | 329 | (gdb) tcreport 0x80a0b3f8 -a 330 | Full talloc report on 'null_context' (total 558651 bytes in 446 blocks) 331 | struct handle_list contains 12 bytes in 1 blocks (ref 67) 0x80a0b3f8 332 | struct security_token contains 428 bytes in 2 blocks (ref 66) 0x809ff178 333 | lib/util_nttoken.c:50 contains 408 bytes in 1 blocks (ref 0) 0x80a089e8 334 | connection_struct contains 531071 bytes in 36 blocks (ref 65) 0x80a07268 335 | struct fd_handle contains 32 bytes in 1 blocks (ref 4) 0x80a08bc8 336 | struct files_struct contains 529681 bytes in 22 blocks (ref 3) 0x80a00c30 337 | struct fake_file_handle contains 529308 bytes in 19 blocks (ref 1) 0x80a083b8 338 | struct pipes_struct contains 529300 bytes in 18 blocks (ref 0) 0x80a08c20 339 | uint8_t contains 1888 bytes in 1 blocks (ref 2) 0x80a07900 340 | struct auth_serversupplied_info contains 934 bytes in 10 blocks (ref 1) 0x80a07428 341 | contains 1 bytes in 1 blocks (ref 4) 0x80a06390 342 | nobody contains 7 bytes in 1 blocks (ref 3) 0x80a06350 343 | struct netr_SamInfo3 contains 290 bytes in 4 blocks (ref 2) 0x80a06158 344 | [SNIP] 345 | 346 | ## Searching 347 | 348 | There are two commands for searching: tcsearch and tcfindaddr. 349 | 350 | tcsearch can be used to find chunks that contain the provided hexadecimal value. 351 | It works by walking the entire tree heirarchy starting from the null_context (if 352 | known), or from a starting chunk provided. 353 | 354 | (gdb) python set_destructor(tc_chunk(0x80a13c88), 0x41414141) 355 | (gdb) tcsearch 0x41414141 0x809f8300 356 | [libtalloc] 0x41414141 found in chunk at 0x80a1d218 357 | [libtalloc] 0x41414141 found in chunk at 0x80a13c88 358 | (gdb) tcchunk -v 0x80a1d218 359 | struct talloc_chunk @ 0x80a1d218 { 360 | next = 0x80a00158 361 | prev = 0x809fb0c8 362 | parent = 0x0 363 | child = 0x0 364 | refs = 0x0 365 | destructor = 0x0 366 | name = 0x8071fcbd (uint8_t) 367 | size = 0x80050 368 | flags = 0xe8150c70 () 369 | limit = 0x0 370 | pool = 0x0 371 | (gdb) tcchunk -x 0x80a1d218 372 | 0x80a1d218 sz:0x00080050, flags:...., name:uint8_t 373 | Chunk data (524368 bytes): 374 | 0x80a1d248: 0x41414141 0x00000000 0x00000000 0x00000000 375 | 0x80a1d258: 0x00000001 0x00000000 0x00000001 0x00020000 376 | 0x80a1d268: 0x00000001 0x00000000 0x00000001 0xaaaa0000 377 | [SNIP] 378 | (gdb) tcchunk -v 0x80a13c88 379 | struct talloc_chunk @ 0x80a13c88 { 380 | next = 0x0 381 | prev = 0x80a140c8 382 | parent = 0x0 383 | child = 0x80a14088 384 | refs = 0x0 385 | destructor = 0x41414141 386 | name = 0x807d9f2f (struct netr_ServerPasswordSet) 387 | size = 0x20 388 | flags = 0xe8150c78 (POOLMEM) 389 | limit = 0x0 390 | pool = 0x80a13248 391 | 392 | tcfindaddr can be used to determine if an address falls within the boundary of a 393 | chunk within the talloc tree. Say you know that 0x80a1d3280 has some data you 394 | control, so you want to see if it falls within a chunk. Note the second address 395 | is the null_context, but can be any chunk that lets us find the top of the heap. 396 | 397 | (gdb) tcfindaddr 0x80a1d328 0x809f8300 398 | [libtalloc] address 0x80a1d328 falls within chunk @ 0x80a1d218 (size 0x80050) 399 | 400 | ## Heap walking 401 | 402 | Some of the tree searching is done using a recursive function that I exposed via 403 | the tcwalk command. It is a helper function that lets you specify a python 404 | method that will be called on every discovered chunk in the tree. 405 | 406 | In the example below we'll call the heap validation method on every chunk in the 407 | heap to see if anything is corrupted. 408 | 409 | (gdb) tcwalk validate_chunk 410 | 0x80a13c88: Chunk has bad destructor pointer 0x41414141 411 | 412 | Note that if null_context hasn't been set you have to pass a chunk address as 413 | the second argument. 414 | 415 | # Contact 416 | 417 | Written by Aaron Adams 418 | 419 | Email: aaron (dot) adams (at) nccgroup (dot) trust 420 | 421 | Twitter: @fidgetingbits 422 | -------------------------------------------------------------------------------- /libtalloc.py: -------------------------------------------------------------------------------- 1 | ### libtalloc.py - GDB talloc analysis plugin 2 | # Written by Aaron Adams 3 | # NCC 2015 4 | ### 5 | # USAGE: 6 | # Run tchelp to see commands 7 | # Always run tcprobe once before other commands 8 | # See README.md for extensive usage 9 | # 10 | # TODO: 11 | # - Add color-coded corrupted chunks when validating the whole heap, instead of 12 | # printing 13 | # - Add more functions that use the heap walk: find all pools, find all pool 14 | # members, find all with size greater than X, etc 15 | # - Add support for the samba-specific stackframe memory contexts 16 | # - Add memlimit support 17 | # - Add pretty printer for talloc_pool_chunk 18 | # - Add an item that shows the amount of data left in a talloc pool, even though 19 | # there isn't an explicit field that holds it. 20 | # - A lot of the chunk reading logic can be put into a superclass that all the 21 | # different chunk objects inherit from 22 | # - If you have pagination on in gdb and prematurely kill the tcreport command 23 | # output, you'll have chunks remaining looped which isn't ideal. Could catch the 24 | # exception and walk the whole heap unsetting the loop flags... 25 | # - Maybe make it auto execute tcprobe on initialization? 26 | ### 27 | 28 | try: 29 | import gdb 30 | except ImportError: 31 | print("Not running inside of GDB, exiting...") 32 | exit() 33 | 34 | import re 35 | import sys 36 | import struct 37 | from os import uname 38 | 39 | # bash color support 40 | color_support = True 41 | if color_support: 42 | c_red = "\033[31m" 43 | c_red_b = "\033[01;31m" 44 | c_green = "\033[32m" 45 | c_green_b = "\033[01;32m" 46 | c_yellow = "\033[33m" 47 | c_yellow_b = "\033[01;33m" 48 | c_blue = "\033[34m" 49 | c_blue_b = "\033[01;34m" 50 | c_purple = "\033[35m" 51 | c_purple_b = "\033[01;35m" 52 | c_teal = "\033[36m" 53 | c_teal_b = "\033[01;36m" 54 | c_none = "\033[0m" 55 | else: 56 | c_red = "" 57 | c_red_b = "" 58 | c_green = "" 59 | c_green_b = "" 60 | c_yellow = "" 61 | c_yellow_b = "" 62 | c_blue = "" 63 | c_blue_b = "" 64 | c_purple = "" 65 | c_purple_b = "" 66 | c_teal = "" 67 | c_teal_b = "" 68 | c_none = "" 69 | c_error = c_red 70 | c_title = c_green_b 71 | c_header = c_yellow_b 72 | c_value = c_blue_b 73 | 74 | _machine = uname()[4] 75 | if _machine == "x86_64": 76 | SIZE_SZ = 8 77 | elif _machine in ("i386", "i686"): 78 | SIZE_SZ = 4 79 | 80 | # We use this as the root of operations on the whole heap. 81 | null_context = None 82 | 83 | TALLOC_ALIGNMENT = 16 84 | TALLOC_ALIGN_MASK = TALLOC_ALIGNMENT - 1 85 | if SIZE_SZ == 4: 86 | TC_SIZE = SIZE_SZ * 10 87 | elif SIZE_SZ == 8: 88 | TC_SIZE = SIZE_SZ * 12 89 | 90 | TC_HDR_SIZE = (TC_SIZE+TALLOC_ALIGN_MASK) & ~TALLOC_ALIGN_MASK 91 | if SIZE_SZ == 4: 92 | TP_SIZE = SIZE_SZ * 3 93 | elif SIZE_SZ == 8: 94 | TP_SIZE = SIZE_SZ * 4 95 | TP_HDR_SIZE = (TP_SIZE+TALLOC_ALIGN_MASK) & ~TALLOC_ALIGN_MASK 96 | 97 | def talloc_chunk_from_ptr(p): 98 | "Ptr to talloc_chunk header" 99 | return (p - TC_HDR_SIZE) 100 | 101 | def ptr_from_talloc_chunk(tc): 102 | "Ptr to chunk data" 103 | return (tc.address + tc.header_size) 104 | 105 | TALLOC_MAGIC_BASE = 0xe814ec70 106 | TALLOC_VERSION_MAJOR = 2 107 | TALLOC_VERSION_MINOR = 0 108 | TALLOC_MAGIC = TALLOC_MAGIC_BASE + (TALLOC_VERSION_MAJOR << 12) \ 109 | + (TALLOC_VERSION_MINOR << 4) 110 | 111 | TALLOC_MAGIC_REFERENCE = 1 112 | MAX_TALLOC_SIZE = 0x10000000 113 | 114 | TALLOC_FLAG_FREE = 1 115 | TALLOC_FLAG_LOOP = 2 116 | TALLOC_FLAG_POOL = 4 117 | TALLOC_FLAG_POOLMEM = 8 118 | TALLOC_FLAG_MASK = ~0xF 119 | 120 | # version found dynamically by tcprobe and friends. heavily relied on for proper 121 | # structure versions 122 | talloc_version = None 123 | # File location of library 124 | talloc_path = None 125 | # Checked against to determine if the script is likely to fail or not. 126 | tested_versions = [] 127 | 128 | # Used by a variety of callbacks that can't relay their data back to callers 129 | # easily 130 | chunklist = [] 131 | 132 | def find_talloc_version(): 133 | "tries to find the talloc version so we can make sure the structures are setup sanely" 134 | 135 | global talloc_version 136 | global talloc_path 137 | 138 | output = gdb.execute("info proc mappings", False, True) 139 | start = output.find("0x") 140 | output = output[start-2:] 141 | found = 0 142 | # [1:] is to skip a blank entry that is at the start 143 | for entry in output.split("\n")[1:]: 144 | if entry.find("talloc") != -1: 145 | found = 1 146 | break 147 | 148 | if found == 0: 149 | print(c_error + "Couldn't find talloc shared library" + c_none) 150 | return 151 | 152 | talloc_lib = entry.split() 153 | talloc_path = talloc_lib[4] 154 | results = gdb.execute("find %s, %s, 'T', 'A', 'L', 'L', 'O', 'C', '_'" % (talloc_lib[0], talloc_lib[1]), False, True) 155 | for version in results.split("\n"): 156 | if version.find("0x") != -1: 157 | ver = read_string(int(version, 16)) 158 | # Weed out anything that's not TALLOC_x.y.z 159 | if ver.find('.') != -1: 160 | idx = ver.find("_") 161 | if idx == -1: 162 | print(c_error + "Version string found seems wrong: %s" % (ver) + c_none) 163 | ver = ver[idx+1:] 164 | ver = ver.split('.') 165 | talloc_version = int(ver[0]), int(ver[1]), int(ver[2]) 166 | 167 | def version_eq_or_newer(wanted_version=(0,0,0)): 168 | return version_cmp(wanted_version) >= 0 169 | 170 | def version_eq_or_older(wanted_version=(0,0,0)): 171 | return version_cmp(wanted_version) <= 0 172 | 173 | def version_cmp(w): 174 | global talloc_version 175 | if talloc_version == None: 176 | find_talloc_version() 177 | if talloc_version == None: 178 | return False 179 | t = talloc_version 180 | for i in xrange(len(t)): 181 | if (t[i] != w[i]): 182 | return t[i] - w[i]; 183 | return 0 184 | 185 | def tc_chunk(p, warn=True, fixup=True): 186 | '''Creates a talloc_chunk() object from a given address. Tries to help by 187 | testing for a legitimate header and if not found, tries again a header's 188 | length backwards''' 189 | tc = talloc_chunk(p) 190 | if not fixup: 191 | return tc 192 | old = tc 193 | if bad_magic(tc): 194 | if warn: 195 | print(c_error + "WARNING: 0x%lx not a talloc_chunk. Assuming ptr to chunk data" % p + c_none) 196 | tc = talloc_chunk(talloc_chunk_from_ptr(p)) 197 | if bad_magic(tc): 198 | # Assume they wanted to pass in the first wrong one if both are wrong... 199 | tc = old 200 | if warn: 201 | print(c_error + "WARNING: 0x%lx also not a talloc_chunk. Corrupt chunk?" % talloc_chunk_from_ptr(p) + c_none) 202 | return tc 203 | 204 | def tc_version(flags): 205 | version = (flags & TALLOC_FLAG_MASK) - TALLOC_MAGIC_BASE 206 | major = version >> 12 207 | minor = (version - (major << 12)) >> 4 208 | return (major, minor) 209 | 210 | def annotate_entry(p): 211 | tc = tc_chunk(p) 212 | print(" - name: %s" % talloc_get_name(tc)) 213 | print(" - size: %d (0x%x) bytes" % (tc.size, tc.size)) 214 | print(" - valid: %s" % is_valid_chunk(tc)) 215 | 216 | # TODO 217 | # - Would be nice to resolve destructor symbols 218 | # - Also more info about tc->refs 219 | def annotate_chunk(p): 220 | "verbosely describe a chunk with all flags" 221 | print(c_title + "===========================") 222 | print("Chunk @ 0x%lx" % p.address) 223 | print(" valid: %s" % is_valid_chunk(p)) 224 | print("===========================" + c_none) 225 | print("next = 0x%lx" % p.next_tc) 226 | if p.next_tc: 227 | tc = annotate_entry(p.next_tc) 228 | print("prev = 0x%lx" % p.prev_tc) 229 | if p.prev_tc: 230 | tc = annotate_entry(p.prev_tc) 231 | print("parent = 0x%lx" % p.parent) 232 | if p.parent: 233 | tc = annotate_entry(p.parent) 234 | print("child = 0x%lx" % p.child) 235 | if p.child: 236 | tc = annotate_entry(p.child) 237 | print("refs = 0x%lx" % p.refs) 238 | print("destructor = 0x%lx" % p.destructor) 239 | print("name = 0x%lx" % p.name) 240 | if p.name: 241 | print(" - %s" % talloc_get_name(p)) 242 | print("size = %d (0x%lx) bytes" % (p.size, p.size)) 243 | print("flags = 0x%lx" % p.flags) 244 | if is_free(p): 245 | print(" - TALLOC_FLAG_FREE") 246 | if is_loop(p): 247 | print(" - TALLOC_FLAG_LOOP") 248 | if is_pool(p): 249 | print(" - TALLOC_FLAG_POOL") 250 | if is_pool_member(p): 251 | print(" - TALLOC_FLAG_POOLMEM") 252 | print("pool = 0x%lx" % p.pool) 253 | if p.pool: 254 | tc = annotate_entry(p.pool) 255 | print("===========================") 256 | if is_valid_chunk(p) == False: 257 | print(" Validation output:") 258 | print("===========================") 259 | validate_chunk(p) 260 | 261 | def get_mappings(): 262 | output = gdb.execute("info proc mappings", False, True) 263 | start = output.find("0x") 264 | output = output[start-2:] 265 | address_map = [] 266 | # [1:] is to skip a blank entry that is at the start 267 | for entry in output.split("\n"): 268 | data = re.sub("\s+", " ", entry.lstrip()).split(" ") 269 | if len(data) < 2: 270 | continue 271 | start_addr = data[0] 272 | end_addr = data[1] 273 | address_map.append((start_addr, end_addr)) 274 | return address_map 275 | 276 | def is_valid_addr(p): 277 | asmap = get_mappings() 278 | for entry in asmap: 279 | if (p >= int(entry[0], 16)) and (p < int(entry[1], 16)): 280 | return True 281 | return False 282 | 283 | def is_valid_chunk(p): 284 | return validate_chunk(p, False) 285 | 286 | def validate_chunk(p, talk=True): 287 | valid = True 288 | if bad_magic(p): 289 | valid = False 290 | if talk: 291 | print("0x%lx: Chunk has bad magic 0x%lx" % (p.address, (p.flags & TALLOC_FLAG_MASK))) 292 | if p.next_tc: 293 | if not is_valid_addr(p.next_tc): 294 | valid = False 295 | if talk: 296 | print("0x%lx: Chunk has bad next pointer 0x%lx" % (p.address, p.next_tc)) 297 | if p.prev_tc: 298 | if not is_valid_addr(p.prev_tc): 299 | valid = False 300 | if talk: 301 | print("0x%lx: Chunk has bad prev pointer 0x%lx" % (p.address, p.prev_tc)) 302 | if p.child: 303 | if not is_valid_addr(p.child): 304 | valid = False 305 | if talk: 306 | print("0x%lx: Chunk has bad child pointer 0x%lx" % (p.address, p.child)) 307 | if p.parent: 308 | if not is_valid_addr(p.parent): 309 | valid = False 310 | if talk: 311 | print("0x%lx: Chunk has bad parent pointer 0x%lx" % (p.address, p.parent)) 312 | if p.refs and (p.refs != TALLOC_MAGIC_REFERENCE): 313 | if not is_valid_addr(p.refs): 314 | valid = False 315 | if talk: 316 | print("0x%lx: Chunk has bad refs pointer 0x%lx" % (p.address, p.refs)) 317 | if p.destructor: 318 | if not is_valid_addr(p.destructor): 319 | valid = False 320 | if talk: 321 | print("0x%lx: Chunk has bad destructor pointer 0x%lx" % (p.address, p.destructor)) 322 | if p.pool: 323 | if not is_valid_addr(p.pool): 324 | valid = False 325 | if talk: 326 | print("0x%lx: Chunk has bad pool pointer 0x%lx" % (p.address, p.pool)) 327 | if p.size >= MAX_TALLOC_SIZE: 328 | valid = False 329 | if talk: 330 | print("0x%lx: Chunk has bad size 0x%lx" % (p.address, p.size)) 331 | 332 | return valid 333 | 334 | def search_chunk(p, search_for): 335 | "searches a chunk. includes the chunk header in the search" 336 | results = [] 337 | try: 338 | out_str = gdb.execute('find 0x%x, 0x%x, %s' % \ 339 | (p.address, p.address + p.header_size + p.size, search_for), \ 340 | to_string = True) 341 | except: 342 | #print(sys.exc_info()[0]) 343 | return results 344 | 345 | str_results = out_str.split('\n') 346 | 347 | for str_result in str_results: 348 | if str_result.startswith('0x'): 349 | results.append(p.address) 350 | 351 | return results 352 | 353 | def tc_search_heap(p, search_for, depth=0, find_top=True): 354 | "walk chunks searching for specified value" 355 | results = [] 356 | if (depth == 0) and (find_top == True): 357 | top = talloc_top_chunk(p) 358 | p = top 359 | 360 | results = search_chunk(p, search_for) 361 | 362 | if p.child: 363 | p = tc_chunk(p.child) 364 | while True: 365 | results.extend(tc_search_heap(p, search_for, (depth+1))) 366 | if not p.next_tc: 367 | break; 368 | p = tc_chunk(p.next_tc) 369 | 370 | return results 371 | 372 | def tc_find_addr(p, search_addr, depth=0, find_top=True): 373 | "walk chunks searching if address falls within chunk" 374 | results = [] 375 | if (depth == 0) and (find_top == True): 376 | top = talloc_top_chunk(p) 377 | p = top 378 | 379 | if (p.address <= search_addr) and ((p.address + TC_HDR_SIZE + p.size) >= search_addr): 380 | results.append(p) 381 | 382 | if p.child: 383 | p = tc_chunk(p.child) 384 | while True: 385 | results.extend(tc_find_addr(p, search_addr, (depth+1))) 386 | if not p.next_tc: 387 | break; 388 | p = tc_chunk(p.next_tc) 389 | 390 | return results 391 | 392 | # This is our workhorse for analyzing the entire heap. Supply a callback and it 393 | # will be passed every encountered chunk and depth. It recurses through every memory 394 | # context from the top (typically null_context) 395 | def tc_walk_heap(p, cb=None, depth=0, find_top=True): 396 | if cb == None: 397 | print(c_error + "WARNING: tc_walk_heap requires a callback..." + c_none) 398 | return 399 | if (depth == 0) and (find_top == True): 400 | top = talloc_top_chunk(p) 401 | p = top 402 | 403 | cb(p) 404 | if p.child: 405 | p = tc_chunk(p.child) 406 | while True: 407 | tc_walk_heap(p, cb, (depth+1)) 408 | if not p.next_tc: 409 | break; 410 | p = tc_chunk(p.next_tc) 411 | 412 | def tc_heap_validate_all(p, depth=0): 413 | tc_walk_heap(p, validate_chunk) 414 | 415 | def tc_print_heap(p, sort=False, find_top=True): 416 | global chunklist 417 | if sort == True: 418 | chunklist[:] = [] 419 | tc_walk_heap(p, collect_chunk, find_top) 420 | chunklist.sort() 421 | for chunk in chunklist: 422 | print(chunk) 423 | else: 424 | tc_walk_heap(p, print_chunk, find_top) 425 | 426 | def collect_chunk(p): 427 | chunklist.append(chunk_info(p)) 428 | 429 | def print_chunk(p): 430 | print(chunk_info(p)) 431 | 432 | def chunk_info(p): 433 | info = [] 434 | info.append(("0x%lx " % p.address)) 435 | info.append(("sz:0x%.08x, " % p.size)) 436 | info.append(("flags:")) 437 | if (is_free(p)): 438 | info.append("F") 439 | else: 440 | info.append(".") 441 | if (is_pool(p)): 442 | info.append("P") 443 | else: 444 | info.append(".") 445 | if (is_pool_member(p)): 446 | info.append("p") 447 | else: 448 | info.append(".") 449 | if (is_loop(p)): 450 | info.append("L") 451 | else: 452 | info.append(".") 453 | info.append((", name:%s" % talloc_get_name(p))) 454 | return ''.join(info) 455 | 456 | def bad_magic(p): 457 | return (p.flags & TALLOC_FLAG_MASK != TALLOC_MAGIC) 458 | 459 | def is_free(p): 460 | "Extract p's TALLOC_FLAG_FREE bit" 461 | return (p.flags & TALLOC_FLAG_FREE) 462 | 463 | def set_free(p): 464 | "set chunk as being free without otherwise disturbing" 465 | p.flags |= TALLOC_FLAG_FREE 466 | p.write() 467 | return 468 | 469 | def clear_free(p): 470 | "set chunk as being inuse without otherwise disturbing" 471 | p.flags &= ~TALLOC_FLAG_FREE 472 | p.write() 473 | return 474 | 475 | def is_loop(p): 476 | "Extract p's TALLOC_FLAG_LOOP bit" 477 | return (p.flags & TALLOC_FLAG_LOOP) 478 | 479 | def set_loop(p): 480 | "set chunk as looped without otherwise disturbing" 481 | p.flags |= TALLOC_FLAG_LOOP 482 | p.write() 483 | return 484 | 485 | def clear_loop(p): 486 | "set chunk as non-looped without otherwise disturbing" 487 | p.flags &= ~TALLOC_FLAG_LOOP 488 | p.write() 489 | return 490 | 491 | def set_destructor(p, fptr): 492 | p.destructor = fptr 493 | p.write() 494 | return 495 | 496 | def set_size(p, sz): 497 | "set chunks size without otherwise disturbing" 498 | p.size = sz 499 | p.write() 500 | return 501 | 502 | def talloc_total_blocks(tc): 503 | total = 0 504 | if is_loop(tc): 505 | return total 506 | set_loop(tc) 507 | total += 1 508 | if tc.child: 509 | p = talloc_chunk(tc.child) 510 | while True: 511 | total += talloc_total_blocks(p) 512 | if not p.next_tc: 513 | break 514 | p = talloc_chunk(p.next_tc) 515 | 516 | clear_loop(tc) 517 | return total 518 | 519 | def talloc_total_size(tc): 520 | total = 0 521 | if is_loop(tc): 522 | return total 523 | set_loop(tc) 524 | 525 | if tc.name != TALLOC_MAGIC_REFERENCE: 526 | total = tc.size 527 | if tc.child: 528 | p = talloc_chunk(tc.child) 529 | while True: 530 | total += talloc_total_size(p) 531 | if not p.next_tc: 532 | break 533 | p = talloc_chunk(p.next_tc) 534 | 535 | clear_loop(tc) 536 | return total 537 | 538 | def talloc_parent_chunk(tc): 539 | "ptr to parent talloc_chunk" 540 | while tc.prev_tc: 541 | tc = talloc_chunk(tc.prev_tc) 542 | if tc.parent: 543 | return talloc_chunk(tc.parent) 544 | return None 545 | 546 | def talloc_top_chunk(tc): 547 | global null_context 548 | while True: 549 | last = tc 550 | tc = talloc_parent_chunk(tc) 551 | if (null_context == None) and (tc != None) and (talloc_get_name(tc) == "null_context"): 552 | null_context = tc 553 | if tc == None: 554 | return last 555 | 556 | def talloc_reference_count(tc): 557 | "number of external references to a pointer" 558 | ret = 0 559 | while tc.next_tc: 560 | ret += 1 561 | tc = talloc_chunk(tc.next_tc) 562 | return ret 563 | 564 | def tc_hexdump(p): 565 | data = ptr_from_talloc_chunk(p) 566 | print(c_title + "Chunk data (%d bytes):" % p.size + c_none) 567 | cmd = "x/%dwx 0x%x\n" % (p.size/4, data) 568 | gdb.execute(cmd, True) 569 | return 570 | 571 | def is_pool(p): 572 | return (p.flags & TALLOC_FLAG_POOL) 573 | 574 | def is_pool_member(p): 575 | return (p.flags & TALLOC_FLAG_POOLMEM) 576 | 577 | def get_chunk_pool(p): 578 | if p.flags & TALLOC_FLAG_POOLMEM == 0: 579 | print(c_error + "WARNING: 0x%lx not part of a pool." \ 580 | % p + c_none) 581 | return None 582 | return p.pool 583 | 584 | def read_string(p, inferior=None): 585 | if inferior == None: 586 | inferior = get_inferior() 587 | if inferior == -1: 588 | return None 589 | string = [] 590 | curp = p 591 | while True: 592 | byte = inferior.read_memory(curp, 1) 593 | if byte[0] == b'\x00': 594 | break; 595 | string.append(byte[0]) 596 | curp += 1 597 | 598 | return b''.join(string).decode('utf-8') 599 | 600 | def talloc_print_parents(tc): 601 | parents = [] 602 | parents.append(tc) 603 | while True: 604 | tc = talloc_parent_chunk(tc) 605 | if tc == None: 606 | break; 607 | parents.append(tc) 608 | 609 | depth = 0 610 | parents.reverse() 611 | for tc in parents: 612 | print("%*s0x%lx: %s" % (depth*2, "", tc.address, talloc_get_name(tc))) 613 | depth += 1 614 | return 615 | 616 | def talloc_print_children(tc, depth=0): 617 | p = tc 618 | if p.child: 619 | p = tc_chunk(p.child) 620 | while True: 621 | poolstr = "" 622 | if is_pool(p): 623 | poolstr = " (POOL)" 624 | print("%*s0x%lx: %s%s" % (depth*2, "", p.address, talloc_get_name(p), poolstr)) 625 | talloc_print_children(p, depth+1) 626 | if not p.next_tc: 627 | break; 628 | p = tc_chunk(p.next_tc) 629 | 630 | def talloc_get_name(tc): 631 | if tc.name == TALLOC_MAGIC_REFERENCE: 632 | return ".reference" 633 | elif tc.name: 634 | try: 635 | return read_string(tc.name); 636 | except RuntimeError: 637 | print(c_error + "Could not read name" + c_none) 638 | return None 639 | else: 640 | return "UNNAMED" 641 | 642 | def talloc_report_callback(tc, depth, is_ref=False): 643 | name = talloc_get_name(tc) 644 | if is_ref: 645 | print("%*sreference to: %s" % (depth*4, "", name)) 646 | elif depth == 0: 647 | print("Full talloc report on '%s' (total %6lu bytes in %3lu blocks)" % \ 648 | (name, talloc_total_size(tc), talloc_total_blocks(tc))) 649 | return 650 | else: 651 | print("%*s%-30s contains %6lu bytes in %3lu blocks (ref %d) 0x%lx" % \ 652 | (depth*4, "", name, talloc_total_size(tc), \ 653 | talloc_total_blocks(tc), talloc_reference_count(tc), tc.address)) 654 | 655 | def talloc_report_full(tc, full=False): 656 | if full == True: 657 | tc = talloc_top_chunk(tc) 658 | talloc_report_depth(tc, 0) 659 | 660 | def talloc_report_depth(tc, depth): 661 | if is_loop(tc): 662 | return 663 | talloc_report_callback(tc, depth) 664 | set_loop(tc) 665 | p = tc 666 | if p.child: 667 | p = talloc_chunk(p.child) 668 | while True: 669 | if p.name == TALLOC_MAGIC_REFERENCE: 670 | h = talloc_reference_handle(ptr_from_talloc_chunk(p)) 671 | talloc_report_callback(talloc_chunk(h.ptr), depth+1, True) 672 | else: 673 | talloc_report_depth(p, depth+1) 674 | if not p.next_tc: 675 | break 676 | p = talloc_chunk(p.next_tc) 677 | 678 | clear_loop(tc) 679 | 680 | def get_inferior(): 681 | try: 682 | if len(gdb.inferiors()) == 0: 683 | print(c_error + "No gdb inferior could be found." + c_none) 684 | return -1 685 | else: 686 | inferior = gdb.inferiors()[0] 687 | return inferior 688 | except AttributeError: 689 | print(c_error + "This gdb's python support is too old." + c_none) 690 | exit() 691 | 692 | 693 | ################################################################################ 694 | class talloc_chunk: 695 | "python representation of a struct talloc_chunk" 696 | 697 | def __init__(self,addr=None,mem=None,min_size=TC_HDR_SIZE,inferior=None,read_data=True,show_pad=False, pool=None): 698 | global talloc_version 699 | self.vspecific = None 700 | self.str_name = "talloc_chunk" 701 | 702 | # Struct members 703 | self.next_tc = None 704 | self.prev_tc = None 705 | self.parent = None 706 | self.child = None 707 | self.refs = None 708 | self.destructor = None 709 | self.name = None 710 | self.size = None 711 | self.flags = None 712 | self.pool = None 713 | self.limit = None 714 | self.pad1 = None 715 | self.pad2 = None 716 | self.pad3 = None 717 | 718 | # Used by child objects to know read offsets 719 | self.struct_size = 0 720 | self.header_size = TC_HDR_SIZE 721 | 722 | # >= TALLOC 2.1.0 we might have a prefixed talloc_pool_hdr 723 | self.pool_hdr = None 724 | 725 | # Init these now to adjust header_size 726 | if version_eq_or_newer((2, 0, 8)): 727 | self.vspecific = talloc_chunk_v208(self) 728 | elif version_eq_or_older((2, 0, 7)): 729 | self.vspecific = talloc_chunk_v207(self) 730 | else: 731 | print("No version detected") 732 | 733 | if addr == None or addr == 0: 734 | if mem == None: 735 | sys.stdout.write(c_error) 736 | sys.stdout.write("Please specify a valid struct talloc_chunk address.") 737 | sys.stdout.write(c_none) 738 | return None 739 | self.address = None 740 | else: 741 | self.address = addr 742 | 743 | if inferior == None and mem == None: 744 | inferior = get_inferior() 745 | if inferior == -1: 746 | return None 747 | 748 | if mem == None: 749 | # a string of raw memory was not provided 750 | try: 751 | if SIZE_SZ == 4: 752 | # XXX - Size is arbitrary. copied from libheap 753 | self.mem = inferior.read_memory(addr, 0x44c) 754 | elif SIZE_SZ == 8: 755 | # XXX - Size is arbitrary. copied from libheap 756 | self.mem = inferior.read_memory(addr, 0x880) 757 | except TypeError: 758 | print(c_error + "Invalid address specified." + c_none) 759 | return None 760 | except RuntimeError: 761 | print(c_error + "Could not read address 0x%x" % addr + c_none) 762 | return None 763 | else: 764 | # a string of raw memory was provided 765 | if len(self.mem) < self.header_size: 766 | sys.stdout.write(c_error) 767 | sys.stdout.write("Insufficient memory provided for a talloc_chunk.") 768 | sys.stdout.write(c_none) 769 | return None 770 | if len(self.mem) == header_size: 771 | #header only provided 772 | read_data = False 773 | 774 | if SIZE_SZ == 4: 775 | (self.next_tc, \ 776 | self.prev_tc, \ 777 | self.parent, \ 778 | self.child, \ 779 | self.refs, \ 780 | self.destructor, \ 781 | self.name, \ 782 | self.size, \ 783 | self.flags, \ 784 | ) = struct.unpack_from("<9I", self.mem, 0x0) 785 | self.struct_size = 9*4 786 | elif SIZE_SZ == 8: 787 | (self.next_tc, \ 788 | self.prev_tc, \ 789 | self.parent, \ 790 | self.child, \ 791 | self.refs, \ 792 | self.destructor, \ 793 | self.name, \ 794 | self.size, \ 795 | self.flags, \ 796 | ) = struct.unpack_from("<8QI", self.mem, 0x0) 797 | self.struct_size = ((8*8) + 4) 798 | 799 | # Depending on the version we have to read in additional data... 800 | if version_eq_or_newer((2, 0, 8)): 801 | self.vspecific.getdata(self) 802 | if version_eq_or_older((2, 0, 7)): 803 | self.vspecific.getdata(self) 804 | 805 | # Create a copy of the pool header 806 | if is_pool(self) and version_eq_or_newer((2, 1, 0)): 807 | if pool != None: 808 | self.pool_hdr = talloc_pool_hdr(self.address-TP_HDR_SIZE, parent=self) 809 | else: 810 | self.pool_hdr 811 | 812 | # always try to find null_context asap 813 | global null_context 814 | if null_context == None and is_valid_chunk(self): 815 | talloc_top_chunk(self) 816 | 817 | def write(self, inferior=None, do_write=True): 818 | if inferior == None: 819 | self.inferior = get_inferior() 820 | if self.inferior == -1: 821 | return None 822 | if SIZE_SZ == 4: 823 | self.mem = struct.pack("<9I", self.next_tc, self.prev_tc, \ 824 | self.parent, self.child, self.refs, self.destructor, self.name,\ 825 | self.size, self.flags) 826 | elif SIZE_SZ == 8: 827 | self.mem = struct.pack("<8QI", self.next_tc, self.prev_tc, \ 828 | self.parent, self.child, self.refs, self.destructor, self.name,\ 829 | self.size, self.flags) 830 | 831 | # Add the version-specific bits 832 | self.vspecific.write(self) 833 | 834 | if do_write: 835 | self.inferior.write_memory(self.address, self.mem) 836 | 837 | def get_flags(self): 838 | "return a string indicating what flags are set" 839 | string = [] 840 | flags = [(is_free, "FREE"), 841 | (is_pool, "POOL"), 842 | (is_pool_member, "POOLMEM"), 843 | (is_loop, "LOOP") 844 | ] 845 | 846 | seen_flag = 0 847 | for item in flags: 848 | if (item[0](self)): 849 | if seen_flag: 850 | string.append(", ") 851 | string.append(item[1]) 852 | seen_flag = 1 853 | 854 | return ''.join(string) 855 | 856 | def __str__(self): 857 | string = [] 858 | string.append("%s%lx%s%s%lx%s%lx%s%lx%s%lx%s%lx%s%lx%s%lx%s%s%lx%s%lx%s%s" % \ 859 | (c_title + "struct " + self.str_name + " @ 0x", \ 860 | self.address, \ 861 | " {", \ 862 | c_none + "\nnext = " + c_value + "0x", 863 | self.next_tc, \ 864 | c_none + "\nprev = " + c_value + "0x", \ 865 | self.prev_tc, \ 866 | c_none + "\nparent = " + c_value + "0x", \ 867 | self.parent, \ 868 | c_none + "\nchild = " + c_value + "0x", \ 869 | self.child, \ 870 | c_none + "\nrefs = " + c_value + "0x", \ 871 | self.refs, \ 872 | c_none + "\ndestructor = " + c_value + "0x", \ 873 | self.destructor, 874 | c_none + "\nname = " + c_value + "0x", \ 875 | self.name, \ 876 | c_none + (" (%s)" % talloc_get_name(self)), \ 877 | c_none + "\nsize = " + c_value + "0x", \ 878 | self.size, \ 879 | c_none + "\nflags = " + c_value + "0x", \ 880 | self.flags, \ 881 | c_none + (" (%s)" % self.get_flags()), 882 | c_none)) 883 | 884 | string.append("%s" % self.vspecific) 885 | return ''.join(string) 886 | 887 | class talloc_chunk_v208(): 888 | "python representation of a struct talloc_chunk with a limit member" 889 | 890 | def __init__(self, parent): 891 | self.parent = parent 892 | # TODO: talloc_chunk already assumes TC_HDR_SIZE so we don't need to 893 | # adjust, but if that changes, we'll need to adjust here 894 | #if SIZE_SZ == 4: 895 | # parent.header_size += (3*4) 896 | #elif SIZE_SZ == 8: 897 | # parent.header_size += ((2*8) + (3*4)) 898 | 899 | def getdata(self, parent): 900 | if SIZE_SZ == 4: 901 | (parent.limit, \ 902 | parent.pool, \ 903 | parent.pad1) = struct.unpack_from("<3I", parent.mem, parent.struct_size) 904 | elif SIZE_SZ == 8: 905 | (parent.pad1, \ 906 | parent.limit, \ 907 | parent.pool) = struct.unpack_from(" : show chunk contents (-v for verbose, -x for data dump)') 1174 | print('[libtalloc] tcvalidate -a : validate chunk (-a for whole heap)') 1175 | print('[libtalloc] tcsearch : search heap for hex value or address') 1176 | print('[libtalloc] tcfindaddr : search heap for address') 1177 | print('[libtalloc] tcwalk : walk whole heap calling func on every chunk') 1178 | print('[libtalloc] tcreport : give talloc_report_full\(\) info on memory context') 1179 | print('[libtalloc] tcdump -s : dump chunks linked to memory context (-s for sorted by addr)') 1180 | print('[libtalloc] tcparents : show all parents of chunk') 1181 | print('[libtalloc] tcchildren : show all children of chunk') 1182 | print('[libtalloc] tcinfo : show information known about heap') 1183 | print('[libtalloc] tcprobe : try to collect information about talloc version') 1184 | print('[libtalloc] tcpool -v -f -l -x : dump information about a talloc pool') 1185 | print('[libtalloc] tchelp : this help message') 1186 | 1187 | class tcchunk(tccmd): 1188 | def __init__(self): 1189 | super(tcchunk, self).__init__("tcchunk", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1190 | 1191 | def help(self): 1192 | print('[libtalloc] usage: tcchunk [-v] [-f] [-x] ') 1193 | print('[libtalloc] a talloc chunk header') 1194 | print('[libtalloc] -v use verbose output (multiples for more verbosity)') 1195 | print('[libtalloc] -f use explicitly, rather than be smart') 1196 | print('[libtalloc] -x hexdump the chunk contents') 1197 | 1198 | def invoke(self, arg, from_tty): 1199 | verbose = 0 1200 | force = False 1201 | hexdump = False 1202 | p = None 1203 | if arg == '': 1204 | self.help() 1205 | return 1206 | for item in arg.split(): 1207 | if item.find("-v") != -1: 1208 | verbose += 1 1209 | if item.find("-f") != -1: 1210 | force = True 1211 | if item.find("-x") != -1: 1212 | hexdump = True 1213 | if item.find("0x") != -1: 1214 | p = int(item, 16) 1215 | if item.find("$") != -1: 1216 | p = self.parse_var(item) 1217 | if item.find("-h") != -1: 1218 | self.help() 1219 | return 1220 | if p == None: 1221 | print(c_error + "WARNING: No address supplied?" + c_none) 1222 | self.help() 1223 | return 1224 | if force: 1225 | p = talloc_chunk(p) 1226 | else: 1227 | p = tc_chunk(p) 1228 | if verbose == 0: 1229 | print(chunk_info(p)) 1230 | elif verbose == 1: 1231 | print(p) 1232 | else: 1233 | annotate_chunk(p) 1234 | if hexdump: 1235 | tc_hexdump(p) 1236 | 1237 | # TODO - Move the chunk listing to a separate command eventually 1238 | class tcpool(tccmd): 1239 | def __init__(self): 1240 | super(tcpool, self).__init__("tcpool", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1241 | 1242 | def help(self): 1243 | print('[libtalloc] usage: tcpool [-v] [-f] [-x] ') 1244 | print('[libtalloc] a talloc pool chunk header') 1245 | print('[libtalloc] -v use verbose output (multiples for more verbosity)') 1246 | print('[libtalloc] -f use explicitly, rather than be smart') 1247 | print('[libtalloc] -l list all chunks inside pool') 1248 | print('[libtalloc] -x hexdump the chunk contents') 1249 | 1250 | def invoke(self, arg, from_tty): 1251 | verbose = 0 1252 | force = False 1253 | hexdump = False 1254 | list_chunks = False 1255 | p = None 1256 | if arg == '': 1257 | self.help() 1258 | return 1259 | for item in arg.split(): 1260 | if item.find("-v") != -1: 1261 | verbose += 1 1262 | if item.find("-f") != -1: 1263 | force = True 1264 | if item.find("-l") != -1: 1265 | list_chunks = True 1266 | if item.find("-x") != -1: 1267 | hexdump = True 1268 | if item.find("0x") != -1: 1269 | p = int(item, 16) 1270 | if item.find("$") != -1: 1271 | p = self.parse_var(item) 1272 | if item.find("-h") != -1: 1273 | self.help() 1274 | return 1275 | if p == None: 1276 | print(c_error + "WARNING: No address supplied?" + c_none) 1277 | self.help() 1278 | return 1279 | if version_eq_or_older((2, 0, 8)): 1280 | p = talloc_pool_chunk(p) 1281 | if list_chunks: 1282 | print("Pool - objects: 0x%lx, total size: 0x%lx, space left: 0x%lx, next free: 0x%lx" % \ 1283 | (p.object_count, \ 1284 | p.size, \ 1285 | (p.size - (p.pool - (p.address + p.header_size))), \ 1286 | p.pool)) 1287 | if verbose == 0: 1288 | print_chunk(p) 1289 | else: 1290 | print(p) 1291 | elif version_eq_or_newer((2, 1, 0)): 1292 | p = talloc_pool_hdr(p) 1293 | if list_chunks: 1294 | print("Pool - objects: 0x%lx, total size: 0x%lx, space left: 0x%lx, next free: 0x%lx" % \ 1295 | (p.object_count, \ 1296 | p.poolsize, \ 1297 | (p.poolsize - (p.end - (p.address + p.header_size + p.chunk.header_size))), \ 1298 | p.end)) 1299 | 1300 | if verbose == 0: 1301 | print_chunk(p.chunk) 1302 | else: 1303 | print(p) 1304 | 1305 | if list_chunks: 1306 | p.dump() 1307 | 1308 | class tcvalidate(tccmd): 1309 | def __init__(self): 1310 | super(tcvalidate, self).__init__("tcvalidate", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1311 | 1312 | def help(self): 1313 | print('[libtalloc] usage: tcvalidate ') 1314 | print('[libtalloc] a talloc chunk header') 1315 | print('[libtalloc] ex: tcvalidate 0x41414141') 1316 | return 1317 | 1318 | def invoke(self, arg, from_tty): 1319 | arg = arg.split() 1320 | if len(arg) != 1: 1321 | self.help() 1322 | return 1323 | if arg[0].find("0x") != -1: 1324 | p = int(arg[0], 16) 1325 | elif arg[0].find("$") != -1: 1326 | p = self.parse_var(arg[0]) 1327 | else: 1328 | self.help() 1329 | return 1330 | 1331 | # not tc_chunk() because we don't want to fixup 1332 | p = talloc_chunk(p) 1333 | if is_valid_chunk(p) == True: 1334 | print("Chunk header is valid") 1335 | 1336 | else: 1337 | print("Chunk header is invalid:") 1338 | validate_chunk(p) 1339 | 1340 | class tcsearch(tccmd): 1341 | def __init__(self): 1342 | super(tcsearch, self).__init__("tcsearch", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1343 | 1344 | def help(self): 1345 | print('[libtalloc] usage: tcsearch ') 1346 | 1347 | def invoke(self, arg, from_tty): 1348 | global null_context 1349 | if arg == '': 1350 | self.help() 1351 | return 1352 | arg = arg.split() 1353 | search_for = arg[0] 1354 | if len(arg) != 2 or arg[1] == '': 1355 | if null_context != None: 1356 | p = null_context 1357 | else: 1358 | print(c_error + "WARNING: Don't know null_context and no address given" + c_none) 1359 | self.help() 1360 | return 1361 | elif arg[1].find("0x") != -1: 1362 | p = int(arg[1], 16) 1363 | p = tc_chunk(p) 1364 | elif arg[1].find("$") != -1: 1365 | p = self.parse_var(arg[1]) 1366 | p = tc_chunk(p) 1367 | else: 1368 | self.help() 1369 | return 1370 | 1371 | results = tc_search_heap(p, search_for) 1372 | 1373 | if len(results) == 0: 1374 | print('[libtalloc] value %s not found' % (search_for)) 1375 | return 1376 | 1377 | for result in results: 1378 | print("[libtalloc] %s found in chunk at 0x%lx" % (search_for, int(result))) 1379 | 1380 | class tcdump(tccmd): 1381 | def __init__(self): 1382 | super(tcdump, self).__init__("tcdump", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1383 | 1384 | def help(self): 1385 | print('[libtalloc] usage: tcdump [-s] [-a] ') 1386 | print('[libtalloc] a talloc chunk header') 1387 | print('[libtalloc] -s sort output linearly by address') 1388 | print('[libtalloc] -a walk from upper most parent chunk') 1389 | print('[libtalloc] ex: tcdump -s -a 0x41414141') 1390 | 1391 | def invoke(self, arg, from_tty): 1392 | global null_context 1393 | p = None 1394 | sort = False 1395 | find_top = False 1396 | if arg == '': 1397 | if null_context == None: 1398 | self.help() 1399 | return 1400 | else: 1401 | p = null_context 1402 | else: 1403 | for item in arg.split(): 1404 | if item.find("-a") != -1: 1405 | find_top = True 1406 | if item.find("-s") != -1: 1407 | sort = True 1408 | if item.find("-h") != -1: 1409 | self.help() 1410 | return 1411 | if item.find("0x") != -1: 1412 | p = int(item,16) 1413 | if item.find("$") != -1: 1414 | p = self.parse_var(item) 1415 | 1416 | if p == None: 1417 | self.help() 1418 | return 1419 | 1420 | if find_top: 1421 | p = talloc_top_chunk(talloc_chunk(p)) 1422 | else: 1423 | p = talloc_chunk(p) 1424 | 1425 | try: 1426 | tc_print_heap(p, sort, find_top) 1427 | except KeyboardInterrupt: 1428 | return 1429 | 1430 | class tcreport(tccmd): 1431 | def __init__(self): 1432 | super(tcreport, self).__init__("tcreport", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1433 | 1434 | def help(self): 1435 | print('[libtalloc] usage: tcreport [-a] ') 1436 | print('[libtalloc] a talloc chunk header') 1437 | print('[libtalloc] -a report on the full heap') 1438 | 1439 | def invoke(self, arg, from_tty): 1440 | full = False 1441 | if arg == '': 1442 | self.help() 1443 | return 1444 | for item in arg.split(): 1445 | if item.find("-a") != -1: 1446 | full = True 1447 | if item.find("0x") != -1: 1448 | p = int(item, 16) 1449 | if item.find("$") != -1: 1450 | p = self.parse_var(item) 1451 | if item.find("-h") != -1: 1452 | self.help() 1453 | return 1454 | talloc_report_full(tc_chunk(p), full) 1455 | 1456 | class tcwalk(tccmd): 1457 | 1458 | def __init__(self): 1459 | super(tcwalk, self).__init__("tcwalk", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1460 | 1461 | def help(self): 1462 | print('[libtalloc] This is a convenience cmd for running callback methods across the whole heap') 1463 | print('[libtalloc] usage: tcwalk ') 1464 | print('[libtalloc] a python callback method') 1465 | print('[libtalloc] a talloc chunk header') 1466 | 1467 | def invoke(self, arg, from_tty): 1468 | arg = arg.split() 1469 | global null_context 1470 | if len(arg) != 2 and null_context == None: 1471 | self.help() 1472 | return 1473 | method = arg[0] 1474 | if null_context: 1475 | chunk = null_context 1476 | elif (arg[1].find("0x") != -1): 1477 | chunk = arg[1] 1478 | elif (arg[1].find("$") != -1): 1479 | chunk = self.parse_var(arg[1]) 1480 | else: 1481 | print(arg[1]) 1482 | self.help() 1483 | return 1484 | 1485 | if chunk == null_context: 1486 | gdb.execute(("python tc_walk_heap(tc_chunk(0x%lx), %s)" % (chunk.address, method)), True) 1487 | else: 1488 | gdb.execute(("python tc_walk_heap(tc_chunk(%s), %s)" % (chunk, method)), True) 1489 | 1490 | class tcparents(tccmd): 1491 | def __init__(self): 1492 | super(tcparents, self).__init__("tcparents", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1493 | 1494 | def help(self): 1495 | print('[libtalloc] usage: tcparents ') 1496 | print('[libtalloc] a talloc chunk header') 1497 | 1498 | def invoke(self, arg, from_tty): 1499 | if arg == '': 1500 | self.help() 1501 | return 1502 | arg = arg.split() 1503 | if arg[0].find("0x") != -1: 1504 | p = int(arg[0], 16) 1505 | elif arg[0].find("$") != -1: 1506 | p = self.parse_var(arg[0]) 1507 | else: 1508 | self.help() 1509 | return 1510 | talloc_print_parents(tc_chunk(p)) 1511 | 1512 | class tcchildren(tccmd): 1513 | def __init__(self): 1514 | super(tcchildren, self).__init__("tcchildren", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1515 | 1516 | def help(self): 1517 | print('[libtalloc] usage: tcchildren ') 1518 | print('[libtalloc] a talloc header') 1519 | 1520 | def invoke(self, arg, from_tty): 1521 | if arg == '': 1522 | self.help() 1523 | return 1524 | arg = arg.split() 1525 | if arg[0].find("0x") != -1: 1526 | p = int(arg[0], 16) 1527 | elif arg[0].find("$") != -1: 1528 | p = self.parse_var(arg[0]) 1529 | else: 1530 | self.help() 1531 | return 1532 | talloc_print_children(tc_chunk(p)) 1533 | 1534 | class tcprobe(tccmd): 1535 | def __init__(self): 1536 | super(tcprobe, self).__init__("tcprobe", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1537 | 1538 | def help(self): 1539 | print('[libtalloc] Find information about libtalloc used by process') 1540 | print('[libtalloc] usage: tcprobe') 1541 | 1542 | # TODO - The version finding logic assumes the last entry found is the highest 1543 | # version. It works in testing, but might not always be true. Should actually 1544 | # compute the highest of the read values or something. 1545 | def invoke(self, arg, from_tty): 1546 | global talloc_version 1547 | global talloc_path 1548 | 1549 | if talloc_version == None: 1550 | find_talloc_version() 1551 | 1552 | print("Version: " + ''.join(str(i) + "." for i in talloc_version) + '\b ') 1553 | print("File: " + talloc_path) 1554 | 1555 | class tcinfo(tccmd): 1556 | def __init__(self): 1557 | super(tcinfo, self).__init__("tcinfo", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1558 | 1559 | def invoke(self, arg, from_tty): 1560 | global null_context 1561 | global talloc_version 1562 | global talloc_path 1563 | 1564 | if null_context != None: 1565 | print("[libtalloc] null_context: 0x%x" % null_context.address) 1566 | else: 1567 | print("[libtalloc] null_context not yet found yet") 1568 | 1569 | if talloc_version == None or talloc_path == None: 1570 | print("[libtalloc] UNKNOWN version! run tcprobe command!") 1571 | else: 1572 | print("[libtalloc] Version: " + ''.join(str(i) + "." for i in talloc_version) + '\b ') 1573 | print("[libtalloc] File: " + talloc_path) 1574 | 1575 | class tcfindaddr(tccmd): 1576 | def __init__(self): 1577 | super(tcfindaddr, self).__init__("tcfindaddr", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) 1578 | 1579 | def help(self): 1580 | print('[libtalloc] usage: tcsearch ') 1581 | print('[libtalloc] -a
search if address falls within known chunks') 1582 | print('[libtalloc] talloc chunk address. needed only if null_context not known') 1583 | 1584 | def invoke(self, arg, from_tty): 1585 | global null_context 1586 | if arg == '': 1587 | self.help() 1588 | return 1589 | arg = arg.split() 1590 | search_addr = int(arg[0], 16) 1591 | if len(arg) != 2 or arg[1] == '': 1592 | if null_context != None: 1593 | p = null_context 1594 | else: 1595 | print(c_error + "WARNING: Don't know null_context and no address given" + c_none) 1596 | self.help() 1597 | return 1598 | elif arg[1].find("0x") != -1: 1599 | p = int(arg[1], 16) 1600 | p = tc_chunk(p) 1601 | elif arg[1].find("$") != -1: 1602 | p = self.parse_var(arg[1]) 1603 | p = tc_chunk(p) 1604 | else: 1605 | self.help() 1606 | return 1607 | 1608 | results = tc_find_addr(p, search_addr) 1609 | 1610 | if len(results) == 0: 1611 | print('[libtalloc] value %s not found' % (search_for)) 1612 | return 1613 | 1614 | for chunk in results: 1615 | print('[libtalloc] address 0x%lx falls within chunk @ 0x%lx (size 0x%lx)' % \ 1616 | (search_addr, chunk.address, chunk.size)) 1617 | 1618 | ################################################################################ 1619 | # GDB PRETTY PRINTERS 1620 | ################################################################################ 1621 | 1622 | class TallocChunkPrinter: 1623 | "pretty print a struct talloc_chunk" 1624 | def __init__(self, val): 1625 | self.val = val 1626 | 1627 | def to_string(self): 1628 | global talloc_version 1629 | string = [] 1630 | string.append("%s%s%lx%s%lx%s%lx%s%lx%s%lx%s%lx%s%lx%s%lx%s%x" % \ 1631 | (c_title + "struct talloc_chunk {", 1632 | c_none + "\nnext = " + c_value + "0x", 1633 | self.val['next'], \ 1634 | c_none + "\nprev = " + c_value + "0x", \ 1635 | self.val['prev'], \ 1636 | c_none + "\nparent = " + c_value + "0x", \ 1637 | self.val['parent'], \ 1638 | c_none + "\nchild = " + c_value + "0x", \ 1639 | self.val['child'], \ 1640 | c_none + "\nrefs = " + c_value + "0x", \ 1641 | self.val['refs'], \ 1642 | c_none + "\ndestructor = " + c_value + "0x", \ 1643 | self.val['destructor'], 1644 | c_none + "\nname = " + c_value + "0x", \ 1645 | self.val['name'], \ 1646 | c_none + "\nsize = " + c_value + "0x", \ 1647 | self.val['size'], \ 1648 | c_none + "\nflags = " + c_value + "0x", \ 1649 | self.val['flags'])) 1650 | 1651 | if version_eq_or_newer((2, 0, 8)): 1652 | string.append("%s%lx" % 1653 | c_none + "\nlimit = " + c_value + "0x", \ 1654 | self.val['limit']) 1655 | 1656 | string.append("%s%lx%s" % 1657 | c_none + "\npool = " + c_value + "0x", \ 1658 | self.val['pool'], \ 1659 | c_none) 1660 | return ''.join(string) 1661 | 1662 | ################################################################################ 1663 | def pretty_print_tc_heap_lookup(val): 1664 | "Look-up and return a pretty-printer that can print val." 1665 | 1666 | # Get the type. 1667 | type = val.type 1668 | 1669 | # If it points to a reference, get the reference. 1670 | if type.code == gdb.TYPE_CODE_REF: 1671 | type = type.target() 1672 | 1673 | # Get the unqualified type, stripped of typedefs. 1674 | type = type.unqualified().strip_typedefs() 1675 | 1676 | # Get the type name. 1677 | typename = type.tag 1678 | if typename == None: 1679 | return None 1680 | elif (typename == "talloc_chunk") or (typename == "tc_chunk"): 1681 | return TallocChunkPrinter(val) 1682 | else: 1683 | print(typename) 1684 | 1685 | # Cannot find a pretty printer. Return None. 1686 | return None 1687 | 1688 | tchelp() 1689 | tcinfo() 1690 | tcchunk() 1691 | tcpool() 1692 | tcvalidate() 1693 | tcsearch() 1694 | tcwalk() 1695 | tcreport() 1696 | tcdump() 1697 | tcparents () 1698 | tcchildren() 1699 | tcprobe() 1700 | tcfindaddr() 1701 | gdb.pretty_printers.append(pretty_print_tc_heap_lookup) 1702 | --------------------------------------------------------------------------------