├── .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 |
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 |
--------------------------------------------------------------------------------