Skip to content

Commit 5924342

Browse files
authored
Support Python 3.13 and make tests for older versions green (#17)
1 parent 44a3b30 commit 5924342

File tree

10 files changed

+97
-17
lines changed

10 files changed

+97
-17
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This debugger extension provides visualizations for Python objects and stacktrac
99

1010
The goal of this project is to provide a similar debugging experience in WinDbg/CDB/NTSD as `already exists in GDB <https://wiki.python.org/moin/DebuggingWithGdb>`_.
1111

12-
Currently, the extension is tested against 32bit and 64bit builds of Python versions 2.7, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 and 3.11.
12+
Currently, the extension is tested against 32bit and 64bit builds of Python versions 2.7, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13.
1313

1414
Installation
1515
============

include/PyFunctionObject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace PyExt::Remote {
2929
auto closure() const -> std::unique_ptr<PyTupleObject>;
3030
auto doc() const -> std::unique_ptr<PyObject>;
3131
auto name() const -> std::unique_ptr<PyStringValue>;
32-
auto dict() const -> std::unique_ptr<PyDictObject>;
32+
auto dict() const -> std::unique_ptr<PyDict> override;
3333
auto weakreflist() const -> std::unique_ptr<PyListObject>;
3434
auto module() const -> std::unique_ptr<PyObject>;
3535
auto annotations() const -> std::unique_ptr<PyDictObject>;

include/PyObject.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ namespace PyExt::Remote {
3939
auto refCount() const -> SSize;
4040
auto type() const -> PyTypeObject;
4141
auto slots() const -> std::vector<std::pair<std::string, std::unique_ptr<PyObject>>>;
42-
auto dict() const -> std::unique_ptr<PyDict>;
42+
auto managedDict() const -> std::unique_ptr<PyDict>;
43+
virtual auto dict() const -> std::unique_ptr<PyDict>;
4344
virtual auto repr(bool pretty = true) const -> std::string;
4445
virtual auto details() const -> std::string;
4546

include/PyTypeObject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "PyVarObject.h"
44
#include "PyMemberDef.h"
55
#include "PyTupleObject.h"
6+
#include "PyDictObject.h"
67
#include <array>
78
#include <string>
89

@@ -24,9 +25,12 @@ namespace PyExt::Remote {
2425
auto documentation() const -> std::string;
2526
auto members() const -> std::vector<std::unique_ptr<PyMemberDef>>;
2627
auto isManagedDict() const -> bool;
28+
auto getStaticBuiltinIndex() const -> SSize;
29+
auto hasInlineValues() const -> bool;
2730
auto dictOffset() const -> SSize;
2831
auto mro() const -> std::unique_ptr<PyTupleObject>;
2932
auto isPython2() const -> bool;
33+
auto dict() const -> std::unique_ptr<PyDict> override;
3034
auto repr(bool pretty = true) const -> std::string override;
3135

3236
};

src/ExtHelpers.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,12 @@ namespace utils {
4343
return oss.str();
4444
}
4545

46+
47+
auto getFullSymbolName(const string& symbolName) -> string
48+
{
49+
ExtBuffer<char> buffer;
50+
g_Ext->FindFirstModule("python???", &buffer, 0);
51+
return buffer.GetBuffer() + "!"s + symbolName;
52+
}
53+
4654
}

src/ExtHelpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,6 @@ namespace utils {
7070
auto getPointerSize() -> int;
7171
auto escapeDml(const std::string& str) -> std::string;
7272
auto link(const std::string& text, const std::string& cmd, const std::string& alt = ""s) -> std::string;
73+
auto getFullSymbolName(const std::string& symbolName) -> std::string;
7374

7475
}

src/objects/PyDictObject.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,12 @@ namespace PyExt::Remote {
139139
optional<ExtRemoteTyped> valuesArray;
140140
if (!isCombined) {
141141
valuesArray = remoteType().Field("ma_values");
142-
143-
// Since Python 3.11 values are stored in `values` of `ma_values`.
144-
if (valuesArray->HasField("values"))
142+
utils::ignoreExtensionError([&] {
143+
// Python >= 3.11
144+
// Find full symbol name because there may be a name collision leading to truncated type info.
145+
valuesArray = ExtRemoteTyped(utils::getFullSymbolName("_dictvalues").c_str(), valuesArray->GetPtr(), true);
145146
valuesArray = valuesArray->Field("values");
147+
});
146148
}
147149

148150
for (SSize i = 0; i < tableSize; ++i) {

src/objects/PyFunctionObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ namespace PyExt::Remote {
6565
}
6666

6767

68-
auto PyFunctionObject::dict() const -> unique_ptr<PyDictObject>
68+
auto PyFunctionObject::dict() const -> unique_ptr<PyDict>
6969
{
7070
return utils::fieldAsPyObject<PyDictObject>(remoteType(), "func_dict");
7171
}

src/objects/PyObject.cpp

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,30 @@ namespace PyExt::Remote {
6262
}
6363

6464

65-
auto PyObject::dict() const -> unique_ptr<PyDict>
65+
auto PyObject::managedDict() const -> unique_ptr<PyDict>
6666
{
67-
if (type().isManagedDict()) {
68-
// Python >= 3.11, see PyObject_GenericGetDict
69-
auto pointerSize = utils::getPointerSize();
70-
Offset valuesPtr;
67+
// Python >= 3.11, see PyObject_GenericGetDict
68+
if (!type().isManagedDict())
69+
return { };
70+
71+
auto pointerSize = utils::getPointerSize();
72+
73+
Offset dictPtr = 0;
74+
utils::ignoreExtensionError([&] {
75+
// Python >= 3.13
76+
auto managedDictPtr = offset() - 3 * pointerSize;
77+
dictPtr = ExtRemoteTyped("PyManagedDictPointer", managedDictPtr, true).Field("dict").GetPtr();
78+
});
79+
if (dictPtr)
80+
return make_unique<PyDictObject>(dictPtr);
81+
82+
Offset valuesPtr;
83+
if (type().hasInlineValues()) {
84+
// Python >= 3.13
85+
auto dictValues = ExtRemoteTyped("(_dictvalues*)((PyObject*)(@$extin)+1)", offset());
86+
valuesPtr = dictValues.Field("values").GetPtr();
87+
} else {
7188
optional<ExtRemoteTyped> dictOrValues;
72-
7389
utils::ignoreExtensionError([&] {
7490
// Python 3.12
7591
auto dictOrValuesPtr = offset() - 3 * pointerSize;
@@ -92,12 +108,20 @@ namespace PyExt::Remote {
92108
return dictPtr ? make_unique<PyDictObject>(dictPtr) : nullptr;
93109
}
94110
}
95-
96-
auto ht = ExtRemoteTyped("PyHeapTypeObject", type().offset(), true);
97-
auto cachedKeys = ht.Field("ht_cached_keys");
98-
return make_unique<PyManagedDict>(cachedKeys.GetPtr(), valuesPtr);
99111
}
100112

113+
auto ht = ExtRemoteTyped("PyHeapTypeObject", type().offset(), true);
114+
auto cachedKeys = ht.Field("ht_cached_keys");
115+
return make_unique<PyManagedDict>(cachedKeys.GetPtr(), valuesPtr);
116+
}
117+
118+
119+
auto PyObject::dict() const -> unique_ptr<PyDict>
120+
{
121+
auto managedDict_ = managedDict();
122+
if (managedDict_ != nullptr)
123+
return managedDict_;
124+
101125
// see https://docs.python.org/3.10/c-api/typeobj.html#c.PyTypeObject.tp_dictoffset
102126
auto dictOffset_ = type().dictOffset();
103127
if (dictOffset_ == 0)

src/objects/PyTypeObject.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,29 @@ namespace PyExt::Remote {
117117
}
118118

119119

120+
auto PyTypeObject::getStaticBuiltinIndex() const -> SSize
121+
{
122+
auto type = remoteType();
123+
auto flagsRaw = type.Field("tp_flags");
124+
auto flags = utils::readIntegral<unsigned long>(flagsRaw);
125+
if (flags & (1 << 1)) { // _Py_TPFLAGS_STATIC_BUILTIN
126+
// Python >= 3.12
127+
auto subclassesRaw = type.Field("tp_subclasses");
128+
auto subclasses = utils::readIntegral<SSize>(subclassesRaw);
129+
return subclasses - 1;
130+
}
131+
return -1;
132+
}
133+
134+
135+
auto PyTypeObject::hasInlineValues() const -> bool
136+
{
137+
auto flagsRaw = remoteType().Field("tp_flags");
138+
auto flags = utils::readIntegral<unsigned long>(flagsRaw);
139+
return flags & (1 << 2); // Py_TPFLAGS_INLINE_VALUES
140+
}
141+
142+
120143
auto PyTypeObject::dictOffset() const -> SSize
121144
{
122145
auto dictOffset = remoteType().Field("tp_dictoffset");
@@ -138,6 +161,23 @@ namespace PyExt::Remote {
138161
}
139162

140163

164+
auto PyTypeObject::dict() const -> unique_ptr<PyDict>
165+
{
166+
Offset dictAddr;
167+
auto builtinIndex = getStaticBuiltinIndex();
168+
if (builtinIndex != -1) {
169+
auto builtins = ExtRemoteTyped("_PyRuntime.interpreters.main->types.builtins"); // TODO: Support multiple interpreters
170+
if (builtins.HasField("initialized")) // Python >= 3.13
171+
builtins = builtins.Field("initialized");
172+
auto builtinState = builtins.ArrayElement(builtinIndex);
173+
dictAddr = builtinState.Field("tp_dict").GetPtr();
174+
} else {
175+
dictAddr = remoteType().Field("tp_dict").GetPtr();
176+
}
177+
return make_unique<PyDictObject>(dictAddr);
178+
}
179+
180+
141181
auto PyTypeObject::repr(bool pretty) const -> string
142182
{
143183
string repr = "<class '" + name() + "'>";

0 commit comments

Comments
 (0)