diff --git a/share/qtcreator/debugger/README.txt b/share/qtcreator/debugger/README.txt index 7a0f6247023..32d8b3498eb 100644 --- a/share/qtcreator/debugger/README.txt +++ b/share/qtcreator/debugger/README.txt @@ -80,6 +80,8 @@ class Value: dereference() -> Value # Dereference if value is pointer, # remove reference if value is reference. hasChildren() -> bool # Whether this object has subobjects. + expand() -> bool # Make sure that children are accessible. + nativeDebuggerValue() -> string # Dumper value returned from the debugger childFromName(string name) -> Value # (optional) childFromField(Field field) -> Value # (optional) @@ -122,6 +124,7 @@ class Field: parseAndEvaluate(string: expr) -> Value # or None if not possible. lookupType(string: name) -> Type # or None if not possible. listOfLocals() -> [ Value ] # List of items currently in scope. +readRawMemory(ULONG64 address, ULONG size) -> bytes # Read a block of data from the virtual address space diff --git a/share/qtcreator/debugger/cdbbridge.py b/share/qtcreator/debugger/cdbbridge.py new file mode 100644 index 00000000000..c2443dcae5e --- /dev/null +++ b/share/qtcreator/debugger/cdbbridge.py @@ -0,0 +1,231 @@ +############################################################################ +# +# Copyright (C) 2016 The Qt Company Ltd. +# Contact: https://www.qt.io/licensing/ +# +# This file is part of Qt Creator. +# +# Commercial License Usage +# Licensees holding valid commercial Qt licenses may use this file in +# accordance with the commercial license agreement provided with the +# Software or, alternatively, in accordance with the terms contained in +# a written agreement between you and The Qt Company. For licensing terms +# and conditions see https://www.qt.io/terms-conditions. For further +# information use the contact form at https://www.qt.io/contact-us. +# +# GNU General Public License Usage +# Alternatively, this file may be used under the terms of the GNU +# General Public License version 3 as published by the Free Software +# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +# included in the packaging of this file. Please review the following +# information to ensure the GNU General Public License requirements will +# be met: https://www.gnu.org/licenses/gpl-3.0.html. +# +############################################################################ + +import inspect +import os +import sys +import threading +import cdbext + +sys.path.insert(1, os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) + +from dumper import * + +class Dumper(DumperBase): + def __init__(self): + DumperBase.__init__(self) + + self.outputLock = threading.Lock() + + self.isCdb = True + self.expandedINames = {} + self.passExceptions = False + self.showQObjectNames = False + self.autoDerefPointers = True + self.useDynamicType = True + self.useFancy = True + self.formats = {} + self.typeformats = {} + self.currentContextValue = None + + self.currentIName = None + self.currentValue = ReportItem() + self.currentType = ReportItem() + self.currentNumChild = None + self.currentMaxNumChild = None + self.currentPrintsAddress = True + self.currentChildType = None + self.currentChildNumChild = -1 + self.currentWatchers = {} + + def fromNativeValue(self, nativeValue): + val = self.Value(self) + val.nativeValue = nativeValue + val.type = self.fromNativeType(nativeValue.type()) + val.lIsInScope = True + val.laddress = nativeValue.address() + return val + + def fromNativeType(self, nativeType): + typeobj = self.Type(self) + typeobj.nativeType = nativeType + typeobj.name = nativeType.name() + typeobj.lbitsize = nativeType.bitsize() + typeobj.code = nativeType.code() + return typeobj + + def nativeTypeFields(self, nativeType): + fields = [] + for nativeField in nativeType.fields(): + field = self.Field(self) + field.name = nativeField.name() + field.parentType = self.fromNativeType(nativeType) + field.ltype = self.fromNativeType(nativeField.type()) + field.lbitsize = nativeField.bitsize() + field.lbitpos = nativeField.bitpos() + fields.append(field) + return fields + + def nativeTypeUnqualified(self, nativeType): + return self.fromNativeType(nativeType.unqualified()) + + def nativeTypePointer(self, nativeType): + return self.fromNativeType(nativeType.target()) + + def nativeTypeStripTypedefs(self, typeobj): + return self.fromNativeType(nativeType.stripTypedef()) + + def nativeTypeFirstBase(self, nativeType): + return None + + def nativeTypeEnumDisplay(self, nativeType, intval): + # TODO: generate fake value + return None + + def enumExpression(self, enumType, enumValue): + ns = self.qtNamespace() + return ns + "Qt::" + enumType + "(" \ + + ns + "Qt::" + enumType + "::" + enumValue + ")" + + def pokeValue(self, typeName, *args): + return None + + def parseAndEvaluate(self, exp): + return cdbext.parseAndEvaluate(exp) + + def nativeTypeTemplateArgument(self, nativeType, position, numeric = False): + return self.fromNativeType(nativeType.templateArgument(position, numeric)) + + def nativeTypeDereference(self, nativeType): + return self.fromNativeType(nativeType.target()) + + def nativeTypeTarget(self, nativeType): + return self.fromNativeType(nativeType.target()) + + def isWindowsTarget(self): + return True + + def isQnxTarget(self): + return False + + def isArmArchitecture(self): + return False + + def isMsvcTarget(self): + return True + + def qtVersionAndNamespace(self): + return ('', 0x50700) #FIXME: use a general approach in dumper or qttypes + + def qtNamespace(self): + return self.qtVersionAndNamespace()[0] + + def qtVersion(self): + self.qtVersionAndNamespace() + return self.qtVersionAndNamespace()[1] + + def qtTypeInfoVersion(self): + return None + + def ptrSize(self): + return cdbext.pointerSize() + + def put(self, stuff): + self.output += stuff + + def lookupNativeType(self, name): + return cdbext.lookupType(name) + + def currentThread(self): + return None + + def currentFrame(self): + return None + + def reportResult(self, result, args): + self.report('result={%s}' % (result)) + + def readRawMemory(self, address, size): + return cdbext.readRawMemory(address, size) + + def findStaticMetaObject(self, typeName): + symbolName = self.mangleName(typeName + '::staticMetaObject') + symbol = self.target.FindFirstGlobalVariable(symbolName) + return symbol.AddressOf().GetValueAsUnsigned() if symbol.IsValid() else 0 + + def warn(self, msg): + self.put('{name="%s",value="",type="",numchild="0"},' % msg) + + def fetchVariables(self, args): + (ok, res) = self.tryFetchInterpreterVariables(args) + if ok: + self.reportResult(res, args) + return + + self.expandedINames = set(args.get('expanded', [])) + self.autoDerefPointers = int(args.get('autoderef', '0')) + self.useDynamicType = int(args.get('dyntype', '0')) + self.useFancy = int(args.get('fancy', '0')) + self.passExceptions = int(args.get('passexceptions', '0')) + self.showQObjectNames = int(args.get('qobjectnames', '0')) + self.currentWatchers = args.get('watchers', {}) + self.typeformats = args.get('typeformats', {}) + self.formats = args.get('formats', {}) + + self.output = '' + partialVariable = args.get('partialvar', "") + isPartial = len(partialVariable) > 0 + + self.currentIName = 'local' + self.put('data=[') + self.anonNumber = 0 + + variables = [] + for val in cdbext.listOfLocals(): + self.currentContextValue = val + name = val.name() + value = self.fromNativeValue(val) + value.name = name + variables.append(value) + + self.handleLocals(variables) + self.handleWatches(args) + + self.put('],partial="%d"' % isPartial) + self.reportResult(self.output, args) + + def report(self, stuff): + with self.outputLock: + sys.stdout.write(stuff + "\n") + + def loadDumpers(self, args): + msg = self.setupDumpers() + self.reportResult(msg, args) + + def findValueByExpression(self, exp): + return cdbext.parseAndEvaluate(exp) + + def nativeDynamicTypeName(self, address, baseType): + return None # FIXME: Seems sufficient, no idea why. diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index 5608113e0af..4ee9a47981e 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -94,7 +94,7 @@ BreakpointAtJavaScriptThrow, \ = range(0, 14) -# Internal codes for types +# Internal codes for types keep in sync with cdbextensions pytype.cpp TypeCodeTypedef, \ TypeCodeStruct, \ TypeCodeVoid, \ diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index 420f5c24c57..ef3cd999cc1 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -680,6 +680,9 @@ class Dumper(DumperBase): # We get i686-w64-mingw32 return 'mingw' in gdb.TARGET_CONFIG.lower() + def isMsvcTarget(self): + return False + def qtVersionString(self): try: return str(gdb.lookup_symbol("qVersion")[0].value()()) diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index 8e8a52518d5..deffc41b1c3 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -474,6 +474,9 @@ class Dumper(DumperBase): def isArmArchitecture(self): return False + def isMsvcTarget(self): + return False + def qtVersionAndNamespace(self): for func in self.target.FindFunctions('qVersion'): name = func.GetSymbol().GetName() diff --git a/src/libs/qtcreatorcdbext/extensioncontext.cpp b/src/libs/qtcreatorcdbext/extensioncontext.cpp index 824822039ed..5fba498dee9 100644 --- a/src/libs/qtcreatorcdbext/extensioncontext.cpp +++ b/src/libs/qtcreatorcdbext/extensioncontext.cpp @@ -35,6 +35,7 @@ #ifdef WITH_PYTHON #include +#include "pycdbextmodule.h" #endif // wdbgexts.h declares 'extern WINDBG_EXTENSION_APIS ExtensionApis;' @@ -160,7 +161,10 @@ HRESULT ExtensionContext::initialize(PULONG Version, PULONG Flags) *Flags = 0; #ifdef WITH_PYTHON + initCdbextPythonModule(); Py_Initialize(); + PyRun_SimpleString("import cdbext"); + PyRun_SimpleString("import sys"); #endif IInterfacePointer client; diff --git a/src/libs/qtcreatorcdbext/pycdbextmodule.cpp b/src/libs/qtcreatorcdbext/pycdbextmodule.cpp new file mode 100644 index 00000000000..3f275e2bb44 --- /dev/null +++ b/src/libs/qtcreatorcdbext/pycdbextmodule.cpp @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "pycdbextmodule.h" + +#include "extensioncontext.h" +#include "symbolgroup.h" + +#include "pyfield.h" +#include "pystdoutredirect.h" +#include "pytype.h" +#include "pyvalue.h" + +#include +#include + +// cdbext python module +static PyObject *cdbext_parseAndEvaluate(PyObject *, PyObject *args) // -> Value +{ + char *expr; + if (!PyArg_ParseTuple(args, "s", &expr)) + return NULL; + CIDebugControl *control = ExtensionCommandContext::instance()->control(); + control->SetExpressionSyntax(DEBUG_EXPR_CPLUSPLUS); + DEBUG_VALUE value; + if (FAILED(control->Evaluate(expr, DEBUG_VALUE_INT64, &value, NULL))) + Py_RETURN_NONE; + return Py_BuildValue("K", value.I64); +} + +static PyObject *cdbext_lookupType(PyObject *, PyObject *args) // -> Type +{ + char *type; + if (!PyArg_ParseTuple(args, "s", &type)) + return NULL; + return lookupType(type); +} + +static PyObject *cdbext_listOfLocals(PyObject *, PyObject *) // -> [ Value ] +{ + ExtensionCommandContext *extCmdCtx = ExtensionCommandContext::instance(); + ULONG frame; + if (FAILED(extCmdCtx->symbols()->GetCurrentScopeFrameIndex(&frame))) + return NULL; + + std::string errorMessage; + LocalsSymbolGroup *sg = ExtensionContext::instance().symbolGroup( + extCmdCtx->symbols(), extCmdCtx->threadId(), int(frame), &errorMessage); + if (!sg) + return NULL; + + const auto children = sg->root()->children(); + auto locals = PyList_New(0); + for (AbstractSymbolGroupNode *abstractChild : children) { + Value *childValue = PyObject_New(Value, value_pytype()); + if (childValue != NULL) { + if (SymbolGroupNode* child = abstractChild->asSymbolGroupNode()) { + childValue->m_index = child->index(); + childValue->m_symbolGroup = sg->debugSymbolGroup(); + } + } + PyList_Append(locals, reinterpret_cast(childValue)); + } + + return locals; +} + +static PyObject *cdbext_pointerSize(PyObject *, PyObject *) +{ + HRESULT isPointer64Bit = ExtensionCommandContext::instance()->control()->IsPointer64Bit(); + return Py_BuildValue("i", isPointer64Bit == S_OK ? 8 : 4); +} + +static PyObject *cdbext_readRawMemory(PyObject *, PyObject *args) +{ + ULONG64 address = 0; + ULONG size = 0; + if (!PyArg_ParseTuple(args, "Kk", &address, &size)) + return NULL; + + char *buffer = new char[size]; + + CIDebugDataSpaces *data = ExtensionCommandContext::instance()->dataSpaces(); + ULONG bytesWritten = 0; + HRESULT hr = data->ReadVirtual(address, buffer, size, &bytesWritten); + if (FAILED(hr)) + bytesWritten = 0; + PyObject *ret = Py_BuildValue("y#", buffer, bytesWritten); + delete[] buffer; + return ret; +} + +static PyMethodDef cdbextMethods[] = { + {"parseAndEvaluate", cdbext_parseAndEvaluate, METH_VARARGS, + "Returns value of expression or None if the expression can not be resolved"}, + {"lookupType", cdbext_lookupType, METH_VARARGS, + "Returns type object or None if the type can not be resolved"}, + {"listOfLocals", cdbext_listOfLocals, METH_NOARGS, + "Returns list of values that are currently in scope"}, + {"pointerSize", cdbext_pointerSize, METH_NOARGS, + "Returns the size of a pointer"}, + {"readRawMemory", cdbext_readRawMemory, METH_VARARGS, + "Read a block of data from the virtual address space"}, + {NULL, NULL, 0, + NULL} /* Sentinel */ +}; + +static struct PyModuleDef cdbextModule = { + PyModuleDef_HEAD_INIT, + "cdbext", /* name of module */ + "bridge to the creator cdb extension", /* module documentation */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + cdbextMethods +}; + +PyMODINIT_FUNC +PyInit_cdbext(void) +{ + if (PyType_Ready(field_pytype()) < 0) + return NULL; + + if (PyType_Ready(type_pytype()) < 0) + return NULL; + + if (PyType_Ready(value_pytype()) < 0) + return NULL; + + stdoutRedirect_pytype()->tp_new = PyType_GenericNew; + if (PyType_Ready(stdoutRedirect_pytype()) < 0) + return NULL; + + PyObject *module = PyModule_Create(&cdbextModule); + if (module == NULL) + return NULL; + + Py_INCREF(field_pytype()); + Py_INCREF(stdoutRedirect_pytype()); + Py_INCREF(type_pytype()); + Py_INCREF(value_pytype()); + + PyModule_AddObject(module, "Field", + reinterpret_cast(field_pytype())); + PyModule_AddObject(module, "StdoutRedirect", + reinterpret_cast(stdoutRedirect_pytype())); + PyModule_AddObject(module, "Type", + reinterpret_cast(type_pytype())); + PyModule_AddObject(module, "Value", + reinterpret_cast(value_pytype())); + + return module; +} + +void initCdbextPythonModule() +{ + PyImport_AppendInittab("cdbext", PyInit_cdbext); +} + +PyObject *pyBool(bool b) +{ + if (b) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} diff --git a/src/libs/qtcreatorcdbext/pycdbextmodule.h b/src/libs/qtcreatorcdbext/pycdbextmodule.h new file mode 100644 index 00000000000..7228c865c17 --- /dev/null +++ b/src/libs/qtcreatorcdbext/pycdbextmodule.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +void initCdbextPythonModule(); + +PyObject *pyBool(bool); + +/* TODO's +class Field: + isBaseClass() -> bool # Whether this is a base class or normal member + +parseAndEvaluate(string: expr) -> Value # or None if not possible. + +*/ diff --git a/src/libs/qtcreatorcdbext/pyfield.cpp b/src/libs/qtcreatorcdbext/pyfield.cpp new file mode 100644 index 00000000000..8fe19cc35df --- /dev/null +++ b/src/libs/qtcreatorcdbext/pyfield.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "pyfield.h" + +#include "pytype.h" +#include "extensioncontext.h" + +PyObject *field_Name(Field *self) +{ + return Py_BuildValue("s", self->m_name); +} + +PyObject *field_isBaseClass(Field *) +{ + return NULL; +} + +bool initTypeAndOffset(Field *field) +{ + auto extcmd = ExtensionCommandContext::instance(); + field->m_initialized = SUCCEEDED(extcmd->symbols()->GetFieldTypeAndOffset( + field->m_module, field->m_parentTypeId, + field->m_name, &field->m_typeId, &field->m_offset)); + return field->m_initialized; +} + +PyObject *field_Type(Field *self) +{ + if (!self->m_initialized) + if (!initTypeAndOffset(self)) + return NULL; + return createType(self->m_module, self->m_typeId); +} + +PyObject *field_ParentType(Field *self) +{ + return createType(self->m_module, self->m_parentTypeId); +} + +PyObject *field_Bitsize(Field *self) +{ + if (!self->m_initialized) + if (!initTypeAndOffset(self)) + return NULL; + ULONG byteSize; + auto extcmd = ExtensionCommandContext::instance(); + if (FAILED(extcmd->symbols()->GetTypeSize(self->m_module, self->m_typeId, &byteSize))) + return NULL; + return Py_BuildValue("k", byteSize * 8); +} + +PyObject *field_Bitpos(Field *self) +{ + if (!self->m_initialized) + if (!initTypeAndOffset(self)) + return NULL; + return Py_BuildValue("k", self->m_offset * 8); +} + +PyObject *field_New(PyTypeObject *type, PyObject *, PyObject *) +{ + Field *self = reinterpret_cast(type->tp_alloc(type, 0)); + if (self != NULL) + initField(self); + return reinterpret_cast(self); +} + +void field_Dealloc(Field *self) +{ + delete[] self->m_name; +} + +void initField(Field *field) +{ + field->m_name = 0; + field->m_initialized = false; + field->m_typeId = 0; + field->m_offset = 0; + field->m_module = 0; + field->m_parentTypeId = 0; +} + +static PyMethodDef fieldMethods[] = { + {"name", PyCFunction(field_Name), METH_NOARGS, + "Return the name of this field or None for anonymous fields"}, + {"isBaseClass", PyCFunction(field_isBaseClass), METH_NOARGS, + "Whether this is a base class or normal member"}, + {"type", PyCFunction(field_Type), METH_NOARGS, + "Type of this member"}, + {"parentType", PyCFunction(field_ParentType), METH_NOARGS, + "Type of class this member belongs to"}, + {"bitsize", PyCFunction(field_Bitsize), METH_NOARGS, + "Size of member in bits"}, + {"bitpos", PyCFunction(field_Bitpos), METH_NOARGS, + "Offset of member in parent type in bits"}, + + {NULL} /* Sentinel */ +}; + +PyTypeObject *field_pytype() +{ + static PyTypeObject cdbext_FieldType = { + PyVarObject_HEAD_INIT(NULL, 0) + "cdbext.Field", /* tp_name */ + sizeof(Field), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)field_Dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Field objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + fieldMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + field_New, /* tp_new */ + }; + + return &cdbext_FieldType; +} diff --git a/src/libs/qtcreatorcdbext/pyfield.h b/src/libs/qtcreatorcdbext/pyfield.h new file mode 100644 index 00000000000..382160fb56b --- /dev/null +++ b/src/libs/qtcreatorcdbext/pyfield.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include + +struct Field +{ + PyObject_HEAD + const char *m_name; // owned + bool m_initialized; + unsigned long m_typeId; + unsigned long m_offset; + unsigned long m_parentTypeId; + ULONG64 m_module; +}; + +PyTypeObject *field_pytype(); + +void initField(Field *field); +bool initTypeAndOffset(Field *field); diff --git a/src/libs/qtcreatorcdbext/pystdoutredirect.cpp b/src/libs/qtcreatorcdbext/pystdoutredirect.cpp new file mode 100644 index 00000000000..d70668131aa --- /dev/null +++ b/src/libs/qtcreatorcdbext/pystdoutredirect.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "pystdoutredirect.h" + +static std::string output; +static PyObject *stdoutRedirect = nullptr; + +struct StdoutRedirect +{ + PyObject_HEAD +}; + +PyObject *stdoutRedirect_write(PyObject * /*self*/, PyObject *args) +{ + char *string; + PyArg_ParseTuple(args, "s", &string); + output += string; + return Py_BuildValue(""); +} + +PyObject *stdoutRedirect_flush(PyObject * /*self*/, PyObject * /*args*/) +{ + return Py_BuildValue(""); +} + +static PyMethodDef StdoutRedirectMethods[] = +{ + {"write", stdoutRedirect_write, METH_VARARGS, "sys.stdout.write"}, + {"flush", stdoutRedirect_flush, METH_VARARGS, "sys.stdout.flush"}, + {0, 0, 0, 0} // sentinel +}; + +PyTypeObject *stdoutRedirect_pytype() +{ + static PyTypeObject cdbext_StdoutRedirectType = + { + PyVarObject_HEAD_INIT(NULL, 0) + "cdbext.StdoutRedirect", /* tp_name */ + sizeof(StdoutRedirect), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "stdout redirector", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + StdoutRedirectMethods, /* tp_methods */ + }; + return &cdbext_StdoutRedirectType; +} + +void startCapturePyStdout() +{ + if (stdoutRedirect != nullptr) + endCapturePyStdout(); + stdoutRedirect = _PyObject_New(stdoutRedirect_pytype()); + if (PySys_SetObject("stdout", stdoutRedirect) != 0) + PySys_SetObject("stdout", PySys_GetObject("__stdout__")); + if (PySys_SetObject("stderr", stdoutRedirect) != 0) + PySys_SetObject("stderr", PySys_GetObject("__stderr__")); + PyObject_CallMethod(stdoutRedirect, "write", "s", "text"); + output.clear(); +} + +std::string getPyStdout() +{ + return output; +} + +void endCapturePyStdout() +{ + PySys_SetObject("stdout", PySys_GetObject("__stdout__")); + PySys_SetObject("stderr", PySys_GetObject("__stderr__")); + Py_DecRef(stdoutRedirect); + stdoutRedirect = nullptr; +} diff --git a/src/libs/qtcreatorcdbext/pystdoutredirect.h b/src/libs/qtcreatorcdbext/pystdoutredirect.h new file mode 100644 index 00000000000..aa688e0b276 --- /dev/null +++ b/src/libs/qtcreatorcdbext/pystdoutredirect.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +PyTypeObject *stdoutRedirect_pytype(); + +void startCapturePyStdout(); +std::string getPyStdout(); +void endCapturePyStdout(); diff --git a/src/libs/qtcreatorcdbext/pytype.cpp b/src/libs/qtcreatorcdbext/pytype.cpp new file mode 100644 index 00000000000..f032c62f63c --- /dev/null +++ b/src/libs/qtcreatorcdbext/pytype.cpp @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "pytype.h" + +#include + +#include "extensioncontext.h" +#include "pycdbextmodule.h" +#include "pyfield.h" +#include "stringutils.h" +#include "symbolgroupvalue.h" + +enum TypeCodes { + TypeCodeTypedef, + TypeCodeStruct, + TypeCodeVoid, + TypeCodeIntegral, + TypeCodeFloat, + TypeCodeEnum, + TypeCodePointer, + TypeCodeArray, + TypeCodeComplex, + TypeCodeReference, + TypeCodeFunction, + TypeCodeMemberPointer, + TypeCodeFortranString +}; + +PyObject *lookupType(const std::string &typeNameIn) +{ + std::string typeName = typeNameIn; + CIDebugSymbols *symbols = ExtensionCommandContext::instance()->symbols(); + ULONG64 module; + ULONG typeId; + if (FAILED(symbols->GetSymbolTypeId(typeName.c_str(), &typeId, &module))) + Py_RETURN_NONE; + return createType(module, typeId); +} + +char *getTypeName(ULONG64 module, ULONG typeId) +{ + char *typeName = 0; + auto symbols = ExtensionCommandContext::instance()->symbols(); + ULONG size = 0; + symbols->GetTypeName(module, typeId, NULL, 0, &size); + if (size > 0) { + typeName = new char[size]; + if (FAILED(symbols->GetTypeName(module, typeId, typeName, size, &size))) { + delete[] typeName; + typeName = new char[1]; + typeName[0] = 0; + } + } + return typeName; +} + +const char *getTypeName(Type *type) +{ + if (type->m_name == 0) + type->m_name = getTypeName(type->m_module, type->m_typeId); + return type->m_name; +} + +PyObject *type_Name(Type *self) +{ + return Py_BuildValue("s", getTypeName(self)); +} + +PyObject *type_bitSize(Type *self) +{ + ULONG size; + auto extcmd = ExtensionCommandContext::instance(); + if (FAILED(extcmd->symbols()->GetTypeSize(self->m_module, self->m_typeId, &size))) + return NULL; + return Py_BuildValue("k", size * 8); +} + +bool isType(const std::string &typeName, const std::vector &types) +{ + return std::find(types.begin(), types.end(), typeName) != types.end(); +} + +PyObject *type_Code(Type *self) +{ + static const std::vector integralTypes({"bool", + "char", "unsigned char", "char16_t", "char32_t", "wchar_t", + "short", "unsigned short", "int", "unsigned int", + "long", "unsigned long", "int64", "unsigned int64"}); + static const std::vector floatTypes({"float", "double"}); + + TypeCodes code = TypeCodeStruct; + const char *typeNameCstr = getTypeName(self); + if (typeNameCstr == 0) + Py_RETURN_NONE; + const std::string typeName(typeNameCstr); + if (SymbolGroupValue::isArrayType(typeName)) + code = TypeCodeArray; + else if (endsWith(typeName, "*")) + code = TypeCodePointer; + else if (typeName.find("") != std::string::npos) + code = TypeCodeFunction; + else if (isType(typeName, integralTypes)) + code = TypeCodeIntegral; + else if (isType(typeName, floatTypes)) + code = TypeCodeFloat; + + return Py_BuildValue("k", code); +} + +PyObject *type_Unqualified(Type *self) +{ + Py_XINCREF(self); + return (PyObject *)self; +} + +PyObject *type_Target(Type *self) +{ + std::string typeName(getTypeName(self)); + if (!endsWith(typeName, "*")) { + Py_XINCREF(self); + return (PyObject *)self; + } + typeName = typeName.substr(0, typeName.length() - 1); + + CIDebugSymbols *symbols = ExtensionCommandContext::instance()->symbols(); + ULONG typeId; + if (FAILED(symbols->GetTypeId(self->m_module, typeName.c_str(), &typeId))) + return NULL; + return createType(self->m_module, typeId); +} + +PyObject *type_StripTypedef(Type *self) +{ + Py_XINCREF(self); + return (PyObject *)self; +} + +PyObject *type_Fields(Type *self) +{ + CIDebugSymbols *symbols = ExtensionCommandContext::instance()->symbols(); + auto fields = PyList_New(0); + for (ULONG fieldIndex = 0;; ++fieldIndex) { + ULONG fieldNameSize = 0; + symbols->GetFieldName(self->m_module, self->m_typeId, fieldIndex, NULL, 0, &fieldNameSize); + if (fieldNameSize == 0) + break; + char *name = new char[fieldNameSize]; + if (FAILED(symbols->GetFieldName(self->m_module, self->m_typeId, fieldIndex, name, + fieldNameSize, NULL))) { + delete[] name; + break; + } + + Field *field = PyObject_New(Field, field_pytype()); + if (field == NULL) + return fields; + initField(field); + field->m_name = name; + field->m_parentTypeId = self->m_typeId; + field->m_module = self->m_module; + PyList_Append(fields, reinterpret_cast(field)); + } + return fields; +} + +std::vector innerTypesOf(const std::string &t) +{ + std::vector rc; + + std::string::size_type pos = t.find('<'); + if (pos == std::string::npos) + return rc; + + rc.reserve(5); + const std::string::size_type size = t.size(); + // Record all elements of level 1 to work correctly for + // 'std::map >' + unsigned level = 0; + std::string::size_type start = 0; + for ( ; pos < size ; pos++) { + const char c = t.at(pos); + switch (c) { + case '<': + if (++level == 1) + start = pos + 1; + break; + case '>': + if (--level == 0) { // last element + std::string innerType = t.substr(start, pos - start); + trimFront(innerType); + trimBack(innerType); + rc.push_back(innerType); + return rc; + } + break; + case ',': + if (level == 1) { // std::map: start anew at ','. + std::string innerType = t.substr(start, pos - start); + trimFront(innerType); + trimBack(innerType); + rc.push_back(innerType); + start = pos + 1; + } + break; + } + } + return rc; +} + +PyObject *type_TemplateArgument(Type *self, PyObject *args) +{ + unsigned int index; + bool numeric; + if (!PyArg_ParseTuple(args, "Ib", &index, &numeric)) + return NULL; + + std::vector innerTypes = innerTypesOf(getTypeName(self)); + if (innerTypes.size() <= index) + Py_RETURN_NONE; + + const std::string &innerType = innerTypes.at(index); + if (numeric) { + try { + return Py_BuildValue("i", std::stoi(innerType)); + } + catch (std::invalid_argument) { + return NULL; + } + } + + return lookupType(innerType); +} + +PyObject *type_New(PyTypeObject *type, PyObject *, PyObject *) +{ + Type *self = reinterpret_cast(type->tp_alloc(type, 0)); + if (self != NULL) { + self->m_name = nullptr; + self->m_typeId = 0; + self->m_module = 0; + } + return reinterpret_cast(self); +} + +void type_Dealloc(Type *self) +{ + delete[] self->m_name; +} + +PyObject *createType(ULONG64 module, ULONG typeId) +{ + Type *type = PyObject_New(Type, type_pytype()); + type->m_module = module; + type->m_typeId = typeId; + type->m_name = nullptr; + return reinterpret_cast(type); +} + +static PyMethodDef typeMethods[] = { + {"name", PyCFunction(type_Name), METH_NOARGS, + "Return the type name"}, + {"bitsize", PyCFunction(type_bitSize), METH_NOARGS, + "Return the size of the type in bits"}, + {"code", PyCFunction(type_Code), METH_NOARGS, + "Return type code"}, + {"unqualified", PyCFunction(type_Unqualified), METH_NOARGS, + "Type without const/volatile"}, + {"target", PyCFunction(type_Target), METH_NOARGS, + "Type dereferenced if it is a pointer type, element if array etc"}, + {"stripTypedef", PyCFunction(type_StripTypedef), METH_NOARGS, + "Type with typedefs removed"}, + {"fields", PyCFunction(type_Fields), METH_NOARGS, + "List of fields (member and base classes) of this type"}, + + {"templateArgument", PyCFunction(type_TemplateArgument), METH_VARARGS, + "Returns template argument at position"}, + + {NULL} /* Sentinel */ +}; + +static PyMemberDef typeMembers[] = { + {const_cast("id"), T_ULONG, offsetof(Type, m_typeId), 0, + const_cast("type id")}, + {const_cast("moduleBase"), T_ULONGLONG, offsetof(Type, m_module), 0, + const_cast("module base address")}, + {NULL} /* Sentinel */ +}; + +PyTypeObject *type_pytype() +{ + static PyTypeObject cdbext_TypeType = { + PyVarObject_HEAD_INIT(NULL, 0) + "cdbext.Type", /* tp_name */ + sizeof(Type), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)type_Dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Type objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + typeMethods, /* tp_methods */ + typeMembers, /* tp_members (just for debugging)*/ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + type_New, /* tp_new */ + }; + + return &cdbext_TypeType; +} diff --git a/src/libs/qtcreatorcdbext/pytype.h b/src/libs/qtcreatorcdbext/pytype.h new file mode 100644 index 00000000000..51d2e2071fc --- /dev/null +++ b/src/libs/qtcreatorcdbext/pytype.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "symbolgroup.h" + +#include +#include "structmember.h" + +#include + +struct Type +{ + PyObject_HEAD + ULONG m_typeId; + ULONG64 m_module; + char *m_name; // owned +}; + +PyTypeObject *type_pytype(); +char *getTypeName(ULONG64 module, ULONG typeId); + +PyObject *lookupType(const std::string &typeName); +PyObject *createType(ULONG64 module, ULONG typeId); diff --git a/src/libs/qtcreatorcdbext/pyvalue.cpp b/src/libs/qtcreatorcdbext/pyvalue.cpp new file mode 100644 index 00000000000..68f42ff4eb2 --- /dev/null +++ b/src/libs/qtcreatorcdbext/pyvalue.cpp @@ -0,0 +1,372 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "pyvalue.h" + +#include "extensioncontext.h" +#include "pycdbextmodule.h" +#include "pytype.h" +#include "pyfield.h" +#include "stringutils.h" + +std::string getSymbolName(CIDebugSymbolGroup *sg, ULONG index) +{ + ULONG size = 0; + sg->GetSymbolName(index, NULL, 0, &size); + if (size == 0) + return std::string(); + std::string name(size, '\0'); + sg->GetSymbolName(index, &name[0], size, &size); + return name; +} + +PyObject *value_Name(Value *self) +{ + if (!self->m_symbolGroup) + return NULL; + const std::string &symbolName = getSymbolName(self->m_symbolGroup, self->m_index); + if (symbolName.empty()) + Py_RETURN_NONE; + return Py_BuildValue("s", symbolName.c_str()); +} + +PyObject *value_Type(Value *self) +{ + if (!self->m_symbolGroup) + return NULL; + DEBUG_SYMBOL_PARAMETERS params; + const HRESULT hr = self->m_symbolGroup->GetSymbolParameters(self->m_index, 1, ¶ms); + if (FAILED(hr)) + return NULL; + return createType(params.Module, params.TypeId); +} + +PyObject *value_AsBytes(Value *self) +{ + if (!self->m_symbolGroup) + return NULL; + ULONG64 address = 0; + if (FAILED(self->m_symbolGroup->GetSymbolOffset(self->m_index, &address))) + return NULL; + ULONG size; + if (FAILED(self->m_symbolGroup->GetSymbolSize(self->m_index, &size))) + return NULL; + + char *buffer = new char[size]; + auto data = ExtensionCommandContext::instance()->dataSpaces(); + ULONG received = 0; + if (FAILED(data->ReadVirtual(address, buffer, size, &received))) + return NULL; + + return PyByteArray_FromStringAndSize(buffer, received); +} + +ULONG64 valueAddress(Value *value) +{ + ULONG64 address = 0; + if (value->m_symbolGroup) + value->m_symbolGroup->GetSymbolOffset(value->m_index, &address); + return address; +} + +PyObject *value_Address(Value *self) +{ + const ULONG64 address = valueAddress(self); + return address == 0 ? NULL : Py_BuildValue("K", address); +} + +bool expandValue(Value *v) +{ + DEBUG_SYMBOL_PARAMETERS params; + if (FAILED(v->m_symbolGroup->GetSymbolParameters(v->m_index, 1, ¶ms))) + return false; + if (params.Flags & DEBUG_SYMBOL_EXPANDED) + return true; + return SUCCEEDED(v->m_symbolGroup->ExpandSymbol(v->m_index, TRUE)); +} + +ULONG numberOfChildren(Value *v) +{ + DEBUG_SYMBOL_PARAMETERS params; + HRESULT hr = v->m_symbolGroup->GetSymbolParameters(v->m_index, 1, ¶ms); + return SUCCEEDED(hr) ? params.SubElements : 0; +} + +PyObject *value_Dereference(Value *self) +{ + if (!self->m_symbolGroup) + return NULL; + DEBUG_SYMBOL_PARAMETERS params; + const HRESULT hr = self->m_symbolGroup->GetSymbolParameters(self->m_index, 1, ¶ms); + if (FAILED(hr)) + return NULL; + + char *name = getTypeName(params.Module, params.TypeId); + + Value *ret = self; + if (endsWith(std::string(name), "*")) { + if (numberOfChildren(self) > 0 && expandValue(self)) { + ULONG symbolCount = 0; + self->m_symbolGroup->GetNumberSymbols(&symbolCount); + if (symbolCount > self->m_index + 1) { + ret = PyObject_New(Value, value_pytype()); + if (ret != NULL) { + ret->m_index = self->m_index + 1; + ret->m_symbolGroup = self->m_symbolGroup; + } + } + } + } + + delete[] name; + return reinterpret_cast(ret); +} + +PyObject *value_HasChildren(Value *self) +{ + if (!self->m_symbolGroup) + return NULL; + return pyBool(numberOfChildren(self) != 0); +} + +PyObject *value_Expand(Value *self) +{ + if (!self->m_symbolGroup) + return NULL; + return pyBool(expandValue(self)); +} + +PyObject *value_NativeDebuggerValue(Value *self) +{ + if (!self->m_symbolGroup) + return NULL; + ULONG size = 0; + self->m_symbolGroup->GetSymbolValueText(self->m_index, NULL, 0, &size); + char *name = new char[size]; + if (FAILED(self->m_symbolGroup->GetSymbolValueText(self->m_index, name, size, &size))) { + delete[] name; + Py_RETURN_NONE; + } + PyObject *ret = Py_BuildValue("s", name); + delete[] name; + return ret; +} + +PyObject *value_ChildFromName(Value *self, PyObject *args) +{ + if (!self->m_symbolGroup) + return NULL; + char *name; + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + const ULONG childCount = numberOfChildren(self); + if (childCount == 0 || !expandValue(self)) + Py_RETURN_NONE; + + for (ULONG childIndex = self->m_index + 1 ; childIndex <= self->m_index + childCount; ++childIndex) { + if (getSymbolName(self->m_symbolGroup, childIndex) == name) { + Value *childValue = PyObject_New(Value, value_pytype()); + if (childValue != NULL) { + childValue->m_index = childIndex; + childValue->m_symbolGroup = self->m_symbolGroup; + } + return reinterpret_cast(childValue); + } + } + + Py_RETURN_NONE; +} + +std::string pointedToSymbolName(ULONG64 address, const std::string &type) +{ + std::ostringstream str; + str << "*(" << type; + if (!type.empty() && type.at(type.size() - 1) == '*') + str << ' '; + str << "*)" << std::showbase << std::hex << address; + return str.str(); +} + +PyObject *value_ChildFromField(Value *self, PyObject *args) +{ + if (!self->m_symbolGroup) + return NULL; + Field *field; + if (!PyArg_ParseTuple(args, "O", &field)) + return NULL; + + if (!field->m_initialized && !initTypeAndOffset(field)) + return NULL; + + ULONG64 address = valueAddress(self); + if (address == 0) + return NULL; + address += field->m_offset; + + auto symbols = ExtensionCommandContext::instance()->symbols(); + ULONG childTypeNameSize = 0; + symbols->GetTypeName(field->m_module, field->m_typeId, NULL, 0, &childTypeNameSize); + std::string childTypeName(childTypeNameSize, '\0'); + symbols->GetTypeName(field->m_module, field->m_typeId, &childTypeName[0], + childTypeNameSize, &childTypeNameSize); + + if (childTypeName.empty()) + return NULL; + + std::string name = pointedToSymbolName(address, childTypeName); + ULONG index = DEBUG_ANY_ID; + if (FAILED(self->m_symbolGroup->AddSymbol(name.c_str(), &index))) + return NULL; + + Value *childValue = PyObject_New(Value, value_pytype()); + if (childValue != NULL) { + childValue->m_index = index; + childValue->m_symbolGroup = self->m_symbolGroup; + } + return reinterpret_cast(childValue); +} + +PyObject *value_ChildFromIndex(Value *self, PyObject *args) +{ + if (!self->m_symbolGroup) + return NULL; + unsigned int index; + if (!PyArg_ParseTuple(args, "I", &index)) + return NULL; + + if (index < 0) + return NULL; + + const ULONG childCount = numberOfChildren(self); + if (childCount <= index || !expandValue(self)) + Py_RETURN_NONE; + + Value *childValue = PyObject_New(Value, value_pytype()); + if (childValue != NULL) { + childValue->m_index = self->m_index + index + 1; + childValue->m_symbolGroup = self->m_symbolGroup; + } + + return reinterpret_cast(childValue); +} + +void value_Dealloc(Value *) +{ } + +PyObject *value_New(PyTypeObject *type, PyObject *, PyObject *) +{ + Value *self = reinterpret_cast(type->tp_alloc(type, 0)); + if (self != NULL) + initValue(self); + return reinterpret_cast(self); +} + +void initValue(Value *value) +{ + value->m_index = 0; + value->m_symbolGroup = nullptr; +} + +static PyMethodDef valueMethods[] = { + {"name", PyCFunction(value_Name), METH_NOARGS, + "Name of this thing or None"}, + {"type", PyCFunction(value_Type), METH_NOARGS, + "Type of this value"}, + {"asBytes", PyCFunction(value_AsBytes), METH_NOARGS, + "Memory contents of this object, or None"}, + {"address", PyCFunction(value_Address), METH_NOARGS, + "Address of this object, or None"}, + {"dereference", PyCFunction(value_Dereference), METH_NOARGS, + "Dereference if value is pointer"}, + {"hasChildren", PyCFunction(value_HasChildren), METH_NOARGS, + "Whether this object has subobjects"}, + {"expand", PyCFunction(value_Expand), METH_NOARGS, + "Make sure that children are accessible."}, + {"nativeDebuggerValue", PyCFunction(value_NativeDebuggerValue), METH_NOARGS, + "Value string returned by the debugger"}, + + {"childFromName", PyCFunction(value_ChildFromName), METH_VARARGS, + "Return the name of this value"}, + {"childFromField", PyCFunction(value_ChildFromField), METH_VARARGS, + "Return the name of this value"}, + {"childFromIndex", PyCFunction(value_ChildFromIndex), METH_VARARGS, + "Return the name of this value"}, + + {NULL} /* Sentinel */ +}; + +static PyMemberDef valueMembers[] = { + {const_cast("index"), T_ULONG, offsetof(Value, m_index), 0, + const_cast("value index in symbolgroup")}, + {NULL} /* Sentinel */ +}; + +PyTypeObject *value_pytype() +{ + static PyTypeObject cdbext_ValueType = + { + PyVarObject_HEAD_INIT(NULL, 0) + "cdbext.Value", /* tp_name */ + sizeof(Value), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)value_Dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Value objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + valueMethods, /* tp_methods */ + valueMembers, /* tp_members (just for debugging)*/ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + value_New, /* tp_new */ + }; + + return &cdbext_ValueType; +} diff --git a/src/libs/qtcreatorcdbext/pyvalue.h b/src/libs/qtcreatorcdbext/pyvalue.h new file mode 100644 index 00000000000..1c5c1ea0036 --- /dev/null +++ b/src/libs/qtcreatorcdbext/pyvalue.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "symbolgroupnode.h" +#include "symbolgroup.h" + +#include +#include "structmember.h" + +struct Value +{ + PyObject_HEAD + ULONG m_index; + CIDebugSymbolGroup *m_symbolGroup; // not owned +}; + +PyTypeObject *value_pytype(); +void initValue(Value *value); diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbext.pro b/src/libs/qtcreatorcdbext/qtcreatorcdbext.pro index decc196bb3e..0e8d7908322 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbext.pro +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbext.pro @@ -103,6 +103,20 @@ exists($$PYTHON_INSTALL_DIR) { INCLUDEPATH += $$PYTHON_INSTALL_DIR/include DEPENDPATH += $$PYTHON_INSTALL_DIR/include + SOURCES += \ + pycdbextmodule.cpp \ + pyfield.cpp \ + pystdoutredirect.cpp \ + pytype.cpp \ + pyvalue.cpp + + HEADERS += \ + pycdbextmodule.h \ + pyfield.h \ + pystdoutredirect.h \ + pytype.h \ + pyvalue.h + #TODO: parse version number for a generic approach CONFIG(release, debug|release): LIBS += -L$$PYTHON_INSTALL_DIR/libs -lpython35 else:CONFIG(debug, debug|release): LIBS += -L$$PYTHON_INSTALL_DIR/libs -lpython35_d diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp index 2b31df6e32b..af259aa5460 100644 --- a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -33,6 +33,7 @@ #ifdef WITH_PYTHON #include +#include "pystdoutredirect.h" #endif #include @@ -579,26 +580,17 @@ extern "C" HRESULT CALLBACK script(CIDebugClient *client, PCSTR argsIn) for (std::string arg : commandTokens(argsIn, &token)) command << arg << ' '; - if (PyRun_SimpleString(command.str().c_str()) == 0) { - ExtensionContext::instance().reportLong('R', token, "script", ""); - } else { - ExtensionContext::instance().report('N', token, 0, "script", - "Error while executing Python code."); - } - - _Py_IDENTIFIER(stdout); - _Py_IDENTIFIER(flush); - - PyObject *fout = _PySys_GetObjectId(&PyId_stdout); - PyObject *tmp; - - if (fout != NULL && fout != Py_None) { - tmp = _PyObject_CallMethodId(fout, &PyId_flush, ""); - if (tmp == NULL) - PyErr_WriteUnraisable(fout); - else - Py_DECREF(tmp); - } + PyObject *ptype = NULL; + PyObject *pvalue = NULL; + PyObject *ptraceback = NULL; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + startCapturePyStdout(); + const char result = (PyRun_SimpleString(command.str().c_str()) == 0) ? 'R' : 'N'; + if (PyErr_Occurred()) + PyErr_Print(); + ExtensionContext::instance().reportLong(result, token, "script", getPyStdout().c_str()); + endCapturePyStdout(); + PyErr_Restore(ptype, pvalue, ptraceback); #else commandTokens(argsIn, &token); ExtensionContext::instance().report('N', token, 0, "script", diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 0e092d3a246..c3eff659b5d 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -657,6 +657,8 @@ void CdbEngine::setupInferior() + " maxStackDepth=" + action(MaximalStackDepth)->value().toString(), NoFlags}); + runCommand({"print(sys.version)", ScriptCommand, CB(setupScripting)}); + runCommand({"pid", ExtensionCommand, [this](const DebuggerResponse &response) { // Fails for core dumps. if (response.resultClass == ResultDone) @@ -1162,6 +1164,14 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd) str << ' ' << dbgCmd.argsToString(); cmd = fullCmd.arg("", ""); fullCmd = fullCmd.arg(" -t ").arg(token); + } else if (dbgCmd.flags == ScriptCommand) { + // Add extension prefix and quotes the script command + // pass along token for identification in hash. + str << m_extensionCommandPrefix + "script %1%2 " << dbgCmd.function; + if (!dbgCmd.args.isNull()) + str << '(' << dbgCmd.argsToPython() << ')'; + cmd = fullCmd.arg("", ""); + fullCmd = fullCmd.arg(" -t ").arg(token); } m_commandForToken.insert(token, dbgCmd); } @@ -1204,97 +1214,136 @@ void CdbEngine::activateFrame(int index) void CdbEngine::doUpdateLocals(const UpdateParameters &updateParameters) { - typedef QHash WatcherHash; + if (m_pythonVersion > 0x030000) { + watchHandler()->notifyUpdateStarted(updateParameters.partialVariables()); - const bool partialUpdate = !updateParameters.partialVariable.isEmpty(); - const bool isWatch = isWatchIName(updateParameters.partialVariable); + DebuggerCommand cmd("theDumper.fetchVariables", ScriptCommand); + watchHandler()->appendFormatRequests(&cmd); + watchHandler()->appendWatchersAndTooltipRequests(&cmd); - const int frameIndex = stackHandler()->currentIndex(); - if (frameIndex < 0 && !isWatch) { - watchHandler()->removeAllData(); - return; - } - const StackFrame frame = stackHandler()->currentFrame(); - if (!frame.isUsable()) { - watchHandler()->removeAllData(); - return; - } + const static bool alwaysVerbose = !qgetenv("QTC_DEBUGGER_PYTHON_VERBOSE").isEmpty(); + cmd.arg("passexceptions", alwaysVerbose); + cmd.arg("fancy", boolSetting(UseDebuggingHelpers)); + cmd.arg("autoderef", boolSetting(AutoDerefPointers)); + cmd.arg("dyntype", boolSetting(UseDynamicType)); + cmd.arg("partialvar", updateParameters.partialVariable); + cmd.arg("qobjectnames", boolSetting(ShowQObjectNames)); - watchHandler()->notifyUpdateStarted(updateParameters.partialVariables()); + StackFrame frame = stackHandler()->currentFrame(); + cmd.arg("context", frame.context); + cmd.arg("nativemixed", isNativeMixedActive()); - /* Watchers: Forcibly discard old symbol group as switching from + cmd.arg("stringcutoff", action(MaximalStringLength)->value().toString()); + cmd.arg("displaystringlimit", action(DisplayStringLimit)->value().toString()); + + //cmd.arg("resultvarname", m_resultVarName); + cmd.arg("partialvar", updateParameters.partialVariable); + + cmd.callback = [this](const DebuggerResponse &response) { + if (response.resultClass == ResultDone) { + showMessage(response.data.toString(), LogMisc); + updateLocalsView(response.data); + } else { + showMessage(response.data["msg"].data(), LogError); + } + watchHandler()->notifyUpdateFinished(); + }; + + runCommand(cmd); + } else { + + typedef QHash WatcherHash; + + const bool partialUpdate = !updateParameters.partialVariable.isEmpty(); + const bool isWatch = isWatchIName(updateParameters.partialVariable); + + const int frameIndex = stackHandler()->currentIndex(); + if (frameIndex < 0 && !isWatch) { + watchHandler()->removeAllData(); + return; + } + const StackFrame frame = stackHandler()->currentFrame(); + if (!frame.isUsable()) { + watchHandler()->removeAllData(); + return; + } + + watchHandler()->notifyUpdateStarted(updateParameters.partialVariables()); + + /* Watchers: Forcibly discard old symbol group as switching from * thread 0/frame 0 -> thread 1/assembly -> thread 0/frame 0 will otherwise re-use it * and cause errors as it seems to go 'stale' when switching threads. * Initial expand, get uninitialized and query */ - QString arguments; - StringInputStream str(arguments); + QString arguments; + StringInputStream str(arguments); - if (!partialUpdate) { - str << "-D"; - // Pre-expand - const QSet expanded = watchHandler()->expandedINames(); - if (!expanded.isEmpty()) { - str << blankSeparator << "-e "; - int i = 0; - foreach (const QString &e, expanded) { - if (i++) - str << ','; - str << e; + if (!partialUpdate) { + str << "-D"; + // Pre-expand + const QSet expanded = watchHandler()->expandedINames(); + if (!expanded.isEmpty()) { + str << blankSeparator << "-e "; + int i = 0; + foreach (const QString &e, expanded) { + if (i++) + str << ','; + str << e; + } } } - } - str << blankSeparator << "-v"; - if (boolSetting(UseDebuggingHelpers)) - str << blankSeparator << "-c"; - if (boolSetting(SortStructMembers)) - str << blankSeparator << "-a"; - const QString typeFormats = watchHandler()->typeFormatRequests(); - if (!typeFormats.isEmpty()) - str << blankSeparator << "-T " << typeFormats; - const QString individualFormats = watchHandler()->individualFormatRequests(); - if (!individualFormats.isEmpty()) - str << blankSeparator << "-I " << individualFormats; - // Uninitialized variables if desired. Quote as safeguard against shadowed - // variables in case of errors in uninitializedVariables(). - if (boolSetting(UseCodeModel)) { - QStringList uninitializedVariables; - getUninitializedVariables(Internal::cppCodeModelSnapshot(), - frame.function, frame.file, frame.line, &uninitializedVariables); - if (!uninitializedVariables.isEmpty()) { - str << blankSeparator << "-u \""; - int i = 0; - foreach (const QString &u, uninitializedVariables) { - if (i++) - str << ','; - str << localsPrefixC << u; - } - str << '"'; - } - } - // Perform watches synchronization only for full updates - if (!partialUpdate) - str << blankSeparator << "-W"; - if (!partialUpdate || isWatch) { - const WatcherHash watcherHash = WatchHandler::watcherNames(); - if (!watcherHash.isEmpty()) { - const WatcherHash::const_iterator cend = watcherHash.constEnd(); - for (WatcherHash::const_iterator it = watcherHash.constBegin(); it != cend; ++it) { - str << blankSeparator << "-w " << "watch." + QString::number(it.value()) - << " \"" << it.key() << '"'; + str << blankSeparator << "-v"; + if (boolSetting(UseDebuggingHelpers)) + str << blankSeparator << "-c"; + if (boolSetting(SortStructMembers)) + str << blankSeparator << "-a"; + const QString typeFormats = watchHandler()->typeFormatRequests(); + if (!typeFormats.isEmpty()) + str << blankSeparator << "-T " << typeFormats; + const QString individualFormats = watchHandler()->individualFormatRequests(); + if (!individualFormats.isEmpty()) + str << blankSeparator << "-I " << individualFormats; + // Uninitialized variables if desired. Quote as safeguard against shadowed + // variables in case of errors in uninitializedVariables(). + if (boolSetting(UseCodeModel)) { + QStringList uninitializedVariables; + getUninitializedVariables(Internal::cppCodeModelSnapshot(), + frame.function, frame.file, frame.line, &uninitializedVariables); + if (!uninitializedVariables.isEmpty()) { + str << blankSeparator << "-u \""; + int i = 0; + foreach (const QString &u, uninitializedVariables) { + if (i++) + str << ','; + str << localsPrefixC << u; + } + str << '"'; } } + // Perform watches synchronization only for full updates + if (!partialUpdate) + str << blankSeparator << "-W"; + if (!partialUpdate || isWatch) { + const WatcherHash watcherHash = WatchHandler::watcherNames(); + if (!watcherHash.isEmpty()) { + const WatcherHash::const_iterator cend = watcherHash.constEnd(); + for (WatcherHash::const_iterator it = watcherHash.constBegin(); it != cend; ++it) { + str << blankSeparator << "-w " << "watch." + QString::number(it.value()) + << " \"" << it.key() << '"'; + } + } + } + + // Required arguments: frame + str << blankSeparator << frameIndex; + + if (partialUpdate) + str << blankSeparator << updateParameters.partialVariable; + + DebuggerCommand cmd("locals", ExtensionCommand); + cmd.args = arguments; + cmd.callback = [this, partialUpdate](const DebuggerResponse &r) { handleLocals(r, partialUpdate); }; + runCommand(cmd); } - - // Required arguments: frame - str << blankSeparator << frameIndex; - - if (partialUpdate) - str << blankSeparator << updateParameters.partialVariable; - - DebuggerCommand cmd("locals", ExtensionCommand); - cmd.args = arguments; - cmd.callback = [this, partialUpdate](const DebuggerResponse &r) { handleLocals(r, partialUpdate); }; - runCommand(cmd); } void CdbEngine::updateAll() @@ -2192,8 +2241,11 @@ void CdbEngine::handleExtensionMessage(char t, int token, const QString &what, c qDebug("### Completed extension command '%s' for token=%d, pending=%d", qPrintable(command.function), token, m_commandForToken.size()); - if (!command.callback) + if (!command.callback) { + if (!message.isEmpty()) // log unhandled output + showMessage(message, LogMisc); return; + } DebuggerResponse response; response.data.m_name = "data"; if (t == 'R') { @@ -2847,6 +2899,46 @@ void CdbEngine::handleAdditionalQmlStack(const DebuggerResponse &response) showMessage("Unable to obtain QML stack trace: " + errorMessage, LogError); } +void CdbEngine::setupScripting(const DebuggerResponse &response) +{ + GdbMi data = response.data; + if (response.resultClass != ResultDone) { + showMessage(data["msg"].data(), LogMisc); + return; + } + const QString &verOutput = data.data(); + const QStringList pythonVersion = verOutput.split(' ').first().split('.'); + + bool ok = false; + if (pythonVersion.size() == 3) { + m_pythonVersion |= pythonVersion[0].toInt(&ok); + if (ok) { + m_pythonVersion = m_pythonVersion << 8; + m_pythonVersion |= pythonVersion[1].toInt(&ok); + if (ok) { + m_pythonVersion = m_pythonVersion << 8; + m_pythonVersion |= pythonVersion[2].toInt(&ok); + } + } + } + if (!ok) { + m_pythonVersion = 0; + showMessage(QString("Can not parse sys.version:\n%1").arg(verOutput), LogWarning); + return; + } + + QString dumperPath = QDir::toNativeSeparators(Core::ICore::resourcePath() + "/debugger"); + dumperPath.replace('\\', "\\\\"); + runCommand({"sys.path.insert(1, '" + dumperPath + "')", ScriptCommand}); + runCommand({"from cdbbridge import Dumper", ScriptCommand}); + runCommand({"print(dir())", ScriptCommand}); + runCommand({"theDumper = Dumper()", ScriptCommand}); + runCommand({"theDumper.loadDumpers(None)", ScriptCommand, + [this](const DebuggerResponse &response) { + watchHandler()->addDumpers(response.data["dumpers"]); + }}); +} + void CdbEngine::mergeStartParametersSourcePathMap() { const DebuggerRunParameters &rp = runParameters(); diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h index 9d6c5d5238d..aaf8b25319c 100644 --- a/src/plugins/debugger/cdb/cdbengine.h +++ b/src/plugins/debugger/cdb/cdbengine.h @@ -158,6 +158,7 @@ private: NoFlags = 0, BuiltinCommand, ExtensionCommand, + ScriptCommand }; bool startConsole(const DebuggerRunParameters &sp, QString *errorMessage); @@ -210,6 +211,7 @@ private: void handleWidgetAt(const DebuggerResponse &response); void handleBreakPoints(const DebuggerResponse &response); void handleAdditionalQmlStack(const DebuggerResponse &response); + void setupScripting(const DebuggerResponse &response); NormalizedSourceFileName sourceMapNormalizeFileNameFromDebugger(const QString &f); void doUpdateLocals(const UpdateParameters ¶ms) override; void updateAll() override; @@ -258,6 +260,7 @@ private: QVariantList m_customSpecialStopData; QList m_sourcePathMappings; QScopedPointer m_coreStopReason; + int m_pythonVersion = 0; // 0xMMmmpp MM = major; mm = minor; pp = patch }; } // namespace Internal