-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit with support for QString and QMap
- Loading branch information
0 parents
commit a088f00
Showing
14 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build | ||
venv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
stages: | ||
- test | ||
|
||
test-formatters: | ||
stage: test | ||
variables: | ||
Qt5_ROOT: /opt/gitlab-runner/builds$QT_SEED_PATH | ||
CMAKE_PRESET: gitlab-linux-gcc-debug | ||
script: | ||
- python -m venv venv | ||
- source venv/bin/activate | ||
- pip install pytest | ||
- pytest test | ||
tags: | ||
- entos-desktop-build-runner |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
command script import lldb_qt_formatters |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
cmake_minimum_required(VERSION 3.22.0) | ||
project(lldb-qt-formatters LANGUAGES CXX) | ||
|
||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) | ||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) | ||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) | ||
|
||
add_subdirectory(examples) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"version": 6, | ||
"cmakeMinimumRequired": { | ||
"major": 3, | ||
"minor": 22, | ||
"patch": 0 | ||
}, | ||
"configurePresets": [ | ||
{ | ||
"name": "default-debug", | ||
"hidden": true, | ||
"binaryDir": "${sourceDir}/build/${presetName}", | ||
"cacheVariables": { | ||
"CMAKE_BUILD_TYPE": "Debug" | ||
} | ||
}, | ||
{ | ||
"name": "linux-gcc-debug", | ||
"inherits": ["default-debug"], | ||
"cacheVariables": { | ||
"CMAKE_CXX_COMPILER": "g++" | ||
} | ||
}, | ||
{ | ||
"name": "gitlab-linux-gcc-debug", | ||
"inherits": ["linux-gcc-debug"], | ||
"cacheVariables": { | ||
"Qt5_ROOT": "$env{Qt5_ROOT}" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
Qt Type Formatters for LLDB | ||
=========================== | ||
|
||
This project adds various [type formatters](https://lldb.llvm.org/use/variable.html#type-format) | ||
to enable more user friendly display of Qt Core Types in LLDB and IDEs that | ||
support it. | ||
|
||
Usage | ||
----- | ||
|
||
Load the type formatters in LLDB by executing | ||
|
||
```.console | ||
command script import lldb_qt_formatters | ||
``` | ||
|
||
or alternatively place the above line in a `.lldbinit` file in the root of the project, or in your `$HOME` directory. | ||
To use a project-specific configuration file | ||
you will need to [enable it](https://lldb.llvm.org/man/lldb.html#configuration-files) | ||
|
||
### VSCode | ||
|
||
VSCode will not source a project `.lldbinit` file even if you enable it as above. To automatically load the | ||
config file, add the following to your `launch.json` file. | ||
|
||
```.json | ||
"configurations": [{ | ||
"type": "lldb", | ||
... | ||
"initCommands": [ | ||
"command source ${workspaceFolder}/.lldbinit" | ||
] | ||
}] | ||
|
||
``` | ||
|
||
Supported Types | ||
--------------- | ||
|
||
* `QMap<Key, Value>` | ||
* `QMapNode<Key, Value>` | ||
* `QString` | ||
|
||
|
||
Contribution Guidelines | ||
----------------------- | ||
|
||
Contributions are welcomed. | ||
See the guide to [variable formatting](https://lldb.llvm.org/use/variable.html) in LLDB, in addition to the | ||
existing formatters in the [LLDB Source](https://github.com/llvm/llvm-project/tree/main/lldb/examples) | ||
|
||
1. Create a test for your formatter. The tests run LLDB against some sample code and examine the formatted | ||
variable output. | ||
2. Implement a new formatter | ||
* Simple types can be supported with a summary string in the `__init__.py` file | ||
(eg, see support for the `QMapNode<Key, Value>` type) | ||
* More complex types can be supported using the Python API (eg `QString`) | ||
* Container types such as `QMap<>` require more elaborate use of the Python API to generate *"Synthetic Children"* | ||
|
||
Formatters are registered with their types in the `__init__.py` file. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
find_package(Qt5 REQUIRED COMPONENTS Core) | ||
|
||
add_executable(example main.cpp) | ||
target_link_libraries(example PRIVATE Qt5::Core) | ||
target_compile_features(example PRIVATE cxx_std_20) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#include <QMap> | ||
#include <QString> | ||
|
||
int main() { | ||
auto hello = QString("Hello World"); | ||
auto demosthenes = QString("Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι"); | ||
|
||
auto map = QMap<QString , uint32_t>{}; | ||
map["one"] = 1; | ||
map["forty-two"] = 42; | ||
map["1.21 gigawatts"] = 1210000; | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from .qstring import * | ||
from .qmap import * | ||
|
||
|
||
def __lldb_init_module(debugger, dict): | ||
init_commands = [ | ||
'type summary add --python-function lldb_qt_formatters.qstring.format_summary "QString"', | ||
'type summary add --summary-string "\{${var.key}: ${var.value}\}" -x "QMapNode<.+>$"', | ||
'type synthetic add --python-class lldb_qt_formatters.qmap.SyntheticChildrenProvider -x "QMap<.+>$"', | ||
'type summary add --expand --python-function lldb_qt_formatters.qmap.format_summary -x "QMap<.+>$"' | ||
] | ||
for command in init_commands: | ||
debugger.HandleCommand(command) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
from typing import List | ||
|
||
from lldb import SBValue | ||
|
||
|
||
def format_summary(valobj: SBValue, internal_dict, options): | ||
""" | ||
Format a summary string for QMap values | ||
:param valobj: | ||
:param internal_dict: unused | ||
:param options: unused | ||
""" | ||
|
||
provider = SyntheticChildrenProvider(valobj.GetNonSyntheticValue(), internal_dict) | ||
return f"Size: {provider.size()}" | ||
|
||
|
||
def _depth_first_traverse(node: SBValue, nodes: List[int]): | ||
# We only need the address of each node as we're going to read its | ||
# value directly from memory later | ||
nodes.append(node.load_addr) | ||
|
||
# QMaps are Red-Black trees but we don't need to know much about the | ||
# details to just extract the data. | ||
left = node.GetChildMemberWithName("left") | ||
if left.GetValueAsUnsigned() != 0: | ||
_depth_first_traverse(left.deref, nodes) | ||
|
||
right = node.GetChildMemberWithName("right") | ||
if right.GetValueAsUnsigned() != 0: | ||
_depth_first_traverse(right.deref, nodes) | ||
|
||
|
||
def _derived_node_type(map_obj: SBValue): | ||
# The actual data in the QMap is in QMapNode<> elements, however QMaps only actually store each node as a | ||
# pointer to a non-templated base class of QMapNode<>. We need to work out what the derived class is supposed to be | ||
# so we can get at the data | ||
map_type = map_obj.type.GetUnqualifiedType() | ||
key_type = map_type.GetTemplateArgumentType(0) | ||
value_type = map_type.GetTemplateArgumentType(1) | ||
node_type = f"QMapNode<{key_type.name}, {value_type.name}>" | ||
# TODO (andrew) this can fail in unusual circumstances - such as trying to use a GCC-built Qt with a Clang-built | ||
# main, but that's only ever going to work by coincidence anyway... | ||
return map_obj.target.FindFirstType(node_type) | ||
|
||
|
||
class SyntheticChildrenProvider: | ||
def __init__(self, valobj, internal_dict): | ||
self.valobj = valobj | ||
self.node_type = _derived_node_type(self.valobj) | ||
|
||
def size(self): | ||
data_member_ptr = self.valobj.GetChildMemberWithName("d") | ||
data_member = data_member_ptr.deref | ||
return data_member.GetChildMemberWithName("size").GetValueAsSigned() | ||
|
||
def update(self): | ||
data_member_ptr = self.valobj.GetChildMemberWithName("d") | ||
data_member = data_member_ptr.deref | ||
# The root node is also a QMapNode<> but doesn't contain any | ||
# data we actually need. | ||
root = data_member.GetChildMemberWithName("header") | ||
nodes = [] | ||
_depth_first_traverse(root, nodes) | ||
self.size = data_member.GetChildMemberWithName("size").GetValueAsSigned() | ||
self.nodes = [] if self.size == 0 else nodes[1:] | ||
|
||
def num_children(self): | ||
return self.size | ||
|
||
@staticmethod | ||
def get_child_index(self, name: str): | ||
index = name.lstrip("[").rstrip("]") | ||
return int(index) | ||
|
||
def get_child_at_index(self, index): | ||
node_addr = self.nodes[index] | ||
child_node = self.valobj.CreateValueFromAddress(f"[{index}]", node_addr, self.node_type) | ||
return child_node | ||
|
||
def has_children(self): | ||
return self.size > 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from lldb import SBError, SBValue | ||
|
||
|
||
def format_summary(valobj: SBValue, internal_dict, options): | ||
""" | ||
Format a summary string for QString values | ||
:param valobj: | ||
:param internal_dict: unused | ||
:param options: unused | ||
""" | ||
if not valobj.process: | ||
return "Unknown" | ||
|
||
data_member = valobj.GetChildMemberWithName("d") | ||
array_member = data_member.deref | ||
|
||
offset_in_bytes = array_member.GetChildMemberWithName("offset").GetValueAsUnsigned() | ||
string_length = array_member.GetChildMemberWithName("size").GetValueAsUnsigned() | ||
address = data_member.GetValueAsUnsigned() | ||
|
||
error = SBError() | ||
character_size = 2 | ||
content = valobj.process.ReadMemory(address + offset_in_bytes, string_length * character_size, error) | ||
|
||
return f'"{bytearray(content).decode("utf-16")}"' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import os | ||
from subprocess import run | ||
from tempfile import NamedTemporaryFile | ||
|
||
import pytest | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def exe(tmp_path_factory): | ||
repository_root = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) | ||
build_folder = tmp_path_factory.mktemp("build") | ||
cmake_preset = os.environ.get("CMAKE_PRESET", "linux-gcc-debug") | ||
cmake_configure = ["cmake", "--preset", cmake_preset, "-S", repository_root, "-B", build_folder] | ||
cmake_build = ["cmake", "--build", build_folder] | ||
run(cmake_configure) | ||
run(cmake_build) | ||
|
||
return build_folder / "bin" / "example" | ||
|
||
|
||
@pytest.fixture | ||
def lldb(request, exe): | ||
preamble = [ | ||
"command script import lldb_qt_formatters", | ||
"breakpoint set --file main.cpp --line 13", | ||
"run" | ||
] | ||
marker = request.node.get_closest_marker("lldb_script") | ||
test_script = marker.args[0] if marker is not None else "" | ||
|
||
script = "\n".join(preamble + [test_script.lstrip(" \n")]) | ||
|
||
with NamedTemporaryFile("w") as fp: | ||
fp.write(script) | ||
fp.flush() | ||
|
||
lldb_command = ["lldb", "--batch", "--source", fp.name, exe] | ||
result = run(lldb_command, check=True, text=True, capture_output=True) | ||
yield result.stdout | ||
|
||
# TODO (andrew) work out how to just print the lldb output if the test fails | ||
print(result.stdout) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import re | ||
|
||
import pytest | ||
|
||
|
||
@pytest.mark.lldb_script("frame variable map") | ||
def test_qmap_of_qstring_uint_summary(lldb): | ||
match = re.search(r"\(QMap<.+>\) map = (?P<summary>.+) {", lldb, re.MULTILINE) | ||
assert match.group('summary') == 'Size: 3' | ||
|
||
|
||
@pytest.mark.lldb_script("frame variable map") | ||
def test_qmap_of_qstring_uint_children(lldb): | ||
match = re.search(r"\[0] = (?P<summary>\{.+})", lldb, re.MULTILINE) | ||
assert match.group('summary') == '{"forty-two": 42}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import re | ||
|
||
import pytest | ||
|
||
|
||
@pytest.mark.lldb_script("frame variable hello") | ||
def test_qstring_summary(lldb): | ||
match = re.search(r"\(QString\) hello = (?P<summary>.+)", lldb, re.MULTILINE) | ||
assert match.group('summary') == '"Hello World"' | ||
|
||
|
||
@pytest.mark.lldb_script("frame variable demosthenes") | ||
def test_qstring_utf8(lldb): | ||
demosthenes = "Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι" | ||
match = re.search(r"\(QString\) demosthenes = (?P<summary>.+)", lldb, re.MULTILINE) | ||
assert match.group('summary') == f'"{demosthenes}"' |