diff --git a/contrib/.vscode/launch.json b/contrib/.vscode/launch.json index 6d04a9973f..c54dd2c8a0 100644 --- a/contrib/.vscode/launch.json +++ b/contrib/.vscode/launch.json @@ -16,10 +16,30 @@ ], "linux": { "MIMode": "gdb", - "miDebuggerPath": "/usr/bin/gdb" + "miDebuggerPath": "/usr/bin/gdb", + "setupCommands": [ + { + "description": "Load QT pretty-printers for GDB", + "text": "python import sys; sys.path.append('${workspaceFolder}/contrib/debugger'); from qt_pretty_printers_gdb import register_qt_printers; register_qt_printers()", + "ignoreFailures": false + }, + { + "description": "Enable pretty-printing for GDB", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] }, "osx": { - "MIMode": "lldb" + "MIMode": "lldb", + "preRunCommands": [ + "command script import ${workspaceFolder}/contrib/debugger/qt_pretty_printers_lldb.py" + ] }, "stopAtEntry": false, "externalConsole": false, @@ -47,10 +67,30 @@ ], "linux": { "MIMode": "gdb", - "miDebuggerPath": "/usr/bin/gdb" + "miDebuggerPath": "/usr/bin/gdb", + "setupCommands": [ + { + "description": "Load QT pretty-printers for GDB", + "text": "python import sys; sys.path.append('${workspaceFolder}/contrib/debugger'); from qt_pretty_printers_gdb import register_qt_printers; register_qt_printers()", + "ignoreFailures": false + }, + { + "description": "Enable pretty-printing for GDB", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] }, "osx": { - "MIMode": "lldb" + "MIMode": "lldb", + "preRunCommands": [ + "command script import ${workspaceFolder}/contrib/debugger/qt_pretty_printers_lldb.py" + ] }, "stopAtEntry": false, "externalConsole": false, diff --git a/contrib/debugger/qt_pretty_printers_gdb.py b/contrib/debugger/qt_pretty_printers_gdb.py new file mode 100644 index 0000000000..e62466a245 --- /dev/null +++ b/contrib/debugger/qt_pretty_printers_gdb.py @@ -0,0 +1,124 @@ +# pylint: disable=line-too-long +""" + QT Pretty Printer for GDB + + Currently supports displaying the following QT types in the debugger + * QString + + + Can be used from GDB via: + + (gdb) python + >import sys + >sys.path.insert(0, '{Your FreeCAD source dir}/contrib/debugger') + >from qt_pretty_printers_gdb import register_qt_printers + >register_qt_printers() + >end + QT pretty-printer script loaded. + QT pretty-printer registered. + (gdb) run + *breakpoint* + (gdb) p testString + $1 = This is a QString in the debugger! + (gdb) + + + + Can be used in VSCode via launch.json + + { + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/bin/FreeCAD", + "args": [], + "stopAtEntry": true, + "cwd": "${workspaceFolder}/build", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Load QT pretty-printers for GDB", + "text": "python import sys; sys.path.append('${workspaceFolder}/contrib/debugger'); from qt_pretty_printers_gdb import register_qt_printers; register_qt_printers()", + "ignoreFailures": false + }, + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ], + "miDebuggerPath": "/usr/bin/gdb" + } + ] + } + + + Note: There may be variances between different operating systems and/or build configurations + in how QString data is stored internally. This was tested on debian12/amd64. @BootsSiR +""" +# pylint: enable=line-too-long + +import gdb + +class QStringPrinter: + """Pretty-printer class for QString objects in GDB.""" + def __init__(self, val): + self.val = val + + def to_string(self): + """Pretty-printer function for QString objects in LLDB.""" + try: + d = self.val["d"] + if d == 0: + return 'null' + + offset = int(d["offset"]) + data_ptr = d.cast(gdb.lookup_type("char").pointer()) + offset + char16_t_ptr = data_ptr.cast(gdb.lookup_type("char16_t").pointer()) + + string_data = [] + i = 0 + while char16_t_ptr[i] != 0: + string_data.append(chr(char16_t_ptr[i])) + i += 1 + + return "".join(string_data) + except gdb.error as e: + if "Cannot access memory at address" in str(e): + return "" + return f"" + except Exception as e: # pylint: disable=broad-except + return f"" + + +def lookup_function(val): + """Maps a type to the appropriate pretty printer""" + try: + qstring_type = gdb.lookup_type("QString") + if val.type.strip_typedefs() == qstring_type: + return QStringPrinter(val) + return None + except gdb.error: + return None + + +def register_qt_printers(objfile=None): + """Register the QString pretty-printer with GDB.""" + if objfile is None: + objfile = gdb.current_objfile() + + # Check if the printer is already registered + if not any(printer == lookup_function for printer in gdb.pretty_printers): # pylint: disable=comparison-with-callable + print("QT pretty-printer script loaded.") + gdb.pretty_printers.append(lookup_function) + print("QT pretty-printer registered.") \ No newline at end of file diff --git a/contrib/debugger/qt_pretty_printers_lldb.py b/contrib/debugger/qt_pretty_printers_lldb.py new file mode 100644 index 0000000000..59a512e978 --- /dev/null +++ b/contrib/debugger/qt_pretty_printers_lldb.py @@ -0,0 +1,107 @@ +# pylint: disable=line-too-long +""" + QT Pretty Printer for LLDB + + Currently supports displaying the following QT types in the debugger + * QString + + + Can be used from LLDB via: + + (lldb) command script import {Your FreeCAD source dir}/contrib/debugger/qt_pretty_printers_lldb.py + + (lldb) frame variable test + (QString) test = "This is a test string." + (lldb) + + + Can be used in VSCode via launch.json (tested with CodeLLDB extension) + + { + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug LLDB", + "stopOnEntry": false, + "program": "${workspaceFolder}/build/bin/FreeCAD", + "args": [], + "cwd": "${workspaceFolder}/build/", + "preLaunchTask": "CMake: build", + "preRunCommands": [ + "command script import ${workspaceFolder}/contrib/debugger/qt_pretty_printers_lldb.py" + ] + } + ] + } + + + Note: There may be variances between different operating systems and/or build configurations + in how QString data is stored internally. This was tested on debian12/amd64 and + macOS/arm64. @BootsSiR +""" +# pylint: enable=line-too-long + +import lldb + +def QStringPrinter(val, _internal_dict): + """Pretty-printer for QString objects in LLDB.""" + try: + # Access the 'd' member of QString + d = val.GetChildMemberWithName("d") + if not d.IsValid(): + return 'null' + + # some machines have a different QString object layout (macOS? arm64?) so + # first we must check if a ptr member exists + ptr_field = d.GetChildMemberWithName("ptr") + + # if the ptr member exists, we will use that as the base address for our text data. + if ptr_field.IsValid(): + # use the ptr value + data_base_address = ptr_field.GetValueAsUnsigned() + else: + # we don't have a ptr member, so let's use an offset from the address of the + # QString::Data as our base address + d_address = d.GetValueAsUnsigned() + + # Assume the string data starts immediately after the first 24 bytes + data_base_address = d_address + 24 + + # if at this point, we don't have a valid data base address + # we cannot proceed + if data_base_address == 0: + return "null" + + # Initialize variables for memory reading + process = val.GetTarget().GetProcess() + error = lldb.SBError() + chunk_size = 256 # Size of each memory chunk to read + string_data = [] + offset = 0 + + # Read memory in chunks until null-terminator is found + while True: + raw_data = process.ReadMemory(data_base_address + offset, chunk_size, error) + if not error.Success(): + return f"" + + # Parse UTF-16 data from the chunk + for i in range(0, len(raw_data), 2): # char16_t is 2 bytes + char16_val = int.from_bytes(raw_data[i:i+2], byteorder='little') + if char16_val == 0: # Null-terminator found + return f'"{ "".join(string_data) }"' + string_data.append(chr(char16_val)) + + # Increase the offset for the next chunk + offset += chunk_size + + except Exception as e: # pylint: disable=broad-except + return f"" + +def __lldb_init_module(debugger, _internal_dict): + """Register the QString pretty-printer with LLDB.""" + print("QT pretty-printer script loaded.") + debugger.HandleCommand('type summary add QString -F qt_pretty_printers_lldb.QStringPrinter') + print("QT pretty-printer registered.")