GDB/LLDB debugger pretty printers w/QString support and vscode launch config (#18960)
* GDB/LLDB debugger pretty printers w/QString support and vscode launch config * Python code now adheres to pep8. Slight refactor to lldb printer for readability. * Resolved a few missed trailing whitespace errors from pylint * Added missing trailing newline I missed in my last commit * Apply suggestions from code review --------- Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
This commit is contained in:
48
contrib/.vscode/launch.json
vendored
48
contrib/.vscode/launch.json
vendored
@@ -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,
|
||||
|
||||
124
contrib/debugger/qt_pretty_printers_gdb.py
Normal file
124
contrib/debugger/qt_pretty_printers_gdb.py
Normal file
@@ -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 "<uninitialized>"
|
||||
return f"<error: {str(e)}>"
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
return f"<unexpected error: {str(e)}>"
|
||||
|
||||
|
||||
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.")
|
||||
107
contrib/debugger/qt_pretty_printers_lldb.py
Normal file
107
contrib/debugger/qt_pretty_printers_lldb.py
Normal file
@@ -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"<error: unable to read memory: {error.GetCString()}>"
|
||||
|
||||
# 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"<error: {str(e)}>"
|
||||
|
||||
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.")
|
||||
Reference in New Issue
Block a user