├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── README.md ├── demo.png ├── examples ├── Makefile └── examples.c └── src ├── Makefile ├── visualize-c-memory.c └── visualize-c-memory.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | *.so 4 | examples/examples 5 | .gdb_history -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Make: compile and debug", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/examples/examples", 9 | "stopAtEntry": true, 10 | "cwd": "${workspaceFolder}/examples", 11 | "environment": [ 12 | {"name":"LD_PRELOAD", "value":"${workspaceFolder}/src/visualize-c-memory.so"}, 13 | ], 14 | "externalConsole": false, 15 | "linux": { 16 | "MIMode": "gdb", 17 | "internalConsoleOptions": "neverOpen" // don't show the debugger console 18 | }, 19 | "setupCommands": [ 20 | { 21 | "description": "Enable pretty-printing for gdb", 22 | "text": "-enable-pretty-printing", 23 | "ignoreFailures": true 24 | }, 25 | { 26 | "text": "source ${workspaceFolder}/src/visualize-c-memory.py" 27 | } 28 | ], 29 | "preLaunchTask": "Make: compile", 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | ////////////// Compile /////////////////////////////////// 5 | { 6 | "type": "shell", 7 | "label": "Make: compile", 8 | "command": "make", 9 | "options": { 10 | "cwd": "${workspaceRoot}/examples" 11 | }, 12 | "presentation": { 13 | "clear": true, 14 | "showReuseMessage": false 15 | }, 16 | "problemMatcher": { 17 | "base": "$gcc", 18 | "fileLocation": [ 19 | "relative", 20 | "${workspaceRoot}/examples" 21 | ] 22 | }, 23 | "group": { 24 | "kind": "build", 25 | "isDefault": true 26 | } 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## visualize-c-memory 2 | 3 | Real-time visualization of the memory of a C program during debugging in VSCode, 4 | using [vscode-debug-visualizer](https://github.com/hediet/vscode-debug-visualizer) 5 | and [GDB's Python API](https://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html). 6 | 7 | 8 | ![](demo.png) 9 | 10 | 11 | ### To try it 12 | 13 | - Install graphviz. 14 | ``` 15 | sudo apt install graphviz 16 | ``` 17 | 18 | - In VSCode, install [vscode-debug-visualizer](https://github.com/hediet/vscode-debug-visualizer). 19 | ``` 20 | Ctrl-P / ext install hediet.debug-visualizer 21 | ``` 22 | 23 | - Clone this repository, open it in VSCode and start debugging (`F5`). 24 | 25 | If the debugger doesn't start, make sure that `gdb`, `make` and VSCode's [ms-vscode.cpptools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) are installed. 26 | 27 | - Select `F1 / Debug Visualizer : New View` and 28 | write `"memory"` (__including__ the quotes) in the visualization window 29 | that opens. 30 | 31 | - A visualization of both the stack and the heap will appear, and it will 32 | update as you step through the code (`F10`, `F11`, etc). 33 | 34 | - Modify [`examples/examples.c`](examples/examples.c) to try your own code. 35 | 36 | Tested in Ubuntu 20.04 and Windows 10 under WSL. 37 | macOS is not supported (it could be adapted, but Apple makes it 38 | [particularly painful](https://dev.to/jasonelwood/setup-gdb-on-macos-in-2020-489k) to use 39 | GDB in macOS anyway). 40 | 41 | 42 | ### To use it in your own project 43 | 44 | - Build `visualize-c-memory.so` (by running `make` in `src`). 45 | - Load the `.so` and the python module in GDB by adding 46 | the following to your `launch.json`: 47 | ``` 48 | "environment": [ 49 | {"name":"LD_PRELOAD", "value":"${workspaceFolder}//visualize-c-memory.so"}, 50 | ], 51 | "setupCommands": [ 52 | { 53 | "text": "source ${workspaceFolder}//visualize-c-memory.py" 54 | } 55 | ], 56 | ``` 57 | See [.vscode/launch.json](.vscode/launch.json) for an example. 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chatziko/visualize-c-memory/d4b66c1d03a37eb85521e3d3d32db41a5ae291a2/demo.png -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | # options 2 | CC = gcc 3 | CFLAGS = -Wall -g 4 | 5 | LIB = ../src 6 | OBJS = examples.o 7 | EXEC = examples 8 | 9 | $(EXEC): $(OBJS) $(LIB)/visualize-c-memory.so 10 | $(CC) $(OBJS) -o $(EXEC) $(LDFLAGS) 11 | 12 | $(LIB)/visualize-c-memory.so: $(LIB)/visualize-c-memory.c 13 | $(MAKE) -C $(LIB) visualize-c-memory.so 14 | 15 | clean: 16 | rm -f $(OBJS) $(EXEC) 17 | -------------------------------------------------------------------------------- /examples/examples.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef struct list_node { 6 | int value; 7 | struct list_node* next; 8 | } ListNode; 9 | 10 | typedef struct { 11 | int field1; 12 | float field2; 13 | } MyStruct; 14 | 15 | 16 | 17 | void local_vars_example() { 18 | int my_int = 1; 19 | float my_float = 2.0; 20 | int my_array[3] = {3, 4, 5}; 21 | MyStruct my_struct = {.field1 = 1, .field2 = 2.0}; 22 | 23 | if(1) { 24 | int block_scope = 1; 25 | printf("local_vars_example: %d\n", block_scope); 26 | } 27 | 28 | printf("local_vars_example: %d %f %d %d\n", my_int, my_float, my_array[0], my_struct.field1); 29 | } 30 | 31 | void pointer_example() { 32 | int foo = 1; 33 | int bar = 2; 34 | 35 | int* pointer = &foo; 36 | *pointer = 3; 37 | 38 | int** double_pointer = &pointer; 39 | *double_pointer = &bar; 40 | **double_pointer = 4; 41 | } 42 | 43 | void malloc_example() { 44 | int* my_int = malloc(sizeof(*my_int)); 45 | *my_int = 1; 46 | 47 | float* my_float = malloc(sizeof(*my_float)); 48 | *my_float = 2.0; 49 | 50 | int* my_int_array = malloc(3 * sizeof(*my_int_array)); 51 | my_int_array[0] = 1; 52 | my_int_array[1] = 2; 53 | my_int_array[2] = 3; 54 | 55 | MyStruct* my_struct = malloc(sizeof(*my_struct)); 56 | my_struct->field1 = 1; 57 | my_struct->field2 = 2; 58 | 59 | free(my_int); 60 | free(my_float); 61 | free(my_int_array); 62 | free(my_struct); 63 | } 64 | 65 | void string_example() { 66 | char string1[5]; 67 | strcpy(string1, "foo"); 68 | 69 | char* string2 = malloc(5); 70 | strcpy(string2, "bar"); 71 | free(string2); 72 | } 73 | 74 | void swap(int* a, int* b) { 75 | int temp = *a; 76 | *a = *b; 77 | *b = temp; 78 | } 79 | 80 | void swap_example() { 81 | int foo = 1; 82 | int bar = 2; 83 | swap(&foo, &bar); 84 | } 85 | 86 | ListNode* create_node(int value) { 87 | ListNode* node = malloc(sizeof(*node)); 88 | node->value = value; 89 | node->next = NULL; 90 | return node; 91 | } 92 | 93 | void list_example() { 94 | ListNode* first = create_node(0); 95 | first->next = create_node(1); 96 | first->next->next = create_node(2); 97 | 98 | free(first->next->next); 99 | free(first->next); 100 | free(first); 101 | } 102 | 103 | int recursive_function(int n) { 104 | int temp = 0; 105 | if(n > 0) 106 | temp = recursive_function(n - 1); 107 | 108 | return n + temp; 109 | } 110 | 111 | void recursion_example() { 112 | int result = recursive_function(3); 113 | printf("recursion_example: %d\n", result); 114 | } 115 | 116 | void memory_leak_example() { 117 | int* my_int; 118 | 119 | my_int = malloc(sizeof(*my_int)); 120 | my_int = malloc(sizeof(*my_int)); 121 | my_int = malloc(sizeof(*my_int)); 122 | 123 | free(my_int); 124 | } 125 | 126 | 127 | 128 | int main() { 129 | local_vars_example(); 130 | pointer_example(); 131 | swap_example(); 132 | malloc_example(); 133 | string_example(); 134 | recursion_example(); 135 | list_example(); 136 | memory_leak_example(); 137 | 138 | printf("\nVisualization examples. To display them, start debugging (F5),\n"); 139 | printf("open Debug Visualizer and type \"memory\" in the window that opens.\n\n"); 140 | 141 | return 0; 142 | } 143 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # options 2 | CC = gcc 3 | CFLAGS = -Wall -g -fpic 4 | LDFLAGS = -shared 5 | 6 | OBJS = visualize-c-memory.o 7 | LIB = visualize-c-memory.so 8 | 9 | $(LIB): $(OBJS) 10 | $(CC) $(OBJS) -o $(LIB) $(LDFLAGS) 11 | 12 | clean: 13 | rm -f $(OBJS) $(LIB) 14 | -------------------------------------------------------------------------------- /src/visualize-c-memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This file should be either linked with a C program directly, or 4 | // compiled into a shared library and loaded via LD_PRELOAD. 5 | // 6 | // It overrides malloc/realloc/free, calling the real ones while recording 7 | // all currenly allocated memory in the heap_contents linked list. 8 | 9 | extern void* __libc_malloc(size_t); 10 | extern void* __libc_realloc(void*, size_t); 11 | extern void* __libc_calloc(size_t, size_t); 12 | extern void* __libc_free(void*); 13 | 14 | 15 | // list management ////////////////////////////////////////////////////////////// 16 | 17 | // List to store all heap pointers created by malloc 18 | typedef struct heap_node { 19 | void* pointer; 20 | size_t size; 21 | char source; // m: malloc, c: callor, r: realloc 22 | struct heap_node* next; 23 | } heap_node; 24 | static heap_node heap_contents = { .next = NULL }; // dummy node of a linked list of heap_node 25 | 26 | static void insert_pointer(void* pointer, int size, char source) { 27 | heap_node* node = __libc_malloc(sizeof(*node)); 28 | node->pointer = pointer; 29 | node->size = size; 30 | node->source = source; 31 | node->next = heap_contents.next; 32 | 33 | heap_contents.next = node; 34 | } 35 | 36 | static void update_pointer(void* pointer, int size, char source, void* old_pointer) { 37 | // for realloc, update the existing entry (to remain in the same position) 38 | for(heap_node* node = heap_contents.next; node != NULL; node = node->next) { 39 | if(node->pointer == old_pointer) { 40 | node->pointer = pointer; 41 | node->size = size; 42 | node->source = source; 43 | break; 44 | } 45 | } 46 | } 47 | 48 | static void remove_pointer(void* pointer) { 49 | // remove from the heap_contents list 50 | for(heap_node* prev = &heap_contents; prev != NULL; prev = prev->next) { 51 | heap_node* node = prev->next; 52 | if(node != NULL && node->pointer == pointer) { 53 | prev->next = node->next; 54 | __libc_free(node); 55 | break; 56 | } 57 | } 58 | } 59 | 60 | 61 | // wrappers //////////////// 62 | 63 | void* malloc(size_t size) { 64 | void* pointer = __libc_malloc(size); 65 | 66 | // libc seems to allocte 1024 bytes on start for its own use. Ignore it. 67 | if(!(heap_contents.next == NULL && size == 1024)) 68 | insert_pointer(pointer, size, 'm'); 69 | 70 | return pointer; 71 | } 72 | 73 | void* realloc(void* old_pointer, size_t size) { 74 | void* pointer = __libc_realloc(old_pointer, size); 75 | update_pointer(pointer, size, 'r', old_pointer); 76 | return pointer; 77 | } 78 | 79 | void* calloc(size_t n, size_t size) { 80 | void* pointer = __libc_calloc(n, size); 81 | insert_pointer(pointer, n * size, 'c'); 82 | return pointer; 83 | } 84 | 85 | void free(void* pointer) { 86 | remove_pointer(pointer); 87 | __libc_free(pointer); 88 | } 89 | -------------------------------------------------------------------------------- /src/visualize-c-memory.py: -------------------------------------------------------------------------------- 1 | import gdb # pyright: reportMissingImports=false 2 | import subprocess 3 | import json 4 | import html 5 | import traceback 6 | 7 | 8 | ### Register pretty printer ###################### 9 | 10 | class MemoryPrinter: 11 | def __init__(self): 12 | pass 13 | def to_string(self): 14 | return visualize_memory() 15 | 16 | def lookup_printer(value): 17 | # Use MemoryPrinter if value is the string "memory" 18 | if value.type.strip_typedefs().code == gdb.TYPE_CODE_ARRAY and value.type.target().strip_typedefs().code == gdb.TYPE_CODE_INT and value.string() == "memory": 19 | return MemoryPrinter() 20 | else: 21 | return None 22 | 23 | gdb.pretty_printers.append(lookup_printer) 24 | 25 | 26 | ### The actual visualization ######################## 27 | 28 | # Returns a json visualization of memory that can be consumed by vscode-debug-visualizer 29 | def visualize_memory(): 30 | try: 31 | return json.dumps({ 32 | 'kind': { 'svg': True }, 33 | 'text': svg_of_memory(), 34 | }) 35 | except BaseException as e: 36 | # display errors using the text visualizer 37 | return json.dumps({ 38 | 'kind': { 'text': True }, 39 | 'text': str(e) + "\n\n\n\n\n\n\n" + traceback.format_exc() 40 | }) 41 | 42 | def svg_of_memory(): 43 | memory = { 44 | 'stack': recs_of_stack(), 45 | 'heap': rec_of_heap(), 46 | } 47 | infer_heap_types(memory) 48 | 49 | # If the heap is too large, show only the last 100 entries 50 | if(len(memory['heap']['values']) > 100): 51 | memory['heap']['name'] = 'Heap (100 most recent entries)' 52 | memory['heap']['values'] = memory['heap']['values'][-100:] 53 | memory['heap']['fields'] = memory['heap']['fields'][-100:] 54 | 55 | dot = f""" 56 | digraph G {{ 57 | layout = neato; 58 | overlap = false; 59 | node [style=none, shape=none]; 60 | 61 | {dot_of_stack(memory)} 62 | dummy[pos="1,0!",style=invis,width=0.8]; // space 63 | {dot_of_heap(memory)} 64 | {dot_of_pointers(memory)} 65 | }} 66 | """ 67 | 68 | # debugging 69 | # print(dot) 70 | # return json.dumps({ 71 | # 'kind': { 'text': True }, 72 | # 'text': dot, 73 | # }) 74 | 75 | # vscode-debug-visualizer can directly display graphviz dot format. However 76 | # its implementation has issues when the visualization is updated, after the 77 | # update it's often corrupted. Maybe this has to do with the fact that 78 | # vscode-debug-visualizer runs graphviz in wasm (via viz.js). 79 | # 80 | # To avoid the isses we run graphviz ourselves, convert to svg, and use the svg visualizer. 81 | # The downside is that graphviz needs to be installed. 82 | svg = subprocess.run( 83 | ['dot', '-T', 'svg'], 84 | input=dot.encode('utf-8'), 85 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 86 | 87 | if svg.returncode != 0: 88 | raise Exception(f"dot failed:\n {svg.stderr.decode('utf-8')}\n\ndot source:\n{dot}") 89 | 90 | return svg.stdout.decode('utf-8') 91 | 92 | def dot_of_stack(memory): 93 | rows = [['Stack']] 94 | for frame_rec in memory['stack']: 95 | rows += rows_of_rec(frame_rec, memory) 96 | 97 | return f""" 98 | stack[pos="0,0!",label=< 99 | {table_of_rows(rows)} 100 | >]; 101 | """ 102 | 103 | def dot_of_heap(memory): 104 | # pos="2,0" makes heap to be slightly on the right of stack/dummy. 105 | # overlap = false will force it further to the right, to avoid overlap. 106 | # but pos="2,0" is important to establish the relative position between the two. 107 | 108 | rows = rows_of_rec(memory['heap'], memory) 109 | return f""" 110 | heap[pos="2,0!",label=< 111 | {table_of_rows(rows)} 112 | >]; 113 | """ 114 | 115 | def table_of_rows(rows): 116 | res = f""" 117 | 118 | """ 119 | 120 | col_n = max([len(row) for row in rows]) 121 | for row in rows: 122 | # the last cell is the address, put it first 123 | row.insert(0, row.pop()) 124 | 125 | # if the row has missing columns, add a colspan to the last cell 126 | if len(row) < col_n: 127 | row[-1] = row[-1].replace('{"".join(row)}\n' 130 | 131 | res += '
' 132 | return res 133 | 134 | def dot_of_pointers(memory): 135 | # construct stack:"XXXXXXX-right":e or heap:"XXXXXX-left":w 136 | def anchor_of_rec(rec): 137 | return rec['area'] + ':"' + rec["address"] + ('-right":e' if rec['area'] == 'stack' else '-left":w') 138 | 139 | res = "" 140 | for rec in find_pointers(memory): 141 | target_rec = lookup_address(rec['value'], memory) 142 | if target_rec is not None: 143 | res += f""" 144 | {anchor_of_rec(rec)} -> {anchor_of_rec(target_rec)}; 145 | """ 146 | return res 147 | 148 | def rows_of_rec(rec, memory): 149 | if rec['kind'] in ['array', 'struct', 'frame']: 150 | res = [] 151 | for i in range(len(rec['values'])): 152 | name = rec['fields'][i] if rec['kind'] != 'array' else str(i) 153 | value_rec = rec['values'][i] 154 | rows = rows_of_rec(value_rec, memory) 155 | 156 | if len(rows) == 0: # it can happen in case of empty array 157 | continue 158 | 159 | # the name is only inserted in the first row, with a rowspan to include all of them 160 | # an empty cell is also added to all other rows, so that len(row) always gives the number of cols 161 | rows[0].insert(0, f""" 162 | {name} 163 | """) 164 | for row in rows[1:]: 165 | row.insert(0, '') 166 | 167 | res += rows 168 | 169 | if rec['kind'] == 'frame': 170 | # at least 170 width, to avoid initial heap looking tiny 171 | res.insert(0, [f'{rec["name"]}']) 172 | 173 | else: 174 | color = 'red' if rec['kind'] == 'pointer' and rec['value'] != "0" and lookup_address(rec['value'], memory) is None else 'black' 175 | res = [[ 176 | f"""{rec['value']}""", 177 | f"""{rec['address']} ({rec['size']})""", 178 | ]] 179 | 180 | return res 181 | 182 | 183 | def address_within_rec(address, rec): 184 | address_i = int(address, 16) 185 | rec_i = int(rec['address'], 16) 186 | return address_i >= rec_i and address_i < rec_i + rec['size'] 187 | 188 | # Check if address is within any of the known records, if so return that record 189 | def lookup_address(address, memory): 190 | for rec in [memory['heap']] + memory['stack']: 191 | res = lookup_address_rec(address, rec) 192 | if res != None: 193 | return res 194 | return None 195 | 196 | def lookup_address_rec(address, rec): 197 | if rec['kind'] in ['array', 'struct', 'frame']: 198 | for value in rec['values']: 199 | res = lookup_address_rec(address, value) 200 | if res != None: 201 | return res 202 | return None 203 | else: 204 | return rec if address_within_rec(address, rec) else None 205 | 206 | 207 | # Check if any of the known (non-void) pointers points to address, if so return the rec of the pointer 208 | def lookup_pointer(address, memory): 209 | for rec in find_pointers(memory): 210 | # exclud void pointers 211 | if rec['value'] == address and rec['type'].target().code != gdb.TYPE_CODE_VOID: 212 | return rec 213 | return None 214 | 215 | # recursively finds all pointers 216 | def find_pointers(memory): 217 | return find_pointers_rec(memory['heap']) + \ 218 | [pointer for frame in memory['stack'] for pointer in find_pointers_rec(frame)] 219 | 220 | def find_pointers_rec(rec): 221 | if rec['kind'] in ['frame', 'array', 'struct']: 222 | return [pointer for rec in rec['values'] for pointer in find_pointers_rec(rec)] 223 | elif rec['kind'] == 'pointer': 224 | return [rec] 225 | else: 226 | return [] 227 | 228 | def format_pointer(val): 229 | return hex(int(val)).replace('0x', "") if val is not None else "" 230 | 231 | def rec_of_heap(): 232 | # we return a 'frame' rec 233 | rec = { 234 | 'kind': 'frame', 235 | 'name': 'Heap', 236 | 'fields': [], 237 | 'values': [], 238 | } 239 | 240 | # get the linked list from watch_heap.c 241 | try: 242 | heap_node_ptr = gdb.parse_and_eval("heap_contents")['next'] 243 | except: 244 | raise Exception( 245 | "Heap information not found.\n" 246 | "You need to load visualize-c-memory.so by setting the environment variable\n" 247 | " LD_PRELOAD=/visualize-c-memory.so\n" 248 | "_or_ link your program with visualize-c-memory.c" 249 | ) 250 | 251 | while int(heap_node_ptr) != 0: 252 | # read node from the linked list 253 | heap_node = heap_node_ptr.dereference() 254 | pointer = heap_node['pointer'] 255 | size = int(heap_node['size']) 256 | source = chr(heap_node['source']) 257 | heap_node_ptr = heap_node['next'] 258 | 259 | # for the moment we have no type information, so we just create an 'untyped' record 260 | rec['fields'].append(f"{'malloc' if source == 'm' else 'realloc' if source == 'r' else 'calloc'}({size})") 261 | rec['values'].append({ 262 | 'name': " ", # space to avoid errors 263 | 'value': "?", 264 | 'size': size, 265 | 'address': format_pointer(pointer), 266 | 'area': 'heap', 267 | 'kind': 'untyped', 268 | }) 269 | 270 | # the linked list contains the heap contents in reverse order 271 | rec['fields'].reverse() 272 | rec['values'].reverse() 273 | 274 | return rec 275 | 276 | def infer_heap_types(memory): 277 | for i,rec in enumerate(memory['heap']['values']): 278 | if rec['kind'] != 'untyped': 279 | continue 280 | 281 | incoming_pointer = lookup_pointer(rec['address'], memory) 282 | if incoming_pointer is None: 283 | continue 284 | 285 | type = incoming_pointer['type'] 286 | if type.target().code == gdb.TYPE_CODE_VOID: 287 | continue # void pointer, not useful 288 | 289 | if type.target().sizeof == 0: 290 | # pointer to incomplete struct, just add the type name to the "?" value 291 | code_name = 'struct ' if type.target().code == gdb.TYPE_CODE_STRUCT else \ 292 | 'union ' if type.target().code == gdb.TYPE_CODE_UNION else '' 293 | rec['value'] = f'? ({code_name}{type.target().name})' 294 | continue 295 | 296 | # we use the type information to get a typed value, then 297 | # replace the heap rec with a new one obtained from the typed value 298 | n = int(rec['size'] / type.target().sizeof) 299 | if n > 1: 300 | # the malloced space is larger than the pointer's target type, most likely this is used as an array 301 | # we treat the pointer as a pointer to array 302 | type = type.target().array(n-1).pointer() 303 | 304 | value = gdb.Value(int(rec['address'], 16)).cast(type).dereference() 305 | memory['heap']['values'][i] = rec_of_value(value, 'heap') 306 | 307 | # the new value might itself contain pointers which can be used to 308 | # type records we already processed. So re-start frrom scratch 309 | return infer_heap_types(memory) 310 | 311 | def recs_of_stack(): 312 | res = [] 313 | frame = gdb.newest_frame() 314 | while frame is not None: 315 | res.append(rec_of_frame(frame)) 316 | frame = frame.older() 317 | 318 | res.reverse() 319 | return res 320 | 321 | def rec_of_frame(frame): 322 | # we want blocks in reverse order, but symbols within the block in the correct order! 323 | blocks = [frame.block()] 324 | while blocks[0].function is None: 325 | blocks.insert(0, blocks[0].superblock) 326 | 327 | rec = { 328 | 'kind': 'frame', 329 | 'name': frame.function().name, 330 | 'fields': [], 331 | 'values': [], 332 | } 333 | for block in blocks: 334 | for symb in block: 335 | # avoid "weird" symbols, eg labels 336 | if not (symb.is_variable or symb.is_argument or symb.is_function or symb.is_constant): 337 | continue 338 | 339 | var = symb.name 340 | rec['fields'].append(var) 341 | rec['values'].append(rec_of_value(symb.value(frame), 'stack')) 342 | 343 | return rec 344 | 345 | def rec_of_value(value, area): 346 | type = value.type.strip_typedefs() 347 | rec = { 348 | 'size': type.sizeof, 349 | 'address': format_pointer(value.address), 350 | 'type': type, 351 | 'area': area, 352 | } 353 | 354 | if type.code == gdb.TYPE_CODE_ARRAY: 355 | # stack arrays of dynamic length (eg int foo[n]) might have huge size before the 356 | # initialization code runs! In this case replace type with one of size 0 357 | if int(type.sizeof) > 1000: 358 | type = type.target().array(-1) 359 | 360 | array_size = int(type.sizeof / type.target().sizeof) 361 | 362 | rec['values'] = [rec_of_value(value[i], area) for i in range(array_size)] 363 | rec['kind'] = 'array' 364 | 365 | elif type.code == gdb.TYPE_CODE_STRUCT: 366 | rec['fields'] = [field.name for field in type.fields()] 367 | rec['values'] = [rec_of_value(value[field], area) for field in type.fields()] 368 | rec['kind'] = 'struct' 369 | 370 | else: 371 | # treat function pointers as scalar values 372 | is_pointer = type.code == gdb.TYPE_CODE_PTR 373 | func_pointer = is_pointer and type.target().code == gdb.TYPE_CODE_FUNC 374 | 375 | if is_pointer and not func_pointer: 376 | rec['value'] = format_pointer(value) 377 | rec['kind'] = 'pointer' 378 | else: 379 | try: 380 | rec['value'] = html.escape(value.format_string()) 381 | except: 382 | rec['value'] = '?' 383 | rec['kind'] = 'other' 384 | if func_pointer: 385 | rec['value'] = rec['value'].replace("0x", "").replace(" ", "
") 386 | 387 | return rec --------------------------------------------------------------------------------