├── .gitignore ├── LICENSE ├── README.md ├── gdbplotlib ├── __init__.py ├── data_extractor.py ├── default.py ├── plot.py ├── save.py ├── std_types.py ├── type_handler.py ├── type_set.py └── util.py ├── images ├── example_1.png ├── example_2.png ├── example_3.png └── example_4.png └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | *$py.class 4 | 5 | build/ 6 | dist/ 7 | *.egg-info/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 George Cholerton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GDBplotlib 2 | 3 | GDBplotlib is an extension to GDB which enables easy visualisation and exporting of data structures. The current implementation is focused on C++, although it could theoretically be extended to work with any language supported by GDB. [Ken Mankoff](https://github.com/mankoff) has created a fork that adds Fortan support, which can be found [here](https://github.com/mankoff/gdbplotlib/tree/fortran). 4 | 5 | ## Features 6 | 7 | * Many different visualisations, such as line graphs, scatter plots and histograms 8 | * Exporting of variables in `.mat`, Python pickle and binary formats 9 | * Works for arbitrarily nested data structures 10 | * Slice support 11 | * Can be easily extended to work with any custom type 12 | 13 | ## Requirements 14 | 15 | * GDB >= 7.0 16 | * Python 3 17 | * NumPy 18 | * Matplotlib 19 | * Scipy (OPTIONAL - for exporting to `.mat`) 20 | 21 | ## Installation 22 | 23 | GDBplotlib can be installed via `pip`: 24 | 25 | ```bash 26 | $ pip install gdbplotlib 27 | ``` 28 | 29 | To make GDBplotlib available in GDB sessions, add the following lines to `~/.gdbinit` (or create it if it doesn't exist): 30 | 31 | ```bash 32 | python 33 | import gdbplotlib 34 | end 35 | ``` 36 | 37 | ## Usage Examples 38 | 39 | Consider the following C++ program: 40 | 41 | ```cpp 42 | #include 43 | #include 44 | 45 | int main() 46 | { 47 | std::array x = {0.1, 0.9, 0.8, 0.7, 0.2, 0.1}; 48 | 49 | int* y = new int[100]; 50 | for (int i = 0; i < 100; ++i) { 51 | y[i] = 50 - i + int(5e-3 * i * i); 52 | } 53 | 54 | std::vector> z(10); 55 | for (int i = 0; i < z.size(); ++i) { 56 | for (int j = 0; j < z[i].size(); ++j) { 57 | z[i][j] = new int[10]; 58 | for (int k = 0; k < 10; ++k) { 59 | z[i][j][k] = i + 2*j + 3*k; 60 | } 61 | } 62 | } 63 | 64 | return 0; 65 | } 66 | ``` 67 | 68 | To create a line graph of `x`, execute the command: 69 | 70 | ``` 71 | plot x 72 | ``` 73 | ![Image](./images/example_1.png) 74 | 75 | GDBplotlib has full support for Python-style slicing. For example, to plot only the first 3 elements, simply execute: 76 | 77 | ``` 78 | plot x[:3] 79 | ``` 80 | ![Image](./images/example_2.png) 81 | 82 | Pointers are an example of an unbounded type - that is a type for which it is not possible to deduce the number of elements. In order to correctly plot the variable `y`, the user must explicitily give an endpoint using the slice syntax: 83 | 84 | ``` 85 | plot y[:100] 86 | ``` 87 | ![Image](./images/example_3.png) 88 | 89 | Note that when slicing an unbounded type, negative start/end slice indices no longer refer to an index relative to the container's end (as in Python), but rather relative its start (as in C indexing). 90 | 91 | GDBplotlib supports data extraction of arbitrarily nested structures. For example, to create a 3D plot of `z`, run: 92 | 93 | ``` 94 | plot3d z[::-1,2,4:8] 95 | ``` 96 | ![Image](./images/example_4.png) 97 | 98 | ## Supported Types 99 | 100 | * `std::vector` 101 | * `std::array` 102 | * C-style array 103 | * Pointer 104 | * All integral and floating point types 105 | * `std::complex` and `std::complex` 106 | 107 | ## Supported Commands 108 | 109 | * `plot VAR...` - Create a 1D line plot of `VAR`, where `VAR` is any 1D or 2D structure 110 | * `plot3d VAR` - Create a 2D surface plot of `VAR`, where `VAR` is a 2D real-valued structure 111 | * `scatter VAR...` - Create a 2D scatter plot of `VAR`, where `VAR` is either a 1D complex-valued structure, an N-by-2 real-valued structure, or two 1D real-valued structures 112 | * `scatter3d VAR...` - Create a 3D scatter plot of `VAR`, where `VAR` is either an N-by-3 real-valued structure, or three 1D real-valued structures 113 | * `hist VAR...` - Create a histogram plot of `VAR`, where `VAR` is any 1D or 2D structure 114 | * `fft VAR...` - Create a power spectral density plot of `VAR`, where `VAR` is any 1D structure 115 | * `save FILE VAR` - Save `VAR` to the file `FILE` in binary format 116 | * `savepy FILE VAR` - Save `VAR` to the file `FILE` in Python pickle format 117 | * `savemat FILE VAR...` - Save `VAR` to the file `FILE` in Matlab format 118 | 119 | ## Custom Types 120 | 121 | It is easy to extend GDBplotlib to handle any desired type. Let's look at an example of how we might implement support for `std::vector`: 122 | 123 | ```python 124 | from gdbplotlib.type_handler import TypeHandler 125 | from gdbplotlib.default import default 126 | 127 | 128 | class StdVector(TypeHandler): 129 | @staticmethod 130 | def can_handle(gdb_type: gdb.Type) -> bool: 131 | return str(gdb_type).startswith("std::vector") 132 | 133 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 134 | size = int(gdb_value["_M_impl"]["_M_finish"] - gdb_value["_M_impl"]["_M_start"]) 135 | return (size,) 136 | 137 | def contained_type(self, gdb_value: gdb.Value) -> gdb.Type: 138 | return gdb_value.type.template_argument(0) 139 | 140 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 141 | return (gdb_value["_M_impl"]["_M_start"] + index[0]).dereference() 142 | 143 | 144 | default.register(StdVector) 145 | ``` 146 | 147 | To handle a custom type, we must create a class derived from the abstract base class `gdbplotlib.TypeHandler`. There are 4 methods that need to be overriden: 148 | 149 | * `can_handle` - Given a type, determine whether this handler is able to handle it. For a `std::vector`, we want to handle any type whose name begins with `std::vector` 150 | * `shape` - Given a value of our type, return the shape (in the NumPy sense) of the container as a tuple. The length of the tuple is equal to the number of dimensions of our type, and the values are size of the given dimension. If a given dimension has an unbounded size (as in the case of a pointer), that dimension should be given a value of `None`. A `std::vector` is 1-dimensional, with a size equal to the difference between the start and end pointers. 151 | * `contained_type` - Given a value of our type, return the type of any contained elements. This is usually either a fixed type, or one of the type's template arguments. For a `std::vector`, it is the first template argument. 152 | * `extract` - Given an index, extract an element from the container. The `index` parameter is an `n`-length tuple, where `n` is the number of dimensions of the container. For a `std::vector`, we increment the start pointer by the first (and only) index, and dereference to get the value. 153 | 154 | Finally, we register our type handler with GDBplotlib so that it can be used with any command. Note that we register the class itself, not its instantiation. 155 | 156 | ```python 157 | class Float(ScalarTypeHandler): 158 | @staticmethod 159 | def can_handle(gdb_type: gdb.Type) -> bool: 160 | return str(gdb_type) == "float" 161 | 162 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 163 | return np.float32(gdb_value) 164 | ``` 165 | 166 | Handling a custom scalar type is a similar process. The main difference is that we derive from `gdbplotlib.ScalarTypeHandler`. As a result, it is not necessary to override `shape` and `contained_type`. Then, in the `extract` method, we extract the value and return it as a NumPy data type. 167 | 168 | The implemntation of a custom type handler relies heavily on the GDB Python API, particularly `gdb.Value` and `gdb.Type`. Documentation for the API can be found at the following [link](https://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html). 169 | 170 | ## Acknowledgements 171 | 172 | Special thanks to [Brian Hone](https://github.com/bthcode), whose [gdb-plot](https://github.com/bthcode/gdb-plot) served as the inspiration for this project. -------------------------------------------------------------------------------- /gdbplotlib/__init__.py: -------------------------------------------------------------------------------- 1 | from . import plot, save -------------------------------------------------------------------------------- /gdbplotlib/data_extractor.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List 3 | 4 | import gdb # pylint: disable=E0401 5 | import gdb.types # pylint: disable=E0401 6 | import numpy as np 7 | 8 | from .default import default 9 | from .type_set import TypeSet 10 | from . import util 11 | 12 | 13 | class SliceSyntaxError(Exception): 14 | pass 15 | 16 | 17 | class VariableError(Exception): 18 | pass 19 | 20 | 21 | def parse_subslice(subslice: str) -> slice: 22 | slice_components = subslice.split(":") 23 | 24 | try: 25 | s = [int(gdb.parse_and_eval(x)) if x else None for x in slice_components] 26 | except (ValueError, gdb.error): 27 | raise SliceSyntaxError(f"Invalid slice component: {subslice}") 28 | 29 | if len(s) == 1: 30 | return slice(s[0], s[0] + 1) 31 | if len(s) == 2: 32 | return slice(s[0], s[1]) 33 | elif len(s) == 3: 34 | return slice(s[0], s[1], s[2]) 35 | else: 36 | raise SliceSyntaxError(f"Invalid slice component: {subslice}") 37 | 38 | 39 | def parse_slice(full_slice: str) -> List[slice]: 40 | slice_dims = full_slice.split(",") 41 | return [parse_subslice(s) for s in slice_dims] 42 | 43 | 44 | def parse_var(var: str): 45 | var_base, slice_str = util.split_variable_and_slice(var) 46 | slices = parse_slice(slice_str) if slice_str else [] 47 | return var_base, slices 48 | 49 | 50 | def extract_var(var: str, type_set: TypeSet = default) -> np.ndarray: 51 | base_var, var_slice = parse_var(var) 52 | 53 | try: 54 | gdb_data = gdb.parse_and_eval(base_var) 55 | except gdb.error: 56 | raise VariableError(f"Invalid variable: {var}") 57 | 58 | gdb_type = gdb.types.get_basic_type(gdb_data.type) 59 | type_handler = type_set.get_handler(gdb_type) 60 | 61 | out = type_handler.extract_all(gdb_data, var_slice) 62 | out_np = np.squeeze(np.array(out)) 63 | return out_np 64 | -------------------------------------------------------------------------------- /gdbplotlib/default.py: -------------------------------------------------------------------------------- 1 | from .type_set import TypeSet 2 | from . import std_types 3 | 4 | default = TypeSet() 5 | default.register(std_types.StdVector) 6 | default.register(std_types.StdVectorBool) 7 | default.register(std_types.StdArray) 8 | default.register(std_types.Pointer) 9 | default.register(std_types.Array) 10 | default.register(std_types.Double) 11 | default.register(std_types.Float) 12 | default.register(std_types.StdComplexDouble) 13 | default.register(std_types.StdComplexFloat) 14 | default.register(std_types.Integral) 15 | default.register(std_types.Bool) -------------------------------------------------------------------------------- /gdbplotlib/plot.py: -------------------------------------------------------------------------------- 1 | import gdb # pylint: disable=E0401 2 | import matplotlib.pyplot as plt 3 | import mpl_toolkits.mplot3d.axes3d as p3 4 | import numpy as np 5 | 6 | from . import data_extractor 7 | 8 | 9 | class PlottingError(Exception): 10 | pass 11 | 12 | 13 | class Legend: 14 | def __init__(self): 15 | self.legend = [] 16 | 17 | def add(self, name: str): 18 | self.legend.append(name) 19 | 20 | def apply(self): 21 | plt.legend(self.legend) 22 | 23 | 24 | def plot_1d(args, plot_function): 25 | legend = Legend() 26 | 27 | for arg in args.split(): 28 | data = data_extractor.extract_var(arg) 29 | 30 | if data.ndim == 2 and not np.iscomplexobj(data): 31 | for i, row in enumerate(data): 32 | plot_function(row) 33 | legend.add(f"{arg}[{i}]") 34 | elif data.ndim == 1: 35 | if np.iscomplexobj(data): 36 | plot_function(np.real(data)) 37 | plot_function(np.imag(data)) 38 | plot_function(np.abs(data)) 39 | legend.add(f"real({arg})") 40 | legend.add(f"imag({arg})") 41 | legend.add(f"abs({arg})") 42 | else: 43 | plot_function(data) 44 | legend.add(arg) 45 | else: 46 | raise PlottingError(f"Unsuitable for plotting: {arg}") 47 | 48 | legend.apply() 49 | plt.grid() 50 | plt.show() 51 | 52 | 53 | class Plot(gdb.Command): 54 | def __init__(self): 55 | super(Plot, self).__init__("plot", gdb.COMMAND_OBSCURE) 56 | 57 | def invoke(self, args, from_tty): 58 | plot_1d(args, plt.plot) 59 | 60 | 61 | class Scatter(gdb.Command): 62 | def __init__(self): 63 | super(Scatter, self).__init__("scatter", gdb.COMMAND_OBSCURE) 64 | 65 | def invoke(self, args, from_tty): 66 | legend = Legend() 67 | temp = [] 68 | 69 | for arg in args.split(): 70 | data = data_extractor.extract_var(arg) 71 | 72 | if data.ndim == 2 and not np.iscomplexobj(data) and 2 in data.shape: 73 | if data.shape[1] == 2: 74 | plt.scatter(data[:,0], data[:,1]) 75 | else: 76 | plt.scatter(data[0], data[1]) 77 | 78 | legend.add(arg) 79 | elif data.ndim == 1: 80 | if np.iscomplexobj(data): 81 | plt.scatter(np.real(data), np.imag(data)) 82 | legend.add(arg) 83 | else: 84 | temp.append(data) 85 | else: 86 | raise PlottingError(f"Unsuitable for plotting: {arg}") 87 | 88 | if len(temp): 89 | if len(temp) == 2: 90 | plt.scatter(temp[0], temp[1]) 91 | legend.add("Data") 92 | else: 93 | raise PlottingError(f"Incorrect number of arguments") 94 | 95 | legend.apply() 96 | plt.grid() 97 | plt.show() 98 | 99 | 100 | class Plot3D(gdb.Command): 101 | def __init__(self): 102 | super(Plot3D, self).__init__("plot3d", gdb.COMMAND_OBSCURE) 103 | 104 | def invoke(self, args, from_tty): 105 | z = data_extractor.extract_var(args) 106 | if z.ndim != 2: 107 | raise PlottingError(f"Unsuitable for plotting: {args}") 108 | 109 | x = np.arange(z.shape[1]) 110 | y = np.arange(z.shape[0]) 111 | xm, ym = np.meshgrid(x, y) 112 | 113 | fig = plt.figure() 114 | ax = p3.Axes3D(fig) 115 | ax.plot_surface(xm, ym, z) 116 | plt.show() 117 | 118 | 119 | class Scatter3D(gdb.Command): 120 | def __init__(self): 121 | super(Scatter3D, self).__init__("scatter3d", gdb.COMMAND_OBSCURE) 122 | 123 | def invoke(self, args, from_tty): 124 | legend = Legend() 125 | temp = [] 126 | 127 | fig = plt.figure() 128 | ax = p3.Axes3D(fig) 129 | 130 | for arg in args.split(): 131 | data = data_extractor.extract_var(arg) 132 | 133 | if data.ndim == 2 and not np.iscomplexobj(data) and 3 in data.shape: 134 | if data.shape[1] == 3: 135 | ax.scatter(data[:,0], data[:,1], data[:,2]) 136 | else: 137 | ax.scatter(data[0], data[1], data[2]) 138 | 139 | legend.add(arg) 140 | elif data.ndim == 1: 141 | temp.append(data) 142 | else: 143 | raise PlottingError(f"Unsuitable for plotting: {arg}") 144 | 145 | if len(temp): 146 | if len(temp) == 3: 147 | ax.scatter(temp[0], temp[1]) 148 | legend.add("Data") 149 | else: 150 | raise PlottingError(f"Incorrect number of arguments") 151 | 152 | legend.apply() 153 | plt.grid() 154 | plt.show() 155 | 156 | 157 | class Hist(gdb.Command): 158 | def __init__(self): 159 | super(Hist, self).__init__("hist", gdb.COMMAND_OBSCURE) 160 | 161 | def invoke(self, args, from_tty): 162 | hist = lambda x: plt.hist(x, bins="auto") 163 | plot_1d(args, hist) 164 | 165 | 166 | class FFT(gdb.Command): 167 | def __init__(self): 168 | super(FFT, self).__init__("fft", gdb.COMMAND_OBSCURE) 169 | 170 | def invoke(self, args, from_tty): 171 | legend = Legend() 172 | fft_db = lambda x: 20*np.log10(np.abs(np.fft.fft(x))) 173 | 174 | for arg in args.split(): 175 | data = data_extractor.extract_var(arg) 176 | 177 | if data.ndim == 2 and not np.iscomplexobj(data): 178 | for i, row in enumerate(data): 179 | plt.plot(fft_db(row)) 180 | legend.add(f"{arg}[{i}]") 181 | elif data.ndim == 1: 182 | plt.plot(fft_db(data)) 183 | legend.add(arg) 184 | else: 185 | raise PlottingError(f"Unsuitable for plotting: {arg}") 186 | 187 | legend.apply() 188 | plt.grid() 189 | plt.ylabel("PSD (dB)") 190 | plt.show() 191 | 192 | 193 | Plot() 194 | Scatter() 195 | Plot3D() 196 | Scatter3D() 197 | Hist() 198 | FFT() -------------------------------------------------------------------------------- /gdbplotlib/save.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | import gdb # pylint: disable=E0401 4 | 5 | from . import data_extractor 6 | from . import util 7 | 8 | try: 9 | import scipy.io 10 | SCIPY_AVAILABLE = True 11 | except ImportError: 12 | SCIPY_AVAILABLE = False 13 | 14 | 15 | class SaveMat(gdb.Command): 16 | def __init__(self): 17 | super(SaveMat, self).__init__("savemat", gdb.COMMAND_OBSCURE) 18 | 19 | def invoke(self, args, from_tty): 20 | if not SCIPY_AVAILABLE: 21 | raise RuntimeError("Scipy not available") 22 | 23 | out = {} 24 | filename, var = args.split() 25 | 26 | for v in var: 27 | data = data_extractor.extract_var(v) 28 | base_var, _ = util.split_variable_and_slice(v) 29 | dict_name = util.strip_non_alphanumeric(base_var) 30 | out[dict_name] = data 31 | 32 | scipy.io.savemat(filename, out) 33 | 34 | 35 | class SavePy(gdb.Command): 36 | def __init__(self): 37 | super(SavePy, self).__init__("savepy", gdb.COMMAND_OBSCURE) 38 | 39 | def invoke(self, args, from_tty): 40 | filename, var = args.split() 41 | data = data_extractor.extract_var(var) 42 | with open(filename, "wb") as file: 43 | pickle.dump(data, file) 44 | 45 | 46 | class Save(gdb.Command): 47 | def __init__(self): 48 | super(Save, self).__init__("save", gdb.COMMAND_OBSCURE) 49 | 50 | def invoke(self, args, from_tty): 51 | filename, var = args.split() 52 | data = data_extractor.extract_var(var) 53 | data.tofile(filename) 54 | 55 | 56 | SaveMat() 57 | SavePy() 58 | Save() -------------------------------------------------------------------------------- /gdbplotlib/std_types.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Tuple, Optional 3 | 4 | import gdb # pylint: disable=E0401 5 | import gdb.types # pylint: disable=E0401 6 | import numpy as np 7 | 8 | from .type_handler import TypeHandler, ScalarTypeHandler 9 | 10 | COMPLEX_REGEX = re.compile("(\\S*) . (\\S*)i") 11 | 12 | 13 | class StdVector(TypeHandler): 14 | @staticmethod 15 | def can_handle(gdb_type: gdb.Type) -> bool: 16 | return str(gdb_type).startswith("std::vector") and str(gdb_type.template_argument(0)) != "bool" 17 | 18 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 19 | size = int(gdb_value["_M_impl"]["_M_finish"] - gdb_value["_M_impl"]["_M_start"]) 20 | return (size,) 21 | 22 | def contained_type(self, gdb_value: gdb.Value) -> gdb.Type: 23 | return gdb_value.type.template_argument(0) 24 | 25 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 26 | return (gdb_value["_M_impl"]["_M_start"] + index[0]).dereference() 27 | 28 | 29 | class StdVectorBool(TypeHandler): 30 | @staticmethod 31 | def can_handle(gdb_type: gdb.Type) -> bool: 32 | return str(gdb_type).startswith("std::vector") and str(gdb_type.template_argument(0)) == "bool" 33 | 34 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 35 | base_size = int(gdb_value["_M_impl"]["_M_finish"]["_M_p"] - gdb_value["_M_impl"]["_M_start"]["_M_p"]) 36 | size = 64 * base_size + int(gdb_value["_M_impl"]["_M_finish"]["_M_offset"]) 37 | print(size) 38 | return (size,) 39 | 40 | def contained_type(self, gdb_value: gdb.Value) -> gdb.Type: 41 | return gdb_value.type.template_argument(0) 42 | 43 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 44 | container_index = index[0]//64 45 | offset = index[0] % 64 46 | b64 = int((gdb_value["_M_impl"]["_M_start"]["_M_p"] + container_index).dereference()) 47 | value = bool(b64 & (1 << offset)) 48 | 49 | return gdb.Value(value) 50 | 51 | 52 | class StdArray(TypeHandler): 53 | @staticmethod 54 | def can_handle(gdb_type: gdb.Type) -> bool: 55 | return str(gdb_type).startswith("std::array") 56 | 57 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 58 | size = int(gdb_value.type.template_argument(1)) 59 | return (size,) 60 | 61 | def contained_type(self, gdb_value: gdb.Value) -> Optional[gdb.Type]: 62 | return gdb_value.type.template_argument(0) 63 | 64 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 65 | return gdb_value["_M_elems"][index[0]] 66 | 67 | 68 | class Pointer(TypeHandler): 69 | @staticmethod 70 | def can_handle(gdb_type: gdb.Type) -> bool: 71 | return gdb_type.code == gdb.TYPE_CODE_PTR 72 | 73 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 74 | return (None,) 75 | 76 | def contained_type(self, gdb_value: gdb.Value) -> Optional[gdb.Type]: 77 | return gdb_value.type.target() 78 | 79 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 80 | return gdb_value[index[0]] 81 | 82 | 83 | class Array(TypeHandler): 84 | @staticmethod 85 | def can_handle(gdb_type: gdb.Type) -> bool: 86 | return gdb_type.code == gdb.TYPE_CODE_ARRAY 87 | 88 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 89 | size = gdb_value.type.range()[1] + 1 90 | return (size, ) 91 | 92 | def contained_type(self, gdb_value: gdb.Value) -> Optional[gdb.Type]: 93 | return gdb_value.type.target() 94 | 95 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 96 | return gdb_value[index[0]] 97 | 98 | 99 | class Double(ScalarTypeHandler): 100 | @staticmethod 101 | def can_handle(gdb_type: gdb.Type) -> bool: 102 | return str(gdb_type) == "double" 103 | 104 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 105 | return np.float64(gdb_value) 106 | 107 | 108 | class Float(ScalarTypeHandler): 109 | @staticmethod 110 | def can_handle(gdb_type: gdb.Type) -> bool: 111 | return str(gdb_type) == "float" 112 | 113 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 114 | return np.float32(gdb_value) 115 | 116 | 117 | def extract_complex(gdb_value) -> complex: 118 | complex_str = str(gdb_value["_M_value"]) 119 | complex_match = COMPLEX_REGEX.search(complex_str) 120 | real, imag = complex_match.group(1), complex_match.group(2) 121 | return float(real) + 1j * float(imag) 122 | 123 | 124 | class StdComplexDouble(ScalarTypeHandler): 125 | @staticmethod 126 | def can_handle(gdb_type: gdb.Type) -> bool: 127 | return str(gdb_type) == "std::complex" 128 | 129 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 130 | return np.complex128(extract_complex(gdb_value)) 131 | 132 | 133 | class StdComplexFloat(ScalarTypeHandler): 134 | @staticmethod 135 | def can_handle(gdb_type: gdb.Type) -> bool: 136 | return str(gdb_type) == "std::complex" 137 | 138 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 139 | return np.complex64(extract_complex(gdb_value)) 140 | 141 | 142 | class Integral(ScalarTypeHandler): 143 | @staticmethod 144 | def can_handle(gdb_type: gdb.Type) -> bool: 145 | return str(gdb_type) in ( 146 | "char", "short", "int", "long", "long long", 147 | "unsigned char", "unsigned short", "unsigned int", "unsigned long", "unsigned long long" 148 | ) 149 | 150 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 151 | return () 152 | 153 | def contained_type(self, gdb_value: gdb.Value) -> Optional[gdb.Type]: 154 | dtype = str(gdb.types.get_basic_type(gdb_value.type)) 155 | prefix = "u" if "unsigned" in dtype else "i" 156 | if "char" in dtype: 157 | size = "1" 158 | elif "short" in dtype: 159 | size = "2" 160 | elif "int" in dtype: 161 | size = "4" 162 | else: 163 | size = "8" 164 | 165 | self.np_dtype = np.dtype(prefix + size) 166 | return None 167 | 168 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 169 | return self.np_dtype.type(gdb_value) 170 | 171 | 172 | class Bool(ScalarTypeHandler): 173 | @staticmethod 174 | def can_handle(gdb_type: gdb.Type) -> bool: 175 | return str(gdb_type) == "bool" 176 | 177 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 178 | return np.bool(gdb_value) -------------------------------------------------------------------------------- /gdbplotlib/type_handler.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Tuple, Optional 3 | 4 | import gdb.types # pylint: disable=E0401 5 | import gdb # pylint: disable=E0401 6 | import numpy as np 7 | 8 | from .type_set import TypeSet 9 | from . import util 10 | 11 | 12 | class TypeHandler(ABC): 13 | @staticmethod 14 | @abstractmethod 15 | def can_handle(gdb_type: gdb.Type) -> bool: 16 | """ 17 | Returns whether the handler is able to extract values from a given type 18 | 19 | Parameters: 20 | gdb_type (gdb.Type): Type to be handled 21 | 22 | Returns: 23 | bool: Can the type be handled? 24 | """ 25 | pass 26 | 27 | def __init__(self, type_set: TypeSet): 28 | self.type_set = type_set 29 | 30 | @abstractmethod 31 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 32 | """ 33 | Gets the shape of a container 34 | 35 | Parameters: 36 | gdb_value (gdb.Value): The container 37 | 38 | Returns: 39 | Tuple[Optional[int]: The shape of the container. Any dimensions with 40 | unbounded size are represented by None 41 | """ 42 | pass 43 | 44 | @abstractmethod 45 | def contained_type(self, gdb_value: gdb.Value) -> Optional[gdb.Type]: 46 | """ 47 | Gets the type of the elements of the container 48 | 49 | Parameters: 50 | gdb_value (gdb.Value): The container 51 | 52 | Returns: 53 | Optional[gdb.Type]: The element type. If the value is a scalar, and as 54 | such does not hold elements, None is returned 55 | """ 56 | pass 57 | 58 | @abstractmethod 59 | def extract(self, gdb_value: gdb.Value, index: Tuple[int, ...]): 60 | """ 61 | Extracts a value from a GDB value 62 | 63 | Parameters: 64 | gdb_value (gdb.Value): GDB value 65 | index (Tuple[int, ...]): The multi-dimensional index of the value to 66 | extract 67 | 68 | Returns: 69 | value: The extracted value. Either a GDB value if gdb_value was a 70 | container, or a Numpy dtype if it was a scalar type 71 | """ 72 | pass 73 | 74 | def extract_all(self, gdb_value: gdb.Value, slices: List[slice]): 75 | shape = self.shape(gdb_value) 76 | contained_type = self.contained_type(gdb_value) 77 | n_dims = len(shape) 78 | scalar_type = (contained_type == None) 79 | 80 | if scalar_type: 81 | return self.extract(gdb_value, None) 82 | 83 | basic_contained_type = gdb.types.get_basic_type(contained_type) 84 | contained_handler = self.type_set.get_handler(basic_contained_type) 85 | current_slices = slices[:n_dims] 86 | contained_slices = slices[n_dims:] 87 | 88 | for _ in range(len(shape) - len(current_slices)): 89 | current_slices.append(slice(None, None, None)) 90 | 91 | def gen_output(slc, shp, index): 92 | if not shp: 93 | contained_gdb_value = self.extract(gdb_value, index) 94 | return contained_handler.extract_all(contained_gdb_value, contained_slices) 95 | else: 96 | out = [] 97 | for i in util.indices_1d(slc[0], shp[0]): 98 | out.append(gen_output(slc[1:], shp[1:], (*index, i))) 99 | 100 | return out 101 | 102 | out = gen_output(current_slices, shape, ()) 103 | return out 104 | 105 | 106 | class ScalarTypeHandler(TypeHandler): 107 | def shape(self, gdb_value: gdb.Value) -> Tuple[Optional[int], ...]: 108 | return () 109 | 110 | def contained_type(self, gdb_value: gdb.Value) -> Optional[gdb.Type]: 111 | return None 112 | -------------------------------------------------------------------------------- /gdbplotlib/type_set.py: -------------------------------------------------------------------------------- 1 | class UnkownTypeError(Exception): 2 | pass 3 | 4 | 5 | class TypeSet: 6 | def __init__(self): 7 | self.handlers = [] 8 | 9 | def register(self, type_handler): 10 | self.handlers.append(type_handler) 11 | 12 | def get_handler(self, gdb_type): 13 | for handler in self.handlers: 14 | if handler.can_handle(gdb_type): 15 | return handler(self) 16 | 17 | raise UnkownTypeError(f"Cannot handle type: {str(gdb_type)}") 18 | -------------------------------------------------------------------------------- /gdbplotlib/util.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List, Tuple, Optional 3 | 4 | 5 | def indices_1d(s: slice, shape: int): 6 | if shape is None: 7 | start = 0 if s.start is None else s.start 8 | stop = 0 if s.stop is None else s.stop 9 | step = 1 if s.step is None else s.step 10 | else: 11 | start, stop, step = s.indices(shape) 12 | 13 | in_range = lambda a, b, c: a < b if c > 0 else a > b 14 | i = start 15 | while in_range(i, stop, step): 16 | yield i 17 | i += step 18 | 19 | 20 | def indices(slices: List[slice], shape: Tuple): 21 | if len(slices) != len(shape): 22 | for i in range(len(shape) - len(slices)): 23 | slices.append(slice(None, None, None)) 24 | 25 | 26 | if len(slices) == 1: 27 | for i in indices_1d(slices[0], shape[0]): 28 | yield (i,) 29 | else: 30 | for i in indices_1d(slices[0], shape[0]): 31 | for j in indices(slices[1:], shape[1:]): 32 | yield (i, *j) 33 | 34 | 35 | def split_variable_and_slice(var: str) -> Tuple[str, Optional[str]]: 36 | lbracket = var.rfind("[") 37 | rbracket = var.rfind("]") 38 | 39 | if lbracket == -1 or rbracket == -1: 40 | return var, None 41 | else: 42 | return var[:lbracket], var[lbracket+1:rbracket] 43 | 44 | 45 | def strip_non_alphanumeric(s: str) -> str: 46 | return re.sub("\\W+", "", s) -------------------------------------------------------------------------------- /images/example_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Neon/gdbplotlib/1a061d52d1a421c34ced7728312613b2831e4eca/images/example_1.png -------------------------------------------------------------------------------- /images/example_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Neon/gdbplotlib/1a061d52d1a421c34ced7728312613b2831e4eca/images/example_2.png -------------------------------------------------------------------------------- /images/example_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Neon/gdbplotlib/1a061d52d1a421c34ced7728312613b2831e4eca/images/example_3.png -------------------------------------------------------------------------------- /images/example_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/X-Neon/gdbplotlib/1a061d52d1a421c34ced7728312613b2831e4eca/images/example_4.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open('README.md') as readme: 4 | long_description = readme.read() 5 | 6 | setuptools.setup( 7 | name='gdbplotlib', 8 | version='0.3.0', 9 | author='George Cholerton', 10 | author_email='gcholerton@gmail.com', 11 | url='https://github.com/X-Neon/gdbplotlib', 12 | packages=setuptools.find_packages(), 13 | install_requires=['numpy', 'matplotlib'], 14 | python_requires='>=3', 15 | description='Plotting and exporting of variables from GDB', 16 | long_description=long_description, 17 | long_description_content_type='text/markdown', 18 | keywords=['gdb', 'debug'], 19 | classifiers=[ 20 | 'Development Status :: 3 - Alpha', 21 | 'Intended Audience :: Developers', 22 | 'Intended Audience :: Science/Research', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Programming Language :: Python :: 3', 25 | 'Topic :: Scientific/Engineering :: Visualization', 26 | 'Topic :: Software Development :: Debuggers' 27 | ] 28 | ) --------------------------------------------------------------------------------