├── .gitignore ├── CREDITS ├── LICENSE ├── README.md ├── debugger ├── __init__.py ├── alloc_hook.cpp ├── analysis │ ├── __init__.py │ └── source_analyser.py ├── debugee.py ├── debugger_api.py ├── enums.py ├── gdbc │ ├── __init__.py │ ├── command_script.py │ ├── gdb_breakpoint_manager.py │ ├── gdb_debugger.py │ ├── gdb_file_manager.py │ ├── gdb_frame_manager.py │ ├── gdb_helper.py │ ├── gdb_launcher.py │ ├── gdb_thread_manager.py │ ├── memory.py │ ├── type.py │ └── variable.py ├── lldbc │ ├── __init__.py │ ├── command_script.py │ ├── lldb_breakpoint_manager.py │ ├── lldb_debugger.py │ ├── lldb_file_manager.py │ ├── lldb_helper.py │ ├── lldb_io_manager.py │ ├── lldb_launcher.py │ ├── lldb_memory_manager.py │ ├── lldb_thread_manager.py │ └── lldb_variable_editor.py ├── mi │ ├── __init__.py │ ├── breakpoint_manager.py │ ├── communicator.py │ ├── file_manager.py │ ├── gdb_pretty_print.py │ ├── heap_manager.py │ ├── io_manager.py │ ├── mi_debugger.py │ ├── parser.py │ ├── thread_manager.py │ └── variable_manager.py ├── net │ ├── __init__.py │ ├── client.py │ ├── command.py │ ├── helper.py │ └── server.py ├── pycgdb │ ├── Makefile │ ├── __init__.py │ ├── breakpoint_manager.py │ ├── debugger.py │ ├── programinfo.py │ ├── programrunner.py │ ├── ptrace.c │ └── ptrace.py ├── util.py └── wscript ├── epydoc ├── examples ├── pointers │ ├── pointers.cpp │ └── wscript ├── sg-struct │ ├── sg-struct.cpp │ ├── sg-struct.h │ └── wscript ├── sort │ ├── sort.cpp │ └── wscript ├── threads │ ├── threads.cpp │ └── wscript └── wscript ├── gui ├── __init__.py ├── app.py ├── config.py ├── console.py ├── dialog.py ├── drawing │ ├── __init__.py │ ├── canvas.py │ ├── drawable.py │ ├── geometry.py │ ├── memtoview.py │ ├── mouse.py │ ├── size.py │ ├── vector.py │ └── widgets.py ├── gui_util.py ├── heap_detail.py ├── initialize.py ├── memory_view.py ├── paths.py ├── selector.py ├── source_edit.py ├── startup_dialog.py ├── tool_manager.py ├── toolbar_manager.py └── window.py ├── install.sh ├── res ├── css │ └── style.css ├── gui │ ├── io_console.glade │ ├── main_window_menu.glade │ ├── main_window_toolbar.glade │ ├── memory_canvas_toolbar.glade │ └── startup_info_dialog.glade └── img │ ├── arrow.png │ ├── circle.png │ ├── circle32x32.png │ └── reload.gif ├── start.sh ├── tests ├── __init__.py ├── conftest.py ├── src │ ├── __init__.py │ ├── compile.py │ ├── test_alloc.cpp │ ├── test_breakpoint_basic.cpp │ ├── test_disassemble.cpp │ ├── test_execution.cpp │ ├── test_frame.cpp │ ├── test_startup_info.cpp │ ├── test_thread.cpp │ ├── test_type.cpp │ └── test_variable.cpp ├── test_mi_alloc.py ├── test_mi_basic.py ├── test_mi_breakpoint.py ├── test_mi_disassemble.py ├── test_mi_execution.py ├── test_mi_frame.py ├── test_mi_parser.py ├── test_mi_startup_info.py ├── test_mi_thread.py ├── test_mi_type.py └── test_mi_variable.py ├── thesis └── thesis.pdf ├── util ├── lldb_patch.sh └── stl_printers.py ├── waf └── wscript /.gitignore: -------------------------------------------------------------------------------- 1 | *.pydevproject 2 | .metadata 3 | .gradle 4 | bin/ 5 | tmp/ 6 | *.tmp 7 | *.bak 8 | *.swp 9 | *~.nib 10 | local.properties 11 | .settings/ 12 | .loadpath 13 | .idea/ 14 | 15 | # Eclipse Core 16 | .project 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # CDT-specific 25 | .cproject 26 | 27 | # JDT-specific (Eclipse Java Development Tools) 28 | .classpath 29 | 30 | # PDT-specific 31 | .buildpath 32 | 33 | # sbteclipse plugin 34 | .target 35 | 36 | # TeXlipse plugin 37 | .texlipse 38 | 39 | # Glade temporary files 40 | *.glade~ 41 | 42 | # Python compiled files 43 | *.pyc 44 | 45 | # waf 46 | build/ 47 | .waf* 48 | .lock* 49 | 50 | docs/ 51 | tests/src/* 52 | !tests/src/*.py 53 | !tests/src/*.cpp 54 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | Images: 2 | Breakpoint icon created by Daniele De Santis 3 | from http://www.iconarchive.com/artist/danieledesantis.html. 4 | Current line icon designed by Freepik from http://www.flaticon.com. 5 | Loading icon generated at http://loading.io and modified at http://ezgif.com. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Devi (debug visualizer) 2 | ======================= 3 | 4 | Devi is a graphical debugger frontend that visualizes the debugged 5 | program's memory in a form of an object diagram. It can be used for 6 | debugging C/C++ programs with the attached GDB 7.11. 7 | 8 | It contains a self-contained communication library that can be adapted 9 | to work with various other debuggers (it can be found in the package 10 | `debugger`). 11 | 12 | The application is licensed under GNU GPLv3. 13 | Resource attributions can be found in CREDITS. 14 | 15 | This software work has been created as a part of a bachelor thesis 16 | at VŠB-TUO: Technical University of Ostrava. The text of the thesis 17 | can be found in the folder `thesis`. 18 | 19 | Dependencies 20 | ============ 21 | * `Python 2.7.x` 22 | * `GTK3 3.10.8+` 23 | * `g++` 24 | * python packages `enum34`, `matplotlib`, `clang` 25 | * debian packages `python-dev`, `clang-3.6`, `texinfo` 26 | * (optional) python package `pytest` (for tests) 27 | * (optional) python package `epydoc` (for documentation generation) 28 | * (optional, only on 64-bit OS) debian package `g++-multilib` (for compiling test binaries) 29 | 30 | Quick package installation 31 | `sudo apt-get install python-enum34 python-matplotlib python-clang-3.6 32 | clang-3.6 g++ texinfo python-dev python-pytest python-epydoc` 33 | 34 | 35 | Build 36 | ===== 37 | The build will download and compile a source distribution of GDB 7.11. 38 | 39 | Use Waf 40 | ``` 41 | ./waf download (this will install necessary packages using Aptitude) 42 | ./waf configure 43 | ./waf build 44 | ``` 45 | or just launch the attached install script 46 | ``` 47 | ./install.sh 48 | ``` 49 | 50 | Launch 51 | ====== 52 | Use Python directly 53 | ``` 54 | python gui/initialize.py [path_to_debugged_binary] 55 | ``` 56 | or use the attached launch script 57 | ``` 58 | ./start.sh 59 | ``` 60 | 61 | You can try the attached example programs (they are placed in 62 | `build/examples` after build). -------------------------------------------------------------------------------- /debugger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/debugger/__init__.py -------------------------------------------------------------------------------- /debugger/alloc_hook.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015-2016 Jakub Beranek 3 | 4 | This file is part of Devi. 5 | 6 | Devi is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Devi is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Devi. If not, see . 18 | */ 19 | 20 | #ifndef _GNU_SOURCE 21 | #define _GNU_SOURCE 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | #define ALLOC_FILE_TMP_VALUE ((FILE*) 1) 34 | 35 | typedef void* (*malloc_orig_t)(size_t size); 36 | static malloc_orig_t malloc_orig = NULL; 37 | 38 | typedef void* (*calloc_orig_t)(size_t num, size_t size); 39 | static calloc_orig_t calloc_orig = NULL; 40 | 41 | typedef void* (*realloc_orig_t)(void* addr, size_t size); 42 | static realloc_orig_t realloc_orig = NULL; 43 | 44 | typedef void (*free_orig_t)(void* addr); 45 | static free_orig_t free_orig = NULL; 46 | 47 | static FILE* alloc_file = NULL; 48 | static bool load_in_progress = false; 49 | 50 | static std::mutex alloc_mutex; 51 | typedef std::mutex devi_mutex; 52 | 53 | #define STATIC_BUFFER_SIZE (1024) 54 | static char static_buffer[STATIC_BUFFER_SIZE] = { 0 }; 55 | static size_t static_buffer_index = 0; 56 | 57 | template 58 | void load_symbol(T& target, const char* name) 59 | { 60 | if (!target) 61 | { 62 | target = (T) dlsym(RTLD_NEXT, name); 63 | assert(target); 64 | } 65 | } 66 | 67 | void close_file(void) 68 | { 69 | if (alloc_file) 70 | { 71 | fclose(alloc_file); 72 | } 73 | } 74 | 75 | void open_alloc_file() 76 | { 77 | if (!alloc_file) 78 | { 79 | alloc_file = ALLOC_FILE_TMP_VALUE; // to prevent stack overflow for malloc in fopen 80 | 81 | char* path = getenv("DEVI_ALLOC_FILE_PATH"); 82 | assert(path); 83 | 84 | alloc_file = fopen(path, "w"); 85 | 86 | assert(alloc_file); 87 | atexit(close_file); 88 | } 89 | } 90 | 91 | void load_symbols() 92 | { 93 | std::lock_guard lock(alloc_mutex); 94 | 95 | if (load_in_progress) 96 | { 97 | return; 98 | } 99 | 100 | load_in_progress = true; 101 | 102 | if (!malloc_orig) load_symbol(malloc_orig, "malloc"); 103 | if (!calloc_orig) load_symbol(calloc_orig, "calloc"); 104 | if (!realloc_orig) load_symbol(realloc_orig, "realloc"); 105 | if (!free_orig) load_symbol(free_orig, "free"); 106 | if (!alloc_file) open_alloc_file(); 107 | 108 | load_in_progress = false; 109 | } 110 | 111 | void* static_alloc(size_t size) 112 | { 113 | assert(static_buffer_index + size <= STATIC_BUFFER_SIZE); 114 | size_t index = static_buffer_index; 115 | static_buffer_index += size; 116 | return static_buffer + index; 117 | } 118 | 119 | void* malloc(size_t size) 120 | { 121 | if (load_in_progress) 122 | { 123 | return static_alloc(size); 124 | } 125 | else load_symbols(); 126 | 127 | void* addr = malloc_orig(size); 128 | fprintf(alloc_file, "malloc %p %zu\n", addr, size); 129 | fflush(alloc_file); 130 | 131 | return addr; 132 | } 133 | 134 | void* calloc(size_t num, size_t size) 135 | { 136 | if (load_in_progress) 137 | { 138 | return static_alloc(size); 139 | } 140 | else load_symbols(); 141 | 142 | void* addr = calloc_orig(num, size); 143 | fprintf(alloc_file, "calloc %p %zu\n", addr, num * size); 144 | fflush(alloc_file); 145 | 146 | return addr; 147 | } 148 | 149 | void* realloc(void* addr, size_t size) 150 | { 151 | if (load_in_progress) 152 | { 153 | return static_alloc(size); 154 | } 155 | else load_symbols(); 156 | 157 | void* addr_new = realloc_orig(addr, size); 158 | fprintf(alloc_file, "realloc %p %p %zu\n", addr, addr_new, size); 159 | fflush(alloc_file); 160 | 161 | return addr_new; 162 | } 163 | 164 | void free(void* addr) 165 | { 166 | if (addr >= static_buffer && addr <= static_buffer + STATIC_BUFFER_SIZE) 167 | { 168 | return; 169 | } 170 | 171 | if (!load_in_progress) 172 | { 173 | load_symbols(); 174 | } 175 | 176 | if (addr == NULL) 177 | { 178 | fprintf(alloc_file, "free NULL\n"); 179 | } 180 | else fprintf(alloc_file, "free %p\n", addr); 181 | fflush(alloc_file); 182 | 183 | free_orig(addr); 184 | } -------------------------------------------------------------------------------- /debugger/analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/debugger/analysis/__init__.py -------------------------------------------------------------------------------- /debugger/analysis/source_analyser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import glob 23 | 24 | 25 | clang_path = glob.glob("/usr/lib/*/*/libclang.so*") 26 | 27 | if len(clang_path) < 1: 28 | clang_path = glob.glob("/usr/lib/libclang.so") 29 | 30 | if len(clang_path) < 1: 31 | raise BaseException("Clang was not found") 32 | 33 | import clang.cindex as cindex # noqa 34 | 35 | cindex.Config.set_library_file(clang_path[0]) 36 | 37 | 38 | class SourceAnalyzer(object): 39 | def __init__(self, file_path=None): 40 | self.tu = None 41 | self.file = None 42 | 43 | if file_path: 44 | self.set_file(file_path) 45 | 46 | def _create_tu_from_file(self, file_path): 47 | return cindex.TranslationUnit.from_source(file_path, []) 48 | 49 | def _get_location(self, offset, column=None): 50 | if column: 51 | return cindex.SourceLocation.from_position(self.tu, self.file, 52 | offset, 53 | column) 54 | else: 55 | return cindex.SourceLocation.from_offset(self.tu, self.file, 56 | offset) 57 | 58 | def set_file(self, file_path): 59 | """ 60 | Parses the given file and returns whether the parsing was successful. 61 | @type file_path: str 62 | @rtype: bool 63 | """ 64 | try: 65 | self.tu = self._create_tu_from_file(file_path) 66 | self.file = cindex.File.from_name(self.tu, file_path) 67 | return True 68 | except: 69 | return False 70 | 71 | def get_symbol_name(self, line, column=None): 72 | """ 73 | Get's name of a symbol defined on the given line and column. 74 | Returns None if no symbol was found or if no file is loaded. 75 | @type line: int 76 | @type column: int 77 | @rtype: str | None 78 | """ 79 | if self.tu is None or self.file is None: 80 | return None 81 | 82 | location = self._get_location(line, column) 83 | cursor = cindex.Cursor.from_location(self.tu, location) 84 | 85 | return cursor.spelling 86 | -------------------------------------------------------------------------------- /debugger/enums.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2015-2016 Jakub Beranek 3 | # 4 | # This file is part of Devi. 5 | # 6 | # Devi is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Devi is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with Devi. If not, see . 18 | # 19 | 20 | from enum import Enum 21 | 22 | 23 | class DebuggerState(Enum): 24 | """ 25 | Represents debugger state. 26 | """ 27 | Started = 1 28 | BinaryLoaded = 2 29 | Running = 3 30 | 31 | 32 | class ProcessState(Enum): 33 | """ 34 | Represents the debugged process' state. 35 | """ 36 | Attaching = 3 37 | Connected = 2 38 | Crashed = 8 39 | Detached = 9 40 | Exited = 10 41 | Invalid = 0 42 | Launching = 4 43 | Running = 6 44 | Stepping = 7 45 | Stopped = 5 46 | Suspended = 11 47 | Unloaded = 1 48 | 49 | 50 | class ThreadState(Enum): 51 | """ 52 | Represents thread state. 53 | """ 54 | Running = 1 55 | Stopped = 2 56 | Exited = 3 57 | 58 | 59 | class StopReason(Enum): 60 | """ 61 | Reason why the debugged process stoped. 62 | """ 63 | Breakpoint = 3 64 | Exception = 6 65 | Exec = 7 66 | Invalid = 0 67 | NoReason = 1 68 | PlanComplete = 8 69 | Signal = 5 70 | ThreadExiting = 9 71 | Trace = 2 72 | Watchpoint = 4 73 | 74 | 75 | class TypeCategory(Enum): 76 | """ 77 | Represents type of C/C++ type. 78 | """ 79 | Any = -1 80 | Array = 1 81 | BlockPointer = 2 82 | Builtin = 4 83 | Class = 8 84 | ComplexFloat = 16 85 | ComplexInteger = 32 86 | Enumeration = 64 87 | Function = 128 88 | Invalid = 0 89 | MemberPointer = 256 90 | ObjCInterface = 1024 91 | ObjCObject = 512 92 | ObjCObjectPointer = 2048 93 | Other = -2147483648 94 | Pointer = 4096 95 | CString = 4097 96 | Reference = 8192 97 | Struct = 16384 98 | Typedef = 32768 99 | Union = 65536 100 | Vector = 131072 101 | String = 42 102 | 103 | def nice_name(self): 104 | name_mappings = { 105 | TypeCategory.Class: "class", 106 | TypeCategory.Struct: "struct" 107 | } 108 | return name_mappings.get(self, str(self)) 109 | 110 | 111 | class BasicTypeCategory(Enum): 112 | """ 113 | Represents type of a primitive C/C++ type. 114 | """ 115 | Bool = 20 116 | Char = 2 117 | Char16 = 8 118 | Char32 = 9 119 | Double = 23 120 | DoubleComplex = 26 121 | Float = 22 122 | FloatComplex = 25 123 | Half = 21 124 | Int = 12 125 | Int128 = 18 126 | Invalid = 0 127 | Long = 14 128 | LongDouble = 24 129 | LongDoubleComplex = 27 130 | LongLong = 16 131 | NullPtr = 31 132 | ObjCClass = 29 133 | ObjCID = 28 134 | ObjCSel = 30 135 | Other = 32 136 | Short = 10 137 | SignedChar = 3 138 | SignedWChar = 6 139 | UnsignedChar = 4 140 | UnsignedInt = 13 141 | UnsignedInt128 = 19 142 | UnsignedLong = 15 143 | UnsignedLongLong = 17 144 | UnsignedShort = 11 145 | UnsignedWChar = 7 146 | Void = 1 147 | WChar = 5 148 | 149 | @staticmethod 150 | def is_char(type): 151 | """ 152 | @type type: BasicTypeCategory 153 | @rtype: bool 154 | """ 155 | return type in (BasicTypeCategory.Char, 156 | BasicTypeCategory.Char16, 157 | BasicTypeCategory.Char32, 158 | BasicTypeCategory.SignedChar, 159 | BasicTypeCategory.UnsignedChar, 160 | BasicTypeCategory.SignedWChar, 161 | BasicTypeCategory.UnsignedWChar, 162 | BasicTypeCategory.WChar) 163 | -------------------------------------------------------------------------------- /debugger/gdbc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/debugger/gdbc/__init__.py -------------------------------------------------------------------------------- /debugger/gdbc/command_script.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import gdb 23 | import json 24 | import os 25 | import sys 26 | 27 | 28 | class TestCommand(gdb.Command): 29 | def __init__(self, debugger): 30 | gdb.Command.__init__(self, "tnext", gdb.COMMAND_NONE) 31 | self.debugger = debugger 32 | 33 | def invoke(self, argument, from_tty): 34 | print(self.debugger.file_manager.get_current_location()) 35 | 36 | data = {} 37 | 38 | with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 39 | "options.json"), "r") as options: 40 | data = json.loads(options.read()) 41 | 42 | sys.path.append(data["code_path"]) 43 | 44 | from debugger.gdbc.gdb_debugger import GdbDebugger # noqa 45 | from debugger.net.server import Server # noqa 46 | 47 | debugger = GdbDebugger(data) 48 | tstcommand = TestCommand(debugger) 49 | 50 | server = Server(data["server_port"], debugger) 51 | server.start() 52 | -------------------------------------------------------------------------------- /debugger/gdbc/gdb_breakpoint_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import gdb 23 | 24 | 25 | class GdbBreakpointManager(object): 26 | def __init__(self, before_bp, after_bp): 27 | self.before_bp = before_bp 28 | self.after_bp = after_bp 29 | gdb.events.stop.connect(self.handle_break) 30 | 31 | self.breakpoints = [] 32 | 33 | def handle_break(self, stop_event): 34 | self.before_bp(stop_event) 35 | 36 | callback = None 37 | 38 | for bp, cb in self.breakpoints: 39 | if bp in stop_event.breakpoints: 40 | callback = cb 41 | break 42 | 43 | if callback: 44 | cb(stop_event) # inspecting callback, continue execution 45 | self.after_bp(stop_event, True) 46 | else: 47 | self.after_bp(stop_event, False) 48 | 49 | def add_breakpoint(self, location, callback=None): 50 | self.breakpoints.append((gdb.Breakpoint(location, internal=True), 51 | callback)) 52 | 53 | def remove_breakpoint(self, location): 54 | for bp, callback in self.breakpoints: 55 | if bp.location == location: 56 | bp.delete() 57 | 58 | self.breakpoints = [bp for bp in self.breakpoints if 59 | bp[0].location != location] 60 | 61 | def remove_all_breakpoints(self): 62 | for bp, callback in self.breakpoints: 63 | bp.delete() 64 | 65 | self.breakpoints = [] 66 | -------------------------------------------------------------------------------- /debugger/gdbc/gdb_debugger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import gdb 23 | import sys 24 | 25 | from gdb_helper import GdbHelper 26 | from gdb_thread_manager import GdbThreadManager 27 | from gdb_frame_manager import GdbFrameManager 28 | from gdb_file_manager import GdbFileManager 29 | from gdb_breakpoint_manager import GdbBreakpointManager 30 | from memory import Memory 31 | from debugger.enums import DebuggerState 32 | 33 | 34 | class GdbDebugger(object): 35 | def __init__(self, options): 36 | self.memory = Memory() 37 | self.gdb_helper = GdbHelper() 38 | self.thread_manager = GdbThreadManager() 39 | self.frame_manager = GdbFrameManager() 40 | self.file_manager = GdbFileManager() 41 | self.bp_manager = GdbBreakpointManager(self.before_bp, self.after_bp) 42 | # self.bp_manager.add_breakpoint("malloc", self.handle_malloc) 43 | # self.bp_manager.add_breakpoint("free", self.handle_free) 44 | 45 | self.set_options() 46 | gdb.events.exited.connect(self.handle_exit) 47 | 48 | if "valgrind_pid" in options: 49 | gdb.execute("target remote | vgdb --pid={}".format( 50 | options["valgrind_pid"])) 51 | 52 | self.change_state(DebuggerState.Started) 53 | 54 | def get_status(self): 55 | return self.state 56 | 57 | def set_options(self): 58 | gdb.execute("set print elements 0") 59 | 60 | def change_state(self, state): 61 | self.state = state 62 | print("Changed state: " + str(DebuggerState(state))) 63 | sys.stdout.flush() 64 | 65 | def handle_exit(self, exit_event): 66 | self.change_state(DebuggerState.Exited) 67 | self.bp_manager.remove_all_breakpoints() 68 | 69 | def before_bp(self, stop_event): 70 | self.change_state(DebuggerState.Stopped) 71 | 72 | """try: 73 | locals = self.thread_manager.get_thread_vars()[0] 74 | self.memory.match_pointers(locals) 75 | 76 | if isinstance(stop_event, gdb.SignalEvent): 77 | print("ERROR: " + str(stop_event.stop_signal)) 78 | 79 | if not isinstance(stop_event, gdb.BreakpointEvent): 80 | return False 81 | 82 | return True 83 | except: 84 | traceback.print_exc() 85 | return False""" 86 | 87 | def after_bp(self, stop_event, continue_exec): 88 | if continue_exec: 89 | self.continue_exec() 90 | 91 | def continue_exec(self): 92 | self.change_state(DebuggerState.Running) 93 | gdb.execute("continue") 94 | 95 | def finish(self): 96 | self.change_state(DebuggerState.Running) 97 | gdb.execute("finish") 98 | 99 | def run(self): 100 | self.change_state(DebuggerState.Running) 101 | gdb.execute("run") 102 | 103 | def next(self): 104 | self.change_state(DebuggerState.Running) 105 | gdb.execute("next") 106 | 107 | def is_valid_memory_frame(self, frame): 108 | return frame.is_valid() and frame.type() in [gdb.NORMAL_FRAME, 109 | gdb.INLINE_FRAME] 110 | 111 | def handle_malloc(self, stop_event): 112 | frame = gdb.newest_frame() 113 | frame_name = frame.name() 114 | 115 | if not self.is_valid_memory_frame(frame): 116 | return 117 | 118 | args = self.gdb_helper.get_args() 119 | 120 | if len(args) == 1: 121 | byte_count = args[0].value 122 | finish = gdb.FinishBreakpoint(internal=False) 123 | 124 | if not finish.is_valid(): 125 | return 126 | 127 | self.finish() 128 | 129 | if not finish.is_valid(): 130 | return 131 | 132 | address = str(finish.return_value) 133 | self.memory.malloc(address, byte_count, frame_name) 134 | 135 | if finish.is_valid(): 136 | finish.delete() 137 | 138 | def handle_free(self, stop_event): 139 | frame = gdb.newest_frame() 140 | 141 | if not self.is_valid_memory_frame(frame): 142 | return 143 | 144 | args = self.gdb_helper.get_args() 145 | 146 | if len(args) == 1: 147 | address = self.gdb_helper.get_args()[0].value 148 | self.memory.free(address) 149 | -------------------------------------------------------------------------------- /debugger/gdbc/gdb_file_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import gdb 23 | 24 | 25 | class GdbFileManager(object): 26 | """ 27 | Returns tuple (filename, line_number). 28 | """ 29 | def get_current_location(self): 30 | try: 31 | frame = gdb.selected_frame() 32 | sal = frame.find_sal() 33 | symtab = sal.symtab 34 | 35 | return (symtab.fullname(), sal.line) 36 | except: 37 | return None 38 | -------------------------------------------------------------------------------- /debugger/gdbc/gdb_frame_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import gdb 23 | 24 | 25 | class GdbFrameManager(object): 26 | def get_frames(self): 27 | frame_lines = gdb.execute("bt", to_string=True) 28 | frames = [] 29 | frame = "" 30 | 31 | for line in frame_lines.split("\n"): 32 | if len(line) > 0 and line[0] == "#": 33 | if frame != "": 34 | frames.append(frame.strip()) 35 | frame = line 36 | else: 37 | frame += " " + line 38 | 39 | frames.append(frame.strip()) 40 | 41 | return frames 42 | -------------------------------------------------------------------------------- /debugger/gdbc/gdb_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import gdb 23 | import re 24 | 25 | from variable import Variable 26 | from type import Type 27 | 28 | 29 | class GdbHelper(object): 30 | def parse_var_description(self, data): 31 | match = re.search(r"([^=]*)=(.*)", data) 32 | return (match.group(1).strip(), match.group(2).strip()) 33 | 34 | def parse_var_descriptions(self, data): 35 | variables = [] 36 | 37 | for line in data.split("\n"): 38 | if "=" in line: 39 | variables.append(self.parse_var_description(line)) 40 | 41 | return variables 42 | 43 | def _get_variables_info(self, var_descriptions): 44 | """ 45 | Returns the given variable names as a list of gdb.Values. 46 | 47 | var_descriptions: list of tuples in the form (name, value) of 48 | visible variables 49 | """ 50 | vars = [] 51 | 52 | for var in var_descriptions: 53 | gdb_val = gdb.parse_and_eval(var[0]) 54 | basic_var = Variable(str(gdb_val), 55 | Type.from_gdb_type(gdb_val.type), 56 | var[0], 57 | gdb_val.address) 58 | vars.append(basic_var) 59 | 60 | return vars 61 | 62 | def get_variables(self, name): 63 | descriptions = self.parse_var_descriptions(gdb.execute( 64 | "info {}".format(name), to_string=True)) 65 | return self._get_variables_info(descriptions) 66 | 67 | def get_locals(self): 68 | return self.get_variables("locals") 69 | 70 | def get_args(self): 71 | return self.get_variables("args") 72 | 73 | def get_globals(self): 74 | return self.get_variables("variables") 75 | -------------------------------------------------------------------------------- /debugger/gdbc/gdb_launcher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import json 23 | import os 24 | import shutil 25 | import subprocess 26 | import tempfile 27 | 28 | 29 | class GdbLauncher(object): 30 | def __init__(self, binary_path, server_port): 31 | self.gdb_path = "/home/kobzol/Downloads/gdb-7.10/gdb/gdb" 32 | self.binary_path = binary_path 33 | self.server_port = server_port 34 | 35 | self.debugger_process = None 36 | self.valgrind_process = None 37 | 38 | self.running = False 39 | 40 | def is_running(self): 41 | if self.running: 42 | if self.debugger_process.poll() is not None: 43 | self.stop() 44 | 45 | return self.running 46 | 47 | def launch(self, program_arguments=None, script_arguments=None, 48 | use_valgrind=False): 49 | if program_arguments is None: 50 | program_arguments = [] 51 | 52 | if script_arguments is None: 53 | script_arguments = {} 54 | 55 | if not self.is_running(): 56 | script_arguments["code_path"] = os.path.dirname( 57 | os.path.dirname(os.path.abspath(__file__))) 58 | script_arguments["server_port"] = self.server_port 59 | 60 | self.prepare_tmp_folder(os.path.join(os.path.join( 61 | script_arguments["code_path"], "gdbc"), "command_script.py")) 62 | 63 | launch_data = [self.gdb_path, 64 | "--silent", 65 | "-x", 66 | self.get_tmp_script_path(), 67 | "--args", 68 | self.binary_path] + program_arguments 69 | 70 | if use_valgrind: 71 | self.valgrind_process = subprocess.Popen( 72 | ["valgrind", 73 | "--tool=memcheck", 74 | "--vgdb=yes", 75 | "--vgdb-error=0", 76 | self.binary_path], 77 | stdout=subprocess.PIPE, 78 | stderr=subprocess.PIPE) 79 | script_arguments["valgrind_pid"] = self.valgrind_process.pid 80 | 81 | self.write_script_options(script_arguments) 82 | 83 | self.debugger_process = subprocess.Popen( 84 | launch_data, 85 | stdin=subprocess.PIPE) 86 | # stdout=subprocess.PIPE, stderr=subprocess.PIPE) 87 | self.running = True 88 | 89 | def stop(self): 90 | if self.is_running(): 91 | self.debugger_process.kill() 92 | 93 | if self.valgrind_process: 94 | self.valgrind_process.kill() 95 | self.valgrind_process = None 96 | 97 | self.debugger_process = None 98 | 99 | self.running = False 100 | shutil.rmtree(self.tmpdir, ignore_errors=True) 101 | 102 | def get_tmp_script_path(self): 103 | return os.path.join(self.tmpdir, "dv_script.py") 104 | 105 | def get_tmp_options_path(self): 106 | return os.path.join(self.tmpdir, "options.json") 107 | 108 | def prepare_tmp_folder(self, script_path): 109 | self.tmpdir = tempfile.mkdtemp(prefix="dv") 110 | shutil.copy(script_path, self.get_tmp_script_path()) 111 | 112 | def write_script_options(self, options): 113 | with open(self.get_tmp_options_path(), "w") as options_file: 114 | options_file.write(json.dumps(options)) 115 | -------------------------------------------------------------------------------- /debugger/gdbc/gdb_thread_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import gdb 23 | from gdb_helper import GdbHelper 24 | 25 | 26 | class GdbThreadManager(object): 27 | def __init__(self): 28 | self.gdb_helper = GdbHelper() 29 | self.thread_stack = [] 30 | 31 | def get_inferior(self): 32 | return gdb.selected_inferior() 33 | 34 | def get_current_thread(self): 35 | return gdb.selected_thread() 36 | 37 | def get_thread(self, thread_id): 38 | return self.get_threads()[thread_id - 1] 39 | 40 | def get_threads(self): 41 | return sorted(self.get_inferior().threads(), key=lambda x: x.num) 42 | 43 | def switch_to_thread(self, thread_id): 44 | self.get_threads()[thread_id - 1].switch() 45 | 46 | def push_thread(self, new_thread_id): 47 | self.thread_stack.append(self.get_current_thread()) 48 | self.switch_to_thread(new_thread_id) 49 | 50 | def pop_thread(self): 51 | self.switch_to_thread(self.thread_stack.pop().num) 52 | 53 | def get_thread_vars(self, thread_id=None): 54 | if thread_id is not None: 55 | self.push_thread(thread_id) 56 | 57 | locals = self.gdb_helper.get_locals() 58 | args = self.gdb_helper.get_args() 59 | 60 | if thread_id is not None: 61 | self.pop_thread() 62 | 63 | return (locals, args) 64 | -------------------------------------------------------------------------------- /debugger/gdbc/memory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import re 23 | 24 | import type 25 | 26 | 27 | class MemoryBlock(object): 28 | def __init__(self, address, length, type=None): 29 | self.address = str(address) 30 | self.length = int(length) 31 | self.type = type 32 | 33 | def __repr__(self): 34 | return "{} ({}, {})".format(self.address, self.length, self.type) 35 | 36 | 37 | class Memory(object): 38 | def __init__(self): 39 | self.blocks = [] 40 | 41 | def match_pointers(self, variables): 42 | pointers = [] 43 | for var in variables: 44 | if var.type.code == type.TypeCode.Pointer: 45 | pointers.append(var) 46 | 47 | for pointer in pointers: 48 | for block in self.blocks: 49 | if block.address == pointer.value: 50 | block.type = pointer.type.target 51 | 52 | def malloc(self, address, length, fn_name): 53 | if address and len(address) > 0: 54 | block = MemoryBlock(address, length) 55 | 56 | if fn_name: 57 | match = re.search(r"operator new\(([^)]*)\)", fn_name) 58 | if match: 59 | type_name = match.group(1) 60 | block.type = type_name 61 | 62 | self.blocks.append(block) 63 | 64 | def free(self, address): 65 | freed_block = [block 66 | for block 67 | in self.blocks 68 | if block.address == address] 69 | 70 | if len(freed_block) == 0: 71 | print("DOUBLE FREE!") 72 | else: 73 | self.blocks[:] = [block 74 | for block 75 | in self.blocks 76 | if block.address != address] 77 | -------------------------------------------------------------------------------- /debugger/gdbc/type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from enum import IntEnum 23 | 24 | 25 | class TypeCode(IntEnum): 26 | Pointer = 1 27 | Array = 2 28 | Struct = 3 29 | Union = 4 30 | Enum = 5 31 | Int = 6 32 | Float = 7 33 | String = 8 34 | Char = 9 35 | Bool = 10 36 | Reference = 11 37 | Function = 12 38 | Method = 13 39 | Void = 14 40 | Unknown = 15 41 | 42 | 43 | class Type(object): 44 | type_cache = {} 45 | 46 | @classmethod 47 | def register_type(cls, gdb_type): 48 | if gdb_type is None: 49 | return 50 | 51 | gdb_type = cls.get_raw_type(gdb_type) 52 | name = gdb_type.name 53 | 54 | if name not in cls.type_cache: 55 | size = gdb_type.sizeof 56 | code = cls.parse_gdb_code(gdb_type.code) 57 | target = None 58 | 59 | if code == TypeCode.Pointer: 60 | target = gdb_type.target().name 61 | 62 | members = [] 63 | 64 | try: 65 | for field in gdb_type.fields(): 66 | member_type = cls.from_gdb_type(field.type) 67 | member_name = field.name 68 | members.append(Member(member_name, name, member_type.name, 69 | member_type.size, 70 | code, 71 | member_type.fields, 72 | member_type.target)) 73 | except: 74 | pass 75 | 76 | final_type = Type(name, size, code, members, target) 77 | cls.type_cache[name] = final_type 78 | 79 | @staticmethod 80 | def get_raw_type(gdb_type): 81 | names_to_keep = ["std::string"] 82 | 83 | if gdb_type.name in names_to_keep: 84 | return gdb_type 85 | else: 86 | return gdb_type.strip_typedefs() 87 | 88 | @classmethod 89 | def from_gdb_type(cls, gdb_type): 90 | cls.register_type(gdb_type) 91 | 92 | return cls.type_cache[cls.get_raw_type(gdb_type).name] 93 | 94 | @staticmethod 95 | def parse_gdb_code(gdb_code): 96 | import gdb 97 | 98 | map = { 99 | gdb.TYPE_CODE_PTR: TypeCode.Pointer, 100 | gdb.TYPE_CODE_ARRAY: TypeCode.Array, 101 | gdb.TYPE_CODE_STRUCT: TypeCode.Struct, 102 | gdb.TYPE_CODE_UNION: TypeCode.Union, 103 | gdb.TYPE_CODE_ENUM: TypeCode.Enum, 104 | gdb.TYPE_CODE_INT: TypeCode.Int, 105 | gdb.TYPE_CODE_FLT: TypeCode.Float, 106 | gdb.TYPE_CODE_STRING: TypeCode.String, 107 | gdb.TYPE_CODE_CHAR: TypeCode.Char, 108 | gdb.TYPE_CODE_BOOL: TypeCode.Bool, 109 | gdb.TYPE_CODE_REF: TypeCode.Reference, 110 | gdb.TYPE_CODE_FUNC: TypeCode.Function, 111 | gdb.TYPE_CODE_METHOD: TypeCode.Method, 112 | gdb.TYPE_CODE_VOID: TypeCode.Void 113 | } 114 | 115 | if gdb_code in map: 116 | return map[gdb_code] 117 | else: 118 | return TypeCode.Unknown 119 | 120 | def __init__(self, name, size, code, fields=None, target=None): 121 | if fields is None: 122 | fields = [] 123 | 124 | self.name = str(name) 125 | self.size = int(size) 126 | self.code = code 127 | self.fields = fields 128 | self.target = target 129 | 130 | def __repr__(self): 131 | repr = "{} ({}, {} bytes)".format(self.name, TypeCode(self.code), 132 | self.size) 133 | 134 | if self.target: 135 | repr += " targets " + str(self.target) 136 | 137 | return repr 138 | 139 | 140 | class Member(Type): 141 | def __init__(self, member_name, parent_name, name, size, code, fields=None, 142 | target=None): 143 | if fields is None: 144 | fields = [] 145 | Type.__init__(self, name, size, code, fields, target) 146 | self.member_name = member_name 147 | self.parent_name = parent_name 148 | -------------------------------------------------------------------------------- /debugger/gdbc/variable.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | class Variable(object): 23 | def __init__(self, value, type=None, name=None, address=None): 24 | self.type = type 25 | self.value = str(value) 26 | self.name = str(name) 27 | self.address = str(address) 28 | 29 | def __repr__(self): 30 | return "{} {}: {} ({})".format(self.type, self.name, self.value, 31 | self.address) 32 | -------------------------------------------------------------------------------- /debugger/lldbc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/debugger/lldbc/__init__.py -------------------------------------------------------------------------------- /debugger/lldbc/command_script.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import json 23 | import os 24 | import sys 25 | 26 | data = {} 27 | 28 | with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 29 | "options.json"), "r") as options: 30 | data = json.loads(options.read()) 31 | 32 | sys.path.append(data["code_path"]) 33 | 34 | from debugger.lldbc.lldb_debugger import LldbDebugger # noqa 35 | from debugger.net.server import Server # noqa 36 | 37 | debugger = LldbDebugger() 38 | 39 | server = Server(data["server_port"], debugger) 40 | server.start() 41 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_breakpoint_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | 24 | from debugger.debugee import Breakpoint 25 | from debugger.enums import DebuggerState 26 | from debugger import debugger_api 27 | 28 | 29 | class LldbBreakpointManager(debugger_api.BreakpointManager): 30 | def __init__(self, debugger): 31 | super(LldbBreakpointManager, self).__init__(debugger) 32 | 33 | def get_breakpoints(self): 34 | bps = [self.debugger.target.GetBreakpointAtIndex(i) 35 | for i in xrange(self.debugger.target.GetNumBreakpoints())] 36 | breakpoints = [] 37 | for bp in bps: 38 | if bp.num_locations > 0: 39 | location = bp.GetLocationAtIndex(0) 40 | address = location.GetAddress().line_entry 41 | line = address.line 42 | file = os.path.abspath(address.file.fullpath) 43 | breakpoints.append(Breakpoint(bp.id, file, line)) 44 | 45 | return breakpoints 46 | 47 | def toggle_breakpoint(self, location, line): 48 | bp = self.find_breakpoint(location, line) 49 | if bp: 50 | return self.remove_breakpoint(location, line) 51 | else: 52 | return self.add_breakpoint(location, line) 53 | 54 | def add_breakpoint(self, location, line): 55 | self.debugger.require_state(DebuggerState.BinaryLoaded) 56 | 57 | location = os.path.abspath(location) 58 | 59 | bp = self.debugger.target.BreakpointCreateByLocation(location, line) 60 | 61 | if bp.IsValid() and bp.num_locations > 0: 62 | return True 63 | else: 64 | self.debugger.target.BreakpointDelete(bp.id) 65 | return False 66 | 67 | def find_breakpoint(self, location, line): 68 | location = os.path.abspath(location) 69 | 70 | bps = self.get_breakpoints() 71 | for bp in bps: 72 | if bp.location == location and bp.line == line: 73 | return bp 74 | 75 | return None 76 | 77 | def remove_breakpoint(self, location, line): 78 | self.debugger.require_state(DebuggerState.BinaryLoaded) 79 | 80 | bp = self.find_breakpoint(location, line) 81 | if bp: 82 | self.debugger.target.BreakpointDelete(bp.number) 83 | return True 84 | else: 85 | return False 86 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_file_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | class LldbFileManager(object): 23 | def __init__(self, debugger): 24 | self.debugger = debugger 25 | 26 | def get_main_source_file(self): 27 | main_functions = self.debugger.target.FindFunctions("main") 28 | main_function = main_functions.GetContextAtIndex(0) 29 | compile_unit = main_function.GetCompileUnit() 30 | source_file = compile_unit.GetFileSpec() 31 | 32 | return source_file.fullpath 33 | 34 | def get_thread_location(self, thread): 35 | frame = thread.GetSelectedFrame() 36 | line_entry = frame.line_entry 37 | 38 | return (line_entry.file.fullpath, line_entry.line) 39 | 40 | def get_current_location(self): 41 | return self.get_thread_location( 42 | self.debugger.thread_manager.get_current_thread()) 43 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import re 23 | 24 | 25 | class LldbHelper(object): 26 | def parse_var_description(self, data): 27 | match = re.search(r"([^=]*)=(.*)", data) 28 | return (match.group(1).strip(), match.group(2).strip()) 29 | 30 | def parse_var_descriptions(self, data): 31 | variables = [] 32 | 33 | for line in data.split("\n"): 34 | if "=" in line: 35 | variables.append(self.parse_var_description(line)) 36 | 37 | return variables 38 | 39 | def _get_variables_info(self, var_descriptions): 40 | """ 41 | Returns the given variable names as a list of gdb.Values. 42 | 43 | var_descriptions: list of tuples in the form (name, value) of 44 | visible variables 45 | """ 46 | vars = [] 47 | 48 | """for var in var_descriptions: 49 | gdb_val = gdb.parse_and_eval(var[0]) 50 | basic_var = Variable(str(gdb_val), 51 | Type.from_gdb_type(gdb_val.type), var[0], gdb_val.address) 52 | vars.append(basic_var)""" 53 | 54 | return vars 55 | 56 | def get_variables(self, name): 57 | """descriptions = self.parse_var_descriptions(gdb.execute("info " + 58 | str(name), to_string=True)) 59 | return self._get_variables_info(descriptions)""" 60 | return None 61 | 62 | def get_locals(self): 63 | return self.get_variables("locals") 64 | 65 | def get_args(self): 66 | return self.get_variables("args") 67 | 68 | def get_globals(self): 69 | return self.get_variables("variables") 70 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_io_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | import tempfile 24 | import threading 25 | 26 | 27 | class LldbIOManager(object): 28 | @staticmethod 29 | def create_pipe(): 30 | tmpdir = tempfile.gettempdir() 31 | temp_name = next(tempfile._get_candidate_names()) 32 | 33 | fifo = os.path.join(tmpdir, temp_name + ".fifo") 34 | 35 | os.mkfifo(fifo) 36 | 37 | return os.path.abspath(fifo) 38 | 39 | def __init__(self): 40 | self.file_threads = [] 41 | self.file_paths = [] 42 | 43 | self.stdin = None 44 | self.stdout = None 45 | self.stderr = None 46 | 47 | def _open_file(self, attribute, mode, file_path): 48 | setattr(self, attribute, open(file_path, mode, buffering=0)) 49 | 50 | def _close_file(self, attribute): 51 | try: 52 | if getattr(self, attribute): 53 | getattr(self, attribute).close() 54 | setattr(self, attribute, None) 55 | except: 56 | pass 57 | 58 | def handle_io(self): 59 | if len(self.file_threads) > 0: 60 | return 61 | 62 | stdin, stdout, stderr = [LldbIOManager.create_pipe() 63 | for _ in xrange(3)] 64 | 65 | self.file_paths += (stdin, stdout, stderr) 66 | 67 | self.file_threads.append(threading.Thread(target=self._open_file, 68 | args=["stdin", 69 | "w", 70 | stdin])) 71 | self.file_threads.append(threading.Thread(target=self._open_file, 72 | args=["stdout", 73 | "r", 74 | stdout])) 75 | self.file_threads.append(threading.Thread(target=self._open_file, 76 | args=["stderr", 77 | "r", 78 | stderr])) 79 | 80 | map(lambda thread: thread.start(), self.file_threads) 81 | 82 | return (stdin, stdout, stderr) 83 | 84 | def stop_io(self): 85 | map(lambda thread: thread.join(), self.file_threads) 86 | 87 | self._close_file("stdin") 88 | self._close_file("stdout") 89 | self._close_file("stderr") 90 | 91 | self.file_threads = [] 92 | 93 | for path in self.file_paths: 94 | try: 95 | os.remove(path) 96 | except: 97 | pass 98 | 99 | self.file_paths = [] 100 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_launcher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import json 23 | import os 24 | import shutil 25 | import subprocess 26 | import tempfile 27 | 28 | 29 | class LldbLauncher(object): 30 | def __init__(self, server_port): 31 | self.python_path = "python2.7" 32 | self.server_port = server_port 33 | self.code_path = os.path.dirname(os.path.dirname( 34 | os.path.abspath(__file__))) 35 | 36 | self.debugger_process = None 37 | self.running = False 38 | 39 | def is_running(self): 40 | return self.running 41 | 42 | def launch(self): 43 | if not self.is_running(): 44 | script_arguments = { 45 | "code_path": self.code_path, 46 | "server_port": self.server_port 47 | } 48 | 49 | self.prepare_tmp_folder(os.path.join( 50 | os.path.join(script_arguments["code_path"], "lldbc"), 51 | "command_script.py")) 52 | self.write_script_options(script_arguments) 53 | 54 | launch_data = [self.python_path, self.get_tmp_script_path()] 55 | 56 | self.debugger_process = subprocess.Popen( 57 | launch_data, 58 | stdin=subprocess.PIPE) 59 | # stdout=subprocess.PIPE, stderr=subprocess.PIPE) 60 | self.running = True 61 | 62 | def stop(self): 63 | if self.is_running(): 64 | self.debugger_process.kill() 65 | self.debugger_process.wait() 66 | self.debugger_process = None 67 | 68 | self.running = False 69 | shutil.rmtree(self.tmpdir, ignore_errors=True) 70 | 71 | def get_tmp_script_path(self): 72 | return os.path.join(self.tmpdir, "dv_script.py") 73 | 74 | def get_tmp_options_path(self): 75 | return os.path.join(self.tmpdir, "options.json") 76 | 77 | def prepare_tmp_folder(self, script_path): 78 | self.tmpdir = tempfile.mkdtemp(prefix="dv") 79 | shutil.copy(script_path, self.get_tmp_script_path()) 80 | 81 | def write_script_options(self, options): 82 | with open(self.get_tmp_options_path(), "w") as options_file: 83 | options_file.write(json.dumps(options)) 84 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_memory_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import enum 23 | 24 | 25 | class MemoryException(Exception): 26 | def __init__(self, *args, **kwargs): 27 | super(MemoryException, self).__init__(*args, **kwargs) 28 | 29 | 30 | class MemoryBlockState(enum.Enum): 31 | Allocated = 0 32 | Freed = 1 33 | 34 | 35 | class MemoryBlock(object): 36 | def __init__(self, address, size): 37 | """ 38 | Represents a heap-allocated memory block. 39 | @type address: int 40 | @type size: int 41 | """ 42 | self.address = address 43 | self.size = size 44 | self.state = MemoryBlockState.Allocated 45 | 46 | def free(self): 47 | if self.state == MemoryBlockState.Freed: 48 | raise MemoryException("Memory block deallocated twice ({0} - {1}" 49 | "bytes)".format(self.address, self.size)) 50 | 51 | self.state = MemoryBlockState.Freed 52 | 53 | 54 | class LldbMemoryManager(object): 55 | def __init__(self, debugger): 56 | """ 57 | @type debugger: lldbc.lldb_debugger.LldbDebugger 58 | """ 59 | self.debugger = debugger 60 | 61 | self.breakpoints = [] 62 | self.memory_blocks = [] 63 | 64 | def _find_block_by_address(self, address): 65 | for block in self.memory_blocks: 66 | if block.address == address: 67 | return block 68 | 69 | return None 70 | 71 | def _handle_free(self, bp): 72 | address = self.debugger.thread_manager.get_current_frame().args[0].\ 73 | value 74 | block = self._find_block_by_address(address) 75 | 76 | if block is None: 77 | raise MemoryException("Non-existent memory block deallocated" 78 | "({0})".format(address)) 79 | else: 80 | block.free() 81 | 82 | def _handle_malloc(self, bp): 83 | self.debugger.debugger.SetAsync(False) 84 | self.debugger.fire_events = False 85 | 86 | try: 87 | thread = self.debugger.thread_manager.get_current_thread() 88 | frame = self.debugger.thread_manager.get_current_frame() 89 | 90 | if len(frame.args) < 1 or frame.args[0] is None: 91 | return 92 | 93 | bytes = frame.args[0].value 94 | 95 | self.debugger.exec_step_out() 96 | address = thread.return_value.value 97 | 98 | block = self._find_block_by_address(address) 99 | 100 | if block is not None: 101 | raise MemoryException( 102 | "Memory block allocated twice ({0} - {1} bytes," 103 | "originally {2} bytes)".format( 104 | address, bytes, block.bytes 105 | )) 106 | else: 107 | self.memory_blocks.append(MemoryBlock(address, bytes)) 108 | finally: 109 | self.debugger.fire_events = True 110 | self.debugger.debugger.SetAsync(True) 111 | 112 | def create_memory_bps(self): 113 | self.breakpoints.append(( 114 | self.debugger.breakpoint_manager.add_breakpoint("free"), 115 | self._handle_free)) 116 | self.breakpoints.append(( 117 | self.debugger.breakpoint_manager.add_breakpoint("malloc"), 118 | self._handle_malloc)) 119 | 120 | def is_memory_bp(self, bp): 121 | for x in self.breakpoints: 122 | if x[0] == bp: 123 | return True 124 | return False 125 | 126 | def handle_memory_bp(self, bp): 127 | for x in self.breakpoints: 128 | if x[0] == bp: 129 | x[1](bp) 130 | return 131 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_thread_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | class LldbThreadManager(object): 23 | def __init__(self, debugger): 24 | """ 25 | @type debugger: lldbc.lldb_debugger.LldbDebugger 26 | """ 27 | self.debugger = debugger 28 | 29 | def get_current_thread(self): 30 | return self.debugger.process.GetSelectedThread() 31 | 32 | def get_threads(self): 33 | return [t for t in self.debugger.process] 34 | 35 | def set_thread_by_index(self, thread_index): 36 | self.debugger.process.SetSelectedThreadByIndexId(thread_index) 37 | 38 | def get_current_frame(self): 39 | return self.get_frames()[0] 40 | 41 | def get_frames(self): 42 | return self.get_current_thread().frames 43 | 44 | def change_frame(self, frameIndex): 45 | """ 46 | @type frameIndex: int 47 | """ 48 | if frameIndex >= len(self.get_frames()): 49 | return 50 | 51 | frame = self.get_frames()[frameIndex] 52 | self.get_current_thread().SetSelectedFrame(frameIndex) 53 | self.debugger.on_frame_changed.notify(frame) 54 | -------------------------------------------------------------------------------- /debugger/lldbc/lldb_variable_editor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from debugger.enums import DebuggerState 23 | 24 | 25 | class LldbVariableEditor(object): 26 | def __init__(self, debugger): 27 | """ 28 | @type debugger: lldbc.lldb_debugger.LldbDebugger 29 | """ 30 | self.debugger = debugger 31 | 32 | def change_variable_in_frame(self, frame, variable): 33 | """ 34 | @type frame: lldb.SBFrame 35 | @type variable: debugee.Variable 36 | """ 37 | self.debugger.require_state(DebuggerState.Running) 38 | frame.EvaluateExpression(variable.path + " = " + variable.value) 39 | -------------------------------------------------------------------------------- /debugger/mi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/debugger/mi/__init__.py -------------------------------------------------------------------------------- /debugger/mi/breakpoint_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | 24 | from debugger.enums import DebuggerState 25 | from debugger.mi.parser import Parser 26 | from debugger import debugger_api 27 | 28 | 29 | class BreakpointManager(debugger_api.BreakpointManager): 30 | def __init__(self, debugger): 31 | super(BreakpointManager, self).__init__(debugger) 32 | self.parser = Parser() 33 | 34 | def add_breakpoint(self, location, line): 35 | """ 36 | Adds a breakpoint, if there is not a breakpoint with the same location 37 | and line already. 38 | @type location: str 39 | @type line: int 40 | @rtype: boolean 41 | """ 42 | self.debugger.require_state(DebuggerState.BinaryLoaded) 43 | 44 | if self.find_breakpoint(location, line) is not None: 45 | return False 46 | 47 | if line is not None: 48 | location += ":" + str(line) 49 | 50 | success = self.debugger.communicator.send( 51 | "-break-insert {0}".format(location)).is_success() 52 | 53 | if success: 54 | self.on_breakpoint_changed.notify(self.find_breakpoint(location, 55 | line)) 56 | 57 | return success 58 | 59 | def toggle_breakpoint(self, location, line): 60 | """ 61 | Toggles a breakpoint on the given location and line. 62 | @type location: str 63 | @type line: int 64 | @rtype: boolean 65 | """ 66 | bp = self.find_breakpoint(location, line) 67 | if bp: 68 | return self.remove_breakpoint(location, line) 69 | else: 70 | return self.add_breakpoint(location, line) 71 | 72 | def get_breakpoints(self): 73 | """ 74 | @rtype: list of debugger.Breakpoint 75 | """ 76 | bps = self.debugger.communicator.send("-break-list") 77 | 78 | if bps: 79 | try: 80 | return self.parser.parse_breakpoints(bps.data) 81 | except: 82 | return [] 83 | else: 84 | return [] 85 | 86 | def find_breakpoint(self, location, line): 87 | """ 88 | @type location: str 89 | @type line: int 90 | @rtype: debugger.Breakpoint | None 91 | """ 92 | location = os.path.abspath(location) 93 | 94 | for bp in self.get_breakpoints(): 95 | if bp.location == location and bp.line == line: 96 | return bp 97 | 98 | return None 99 | 100 | def remove_breakpoint(self, location, line): 101 | """ 102 | @type location: str 103 | @type line: int 104 | @rtype: boolean 105 | """ 106 | self.debugger.require_state(DebuggerState.BinaryLoaded) 107 | 108 | bp = self.find_breakpoint(location, line) 109 | 110 | if bp: 111 | success = self.debugger.communicator.send( 112 | "-break-delete {0}".format(bp.number)).is_success() 113 | 114 | if success: 115 | self.on_breakpoint_changed.notify(bp) 116 | return success 117 | else: 118 | return False 119 | -------------------------------------------------------------------------------- /debugger/mi/gdb_pretty_print.py: -------------------------------------------------------------------------------- 1 | import gdb 2 | 3 | 4 | class Iterator: 5 | """Compatibility mixin for iterators 6 | 7 | Instead of writing next() methods for iterators, write 8 | __next__() methods and use this mixin to make them work in 9 | Python 2 as well as Python 3. 10 | 11 | Idea stolen from the "six" documentation: 12 | 13 | """ 14 | 15 | def next(self): 16 | return self.__next__() 17 | 18 | 19 | class StdVectorPrinter: 20 | "Print a std::vector" 21 | 22 | class _iterator(Iterator): 23 | def __init__(self, start, finish, bitvec): 24 | self.bitvec = bitvec 25 | if bitvec: 26 | self.item = start['_M_p'] 27 | self.so = start['_M_offset'] 28 | self.finish = finish['_M_p'] 29 | self.fo = finish['_M_offset'] 30 | itype = self.item.dereference().type 31 | self.isize = 8 * itype.sizeof 32 | else: 33 | self.item = start 34 | self.finish = finish 35 | self.count = 0 36 | 37 | def __iter__(self): 38 | return self 39 | 40 | def __next__(self): 41 | count = self.count 42 | self.count += 1 43 | 44 | if self.bitvec: 45 | if self.item == self.finish and self.so >= self.fo: 46 | raise StopIteration 47 | elt = self.item.dereference() 48 | if elt & (1 << self.so): 49 | obit = 1 50 | else: 51 | obit = 0 52 | self.so = self.so + 1 53 | if self.so >= self.isize: 54 | self.item = self.item + 1 55 | self.so = 0 56 | return ("v{0}".format(count), obit) 57 | else: 58 | if self.item == self.finish: 59 | raise StopIteration 60 | elt = self.item.dereference() 61 | self.item = self.item + 1 62 | 63 | return ("v{0}".format(count), elt) 64 | 65 | def __init__(self, typename, val): 66 | self.typename = typename 67 | self.val = val 68 | self.is_bool = val.type.template_argument(0).code == gdb.TYPE_CODE_BOOL 69 | 70 | def to_string(self): 71 | return None 72 | 73 | def display_hint(self): 74 | return "string" 75 | 76 | 77 | def str_lookup_function(val): 78 | if val is None or val.type is None or val.type.name is None: 79 | return None 80 | if val.type.name.startswith("std::vector"): 81 | return StdVectorPrinter("std::vector", val) 82 | else: 83 | return None 84 | 85 | 86 | def register_printers(objfile): 87 | objfile.pretty_printers.append(str_lookup_function) 88 | 89 | gdb.events.new_objfile.connect( 90 | lambda event: register_printers(event.new_objfile)) 91 | -------------------------------------------------------------------------------- /debugger/mi/heap_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | import threading 24 | import select 25 | import traceback 26 | 27 | from debugger.debugee import HeapBlock 28 | from debugger import debugger_api, util 29 | from debugger.util import Logger 30 | 31 | 32 | class HeapManager(debugger_api.HeapManager): 33 | def __init__(self, debugger): 34 | """ 35 | @type debugger: debugger.Debugger 36 | """ 37 | super(HeapManager, self).__init__(debugger) 38 | self.heap = [] 39 | """@type heap: list of HeapBlock""" 40 | 41 | self.total_allocations = 0 42 | self.total_deallocations = 0 43 | 44 | self.read_thread = None 45 | self.alloc_path = None 46 | self.file = None 47 | self.stop_flag = threading.Event() 48 | 49 | def watch(self): 50 | """ 51 | @rtype: str 52 | """ 53 | assert self.read_thread is None 54 | 55 | self.total_allocations = 0 56 | self.total_deallocations = 0 57 | 58 | self.stop_flag.clear() 59 | 60 | self.alloc_path = util.create_pipe() 61 | 62 | self.read_thread = threading.Thread(target=self._read_thread, 63 | args=(self.alloc_path,)) 64 | self.read_thread.daemon = True 65 | self.read_thread.start() 66 | 67 | return self.alloc_path 68 | 69 | def stop(self): 70 | self.stop_flag.set() 71 | 72 | self.read_thread.join() 73 | 74 | try: 75 | os.remove(self.alloc_path) 76 | except: 77 | pass 78 | 79 | self.heap = [] 80 | self.read_thread = None 81 | 82 | def find_block_by_address(self, addr): 83 | """ 84 | @type addr: str 85 | @rtype: HeapBlock | None 86 | """ 87 | for block in self.heap: 88 | if block.address == addr: 89 | return block 90 | 91 | return None 92 | 93 | def get_total_allocations(self): 94 | """ 95 | @rtype: int 96 | """ 97 | return self.total_allocations 98 | 99 | def get_total_deallocations(self): 100 | """ 101 | @rtype: int 102 | """ 103 | return self.total_deallocations 104 | 105 | def _read_thread(self, alloc_path): 106 | """ 107 | @type alloc_path: str 108 | """ 109 | try: 110 | with os.fdopen(os.open(alloc_path, os.O_NONBLOCK | os.O_RDONLY), 111 | "r", 1) as alloc_file: 112 | self.alloc_file = alloc_file 113 | 114 | while not self.stop_flag.is_set(): 115 | if len(select.select([alloc_file], [], [], 0.1)[0]) != 0: 116 | line = alloc_file.readline()[:-1] 117 | 118 | if line: 119 | self._handle_message(line) 120 | except: 121 | Logger.debug(traceback.format_exc()) 122 | 123 | def _handle_malloc(self, addr, size, notify=True): 124 | """ 125 | @type addr: str 126 | @type size: str 127 | """ 128 | size = int(size) 129 | block = HeapBlock(addr, size) 130 | self.heap.append(block) 131 | 132 | self.total_allocations += 1 133 | 134 | if notify: 135 | self.on_heap_change.notify(self.heap) 136 | 137 | def _handle_realloc(self, addr, new_addr, size): 138 | """ 139 | @type addr: str 140 | @type new_addr: str 141 | @type size: str 142 | """ 143 | self._handle_free(addr, False) 144 | self._handle_malloc(new_addr, size, False) 145 | 146 | self.on_heap_change.notify(self.heap) 147 | 148 | def _handle_free(self, addr, notify=True): 149 | """ 150 | @type addr: str 151 | """ 152 | if addr == "NULL": 153 | return 154 | 155 | block = self.find_block_by_address(addr) 156 | if not block: 157 | self.on_free_error.notify(addr) 158 | 159 | self.heap.remove(block) 160 | 161 | self.total_deallocations += 1 162 | 163 | if notify: 164 | self.on_heap_change.notify(self.heap) 165 | 166 | def _handle_message(self, message): 167 | """ 168 | @type message: str 169 | """ 170 | util.Logger.debug("HEAP: {}".format(message)) 171 | 172 | msg_parts = message.split(" ") 173 | action = msg_parts[0] 174 | args = msg_parts[1:] 175 | 176 | try: 177 | if action in ("malloc", "calloc"): 178 | self._handle_malloc(*args) 179 | elif action == "realloc": 180 | self._handle_realloc(*args) 181 | elif action == "free": 182 | self._handle_free(*args) 183 | else: 184 | Logger.debug("Unknown allocation action: {}".format(action)) 185 | except: 186 | Logger.debug(traceback.format_exc()) 187 | -------------------------------------------------------------------------------- /debugger/mi/io_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | import threading 24 | 25 | from debugger import util, debugger_api 26 | 27 | 28 | class IOManager(debugger_api.IOManager): 29 | def __init__(self): 30 | super(IOManager, self).__init__() 31 | 32 | self.file_threads = [] 33 | self.file_paths = [] 34 | 35 | def _open_file(self, attribute, mode, file_path): 36 | setattr(self, 37 | attribute, 38 | open(file_path, mode, buffering=0)) 39 | 40 | def _close_file(self, attribute): 41 | try: 42 | if getattr(self, attribute): 43 | getattr(self, attribute).close() 44 | setattr(self, attribute, None) 45 | except: 46 | pass 47 | 48 | def handle_io(self): 49 | if len(self.file_threads) > 0: 50 | return 51 | 52 | stdin, stdout, stderr = [util.create_pipe() for _ in xrange(3)] 53 | 54 | self.file_paths += (stdin, stdout, stderr) 55 | 56 | self._create_thread(["stdout", "r", stdout]) 57 | self._create_thread(["stderr", "r", stderr]) 58 | self._create_thread(["stdin", "w", stdin]) 59 | 60 | map(lambda thread: thread.start(), self.file_threads) 61 | 62 | return (stdin, stdout, stderr) 63 | 64 | def stop_io(self): 65 | map(lambda thread: thread.join(), self.file_threads) 66 | 67 | self._close_file("stdin") 68 | self._close_file("stdout") 69 | self._close_file("stderr") 70 | 71 | self.file_threads = [] 72 | 73 | for path in self.file_paths: 74 | try: 75 | os.remove(path) 76 | except: 77 | pass 78 | 79 | self.file_paths = [] 80 | 81 | def _create_thread(self, args): 82 | thread = threading.Thread(target=self._open_file, args=args) 83 | thread.daemon = True 84 | self.file_threads.append(thread) 85 | -------------------------------------------------------------------------------- /debugger/net/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/debugger/net/__init__.py -------------------------------------------------------------------------------- /debugger/net/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import time 23 | import threading 24 | import select 25 | import socket 26 | import sys 27 | 28 | from command import Command, CommandType 29 | 30 | 31 | class Client(object): 32 | def __init__(self, address, async=False): 33 | self.address = address 34 | self.async = async 35 | 36 | self.connected = False 37 | self.socket = None 38 | 39 | self.listening = False 40 | self.receive_thread = None 41 | 42 | self.waiting_messages = {} 43 | self.command_lock = threading.Lock() 44 | 45 | def is_connected(self): 46 | return self.connected 47 | 48 | def connect(self): 49 | if self.is_connected(): 50 | return 51 | 52 | try: 53 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 54 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 55 | sock.connect(self.address) 56 | except: 57 | return False 58 | 59 | self.socket = sock 60 | self.connected = True 61 | 62 | if self.async: 63 | self.receive_thread = threading.Thread( 64 | target=self.receive_thread_fn) 65 | self.listening = True 66 | self.receive_thread.start() 67 | 68 | return True 69 | 70 | def connect_repeat(self, timeout=5): 71 | start_time = time.clock() 72 | 73 | while not self.connect() and time.clock() < start_time + timeout: 74 | time.sleep(0.1) 75 | 76 | return self.is_connected() 77 | 78 | def disconnect(self): 79 | if not self.is_connected(): 80 | return 81 | 82 | if self.receive_thread: 83 | self.listening = False 84 | self.receive_thread.join() 85 | self.receive_thread = None 86 | 87 | if self.socket: 88 | self.socket.close() 89 | self.socket = None 90 | 91 | self.connected = False 92 | 93 | def disconnect_async(self): 94 | if not self.is_connected(): 95 | return 96 | 97 | self.receive_thread = None 98 | self.listening = False 99 | 100 | if self.socket: 101 | self.socket.close() 102 | self.socket = None 103 | 104 | self.connected = False 105 | 106 | def is_data_available(self, timeout=0.1): 107 | return len(select.select([self.socket], [], [], timeout)[0]) != 0 108 | 109 | def receive_thread_fn(self): 110 | while self.listening: 111 | if self.is_data_available(0.1): 112 | try: 113 | command = Command.receive(self.socket) 114 | self.handle_command(command) 115 | except: 116 | self.disconnect_async() 117 | 118 | def handle_command(self, command): 119 | if command.type == CommandType.Result: 120 | query_id = command.data["query_id"] 121 | callback = self.waiting_messages.pop(query_id, None) 122 | 123 | if callback: 124 | if "error" in command.data: 125 | callback(True, command.data["error"]) 126 | 127 | print("ERROR CLIENT: " + str(command.data["error"])) 128 | sys.stdout.flush() 129 | elif "result" in command.data: 130 | callback(False, command.data["result"]) 131 | 132 | def send(self, command, callback=None): 133 | if self.socket: 134 | self.waiting_messages[command.id] = callback 135 | command.send(self.socket) 136 | 137 | def exec_command(self, properties, args=None, callback=None): 138 | data = {"properties": properties} 139 | 140 | if args is not None: 141 | data["arguments"] = args 142 | 143 | cmd = Command(CommandType.Execute, data) 144 | 145 | self.command_lock.acquire() 146 | self.send(cmd, callback) 147 | 148 | result = None 149 | 150 | if not self.async: 151 | result = Command.receive(self.socket) 152 | 153 | self.command_lock.release() 154 | 155 | return result.data["result"] 156 | 157 | def stop_server(self): 158 | self.send(Command(CommandType.StopServer)) 159 | 160 | def cmd_load_binary(self, binary_path, callback=None): 161 | return self.exec_command(["load_binary"], binary_path, callback) 162 | 163 | def cmd_get_location(self, callback=None): 164 | return self.exec_command(["file_manager", "get_current_location"], 165 | None, callback) 166 | 167 | def cmd_get_main_file(self, callback=None): 168 | return self.exec_command(["file_manager", "get_main_source_file"], 169 | None, callback) 170 | 171 | def cmd_get_debugger_state(self, callback=None): 172 | return self.exec_command(["get_state"], None, callback) 173 | -------------------------------------------------------------------------------- /debugger/net/command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import enum 23 | import json 24 | import jsonpickle 25 | import struct 26 | import uuid 27 | 28 | 29 | class CommandType(enum.Enum): 30 | Loopback = 1 31 | Execute = 2 32 | Result = 3 33 | StopServer = 4 34 | 35 | 36 | class EnumEncoder(json.JSONEncoder): 37 | @staticmethod 38 | def parse_enum(d): 39 | if "__enum__" in d: 40 | name, member = d["__enum__"].split(".") 41 | return getattr(globals()[name], member) 42 | else: 43 | return d 44 | 45 | @staticmethod 46 | def byteify(input): 47 | if isinstance(input, dict): 48 | return {EnumEncoder.byteify(key): EnumEncoder.byteify(value) 49 | for key, value 50 | in input.iteritems()} 51 | elif isinstance(input, list): 52 | return [EnumEncoder.byteify(element) for element in input] 53 | elif isinstance(input, unicode): 54 | return input.encode('utf-8') 55 | else: 56 | return input 57 | 58 | def default(self, obj): 59 | if isinstance(obj, enum.Enum): 60 | return {"__enum__": str(obj)} 61 | return json.JSONEncoder.default(self, obj) 62 | 63 | 64 | class Command(object): 65 | @staticmethod 66 | def receive(client): 67 | length = struct.unpack_from(". 19 | # 20 | 21 | 22 | class NetHelper(object): 23 | """ 24 | Helper class for network operations. 25 | """ 26 | @staticmethod 27 | def get_free_port(): 28 | """ 29 | Returns a free port that can be bound. 30 | @rtype: int 31 | """ 32 | import socket 33 | 34 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 35 | s.bind(("", 0)) 36 | port = s.getsockname()[1] 37 | s.close() 38 | 39 | return port 40 | -------------------------------------------------------------------------------- /debugger/pycgdb/Makefile: -------------------------------------------------------------------------------- 1 | all: cpython cpp_test clean 2 | 3 | cpp_test: 4 | gcc -g -S -c test.c 5 | gcc test.s -o test 6 | 7 | cpython: 8 | gcc -c -fPIC ptrace.c -o ptrace.o 9 | gcc -shared -Wl,-soname,libptrace.so -o libptrace.so ptrace.o 10 | 11 | clean: 12 | rm *.o 13 | rm *.s 14 | -------------------------------------------------------------------------------- /debugger/pycgdb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/debugger/pycgdb/__init__.py -------------------------------------------------------------------------------- /debugger/pycgdb/breakpoint_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | import ptrace 24 | 25 | from debugger.debugee import Breakpoint 26 | 27 | 28 | class BreakpointManager(object): 29 | BREAKPOINT_ID = 0 30 | 31 | def __init__(self, debugger): 32 | self.debugger = debugger 33 | self.breakpoints = [] 34 | self.original_instructions = {} 35 | 36 | def add_breakpoint(self, file, line): 37 | file = os.path.abspath(file) 38 | 39 | assert self.debugger.program_info.has_location(file, line) 40 | 41 | for bp in self.breakpoints: # assumes different (file, line) maps to 42 | # different addresses 43 | if bp.location == file and bp.line == line: 44 | return 45 | 46 | self.breakpoints.append(Breakpoint(BreakpointManager.BREAKPOINT_ID, 47 | file, line)) 48 | BreakpointManager.BREAKPOINT_ID += 1 49 | 50 | def set_breakpoints(self, pid): 51 | for bp in self.breakpoints: 52 | if bp.number not in self.original_instructions: 53 | addr = self.debugger.program_info.get_address(bp.location, 54 | bp.line) 55 | inst = ptrace.ptrace_get_instruction(pid, addr) 56 | self.original_instructions[bp.number] = inst 57 | inst = (inst & 0xFFFFFF00) | 0xCC 58 | assert ptrace.ptrace_set_instruction(pid, addr, inst) 59 | 60 | def has_breakpoint_for_address(self, address): 61 | return self._get_breakpoint_for_address(address) is not None 62 | 63 | def restore_instruction(self, pid, address): 64 | bp = self._get_breakpoint_for_address(address) 65 | inst = self.original_instructions[bp.number] 66 | assert ptrace.ptrace_set_instruction(pid, address, inst) 67 | assert ptrace.ptrace_get_instruction(pid, address) == inst 68 | 69 | del self.original_instructions[bp.number] 70 | 71 | def _get_breakpoint_for_address(self, address): 72 | for bp in self.breakpoints: 73 | if self.debugger.program_info.get_address( 74 | bp.location, bp.line) == address: 75 | return bp 76 | 77 | return None 78 | -------------------------------------------------------------------------------- /debugger/pycgdb/debugger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | import os 22 | from elftools.elf.elffile import ELFFile 23 | 24 | from programinfo import ProgramInfo 25 | from breakpoint_manager import BreakpointManager 26 | from programrunner import ProgramRunner 27 | 28 | 29 | class Debugger(object): 30 | def __init__(self): 31 | self.loaded_file = None 32 | self.program_info = None 33 | self.runner = None 34 | self.breakpoint_manager = BreakpointManager(self) 35 | 36 | def load_binary(self, file): 37 | file = os.path.abspath(file) 38 | 39 | assert os.path.isfile(file) 40 | 41 | with open(file, "r") as binary_file: 42 | elffile = ELFFile(binary_file) 43 | assert elffile.has_dwarf_info() 44 | 45 | self.loaded_file = file 46 | self.program_info = ProgramInfo(elffile) 47 | self.runner = ProgramRunner(self, file) 48 | 49 | def run(self, *args): 50 | self.runner.args = args 51 | self.runner.run() 52 | -------------------------------------------------------------------------------- /debugger/pycgdb/programinfo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | 24 | 25 | class ProgramInfo(object): 26 | def __init__(self, elffile): 27 | """ 28 | @type elffile: elftools.elf.elffile.ELFFile 29 | """ 30 | 31 | dwarf_info = elffile.get_dwarf_info() 32 | self.files = {} 33 | self.addresses = {} 34 | 35 | for cu in dwarf_info.iter_CUs(): 36 | line_program = dwarf_info.line_program_for_CU(cu) 37 | 38 | if line_program: 39 | for line_entry in line_program.get_entries(): 40 | if line_entry.state: 41 | self._parse_line_state(line_program.header.file_entry, 42 | line_entry.state) 43 | 44 | for die in cu.iter_DIEs(): 45 | self._parse_die(die) 46 | 47 | def has_file(self, file): 48 | return os.path.abspath(file) in self.files 49 | 50 | def has_location(self, file, line): 51 | file = os.path.abspath(file) 52 | 53 | return self.has_file(file) and line in self.files[file] 54 | 55 | def get_address(self, file, line): 56 | file = os.path.abspath(file) 57 | 58 | if not self.has_location(file, line): 59 | return None 60 | else: 61 | return self.files[file][line][0] 62 | 63 | def get_location(self, address): 64 | if address in self.addresses: 65 | return self.addresses[address] 66 | else: 67 | return None 68 | 69 | def _parse_die(self, die): 70 | for child in die.iter_children(): 71 | self._parse_die(child) 72 | 73 | def _parse_line_state(self, files, line_state): 74 | file = os.path.abspath(files[line_state.file - 1].name) 75 | line = line_state.line 76 | address = line_state.address 77 | 78 | if file not in self.files: 79 | self.files[file] = {} 80 | 81 | if line not in self.files[file]: 82 | self.files[file][line] = [] 83 | 84 | self.files[file][line].append(address) 85 | self.addresses[address] = (file, line) 86 | -------------------------------------------------------------------------------- /debugger/pycgdb/programrunner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | import ptrace 24 | import Queue 25 | import signal 26 | import threading 27 | import traceback 28 | 29 | from debugger.enums import ProcessState 30 | from debugger.util import EventBroadcaster 31 | 32 | 33 | class ProcessExitException(BaseException): 34 | pass 35 | 36 | 37 | class ProgramRunner(object): 38 | def __init__(self, debugger, file, *args): 39 | self.debugger = debugger 40 | self.file = os.path.abspath(file) 41 | self.args = args 42 | self.parent_thread = None 43 | self.on_signal = EventBroadcaster() 44 | self.msg_queue = Queue.Queue() 45 | self.child_pid = None 46 | self.last_signal = None 47 | 48 | def run(self): 49 | assert not self.parent_thread 50 | 51 | self.parent_thread = threading.Thread(target=self._start_process) 52 | self.parent_thread.start() 53 | 54 | def exec_step_single(self): 55 | self._add_queue_command("step-single") 56 | 57 | def exec_continue(self): 58 | self._add_queue_command("continue") 59 | 60 | def exec_interrupt(self): 61 | try: 62 | os.kill(self.child_pid, signal.SIGUSR1) 63 | return True 64 | except: 65 | return False 66 | 67 | def exec_kill(self): 68 | try: 69 | os.kill(self.child_pid, signal.SIGKILL) 70 | return True 71 | except: 72 | return False 73 | 74 | def _add_queue_command(self, cmd): 75 | self.msg_queue.put(cmd) 76 | 77 | def _start_process(self): 78 | child_pid = os.fork() 79 | 80 | if child_pid: 81 | self.child_pid = child_pid 82 | self._parent() 83 | else: 84 | self._child() 85 | 86 | def _child(self): 87 | if ptrace.ptrace(ptrace.PTRACE_TRACEME) < 0: 88 | raise Exception() 89 | 90 | os.execl(self.file, self.file, *self.args) 91 | exit(0) 92 | 93 | def _parent(self): 94 | try: 95 | while True: 96 | pid, status = os.waitpid(self.child_pid, os.WNOHANG) 97 | 98 | if pid != 0: 99 | self._handle_child_status(status) 100 | 101 | try: 102 | command = self.msg_queue.get(True, 0.1) 103 | self._handle_command(self.child_pid, status, command) 104 | except Queue.Empty: 105 | pass 106 | except: 107 | traceback.print_exc() 108 | except OSError: 109 | self.on_signal.notify(ProcessState.Exited) 110 | except ProcessExitException: 111 | pass 112 | except: 113 | traceback.print_exc() 114 | 115 | self.child_pid = None 116 | self.parent_thread = None 117 | 118 | def _handle_child_status(self, status): 119 | if os.WIFSTOPPED(status): 120 | self._on_stop(status) 121 | elif os.WIFEXITED(status): 122 | self.on_signal.notify(ProcessState.Exited, 123 | os.WTERMSIG(status), 124 | os.WEXITSTATUS(status)) 125 | raise ProcessExitException() 126 | 127 | def _handle_command(self, pid, status, command): 128 | if command == "continue": 129 | self._do_continue(pid) 130 | elif command == "step-single": 131 | self._do_step_single(pid) 132 | 133 | def _on_stop(self, status): 134 | self.last_signal = os.WSTOPSIG(status) 135 | self.debugger.breakpoint_manager.set_breakpoints(self.child_pid) 136 | 137 | self.on_signal.notify(ProcessState.Stopped, self.last_signal) 138 | 139 | def _continue_after_breakpoint(self, exec_continue): 140 | pid = self.child_pid 141 | regs = ptrace.ptrace_getregs(pid) 142 | orig_address = regs.eip - 1 143 | if self.debugger.breakpoint_manager.has_breakpoint_for_address( 144 | orig_address): 145 | self.debugger.breakpoint_manager.restore_instruction(pid, 146 | orig_address) 147 | regs.eip -= 1 148 | assert ptrace.ptrace_setregs(pid, regs) 149 | self._do_step_single(pid) 150 | pid, status = os.waitpid(pid, 0) 151 | self.debugger.breakpoint_manager.set_breakpoints(pid) 152 | 153 | if exec_continue: 154 | self.exec_continue() 155 | else: 156 | self._handle_child_status(status) 157 | return True 158 | else: 159 | return False 160 | 161 | def _do_continue(self, pid): 162 | if self.last_signal == 5: # sigtrap 163 | if self._continue_after_breakpoint(True): 164 | return 165 | 166 | ptrace.ptrace(ptrace.PTRACE_CONT, pid) 167 | 168 | def _do_step_single(self, pid): 169 | if self.last_signal == 5: # sigtrap 170 | if self._continue_after_breakpoint(False): 171 | return 172 | 173 | ptrace.ptrace(ptrace.PTRACE_SINGLESTEP, pid) 174 | -------------------------------------------------------------------------------- /debugger/pycgdb/ptrace.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015-2016 Jakub Beranek 3 | 4 | This file is part of Devi. 5 | 6 | Devi is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | Devi is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with Devi. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | long py_ptrace(int request, int pid, void* addr, void* data) 24 | { 25 | return ptrace(request, pid, addr, data); 26 | } 27 | 28 | int py_errno() 29 | { 30 | return errno; 31 | } 32 | 33 | int main(int argc, char** argv) 34 | { 35 | return 0; 36 | } -------------------------------------------------------------------------------- /debugger/pycgdb/ptrace.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import ctypes 23 | import os 24 | 25 | lib = ctypes.cdll.LoadLibrary(os.path.join(os.path.dirname( 26 | os.path.abspath(__file__)), 'libptrace.so')) 27 | 28 | PTRACE_TRACEME = 0 29 | PTRACE_PEEKTEXT = 1 30 | PTRACE_POKETEXT = 4 31 | PTRACE_CONT = 7 32 | PTRACE_SINGLESTEP = 9 33 | PTRACE_GETREGS = 12 34 | PTRACE_SETREGS = 13 35 | 36 | 37 | class UserRegs64(ctypes.Structure): 38 | _fields_ = [ 39 | ("r15", ctypes.c_ulonglong), 40 | ("r14", ctypes.c_ulonglong), 41 | ("r13", ctypes.c_ulonglong), 42 | ("r12", ctypes.c_ulonglong), 43 | ("rbp", ctypes.c_ulonglong), 44 | ("rbx", ctypes.c_ulonglong), 45 | ("r11", ctypes.c_ulonglong), 46 | ("r10", ctypes.c_ulonglong), 47 | ("r9", ctypes.c_ulonglong), 48 | ("r8", ctypes.c_ulonglong), 49 | ("rax", ctypes.c_ulonglong), 50 | ("rcx", ctypes.c_ulonglong), 51 | ("rdx", ctypes.c_ulonglong), 52 | ("rsi", ctypes.c_ulonglong), 53 | ("rdi", ctypes.c_ulonglong), 54 | ("orig_rax", ctypes.c_ulonglong), 55 | ("rip", ctypes.c_ulonglong), 56 | ("cs", ctypes.c_ulonglong), 57 | ("eflags", ctypes.c_ulonglong), 58 | ("rsp", ctypes.c_ulonglong), 59 | ("ss", ctypes.c_ulonglong), 60 | ("fs_base", ctypes.c_ulonglong), 61 | ("gs_base", ctypes.c_ulonglong), 62 | ("ds", ctypes.c_ulonglong), 63 | ("es", ctypes.c_ulonglong), 64 | ("fs", ctypes.c_ulonglong), 65 | ("gs", ctypes.c_ulonglong) 66 | ] 67 | 68 | 69 | class UserRegs32(ctypes.Structure): 70 | _fields_ = [ 71 | ("ebx", ctypes.c_long), 72 | ("ecx", ctypes.c_long), 73 | ("edx", ctypes.c_long), 74 | ("esi", ctypes.c_long), 75 | ("edi", ctypes.c_long), 76 | ("ebp", ctypes.c_long), 77 | ("eax", ctypes.c_long), 78 | ("xds", ctypes.c_long), 79 | ("xes", ctypes.c_long), 80 | ("xfs", ctypes.c_long), 81 | ("xgs", ctypes.c_long), 82 | ("orig_eax", ctypes.c_long), 83 | ("eip", ctypes.c_long), 84 | ("xcs", ctypes.c_long), 85 | ("eflags", ctypes.c_long), 86 | ("esp", ctypes.c_long), 87 | ("xss", ctypes.c_long), 88 | ] 89 | 90 | 91 | def ptrace(request, pid=0, addr=0, data=0): 92 | return lib.py_ptrace(request, pid, addr, data) 93 | 94 | 95 | def ptrace_getregs(pid, bit32=True): 96 | regs = UserRegs32() 97 | 98 | if not bit32: 99 | regs = UserRegs64() 100 | 101 | regs_p = ctypes.pointer(regs) 102 | assert lib.py_ptrace(PTRACE_GETREGS, pid, 0, regs_p) >= 0 103 | return regs_p.contents 104 | 105 | 106 | def ptrace_setregs(pid, regs): 107 | regs_p = ctypes.pointer(regs) 108 | return lib.py_ptrace(PTRACE_SETREGS, pid, 0, regs_p) >= 0 109 | 110 | 111 | def ptrace_get_instruction(pid, address): 112 | return lib.py_ptrace(PTRACE_PEEKTEXT, pid, address) 113 | 114 | 115 | def ptrace_set_instruction(pid, address, instruction): 116 | return lib.py_ptrace(PTRACE_POKETEXT, pid, address, instruction) >= 0 117 | 118 | 119 | def get_error(): 120 | return lib.py_errno() 121 | -------------------------------------------------------------------------------- /debugger/wscript: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | def build(ctx): 5 | ctx.shlib( 6 | source="alloc_hook.cpp", 7 | cxxflags="-std=c++11", 8 | lib="dl", 9 | target="allochook" 10 | ) 11 | -------------------------------------------------------------------------------- /epydoc: -------------------------------------------------------------------------------- 1 | [epydoc] # Epydoc section marker (required by ConfigParser) 2 | 3 | modules: debugger/* 4 | modules: gui/* 5 | output: html 6 | target: docs/ 7 | docformat: epytext 8 | name: Debug visualizer 9 | frames: yes 10 | private: no 11 | imports: no 12 | verbosity: 0 13 | parse: yes 14 | introspect: yes 15 | sourcecode: yes 16 | -------------------------------------------------------------------------------- /examples/pointers/pointers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int a = 5; 6 | int b = 6; 7 | int* p = &a; 8 | int& r = b; 9 | 10 | while (true) 11 | { 12 | // place break here, change the value of the pointer or the reference 13 | // and observe the output 14 | std::cout << *p << " " << r << std::endl; 15 | } 16 | 17 | return 0; 18 | } -------------------------------------------------------------------------------- /examples/pointers/wscript: -------------------------------------------------------------------------------- 1 | def build(ctx): 2 | ctx.program( 3 | source="pointers.cpp", 4 | target="pointers", 5 | linkflags=ctx.env.CXXFLAGS 6 | ) 7 | -------------------------------------------------------------------------------- /examples/sg-struct/sg-struct.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "sg-struct.h" 6 | 7 | Ship::Ship(std::string name, double attack) : name(name), attack(attack) 8 | { 9 | 10 | } 11 | 12 | double Ship::getHealth() const 13 | { 14 | return this->health; 15 | } 16 | double Ship::getAttack() const 17 | { 18 | return this->attack; 19 | } 20 | std::string Ship::getName() const 21 | { 22 | return this->name; 23 | } 24 | 25 | void Ship::receiveDamage(double amount) 26 | { 27 | this->health -= amount; 28 | } 29 | 30 | std::default_random_engine generator((unsigned int) time(nullptr)); 31 | std::uniform_int_distribution distribution(2, 8); 32 | 33 | int main() 34 | { 35 | Ship sg1("O'Neill-class ship", 8.0); 36 | Ship ori("Ori warship", 10.0); 37 | Ship* ships[2] = { &sg1, &ori }; 38 | 39 | while (sg1.getHealth() > 0.0 && ori.getHealth() > 0.0) 40 | { 41 | for (int i = 0; i < 2; i++) 42 | { 43 | int value = distribution(generator); 44 | 45 | if (i == 0 && distribution(generator) > 6) 46 | { 47 | value /= 2.0; 48 | } 49 | 50 | int target = 1 - i; 51 | double damage = ships[i]->getAttack() * (1.0 / value); 52 | ships[target]->receiveDamage(damage); 53 | std::cout << ships[target]->getName() << " received " 54 | << damage << " damage from " 55 | << ships[i]->getName() << std::endl; 56 | } 57 | } 58 | 59 | int winner = 0; 60 | if (ships[1]->getHealth() > ships[0]->getHealth()) 61 | { 62 | winner = 1; 63 | } 64 | 65 | std::cout << "The winner is " << ships[winner]->getName() << std::endl; 66 | 67 | return 0; 68 | } -------------------------------------------------------------------------------- /examples/sg-struct/sg-struct.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Ship 6 | { 7 | public: 8 | Ship(std::string name, double attack = 0.0); 9 | 10 | double getHealth() const; 11 | double getAttack() const; 12 | std::string getName() const; 13 | 14 | void receiveDamage(double amount); 15 | 16 | private: 17 | std::string name; 18 | double attack; 19 | double health = 100.0; 20 | }; -------------------------------------------------------------------------------- /examples/sg-struct/wscript: -------------------------------------------------------------------------------- 1 | def build(ctx): 2 | ctx.program( 3 | source="sg-struct.cpp", 4 | target="sg-struct", 5 | linkflags=ctx.env.CXXFLAGS 6 | ) 7 | -------------------------------------------------------------------------------- /examples/sort/sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | template 6 | void insertion_sort(std::vector& data) 7 | { 8 | for (size_t i = 1; i < data.size(); i++) 9 | { 10 | T orig = data[i]; 11 | int j = i; 12 | while (j > 0 && data[j - 1] > orig) 13 | { 14 | data[j] = data[j - 1]; 15 | j--; 16 | } 17 | data[j] = orig; 18 | } 19 | } 20 | 21 | int main() 22 | { 23 | srand((unsigned int) time(nullptr)); 24 | std::vector data; 25 | 26 | for (int i = 0; i < 6; i++) 27 | { 28 | data.push_back(rand() % 100); 29 | } 30 | 31 | insertion_sort(data); 32 | 33 | for (size_t i = 0; i < data.size(); i++) 34 | { 35 | std::cout << data[i] << " " << std::endl; 36 | } 37 | 38 | return 0; 39 | } -------------------------------------------------------------------------------- /examples/sort/wscript: -------------------------------------------------------------------------------- 1 | def build(ctx): 2 | ctx.program( 3 | source="sort.cpp", 4 | target="sort", 5 | linkflags=ctx.env.CXXFLAGS 6 | ) 7 | -------------------------------------------------------------------------------- /examples/threads/threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct SyncData 7 | { 8 | public: 9 | SyncData() : buffer('\0'), bufferSize(0) 10 | { 11 | pthread_cond_init(&this->read_cond_var, NULL); 12 | pthread_cond_init(&this->write_cond_var, NULL); 13 | } 14 | ~SyncData() 15 | { 16 | pthread_cond_destroy(&this->read_cond_var); 17 | pthread_cond_destroy(&this->write_cond_var); 18 | } 19 | 20 | pthread_mutex_t mutex; 21 | pthread_cond_t read_cond_var; 22 | pthread_cond_t write_cond_var; 23 | char buffer; 24 | int bufferSize; 25 | }; 26 | 27 | void* thread_fn(void* arg) 28 | { 29 | SyncData* syncData = (SyncData*) arg; 30 | while (true) 31 | { 32 | pthread_mutex_lock(&syncData->mutex); 33 | 34 | while (syncData->bufferSize < 1) 35 | { 36 | pthread_cond_wait(&syncData->read_cond_var, &syncData->mutex); 37 | } 38 | 39 | if (!syncData->buffer) 40 | { 41 | pthread_mutex_unlock(&syncData->mutex); 42 | break; 43 | } 44 | 45 | std::cout << "Hello from the other side: " << syncData->buffer << std::endl; 46 | syncData->bufferSize = 0; 47 | 48 | pthread_cond_signal(&syncData->write_cond_var); 49 | pthread_mutex_unlock(&syncData->mutex); 50 | } 51 | } 52 | 53 | int main() 54 | { 55 | SyncData syncData; 56 | 57 | pthread_t thread; 58 | int res = pthread_create(&thread, NULL, thread_fn, &syncData); 59 | 60 | if (res) exit(1); 61 | 62 | const char* msg = "hello"; 63 | size_t len = strlen(msg) + 1; 64 | 65 | for (int i = 0; i < len; i++) 66 | { 67 | pthread_mutex_lock(&syncData.mutex); 68 | 69 | while (syncData.bufferSize > 0) 70 | { 71 | pthread_cond_wait(&syncData.write_cond_var, &syncData.mutex); 72 | } 73 | 74 | if (msg[i]) 75 | { 76 | std::cout << "Main thread sends " << msg[i] << std::endl; 77 | } 78 | 79 | syncData.buffer = msg[i]; 80 | syncData.bufferSize = 1; 81 | 82 | pthread_cond_signal(&syncData.read_cond_var); 83 | pthread_mutex_unlock(&syncData.mutex); 84 | } 85 | 86 | pthread_join(thread, NULL); 87 | 88 | return 0; 89 | } -------------------------------------------------------------------------------- /examples/threads/wscript: -------------------------------------------------------------------------------- 1 | def build(ctx): 2 | ctx.program( 3 | source="threads.cpp", 4 | target="threads", 5 | linkflags=ctx.env.CXXFLAGS 6 | ) 7 | -------------------------------------------------------------------------------- /examples/wscript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | def build(ctx): 6 | ctx.env.append_value("CXXFLAGS", "-g") 7 | ctx.env.append_value("CXXFLAGS", "-O0") 8 | ctx.env.append_value("CXXFLAGS", "-pthread") 9 | ctx.env.append_value("CXXFLAGS", "-std=c++11") 10 | 11 | ctx.recurse("pointers") 12 | ctx.recurse("sort") 13 | ctx.recurse("threads") 14 | ctx.recurse("sg-struct") 15 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/gui/__init__.py -------------------------------------------------------------------------------- /gui/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | from __future__ import print_function 22 | 23 | 24 | from gi.repository import Gtk 25 | 26 | from debugger.debugger_api import StartupInfo 27 | from debugger.mi.mi_debugger import MiDebugger 28 | from window import MainWindow 29 | 30 | 31 | class VisualiserApp(object): 32 | def __init__(self): 33 | self.debugger = MiDebugger() 34 | self.startup_info = StartupInfo() 35 | self.main_window = MainWindow(self) 36 | Gtk.init() 37 | 38 | def quit(self): 39 | self.debugger.terminate() 40 | Gtk.main_quit() 41 | 42 | def start(self): 43 | self.main_window.show() 44 | Gtk.main() 45 | -------------------------------------------------------------------------------- /gui/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | import paths 24 | 25 | from gi.repository import Gtk 26 | from gi.repository import Gdk 27 | 28 | 29 | class Config(object): 30 | UI_DIR = os.path.join(paths.DIR_ROOT, paths.DIR_RES, "gui") 31 | GUI_MAIN_WINDOW_MENU = None 32 | GUI_MAIN_WINDOW_TOOLBAR = None 33 | GUI_IO_CONSOLE = None 34 | GUI_MEMORY_CANVAS_TOOLBAR = None 35 | GUI_STARTUP_INFO_DIALOG = None 36 | 37 | @staticmethod 38 | def get_gui_builder(path): 39 | return Gtk.Builder.new_from_file(os.path.join(Config.UI_DIR, 40 | path + ".glade")) 41 | 42 | @staticmethod 43 | def preload(): 44 | Config.UI_DIR = os.path.join(paths.DIR_ROOT, paths.DIR_RES, "gui") 45 | Config.GUI_MAIN_WINDOW_MENU = Config.get_gui_builder( 46 | "main_window_menu") 47 | Config.GUI_MAIN_WINDOW_TOOLBAR = Config.get_gui_builder( 48 | "main_window_toolbar") 49 | Config.GUI_IO_CONSOLE = Config.get_gui_builder( 50 | "io_console") 51 | Config.GUI_MEMORY_CANVAS_TOOLBAR = Config.get_gui_builder( 52 | "memory_canvas_toolbar") 53 | Config.GUI_STARTUP_INFO_DIALOG = Config.get_gui_builder( 54 | "startup_info_dialog" 55 | ) 56 | 57 | provider = Gtk.CssProvider.new() 58 | screen = Gdk.Screen.get_default() 59 | Gtk.StyleContext.add_provider_for_screen( 60 | screen, 61 | provider, 62 | Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) 63 | provider.load_from_path(os.path.join(paths.DIR_RES, 64 | "css", 65 | "style.css")) 66 | -------------------------------------------------------------------------------- /gui/dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from gi.repository import Gtk 23 | 24 | from gui_util import require_gui_thread 25 | 26 | 27 | class FileOpenDialog(object): 28 | @staticmethod 29 | @require_gui_thread 30 | def select_file(title, parent, initial_path=None): 31 | """ 32 | @type title: str 33 | @type parent: Gtk.Widget 34 | @type initial_path: str 35 | @rtype: str 36 | """ 37 | dialog = FileOpenDialog(title, parent, False, initial_path) 38 | 39 | file = dialog.open() 40 | dialog.destroy() 41 | 42 | return file 43 | 44 | @staticmethod 45 | @require_gui_thread 46 | def select_folder(title, parent, initial_path=None): 47 | """ 48 | @type title: str 49 | @type parent: Gtk.Widget 50 | @type initial_path: str 51 | @rtype: str 52 | """ 53 | dialog = FileOpenDialog(title, parent, True, initial_path) 54 | 55 | folder = dialog.open() 56 | dialog.destroy() 57 | 58 | return folder 59 | 60 | def __init__(self, title, parent, directory=False, initial_path=None): 61 | """ 62 | Opens a file or folder chooser dialog. 63 | @type title: str 64 | @type parent: Gtk.Widget 65 | @type directory: bool 66 | @type initial_path: str 67 | """ 68 | type = Gtk.FileChooserAction.OPEN 69 | 70 | if directory: 71 | type = Gtk.FileChooserAction.SELECT_FOLDER 72 | 73 | self.dialog = Gtk.FileChooserDialog(title, parent, 74 | type, 75 | (Gtk.STOCK_CANCEL, 76 | Gtk.ResponseType.CANCEL, 77 | Gtk.STOCK_OPEN, 78 | Gtk.ResponseType.OK)) 79 | 80 | if initial_path: 81 | self.dialog.set_current_folder(initial_path) 82 | 83 | @require_gui_thread 84 | def open(self): 85 | response = self.dialog.run() 86 | if response == Gtk.ResponseType.OK: 87 | return self.dialog.get_filename() 88 | else: 89 | return None 90 | 91 | @require_gui_thread 92 | def destroy(self): 93 | self.dialog.destroy() 94 | 95 | 96 | class MessageBox(Gtk.MessageDialog): 97 | @staticmethod 98 | @require_gui_thread 99 | def show(text, title, parent, msg_type=Gtk.MessageType.ERROR): 100 | dialog = Gtk.MessageDialog(parent, 0, msg_type, Gtk.ButtonsType.CLOSE, 101 | text=text, title=title) 102 | dialog.connect("response", lambda widget, response: widget.destroy()) 103 | dialog.show_all() 104 | -------------------------------------------------------------------------------- /gui/drawing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/gui/drawing/__init__.py -------------------------------------------------------------------------------- /gui/drawing/geometry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from size import Size 23 | from vector import Vector 24 | 25 | 26 | class RectangleBBox(object): 27 | @staticmethod 28 | def contain(bboxes): 29 | if len(bboxes) < 1: 30 | return RectangleBBox(Vector(0, 0), Size(0, 0)) 31 | 32 | min_x = bboxes[0].x 33 | max_x = bboxes[0].x + bboxes[0].width 34 | min_y = bboxes[0].y 35 | max_y = bboxes[0].y + bboxes[0].height 36 | 37 | for bbox in bboxes[1:]: 38 | min_x = min(min_x, bbox.x) 39 | max_x = max(max_x, bbox.x + bbox.width) 40 | min_y = min(min_y, bbox.y) 41 | max_y = max(max_y, bbox.y + bbox.height) 42 | 43 | return RectangleBBox((min_x, min_y), (max_x - min_x, max_y - min_y)) 44 | 45 | @staticmethod 46 | def from_margin(position, margin): 47 | return RectangleBBox(position, margin.to_size()) 48 | 49 | def __init__(self, position=(0, 0), size=(0, 0)): 50 | self.position = Vector.vectorize(position) 51 | self.size = Size.make_size(size) 52 | 53 | self.x = self.position.x 54 | self.y = self.position.y 55 | self.width = self.size.width 56 | self.height = self.size.height 57 | 58 | def moved(self, offset): 59 | return RectangleBBox(self.position.add(Vector.vectorize(offset)), 60 | self.size) 61 | 62 | def scaled(self, scale): 63 | return RectangleBBox(self.position, self.size + Size.make_size(scale)) 64 | 65 | def copy(self): 66 | return RectangleBBox((self.x, self.y), (self.width, self.height)) 67 | 68 | def is_point_inside(self, point): 69 | """ 70 | @type point: Vector 71 | """ 72 | return (self.position.x <= point.x <= self.position.x + self.width and 73 | self.position.y <= point.y <= self.position.y + self.height) 74 | 75 | def __add__(self, other): 76 | if isinstance(other, Padding): 77 | size = self.size.copy() 78 | size.width += other.left + other.right 79 | size.height += other.top + other.bottom 80 | return RectangleBBox(self.position.copy(), size) 81 | elif isinstance(other, Margin): 82 | position = self.position.copy() 83 | position.x -= other.left 84 | position.y -= other.top 85 | size = self.size.copy() 86 | size.width += other.right 87 | size.height += other.bottom 88 | 89 | return RectangleBBox(position, size) 90 | else: 91 | raise AttributeError("RectangleBBox can be only added with margin" 92 | "or padding, added with {}".format(other)) 93 | 94 | def __repr__(self): 95 | return "Rectangle[Position: {}, Size: {}]".format(self.position, 96 | self.size) 97 | 98 | 99 | class Margin(object): 100 | @staticmethod 101 | def all(size): 102 | """ 103 | Makes a margin with all sub-margins equal to the given value. 104 | @type size: float 105 | @rtype: Margin 106 | """ 107 | return Margin(size, size, size, size) 108 | 109 | def __init__(self, top=0.0, right=0.0, bottom=0.0, left=0.0): 110 | """ 111 | @type top: float 112 | @type right: float 113 | @type bottom: float 114 | @type left: float 115 | """ 116 | self.top = top 117 | self.right = right 118 | self.bottom = bottom 119 | self.left = left 120 | 121 | def to_size(self): 122 | """ 123 | @rtype: size.Size 124 | """ 125 | return Size(self.right + self.left, self.bottom + self.top) 126 | 127 | def __repr__(self): 128 | return "Margin[Top: {}, Right: {}, Bottom: {}, Left: {}]".format( 129 | self.top, self.right, self.bottom, self.left) 130 | 131 | 132 | class Padding(Margin): 133 | pass 134 | -------------------------------------------------------------------------------- /gui/drawing/memtoview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import drawable 23 | from debugger.enums import TypeCategory 24 | from geometry import Padding 25 | from size import Size 26 | 27 | 28 | class MemToViewTransformer(object): 29 | def __init__(self, canvas): 30 | """ 31 | @type canvas: canvas.Canvas 32 | """ 33 | self.function_map = { 34 | TypeCategory.Builtin: self.create_basic, 35 | TypeCategory.String: self.create_basic, 36 | TypeCategory.CString: self.create_basic, 37 | TypeCategory.Enumeration: self.create_basic, 38 | TypeCategory.Function: self.create_basic, 39 | TypeCategory.Pointer: self.create_pointer, 40 | TypeCategory.Reference: self.create_pointer, 41 | TypeCategory.Struct: self.create_struct, 42 | TypeCategory.Class: self.create_struct, 43 | TypeCategory.Union: self.create_struct, 44 | TypeCategory.Array: self.create_vector, 45 | TypeCategory.Vector: self.create_vector 46 | } 47 | self.canvas = canvas 48 | 49 | def create_struct(self, var): 50 | """ 51 | @type var: debugee.Variable 52 | @rtype: drawable.StructDrawable 53 | """ 54 | return drawable.StructDrawable(self.canvas, var) 55 | 56 | def create_pointer(self, var): 57 | """ 58 | @type var: debugee.Variable 59 | @rtype: drawable.PointerDrawable 60 | """ 61 | return drawable.PointerDrawable(self.canvas, var, 62 | padding=Padding.all(5), 63 | size=Size(-1, 20)) 64 | 65 | def create_basic(self, var): 66 | """ 67 | @type var: debugee.Variable 68 | @rtype: drawable.VariableDrawable 69 | """ 70 | return drawable.VariableDrawable(self.canvas, var, 71 | padding=Padding.all(5), 72 | size=Size(-1, 20), 73 | max_size=Size(150, -1)) 74 | 75 | def create_vector(self, var): 76 | """ 77 | @type var: debugee.Variable 78 | @rtype: drawable.VectorDrawable 79 | """ 80 | return drawable.VectorDrawable(self.canvas, var) 81 | 82 | def transform_var(self, var): 83 | """ 84 | @type var: debugee.Variable 85 | @rtype drawable.Drawable 86 | """ 87 | type = var.type 88 | 89 | if not type.is_valid(): 90 | return None 91 | 92 | create_fn = self.function_map.get(type.type_category, None) 93 | 94 | if create_fn: 95 | return create_fn(var) 96 | else: 97 | return None 98 | 99 | def transform_frame(self, frame): 100 | """ 101 | @type frame: debugee.Frame 102 | @rtype: drawable.StackFrameDrawable 103 | """ 104 | frame_drawable = drawable.StackFrameDrawable(self.canvas, frame) 105 | return frame_drawable 106 | -------------------------------------------------------------------------------- /gui/drawing/mouse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from gi.repository import Gdk 23 | 24 | from enum import Enum 25 | 26 | from vector import Vector 27 | 28 | 29 | class ClickedState(Enum): 30 | Released = 1 31 | ReadyToPress = 2 32 | Pressed = 3 33 | 34 | 35 | class PositionState(Enum): 36 | Inside = 1 37 | Outside = 2 38 | 39 | 40 | class MouseButtonState(Enum): 41 | Up = 1 42 | Down = 2 43 | 44 | 45 | class MouseData(object): 46 | def __init__(self, lb_state, rb_state, position): 47 | """ 48 | @type lb_state: MouseButtonState 49 | @type rb_state: MouseButtonState 50 | @type position: drawing.vector.Vector 51 | """ 52 | self.lb_state = lb_state 53 | self.rb_state = rb_state 54 | self.position = position 55 | 56 | def copy(self): 57 | return MouseData(self.lb_state, self.rb_state, self.position.copy()) 58 | 59 | def __repr__(self): 60 | return "MouseData: {}, {}, {}".format(self.lb_state, 61 | self.rb_state, 62 | self.position) 63 | 64 | 65 | class ClickHandler(object): 66 | def __init__(self, drawable): 67 | """ 68 | Handles clicks. 69 | @type drawable: drawable.Drawable 70 | """ 71 | self.drawable = drawable 72 | self.click_state = ClickedState.Released 73 | self.position_state = PositionState.Outside 74 | 75 | self.propagated_handlers = [] 76 | 77 | def _is_point_inside(self, point): 78 | return self.drawable.get_rect().is_point_inside(point) 79 | 80 | def _handle_mouse_up(self, mouse_data): 81 | """ 82 | @type mouse_data: MouseData 83 | """ 84 | if self._is_point_inside(mouse_data.position): 85 | if self.click_state == ClickedState.Released: 86 | self.click_state = ClickedState.ReadyToPress 87 | if self.click_state == ClickedState.Pressed: 88 | self.drawable.on_mouse_click.notify(mouse_data) 89 | self.click_state = ClickedState.ReadyToPress 90 | else: 91 | self.click_state = ClickedState.Released 92 | 93 | def _handle_mouse_down(self, mouse_data): 94 | """ 95 | @type mouse_data: MouseData 96 | """ 97 | if self._is_point_inside(mouse_data.position): 98 | if self.click_state == ClickedState.ReadyToPress: 99 | self.click_state = ClickedState.Pressed 100 | 101 | def _handle_mouse_move(self, mouse_data): 102 | """ 103 | @type mouse_data: MouseData 104 | """ 105 | next_state = PositionState.Outside 106 | if self._is_point_inside(mouse_data.position): 107 | next_state = PositionState.Inside 108 | 109 | if self.position_state != next_state: 110 | if next_state == PositionState.Outside: 111 | self.drawable.on_mouse_leave.notify(mouse_data) 112 | else: 113 | self.drawable.on_mouse_enter.notify(mouse_data) 114 | 115 | self.position_state = next_state 116 | 117 | def propagate_handler(self, handler): 118 | """ 119 | @type handler: ClickHandler 120 | """ 121 | self.propagated_handlers.append(handler) 122 | 123 | def handle_mouse_event(self, mouse_data): 124 | """ 125 | @type mouse_data: MouseData 126 | """ 127 | if mouse_data.lb_state == MouseButtonState.Down: 128 | self._handle_mouse_down(mouse_data) 129 | elif mouse_data.lb_state == MouseButtonState.Up: 130 | self._handle_mouse_up(mouse_data) 131 | 132 | self._handle_mouse_move(mouse_data) 133 | 134 | for handler in self.propagated_handlers: 135 | handler.handle_mouse_event(mouse_data) 136 | 137 | 138 | class TranslationHandler(object): 139 | def __init__(self, canvas): 140 | """ 141 | Handles translation of canvas using right mouse dragging. 142 | @type canvas: canvas.Canvas 143 | """ 144 | self.canvas = canvas 145 | self.mouse_state = MouseButtonState.Up 146 | self.position = Vector(0, 0) 147 | 148 | def set_drag_cursor(self): 149 | self.canvas.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1)) 150 | 151 | def set_default_cursor(self): 152 | self.canvas.set_cursor(None) 153 | 154 | def handle_mouse_event(self, mouse_data): 155 | """ 156 | @type mouse_data: MouseData 157 | """ 158 | if self.mouse_state == MouseButtonState.Up: 159 | if mouse_data.rb_state == MouseButtonState.Down: 160 | self.set_drag_cursor() 161 | else: 162 | if mouse_data.rb_state == MouseButtonState.Up: 163 | self.set_default_cursor() 164 | else: 165 | diff = mouse_data.position - self.position 166 | self.canvas.translate_by(diff) 167 | 168 | self.position = mouse_data.position.copy() 169 | self.mouse_state = mouse_data.rb_state 170 | 171 | def is_dragging(self): 172 | return self.mouse_state == MouseButtonState.Down 173 | -------------------------------------------------------------------------------- /gui/drawing/size.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import numbers 23 | from vector import Vector 24 | 25 | 26 | class Size(object): 27 | @staticmethod 28 | def make_size(size): 29 | if isinstance(size, Size): 30 | return size 31 | elif isinstance(size, Vector): 32 | return Size(size.x, size.y) 33 | else: 34 | return Size(size[0], size[1]) 35 | 36 | def __init__(self, width, height): 37 | self.width = width 38 | self.height = height 39 | 40 | def copy(self): 41 | return Size(self.width, self.height) 42 | 43 | def __add__(self, other): 44 | assert isinstance(other, Size) 45 | 46 | return Size(self.width + other.width, self.height + other.height) 47 | 48 | def __sub__(self, other): 49 | assert isinstance(other, Size) 50 | 51 | return Size(self.width - other.width, self.height - other.height) 52 | 53 | def __mul__(self, other): 54 | assert isinstance(other, numbers.Number) 55 | 56 | return Size(self.width * other, self.height * other) 57 | 58 | def __div__(self, other): 59 | assert isinstance(other, numbers.Number) 60 | 61 | return Size(self.width / other, self.height / other) 62 | 63 | def __repr__(self): 64 | return "(" + str(self.width) + "," + str(self.height) + ")" 65 | -------------------------------------------------------------------------------- /gui/drawing/vector.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import math 23 | import numbers 24 | 25 | 26 | class Vector(object): 27 | @staticmethod 28 | def from_points(point_from, point_to): 29 | return Vector(point_to[0] - point_from[0], point_to[1] - point_from[1]) 30 | 31 | @staticmethod 32 | def vectorize(vector): 33 | if isinstance(vector, Vector): 34 | return vector 35 | else: 36 | return Vector(vector[0], vector[1]) 37 | 38 | def __init__(self, x=0.0, y=0.0): 39 | self.x = x 40 | self.y = y 41 | 42 | def set_position(self, x, y): 43 | self.x = x 44 | self.y = y 45 | 46 | def add(self, vector): 47 | vector = Vector.vectorize(vector) 48 | 49 | return Vector(vector.x + self.x, vector.y + self.y) 50 | 51 | def sub(self, vector): 52 | vector = Vector.vectorize(vector) 53 | 54 | return Vector(self.x - vector.x, self.y - vector.y) 55 | 56 | def dot(self, vector): 57 | vector = Vector.vectorize(vector) 58 | 59 | return self.x * vector.x + self.y * vector.y 60 | 61 | def length(self): 62 | return math.sqrt(self.x * self.x + self.y * self.y) 63 | 64 | def normalized(self): 65 | length = self.length() 66 | 67 | if length == 0: 68 | return Vector(0, 0) 69 | else: 70 | return Vector(self.x / length, self.y / length) 71 | 72 | def scaled(self, scale): 73 | return Vector(self.x * scale, self.y * scale) 74 | 75 | def inverse(self): 76 | return Vector(-self.x, -self.y) 77 | 78 | def angle(self): 79 | return math.degrees(math.atan2(self.x, -self.y)) 80 | 81 | def rotate(self, angle, point=(0, 0)): 82 | """ 83 | Returns a copy of this vector rotated by the given angle in degrees 84 | around the given point. 85 | @type angle: float 86 | @type point: Vector 87 | @rtype: Vector 88 | """ 89 | point = Vector.vectorize(point) 90 | 91 | theta = math.radians(angle) 92 | 93 | sin = math.sin(theta) 94 | cos = math.cos(theta) 95 | 96 | px = self.x - point.x 97 | py = self.y - point.y 98 | 99 | rot_x = px * cos - py * sin 100 | rot_y = px * sin + py * cos 101 | 102 | rot_x += point.x 103 | rot_y += point.y 104 | 105 | return Vector(rot_x, rot_y) 106 | 107 | def to_point(self): 108 | return (self.x, self.y) 109 | 110 | def copy(self): 111 | return Vector(self.x, self.y) 112 | 113 | def __add__(self, other): 114 | """ 115 | @type other: Vector 116 | @rtype: Vector 117 | """ 118 | assert isinstance(other, Vector) 119 | 120 | return self.add(other) 121 | 122 | def __sub__(self, other): 123 | """ 124 | @type other: Vector 125 | @rtype: Vector 126 | """ 127 | assert isinstance(other, Vector) 128 | 129 | return Vector(self.x - other.x, self.y - other.y) 130 | 131 | def __neg__(self): 132 | """ 133 | @rtype: Vector 134 | """ 135 | return Vector(-self.x, -self.y) 136 | 137 | def __mul__(self, other): 138 | """ 139 | @type other: Vector | Number 140 | @rtype: Vector 141 | """ 142 | if isinstance(other, numbers.Number): 143 | return Vector(self.x * other, self.y * other) 144 | elif isinstance(other, Vector): 145 | return self.x * other.x + self.y * other.y 146 | else: 147 | raise TypeError 148 | 149 | def __div__(self, other): 150 | """ 151 | @type other: Number 152 | @rtype: Vector 153 | """ 154 | assert isinstance(other, numbers.Number) 155 | 156 | return Vector(self.x / other, self.y / other) 157 | 158 | def __repr__(self): 159 | return "[" + str(self.x) + "," + str(self.y) + "]" 160 | -------------------------------------------------------------------------------- /gui/drawing/widgets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from gi.repository import Gtk 23 | 24 | from gui.gui_util import require_gui_thread 25 | from debugger.util import EventBroadcaster 26 | 27 | 28 | class ValueEntry(Gtk.Frame): 29 | @require_gui_thread 30 | def __init__(self, title, text): 31 | Gtk.Frame.__init__(self) 32 | 33 | self.set_label(title) 34 | self.set_label_align(0.0, 0.0) 35 | 36 | self.box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) 37 | self.box.set_margin_bottom(5) 38 | self.box.set_margin_left(2) 39 | self.text_entry = Gtk.Entry() 40 | self.text_entry.set_text(text) 41 | self.confirm_button = Gtk.Button(label="Set") 42 | 43 | self.get_style_context().add_class("value-entry") 44 | 45 | self.box.pack_start(self.text_entry, False, False, 0) 46 | self.box.pack_start(self.confirm_button, False, False, 5) 47 | self.add(self.box) 48 | self.show_all() 49 | 50 | self.confirm_button.connect("clicked", 51 | lambda btn: self._handle_confirm_click()) 52 | 53 | self.on_value_entered = EventBroadcaster() 54 | 55 | @require_gui_thread 56 | def set_value(self, value): 57 | """ 58 | @type value: str 59 | """ 60 | self.text_entry.set_text(value) 61 | 62 | def _handle_confirm_click(self): 63 | value = self.text_entry.get_text() 64 | self.set_value("") 65 | 66 | self.on_value_entered.notify(value) 67 | self.hide() 68 | -------------------------------------------------------------------------------- /gui/gui_util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from gi.repository import GObject 23 | 24 | import inspect 25 | import threading 26 | 27 | 28 | def run_on_gui(function, *args, **kwargs): 29 | """ 30 | @type function: callable 31 | """ 32 | GObject.idle_add(lambda *x: function(*args, **kwargs)) 33 | 34 | 35 | def assert_is_gui_thread(): 36 | func = inspect.currentframe().f_back.f_back.f_code 37 | 38 | if not isinstance(threading.current_thread(), threading._MainThread): 39 | raise Exception("Not on the main thread at {0} ({1}:{2})".format( 40 | func.co_name, func.co_filename, func.co_firstlineno)) 41 | 42 | 43 | def require_gui_thread(func): 44 | """ 45 | @type func: callable 46 | @rtype: callable 47 | """ 48 | def check(*args, **kwargs): 49 | assert_is_gui_thread() 50 | return func(*args, **kwargs) 51 | return check 52 | 53 | 54 | def truncate(data, length, end=None): 55 | """ 56 | Truncates the data string to the given length (inclusive). 57 | If end is given, the end is appended to the truncated input. 58 | The final length will be <= len, so end will not go over len. 59 | @type data: str 60 | @type length: int 61 | @type end: str 62 | @rtype: str 63 | """ 64 | if end: 65 | end_len = len(end) 66 | trunc_length = length - end_len 67 | if trunc_length < 1: 68 | return end 69 | else: 70 | return data[:trunc_length] + end 71 | 72 | else: 73 | return data[:length] 74 | 75 | 76 | class Cooldown(object): 77 | def __init__(self, value): 78 | """ 79 | @type value: float 80 | """ 81 | self.value = value 82 | self.delta = 0.0 83 | 84 | def update(self, delta): 85 | self.delta += delta 86 | 87 | def is_ready(self): 88 | return self.delta >= self.value 89 | 90 | def reset(self): 91 | self.delta = 0.0 92 | 93 | def reset_if_ready(self): 94 | if self.is_ready(): 95 | self.reset() 96 | return True 97 | else: 98 | return False 99 | -------------------------------------------------------------------------------- /gui/heap_detail.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from gi.repository import Gtk, GObject 23 | 24 | import time 25 | 26 | from debugger.enums import ProcessState 27 | from gui_util import require_gui_thread, run_on_gui 28 | 29 | import matplotlib 30 | 31 | matplotlib.use('gtk3agg') 32 | from matplotlib.backends.backend_gtk3agg\ 33 | import FigureCanvasGTK3Agg as Canvas # noqa 34 | from matplotlib.figure import Figure # noqa 35 | 36 | 37 | class HeapGraph(Gtk.ScrolledWindow): 38 | def __init__(self, debugger): 39 | """ 40 | @type debugger: debugger.Debugger 41 | """ 42 | Gtk.ScrolledWindow.__init__(self) 43 | self.debugger = debugger 44 | self.debugger.heap_manager.on_heap_change.subscribe( 45 | lambda heap: self._handle_heap_change(heap)) 46 | self.debugger.on_process_state_changed.subscribe( 47 | lambda state, data: self._handle_process_change(state)) 48 | self.sizes = [] 49 | self.times = [] 50 | 51 | figure = Figure() 52 | self.axis = figure.add_subplot(111) 53 | 54 | figure.subplots_adjust(bottom=0.3) 55 | 56 | self.canvas = Canvas(figure) 57 | self.add_with_viewport(self.canvas) 58 | 59 | self.heap_size = 0 60 | self.start_time = 0 61 | 62 | def redraw(self): 63 | self.axis.set_xlabel('time [s]') 64 | self.axis.set_ylabel('heap size [MiB]') 65 | self.axis.plot(self.times, self.sizes, "r") 66 | self.canvas.queue_draw() 67 | 68 | def _reset(self): 69 | self.sizes = [] 70 | self.times = [] 71 | self.heap_size = 0 72 | self.start_time = time.time() 73 | self.axis.cla() 74 | 75 | def _handle_process_change(self, state): 76 | """ 77 | @type state: enums.ProcessState 78 | """ 79 | if state == ProcessState.Launching: 80 | self._reset() 81 | self.redraw() 82 | elif state == ProcessState.Running: 83 | self._schedule_refresh() 84 | 85 | def _timer_tick(self): 86 | self.sizes.append(self.heap_size) 87 | self.times.append(time.time() - self.start_time) 88 | self.redraw() 89 | return self.debugger.process_state == ProcessState.Running 90 | 91 | def _schedule_refresh(self): 92 | GObject.timeout_add(1000, 93 | self._timer_tick) 94 | 95 | def _handle_heap_change(self, heap): 96 | """ 97 | @type heap: list of debugee.HeapBlock 98 | """ 99 | size = 0 100 | for block in heap: 101 | size += block.size 102 | 103 | self.heap_size = size / 1024.0 # size in MiBs 104 | 105 | 106 | class HeapDetail(Gtk.Box): 107 | def __init__(self, debugger, *args, **kwargs): 108 | """ 109 | @type debugger: debugger.Debugger 110 | """ 111 | Gtk.Box.__init__(self, *args, **kwargs) 112 | self.set_orientation(Gtk.Orientation.VERTICAL) 113 | 114 | self.debugger = debugger 115 | self.debugger.heap_manager.on_heap_change.subscribe( 116 | lambda heap: self._handle_heap_change(heap)) 117 | 118 | self.stats_wrapper = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) 119 | self.stats_wrapper.set_margin_bottom(5) 120 | self.pack_start(self.stats_wrapper, False, False, 0) 121 | 122 | self.block_tracker = self._create_stat_label() 123 | self.total_allocation_tracker = self._create_stat_label() 124 | self.total_deallocation_tracker = self._create_stat_label() 125 | self.total_memory_tracker = self._create_stat_label() 126 | 127 | self.graph = HeapGraph(debugger) 128 | self.pack_start(self.graph, True, True, 0) 129 | 130 | self.update_blocks([]) 131 | 132 | @require_gui_thread 133 | def update_blocks(self, heap): 134 | """ 135 | @type heap: list of debugee.HeapBlock 136 | """ 137 | self.block_tracker.set_label("Block count: {}".format(len(heap))) 138 | self.total_allocation_tracker.set_label( 139 | "Total allocations: {}".format( 140 | self.debugger.heap_manager.get_total_allocations())) 141 | self.total_deallocation_tracker.set_label( 142 | "Total deallocations: {}".format( 143 | self.debugger.heap_manager.get_total_deallocations())) 144 | self.total_memory_tracker.set_label("Heap size: {} b".format( 145 | sum([block.size for block in heap]))) 146 | 147 | def _create_stat_label(self): 148 | """ 149 | @rtype: Gtk.Widget 150 | """ 151 | view = Gtk.Label() 152 | view.set_margin_left(5) 153 | view.set_justify(Gtk.Justification.LEFT) 154 | view.set_alignment(0, 0) 155 | 156 | self.stats_wrapper.pack_start(view, False, False, 0) 157 | 158 | return view 159 | 160 | def _handle_heap_change(self, heap): 161 | """ 162 | @type heap: list of debugeee.HeapBlock 163 | """ 164 | run_on_gui(self.update_blocks, heap) 165 | -------------------------------------------------------------------------------- /gui/initialize.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import paths 23 | import sys 24 | from config import Config 25 | 26 | sys.path.append(paths.DIR_ROOT) 27 | 28 | from app import VisualiserApp # noqa 29 | 30 | if __name__ == '__main__': 31 | Config.preload() 32 | app = VisualiserApp() 33 | app.start() 34 | -------------------------------------------------------------------------------- /gui/paths.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | import os 23 | 24 | DIR_GUI = os.path.dirname(os.path.abspath(__file__)) 25 | DIR_ROOT = os.path.dirname(DIR_GUI) 26 | 27 | DIR_DEBUGGER = os.path.join(DIR_ROOT, "debugger") 28 | DIR_RES = os.path.join(DIR_ROOT, "res") 29 | 30 | 31 | def get_root_path(path): 32 | return os.path.join(DIR_ROOT, path) 33 | 34 | 35 | def get_resource(path): 36 | return os.path.join(DIR_RES, path) 37 | -------------------------------------------------------------------------------- /gui/tool_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from gi.repository import Gtk 23 | 24 | from gui_util import require_gui_thread 25 | 26 | 27 | class ToolManager(Gtk.Notebook): 28 | def __init__(self): 29 | super(ToolManager, self).__init__() 30 | 31 | self.set_tab_pos(Gtk.PositionType.BOTTOM) 32 | 33 | @require_gui_thread 34 | def get_active_tool(self): 35 | selected = self.get_current_page() 36 | 37 | if selected != -1: 38 | return self.get_nth_page(selected) 39 | else: 40 | return None 41 | 42 | @require_gui_thread 43 | def _handle_tab_click(self, widget): 44 | self.get_active_tool().set_visible(False) 45 | 46 | @require_gui_thread 47 | def add_tool(self, label, widget): 48 | """ 49 | @type label: string 50 | @type widget: Gtk.Widget 51 | """ 52 | self.append_page(widget, Gtk.Label(label=label)) 53 | -------------------------------------------------------------------------------- /gui/toolbar_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (C) 2015-2016 Jakub Beranek 4 | # 5 | # This file is part of Devi. 6 | # 7 | # Devi is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Devi is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Devi. If not, see . 19 | # 20 | 21 | 22 | from debugger.enums import ProcessState, DebuggerState 23 | from debugger.util import EventBroadcaster 24 | from gui_util import require_gui_thread, run_on_gui 25 | 26 | 27 | class ToolbarManager(object): 28 | def __init__(self, toolbar_builder, debugger): 29 | signals = { 30 | "toolbar-run": lambda *x: self.run(), 31 | "toolbar-continue": lambda *x: self.cont(), 32 | "toolbar-stop": lambda *x: self.stop(), 33 | "toolbar-pause": lambda *x: self.pause(), 34 | "toolbar-step-over": lambda *x: self.step_over(), 35 | "toolbar-step-in": lambda *x: self.step_in(), 36 | "toolbar-step-out": lambda *x: self.step_out() 37 | } 38 | 39 | toolbar_builder.connect_signals(signals) 40 | 41 | self.toolbar_builder = toolbar_builder 42 | self.toolbar = toolbar_builder.get_object("toolbar") 43 | self.debugger = debugger 44 | self.debugger.on_process_state_changed.subscribe( 45 | self._handle_process_state_change) 46 | self.debugger.on_debugger_state_changed.subscribe( 47 | self._handle_debugger_state_change) 48 | 49 | self.grp_halt_control = ["stop", "pause"] 50 | self.grp_step = ["continue", "step_over", "step_in", "step_out"] 51 | 52 | self.on_run_process = EventBroadcaster() 53 | 54 | @require_gui_thread 55 | def _get_items(self): 56 | return [self.toolbar.get_nth_item(i) 57 | for i in xrange(0, self.toolbar.get_n_items())] 58 | 59 | def _state_exited(self): 60 | self._change_grp_state(self.grp_halt_control, False) 61 | self._change_grp_state(self.grp_step, False) 62 | self._change_state("run", True) 63 | 64 | def _state_stopped(self): 65 | self._change_grp_state(self.grp_halt_control, False) 66 | self._change_grp_state(self.grp_step, True) 67 | 68 | self._change_state("stop", True) 69 | 70 | def _state_running(self): 71 | self._change_grp_state(self.grp_halt_control, True) 72 | self._change_grp_state(self.grp_step, False) 73 | self._change_state("run", False) 74 | 75 | def _handle_process_state_change(self, state, event_data): 76 | if state == ProcessState.Exited: 77 | self._state_exited() 78 | elif state == ProcessState.Stopped: 79 | self._state_stopped() 80 | elif state == ProcessState.Running: 81 | self._state_running() 82 | 83 | def _handle_debugger_state_change(self, state, old_value): 84 | if (state.is_set(DebuggerState.BinaryLoaded) and 85 | not state.is_set(DebuggerState.Running)): 86 | self._change_state("run", True) 87 | else: 88 | self._change_state("run", False) 89 | 90 | def _change_state(self, item_name, sensitive=True): 91 | run_on_gui(self._change_state_ui, item_name, sensitive) 92 | 93 | def _change_grp_state(self, group, sensitive=True): 94 | for item in group: 95 | self._change_state(item, sensitive) 96 | 97 | @require_gui_thread 98 | def _change_state_ui(self, item_name, sensitive=True): 99 | item = self.toolbar_builder.get_object(item_name) 100 | item.set_sensitive(sensitive) 101 | 102 | def run(self): 103 | self.on_run_process.notify() 104 | 105 | def cont(self): 106 | self.debugger.exec_continue() 107 | 108 | def stop(self): 109 | self.debugger.quit_program() 110 | 111 | def pause(self): 112 | self.debugger.exec_pause() 113 | 114 | def step_over(self): 115 | self.debugger.exec_step_over() 116 | 117 | def step_in(self): 118 | self.debugger.exec_step_in() 119 | 120 | def step_out(self): 121 | self.debugger.exec_step_out() 122 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | pushd ${DIR} 5 | ./waf download && 6 | ./waf configure && 7 | ./waf build 8 | popd 9 | -------------------------------------------------------------------------------- /res/css/style.css: -------------------------------------------------------------------------------- 1 | .value-entry { 2 | background-color: #606060; 3 | border: 1px solid #FFFFFF; 4 | } 5 | .value-entry > GtkLabel { 6 | color: #FFFFFF; 7 | } -------------------------------------------------------------------------------- /res/gui/io_console.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | vertical 9 | 10 | 11 | True 12 | False 13 | 14 | 15 | True 16 | False 17 | 5 18 | I/O console 19 | 20 | 21 | False 22 | True 23 | 0 24 | 25 | 26 | 27 | 28 | True 29 | False 30 | 5 31 | 32 | 33 | stdin 34 | True 35 | True 36 | False 37 | Show/hide standard input 38 | 0 39 | True 40 | True 41 | 42 | 43 | 44 | False 45 | True 46 | 0 47 | 48 | 49 | 50 | 51 | stdout 52 | True 53 | True 54 | False 55 | Show/hide standard output 56 | 0 57 | True 58 | True 59 | 60 | 61 | 62 | False 63 | True 64 | 1 65 | 66 | 67 | 68 | 69 | stderr 70 | True 71 | True 72 | False 73 | Show/hide standard error output 74 | 0 75 | True 76 | True 77 | 78 | 79 | 80 | False 81 | True 82 | 2 83 | 84 | 85 | 86 | 87 | Clear 88 | True 89 | True 90 | True 91 | Clear console 92 | end 93 | 94 | 95 | 96 | False 97 | False 98 | 3 99 | 100 | 101 | 102 | 103 | False 104 | True 105 | end 106 | 1 107 | 108 | 109 | 110 | 111 | False 112 | True 113 | 0 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /res/gui/memory_canvas_toolbar.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | False 9 | 10 | 11 | True 12 | False 13 | Reset origin 14 | True 15 | Reset _origin 16 | True 17 | gtk-home 18 | 19 | 20 | 21 | False 22 | True 23 | 24 | 25 | 26 | 27 | True 28 | False 29 | Zoom out 30 | True 31 | Zoom _out 32 | True 33 | gtk-zoom-out 34 | 35 | 36 | 37 | False 38 | True 39 | 40 | 41 | 42 | 43 | True 44 | False 45 | Zoom in 46 | True 47 | Zoom _in 48 | True 49 | gtk-zoom-in 50 | 51 | 52 | 53 | False 54 | True 55 | 56 | 57 | 58 | 59 | True 60 | False 61 | Reset zoom 62 | True 63 | _Reset zoom 64 | True 65 | gtk-zoom-100 66 | 67 | 68 | 69 | False 70 | True 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /res/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/res/img/arrow.png -------------------------------------------------------------------------------- /res/img/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/res/img/circle.png -------------------------------------------------------------------------------- /res/img/circle32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/res/img/circle32x32.png -------------------------------------------------------------------------------- /res/img/reload.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/res/img/reload.gif -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | pushd ${DIR} 5 | python gui/initialize.py 6 | popd 7 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import traceback 5 | from collections import Iterable 6 | 7 | import pytest 8 | import sys 9 | 10 | from debugger.enums import ProcessState 11 | from tests.src import compile 12 | 13 | path = os.path.dirname(os.path.abspath(__file__)) 14 | 15 | TEST_DIR = path 16 | TEST_SRC_DIR = os.path.join(TEST_DIR, "src") 17 | ROOT_DIR = os.path.dirname(TEST_DIR) 18 | SRC_DIR = os.path.join(ROOT_DIR, "debugger") 19 | 20 | sys.path.append(SRC_DIR) 21 | os.chdir(TEST_DIR) 22 | 23 | from debugger.mi.mi_debugger import MiDebugger # noqa 24 | from debugger.mi.parser import Parser # noqa 25 | 26 | 27 | compile.compile_tests() 28 | 29 | 30 | class AsyncState(object): 31 | def __init__(self): 32 | self.state = 0 33 | 34 | def inc(self): 35 | self.state += 1 36 | 37 | 38 | @pytest.fixture(scope="function") 39 | def debugger(): 40 | return MiDebugger() 41 | 42 | 43 | @pytest.fixture(scope="module") 44 | def parser(): 45 | return Parser() 46 | 47 | 48 | def setup_debugger(debugger, binary, lines, on_state_change=None, 49 | startup_info=None, cont=True, wait=True): 50 | assert debugger.load_binary("src/{}".format(binary)) 51 | 52 | test_exception = [] 53 | 54 | if not isinstance(lines, Iterable): 55 | lines = [lines] 56 | 57 | for line in lines: 58 | assert debugger.breakpoint_manager.add_breakpoint( 59 | "src/{}.cpp".format(binary), line) 60 | 61 | if on_state_change: 62 | def on_stop(state, data): 63 | if state == ProcessState.Stopped: 64 | try: 65 | on_state_change() 66 | if cont: 67 | debugger.exec_continue() 68 | except Exception as exc: 69 | test_exception.append(traceback.format_exc(exc)) 70 | debugger.quit_program() 71 | 72 | debugger.on_process_state_changed.subscribe(on_stop) 73 | 74 | assert debugger.launch(startup_info) 75 | 76 | if wait: 77 | debugger.wait_for_exit() 78 | 79 | if len(test_exception) > 0: 80 | pytest.fail(test_exception[0], pytrace=False) 81 | -------------------------------------------------------------------------------- /tests/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/tests/src/__init__.py -------------------------------------------------------------------------------- /tests/src/compile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import subprocess 5 | 6 | src_path = os.path.dirname(os.path.abspath(__file__)) 7 | 8 | 9 | def compile_tests(): 10 | for file in os.listdir(src_path): 11 | if file.endswith(".cpp"): 12 | basename = os.path.splitext(file)[0] 13 | subprocess.check_call(["g++", 14 | "-g", 15 | "-O0", 16 | "-pthread", 17 | "-std=c++11", 18 | "-m32", 19 | "{}".format(os.path.join(src_path, file)), 20 | "-o{}".format( 21 | os.path.join(src_path, basename))]) 22 | 23 | 24 | if __name__ == "__main__": 25 | compile_tests() 26 | -------------------------------------------------------------------------------- /tests/src/test_alloc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) 4 | { 5 | int* data = (int*) malloc(1024); 6 | 7 | if (data[0]) // without this the allocation is optimized away 8 | { 9 | data[5] = 8; 10 | } 11 | 12 | free(data); 13 | 14 | return 0; 15 | } -------------------------------------------------------------------------------- /tests/src/test_breakpoint_basic.cpp: -------------------------------------------------------------------------------- 1 | int main(int argc, char** argv) 2 | { 3 | int a = 5; 4 | 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/src/test_disassemble.cpp: -------------------------------------------------------------------------------- 1 | int main(int argc, char** argv) 2 | { 3 | int a = 5; 4 | a += 10; 5 | 6 | return 0; 7 | } -------------------------------------------------------------------------------- /tests/src/test_execution.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int test(int b) 4 | { 5 | int a = rand() * b; 6 | return a + b; 7 | } 8 | 9 | int main() 10 | { 11 | test(5); 12 | int b = 6; 13 | int c = 7; 14 | 15 | while (true); 16 | 17 | return 0; 18 | } -------------------------------------------------------------------------------- /tests/src/test_frame.cpp: -------------------------------------------------------------------------------- 1 | int test(int a, float d) 2 | { 3 | int b = a + 1; 4 | float c = d + a; 5 | 6 | return b + c; 7 | } 8 | 9 | int main(int argc, char** argv) 10 | { 11 | int b = 5; 12 | b = test(b, 5.0f); 13 | 14 | return 0; 15 | } -------------------------------------------------------------------------------- /tests/src/test_startup_info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char** argv) 5 | { 6 | std::cout << argc << std::endl; 7 | 8 | for (int i = 0; i < argc; i++) 9 | { 10 | std::cout << argv[i] << std::endl; 11 | } 12 | 13 | std::cout << getenv("DEVI_ENV_TEST") << std::endl; 14 | 15 | return 0; 16 | } -------------------------------------------------------------------------------- /tests/src/test_thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | volatile int a = 0; 6 | 7 | void* test(void* param) 8 | { 9 | while (true) 10 | { 11 | a++; 12 | 13 | if (a == 5) 14 | { 15 | a--; 16 | } 17 | } 18 | } 19 | 20 | int main() 21 | { 22 | pthread_t thread; 23 | int result = pthread_create(&thread, NULL, test, NULL); 24 | 25 | sleep(1); 26 | 27 | while (true) 28 | { 29 | a++; 30 | 31 | if (a == 5) 32 | { 33 | a--; 34 | } 35 | } 36 | 37 | pthread_join(thread, NULL); 38 | 39 | return 0; 40 | } -------------------------------------------------------------------------------- /tests/src/test_type.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class classA 5 | { 6 | 7 | }; 8 | 9 | struct structA 10 | { 11 | 12 | }; 13 | 14 | union unionA 15 | { 16 | 17 | }; 18 | 19 | enum enumA 20 | { 21 | 22 | }; 23 | 24 | enum class enumB 25 | { 26 | 27 | }; 28 | 29 | void test() 30 | { 31 | 32 | } 33 | 34 | int main() 35 | { 36 | int varInt; 37 | unsigned short varUnsignedShort; 38 | float varFloat; 39 | classA varClassA; 40 | structA varStructA; 41 | unionA varUnionA; 42 | enumA varEnumA; 43 | enumB varEnumB; 44 | std::vector varVector; 45 | std::string varString; 46 | int varArray[10]; 47 | int* varPointer; 48 | int& varReference = varInt; 49 | void (*varFunctionPointer)() = test; 50 | 51 | return 0; 52 | } -------------------------------------------------------------------------------- /tests/src/test_variable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct structA 5 | { 6 | int x; 7 | std::string y; 8 | }; 9 | 10 | class classA 11 | { 12 | public: 13 | structA str; 14 | std::vector vec; 15 | 16 | void test() { } 17 | }; 18 | 19 | union unionA 20 | { 21 | public: 22 | int a; 23 | int b; 24 | }; 25 | 26 | enum EnumA { A, B }; 27 | enum class EnumB { A, B }; 28 | 29 | int test(int a, float b) 30 | { 31 | 32 | } 33 | 34 | int main() 35 | { 36 | std::vector vec = { 1, 2, 3 }; 37 | int a = 5; 38 | float b = 5.5f; 39 | bool c = true; 40 | std::string d = "hello"; 41 | int e[10] = { 1, 2, 3 }; 42 | 43 | structA strA; 44 | strA.x = a; 45 | strA.y = d; 46 | 47 | classA clsA; 48 | clsA.str = strA; 49 | clsA.vec = std::vector(); 50 | clsA.vec.push_back(a); 51 | 52 | EnumA enumA = EnumA::A; 53 | EnumB enumB = EnumB::B; 54 | 55 | unionA uniA; 56 | uniA.a = 5; 57 | 58 | int (*fn_pointer)(int, float) = test; 59 | 60 | return 0; 61 | } -------------------------------------------------------------------------------- /tests/test_mi_alloc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This test depends on particular implementation of glibc (that does not 5 | allocate memory on it's own for the given program (src/test_alloc.cpp), 6 | so it may not work elsewhere. 7 | """ 8 | 9 | import copy 10 | 11 | from tests.conftest import setup_debugger 12 | 13 | TEST_FILE = "test_alloc" 14 | TEST_LINE = 8 15 | 16 | 17 | def test_alloc(debugger): 18 | heap = [] 19 | debugger.heap_manager.on_heap_change.subscribe(lambda new_heap: 20 | heap.append( 21 | copy.copy(new_heap))) 22 | 23 | def test_alloc_cb(): 24 | assert len(heap[0]) == 1 25 | assert heap[0][0].size == 1024 26 | assert len(heap[1]) == 0 27 | 28 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_alloc_cb) 29 | -------------------------------------------------------------------------------- /tests/test_mi_basic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from debugger.enums import DebuggerState 6 | from tests.conftest import TEST_SRC_DIR 7 | 8 | 9 | def test_stop_no_launch(debugger): 10 | debugger.load_binary(os.path.join(TEST_SRC_DIR, "test_breakpoint_basic")) 11 | debugger.quit_program() 12 | 13 | 14 | def test_load_file(debugger): 15 | assert debugger.load_binary(os.path.join(TEST_SRC_DIR, 16 | "test_breakpoint_basic")) 17 | assert debugger.state.is_set(DebuggerState.BinaryLoaded) 18 | assert debugger.file_manager.get_main_source_file() == os.path.join( 19 | TEST_SRC_DIR, "test_breakpoint_basic.cpp") 20 | -------------------------------------------------------------------------------- /tests/test_mi_breakpoint.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from tests.conftest import TEST_SRC_DIR 6 | 7 | 8 | def test_bp_add(debugger): 9 | src_file = "src/test_breakpoint_basic.cpp" 10 | line = 5 11 | 12 | debugger.load_binary(os.path.join(TEST_SRC_DIR, "test_breakpoint_basic")) 13 | assert debugger.breakpoint_manager.add_breakpoint(src_file, line) 14 | assert not debugger.breakpoint_manager.add_breakpoint(src_file, 100) 15 | assert not debugger.breakpoint_manager.add_breakpoint("", line) 16 | 17 | debugger.breakpoint_manager.add_breakpoint(src_file, 1) 18 | 19 | assert len(debugger.breakpoint_manager.get_breakpoints()) == 2 20 | 21 | 22 | def test_bp_remove(debugger): 23 | src_file = "src/test_breakpoint_basic.cpp" 24 | line = 5 25 | 26 | debugger.load_binary(os.path.join(TEST_SRC_DIR, "test_breakpoint_basic")) 27 | debugger.breakpoint_manager.add_breakpoint(src_file, line) 28 | 29 | assert debugger.breakpoint_manager.remove_breakpoint(src_file, line) 30 | assert len(debugger.breakpoint_manager.get_breakpoints()) == 0 31 | -------------------------------------------------------------------------------- /tests/test_mi_disassemble.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This test is made towards a specific version of GDB and operating system 5 | (i686), so it may not work elsewhere. 6 | """ 7 | 8 | from tests.conftest import setup_debugger 9 | 10 | TEST_FILE = "test_disassemble" 11 | SRC_FILE = "src/{}.cpp".format(TEST_FILE) 12 | 13 | 14 | def generate_instructions(instruction, reg32bit, reg64bit): 15 | return (instruction.format(reg32bit), 16 | instruction.format(reg64bit)) 17 | 18 | 19 | def test_address_invalid_file(debugger): 20 | def test_address_invalid_file_cb(): 21 | assert debugger.file_manager.get_line_address("x.cpp", 0) is None 22 | 23 | setup_debugger(debugger, TEST_FILE, 3, test_address_invalid_file_cb) 24 | 25 | 26 | def test_address_invalid_line(debugger): 27 | def test_address_invalid_line_cb(): 28 | assert debugger.file_manager.get_line_address(SRC_FILE, 100) is None 29 | 30 | setup_debugger(debugger, TEST_FILE, 3, test_address_invalid_line_cb) 31 | 32 | 33 | def test_address_no_code(debugger): 34 | def test_address_no_code_cb(): 35 | assert debugger.file_manager.get_line_address(SRC_FILE, 5) is None 36 | 37 | setup_debugger(debugger, TEST_FILE, 3, test_address_no_code_cb) 38 | 39 | 40 | def test_address_with_code(debugger): 41 | def test_address_with_code_cb(): 42 | line_address = debugger.file_manager.get_line_address(SRC_FILE, 3) 43 | assert isinstance(line_address, tuple) 44 | assert len(line_address) == 2 45 | assert int(line_address[0], 16) < int(line_address[1], 16) 46 | 47 | setup_debugger(debugger, TEST_FILE, 3, test_address_with_code_cb) 48 | 49 | 50 | def test_disassemble(debugger): 51 | def test_disassemble_cb(): 52 | disas = debugger.file_manager.disassemble(SRC_FILE, 3) 53 | 54 | assert len(disas) == 6 55 | 56 | decl_ds = disas[1] 57 | 58 | assert decl_ds["line"] == 3 59 | assert len(decl_ds["instructions"]) == 1 60 | assert decl_ds["instructions"][0] in ( 61 | generate_instructions("movl $0x5,-0x4(%{})", "ebp", "rbp")) 62 | 63 | assign_ds = disas[2] 64 | 65 | assert assign_ds["line"] == 4 66 | assert len(assign_ds["instructions"]) == 1 67 | assert assign_ds["instructions"][0] in ( 68 | generate_instructions("addl $0xa,-0x4(%{})", "ebp", "rbp")) 69 | 70 | setup_debugger(debugger, TEST_FILE, 3, test_disassemble_cb) 71 | -------------------------------------------------------------------------------- /tests/test_mi_execution.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import time 5 | 6 | from tests.conftest import AsyncState, setup_debugger 7 | 8 | TEST_FILE = "test_execution" 9 | 10 | 11 | def make_location(line): 12 | return (os.path.abspath("src/{}.cpp".format(TEST_FILE)), line) 13 | 14 | 15 | def test_step_over(debugger): 16 | state = AsyncState() 17 | 18 | def test_step_over_cb(): 19 | if state.state == 0: 20 | state.inc() 21 | debugger.exec_step_over() 22 | else: 23 | assert debugger.file_manager.get_current_location() ==\ 24 | make_location(12) 25 | debugger.quit_program() 26 | 27 | setup_debugger(debugger, TEST_FILE, 11, test_step_over_cb, cont=False) 28 | 29 | 30 | def test_step_in(debugger): 31 | state = AsyncState() 32 | 33 | def test_step_in_cb(): 34 | if state.state == 0: 35 | state.inc() 36 | debugger.exec_step_in() 37 | else: 38 | assert debugger.file_manager.get_current_location() ==\ 39 | make_location(5) 40 | debugger.quit_program() 41 | 42 | setup_debugger(debugger, TEST_FILE, 11, test_step_in_cb, cont=False) 43 | 44 | 45 | def test_step_out(debugger): 46 | state = AsyncState() 47 | 48 | def test_step_out_cb(): 49 | if state.state == 0: 50 | state.inc() 51 | debugger.exec_step_out() 52 | else: 53 | location = debugger.file_manager.get_current_location() 54 | assert location[0] == make_location(11)[0] 55 | assert location[1] in (11, 12) 56 | debugger.quit_program() 57 | 58 | setup_debugger(debugger, TEST_FILE, 5, test_step_out_cb, cont=False) 59 | 60 | 61 | def test_continue(debugger): 62 | state = AsyncState() 63 | 64 | def test_continue_cb(): 65 | if state.state == 0: 66 | state.inc() 67 | debugger.exec_continue() 68 | else: 69 | assert debugger.file_manager.get_current_location() ==\ 70 | make_location(13) 71 | debugger.quit_program() 72 | 73 | setup_debugger(debugger, TEST_FILE, (11, 13), test_continue_cb, 74 | cont=False) 75 | 76 | 77 | def test_pause(debugger): 78 | def test_pause_cb(): 79 | debugger.quit_program() 80 | 81 | setup_debugger(debugger, TEST_FILE, [], test_pause_cb, 82 | cont=False, wait=False) 83 | 84 | time.sleep(0.5) 85 | debugger.exec_pause() 86 | 87 | 88 | def test_stop(debugger): 89 | setup_debugger(debugger, TEST_FILE, [], None, 90 | cont=False, wait=False) 91 | 92 | time.sleep(0.5) 93 | debugger.quit_program() 94 | -------------------------------------------------------------------------------- /tests/test_mi_frame.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from tests.conftest import setup_debugger 6 | 7 | TEST_FILE = "test_frame" 8 | TEST_LINE = 6 9 | 10 | 11 | def test_frame_list(debugger): 12 | def test_frame_list_cb(): 13 | assert len(debugger.thread_manager.get_frames()) == 2 14 | 15 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_frame_list_cb) 16 | 17 | 18 | def test_frame_properties(debugger): 19 | def test_frame_properties_cb(): 20 | frame = debugger.thread_manager.get_current_frame(False) 21 | 22 | assert frame.file == os.path.abspath("src/{}.cpp".format(TEST_FILE)) 23 | assert frame.line == TEST_LINE 24 | assert frame.func == "test" 25 | assert frame.level == 0 26 | assert len(frame.variables) == 0 27 | 28 | setup_debugger(debugger, TEST_FILE, TEST_LINE, 29 | test_frame_properties_cb) 30 | 31 | 32 | def test_frame_select(debugger): 33 | def test_frame_select_cb(): 34 | assert debugger.thread_manager.change_frame(1) 35 | 36 | frame = debugger.thread_manager.get_current_frame(False) 37 | 38 | assert frame.func == "main" 39 | 40 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_frame_select_cb) 41 | 42 | 43 | def test_frame_locals(debugger): 44 | def test_frame_locals_cb(): 45 | frame = debugger.thread_manager.get_current_frame(True) 46 | 47 | assert len(frame.variables) == 4 48 | 49 | var_names = [var.name for var in frame.variables] 50 | assert set(var_names) == {"a", "b", "c", "d"} 51 | 52 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_frame_locals_cb) 53 | -------------------------------------------------------------------------------- /tests/test_mi_parser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | def test_remove_label(parser): 5 | data = "[aaa={b=\"c\"}]" 6 | 7 | assert parser._remove_array_labels(data) == "[{b=\"c\"}]" 8 | 9 | 10 | def test_modify_label(parser): 11 | data = "asd = {dsa = 5, c=\"c = 8, 6 98 {}\" }" 12 | 13 | assert parser._modify_labels(data) == "\"asd\": {\"dsa\":"\ 14 | " 5, \"c\":\"c = 8, 6 98 {}\" }" 15 | 16 | 17 | def test_parse(parser): 18 | dict = parser.parse("{a=5, c=[1, 2]}") 19 | 20 | assert "a" in dict and dict["a"] == 5 21 | assert "c" in dict and len(dict["c"]) == 2 22 | -------------------------------------------------------------------------------- /tests/test_mi_startup_info.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | from debugger.debugger_api import StartupInfo 6 | from tests.conftest import setup_debugger 7 | 8 | TEST_FILE = "test_startup_info" 9 | TEST_LINE = 15 10 | 11 | 12 | def test_startup_info(debugger): 13 | """ 14 | @type debugger: debugger.debugger_api.Debugger 15 | """ 16 | env_value = "test test_env" 17 | startup_info = StartupInfo("test1 test2", os.getcwd(), 18 | [("DEVI_ENV_TEST", env_value)]) 19 | 20 | def test_startup_info_cb(): 21 | lines = [] 22 | 23 | for x in xrange(5): 24 | lines.append(debugger.io_manager.stdout.readline()[:-1]) 25 | 26 | assert lines == [ 27 | "3", 28 | os.path.abspath("src/{}".format(TEST_FILE)), 29 | "test1", 30 | "test2", 31 | env_value 32 | ] 33 | 34 | setup_debugger(debugger, TEST_FILE, TEST_LINE, 35 | test_startup_info_cb, startup_info) 36 | -------------------------------------------------------------------------------- /tests/test_mi_thread.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from tests.conftest import setup_debugger 4 | 5 | TEST_FILE = "test_thread" 6 | TEST_LINE = 29 7 | 8 | 9 | def select_other_thread(debugger): 10 | thread_info = debugger.thread_manager.get_thread_info() 11 | selected = None 12 | 13 | for thread in thread_info.threads: 14 | if thread != thread_info.selected_thread: 15 | debugger.thread_manager.set_thread_by_index(thread.id) 16 | selected = thread 17 | 18 | return selected 19 | 20 | 21 | def test_thread_info(debugger): 22 | def test_thread_info_cb(): 23 | thread_info = debugger.thread_manager.get_thread_info() 24 | 25 | assert len(thread_info.threads) == 2 26 | assert thread_info.selected_thread.id == 1 27 | 28 | debugger.quit_program() 29 | 30 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_thread_info_cb, 31 | cont=False) 32 | 33 | 34 | def test_thread_switch(debugger): 35 | def test_thread_switch_cb(): 36 | selected = select_other_thread(debugger) 37 | 38 | thread_info = debugger.thread_manager.get_thread_info() 39 | assert thread_info.selected_thread.id == selected.id 40 | 41 | debugger.quit_program() 42 | 43 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_thread_switch_cb, 44 | cont=False) 45 | 46 | 47 | def test_thread_location(debugger): 48 | def test_thread_location_cb(): 49 | assert debugger.file_manager.get_current_location()[1] in range(27, 35) 50 | 51 | select_other_thread(debugger) 52 | 53 | assert debugger.file_manager.get_current_location()[1] in range(9, 18) 54 | 55 | debugger.quit_program() 56 | 57 | setup_debugger(debugger, TEST_FILE, TEST_LINE, 58 | test_thread_location_cb, cont=False) 59 | 60 | 61 | def test_thread_frame(debugger): 62 | def test_thread_frame_cb(): 63 | thread_info = debugger.thread_manager.get_thread_info() 64 | frame = debugger.thread_manager.get_current_frame(True) 65 | 66 | assert thread_info.selected_thread.frame.func == frame.func 67 | vars = [var.name for var in frame.variables] 68 | assert "thread" in vars 69 | assert "result" in vars 70 | 71 | select_other_thread(debugger) 72 | 73 | frame = debugger.thread_manager.get_current_frame(True) 74 | assert "param" in [var.name for var in frame.variables] 75 | 76 | debugger.quit_program() 77 | 78 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_thread_frame_cb, 79 | cont=False) 80 | -------------------------------------------------------------------------------- /tests/test_mi_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from debugger.enums import TypeCategory, BasicTypeCategory 4 | from tests.conftest import setup_debugger 5 | 6 | TEST_FILE = "test_type" 7 | TEST_LINE = 51 8 | 9 | 10 | def check_type(debugger, variable_name, type_name, type_category, 11 | basic_type_category=BasicTypeCategory.Invalid): 12 | type = debugger.variable_manager.get_type(variable_name) 13 | 14 | if isinstance(type_name, str): 15 | assert type.name == type_name 16 | elif hasattr(type_name, "__call__"): 17 | assert type_name(type.name) 18 | 19 | assert type.type_category == type_category 20 | assert type.basic_type_category == basic_type_category 21 | 22 | return type 23 | 24 | 25 | def test_types(debugger): 26 | def test_types_cb(): 27 | check_type(debugger, "varInt", "int", TypeCategory.Builtin, 28 | BasicTypeCategory.Int) 29 | check_type(debugger, "varUnsignedShort", "unsigned short", 30 | TypeCategory.Builtin, BasicTypeCategory.UnsignedShort) 31 | check_type(debugger, "varFloat", "float", TypeCategory.Builtin, 32 | BasicTypeCategory.Float) 33 | check_type(debugger, "varClassA", "classA", TypeCategory.Class) 34 | check_type(debugger, "varStructA", "structA", TypeCategory.Struct) 35 | check_type(debugger, "varUnionA", "unionA", TypeCategory.Union) 36 | check_type(debugger, "varEnumA", "enumA", TypeCategory.Enumeration) 37 | check_type(debugger, "varEnumB", "enumB", TypeCategory.Enumeration) 38 | type = check_type(debugger, "varVector", 39 | lambda name: name.startswith("std::vector"), 40 | TypeCategory.Vector) 41 | assert type.child_type.name == "int" 42 | 43 | check_type(debugger, "varString", "std::string", TypeCategory.String) 44 | 45 | type = check_type(debugger, "varArray", "int [10]", TypeCategory.Array) 46 | assert type.count == 10 47 | assert type.child_type.name == "int" 48 | 49 | check_type(debugger, "varPointer", "int *", TypeCategory.Pointer) 50 | check_type(debugger, "varReference", "int &", TypeCategory.Reference) 51 | check_type(debugger, "varFunctionPointer", "void (*)(void)", 52 | TypeCategory.Function) 53 | 54 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_types_cb) 55 | -------------------------------------------------------------------------------- /tests/test_mi_variable.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | from collections import Iterable 4 | 5 | from debugger.enums import TypeCategory 6 | from tests.conftest import setup_debugger 7 | 8 | int_size = (4, 8) 9 | TEST_FILE = "test_variable" 10 | TEST_LINE = 60 11 | 12 | 13 | def check_variable(debugger, expression, value=None, size=None): 14 | variable = debugger.variable_manager.get_variable(expression) 15 | 16 | assert variable.path == expression 17 | 18 | if value is not None: 19 | assert variable.value == value 20 | 21 | if size: 22 | if isinstance(size, Iterable): 23 | assert variable.type.size in size 24 | else: 25 | assert variable.type.size == size 26 | 27 | return variable 28 | 29 | 30 | def test_values(debugger): 31 | def test_values_cb(): 32 | check_variable(debugger, "a", "5", int_size) 33 | check_variable(debugger, "b", "5.5", int_size) 34 | check_variable(debugger, "c", "true", 1) 35 | check_variable(debugger, "d", "hello") 36 | var = check_variable(debugger, "e") 37 | assert var.data_address == var.address 38 | assert var.max_size == 10 39 | arr_item = debugger.variable_manager.get_variable("e[2]") 40 | assert arr_item.value == "3" 41 | assert var.get_index_by_address(arr_item.address) == 2 42 | 43 | check_variable(debugger, "strA.x", "5", int_size) 44 | 45 | vec = debugger.variable_manager.get_variable("vec") 46 | vec.count = vec.max_size 47 | debugger.variable_manager.get_vector_items(vec) 48 | assert map(lambda child: int(child.value), vec.children) == [1, 2, 3] 49 | 50 | vec_item = debugger.variable_manager.get_variable( 51 | "*((({}){}) + 2)".format( 52 | vec.type.child_type.name, 53 | vec.data_address)) 54 | 55 | assert vec_item.value == "3" 56 | assert vec.get_index_by_address(vec_item.address) == 2 57 | 58 | assert vec.data_address != vec.address 59 | 60 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_values_cb) 61 | 62 | 63 | def test_composite(debugger): 64 | def test_composite_cb(): 65 | strA = debugger.variable_manager.get_variable("strA") 66 | assert strA.type.type_category == TypeCategory.Struct 67 | 68 | children = strA.children 69 | assert children[0].name == "x" 70 | assert children[0].value == "5" 71 | assert children[1].name == "y" 72 | assert children[1].value == "hello" 73 | 74 | clsA = debugger.variable_manager.get_variable("clsA") 75 | assert clsA.type.type_category == TypeCategory.Class 76 | assert clsA.children[0].children[0].value == strA.children[0].value 77 | 78 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_composite_cb) 79 | 80 | 81 | def test_enum(debugger): 82 | def test_enum_cb(): 83 | enumA = debugger.variable_manager.get_variable("enumA") 84 | assert enumA.value == "A" 85 | 86 | enumB = debugger.variable_manager.get_variable("enumB") 87 | assert enumB.value == "EnumB::B" 88 | 89 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_enum_cb) 90 | 91 | 92 | def test_union(debugger): 93 | def test_union_cb(): 94 | uniA = debugger.variable_manager.get_variable("uniA") 95 | assert uniA.children[0].value == "5" 96 | assert uniA.children[0].value == uniA.children[1].value 97 | assert uniA.children[0].address == uniA.children[1].address 98 | 99 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_union_cb) 100 | 101 | 102 | def test_function_pointer(debugger): 103 | def test_function_pointer_cb(): 104 | fn_pointer = debugger.variable_manager.get_variable("fn_pointer") 105 | assert fn_pointer.type.name == "int (*)(int, float)" 106 | assert re.match("0x(\w)+ ", fn_pointer.value) 107 | 108 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_function_pointer_cb) 109 | 110 | 111 | def test_update_variable(debugger): 112 | def test_update_variable_cb(): 113 | a = debugger.variable_manager.get_variable("a") 114 | a.value = "8" 115 | 116 | assert debugger.variable_manager.get_variable("a").value == "8" 117 | 118 | d = debugger.variable_manager.get_variable("d") 119 | d.value = "hi" 120 | 121 | assert debugger.variable_manager.get_variable("d").value == "hi" 122 | 123 | vec = debugger.variable_manager.get_variable("vec") 124 | vec.count = vec.max_size 125 | debugger.variable_manager.get_vector_items(vec) 126 | vec.children[0].value = "10" 127 | 128 | vec = debugger.variable_manager.get_variable("vec") 129 | vec.count = vec.max_size 130 | debugger.variable_manager.get_vector_items(vec) 131 | assert vec.children[0].value == "10" 132 | 133 | setup_debugger(debugger, TEST_FILE, TEST_LINE, 134 | test_update_variable_cb) 135 | 136 | 137 | def test_get_memory(debugger): 138 | def test_get_memory_cb(): 139 | var = debugger.variable_manager.get_variable("a") 140 | 141 | assert [5, 0, 0, 0] == debugger.variable_manager.get_memory( 142 | var.address, var.type.size) 143 | assert len(debugger.variable_manager.get_memory( 144 | var.address, 128)) == 128 145 | 146 | setup_debugger(debugger, TEST_FILE, TEST_LINE, test_get_memory_cb) 147 | -------------------------------------------------------------------------------- /thesis/thesis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/thesis/thesis.pdf -------------------------------------------------------------------------------- /util/lldb_patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LLDB_VERSION="3.6" 4 | 5 | if [ "$#" -ge 1 ]; then 6 | LLDB_VERSION=$1 7 | fi 8 | 9 | cd /usr/lib/llvm-$LLDB_VERSION/lib/python2.7/site-packages/lldb || exit 1 10 | sudo rm _lldb.so 11 | sudo ln -s ../../../liblldb.so.1 _lldb.so 12 | sudo rm libLLVM-$LLDB_VERSION.0.so.1 13 | sudo ln -s ../../../libLLVM-$LLDB_VERSION.0.so.1 libLLVM-$LLDB_VERSION.0.so.1 14 | sudo rm libLLVM-$LLDB_VERSION.so.1 15 | sudo ln -s ../../../libLLVM-$LLDB_VERSION.0.so.1 libLLVM-$LLDB_VERSION.so.1 16 | -------------------------------------------------------------------------------- /waf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kobzol/debug-visualizer/f7345869c9e0ab459a2d355382f41c6ebe7378fc/waf -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import print_function 5 | 6 | import os 7 | import shutil 8 | import subprocess 9 | import urllib2 10 | import tarfile 11 | import hashlib 12 | 13 | 14 | gdb_url = "http://ftp.gnu.org/gnu/gdb/gdb-7.11.tar.gz" 15 | gdb_build_dir = os.path.abspath("./build/gdb-build") 16 | gdb_extract_dir = os.path.abspath("./build/gdb-source") 17 | gdb_src_dir = os.path.join(gdb_extract_dir, "gdb-7.11") 18 | gdb_src_zip = os.path.abspath("./build/gdb-7.11.tar.gz") 19 | gdb_archive_length = 34526368 20 | gdb_archive_sha256 = "9382f5534aa0754169e1e09b5f1a3b77d1fa8c59c1e57617e0" \ 21 | "6af37cb29c669a" 22 | 23 | 24 | def hash(path): 25 | digest = hashlib.sha256() 26 | with open(path, "rb") as file: 27 | buf = file.read(4096) 28 | while len(buf) > 0: 29 | digest.update(buf) 30 | buf = file.read(4096) 31 | return digest.hexdigest() 32 | 33 | 34 | def build_gdb(): 35 | if (not os.path.isfile(gdb_src_zip) or 36 | hash(gdb_src_zip) != gdb_archive_sha256): 37 | print("Downloading GDB 7.11...") 38 | 39 | gdb = urllib2.urlopen(gdb_url) 40 | if gdb.getcode() != 200: 41 | raise BaseException("GDB could not be downloaded, error code {}". 42 | format(gdb.getcode())) 43 | 44 | info = gdb.info() 45 | length = info["Content-Length"] 46 | total_read = 0 47 | 48 | with open(gdb_src_zip, 'wb') as archive: 49 | while True: 50 | data = gdb.read(16384) 51 | if len(data) < 1: 52 | break 53 | else: 54 | total_read += len(data) 55 | archive.write(data) 56 | print("\r{:0.2f} %".format((total_read / float(length)) * 57 | 100.0), end="") 58 | print("\n") 59 | 60 | if not os.path.isfile(os.path.join(gdb_build_dir, "gdb")): 61 | print("Extracting GDB...") 62 | 63 | with tarfile.open(gdb_src_zip) as archive: 64 | archive.extractall(gdb_extract_dir) 65 | 66 | cwd = os.getcwd() 67 | os.chdir(gdb_src_dir) 68 | 69 | print("Compiling and installing GDB...") 70 | 71 | result = subprocess.call("./configure --prefix={0} --bindir={0}" 72 | " --with-python" 73 | .format(gdb_build_dir), shell=True) 74 | try: 75 | if result == 0: 76 | result == subprocess.call("make -j4", shell=True) 77 | 78 | if result != 0: 79 | raise BaseException("GDB could no be compiled") 80 | else: 81 | result == subprocess.call("make install", shell=True) 82 | 83 | if result != 0: 84 | raise BaseException("GDB could not be installed") 85 | else: 86 | raise BaseException("GDB could not be configured") 87 | except Exception as exc: 88 | print(exc.message) 89 | shutil.rmtree(gdb_extract_dir, ignore_errors=True) 90 | shutil.rmtree(gdb_build_dir, ignore_errors=True) 91 | 92 | os.chdir(cwd) 93 | 94 | 95 | def patch_lldb(conf): 96 | exit_status = subprocess.call(["./util/lldb_patch.sh", conf.options.lldb], 97 | stdout=subprocess.PIPE, 98 | stderr=subprocess.PIPE) 99 | 100 | if exit_status == 0: 101 | print("LLDB patched") 102 | else: 103 | print("LLDB not found") 104 | 105 | 106 | def options(opt): 107 | opt.add_option("-l", "--lldb", default="", action="store", 108 | help="version of LLDB to use") 109 | 110 | opt.load("python") 111 | opt.load("compiler_cxx") 112 | 113 | 114 | def configure(conf): 115 | conf.load("python") 116 | conf.load("compiler_cxx") 117 | 118 | patch_lldb(conf) 119 | 120 | conf.check_python_version((2, 7, 0)) 121 | if conf.options.lldb: 122 | conf.check_python_module("lldb") 123 | conf.check_python_module("enum") 124 | conf.check_python_module("gi.repository.Gtk") 125 | conf.check_python_module("matplotlib") 126 | conf.check_python_module("clang.cindex") 127 | 128 | 129 | def build(ctx): 130 | ctx.recurse("debugger") 131 | ctx.recurse("examples") 132 | build_gdb() 133 | 134 | 135 | def download(ctx): 136 | apt_args = ["g++", "texinfo", "clang-3.6", "python-dev", 137 | "python-matplotlib", "python-enum34", "python-clang-3.6"] 138 | subprocess.check_call(["sudo", "apt-get", "install"] + apt_args) 139 | 140 | 141 | def clean(ctx): 142 | import shutil 143 | shutil.rmtree("./docs", True) 144 | shutil.rmtree("./build", True) 145 | subprocess.call(["find", ".", "-name", "*.pyc", "-delete"]) 146 | 147 | 148 | def docs(ctx): 149 | try: 150 | import epydoc 151 | subprocess.call(["epydoc", "epydoc", "-v", "--config", "epydoc"]) 152 | except ImportError: 153 | raise ImportError("Couldn't not import package epydoc," 154 | "do you have it installed?") 155 | --------------------------------------------------------------------------------