Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debug Qt app in Visual Studio and inspect opaque objects (e.g. QDir or QFileInfo)

I am debugging a Qt application in Visual Studio 2013. I have the official Visual Studio plugin from Qt installed. I can see the contents of QString, but for any other QObject such as a QDir or a QFileInfo object I am not able to see its contents.

Am I doing something wrong or is this simply not possible?

When I unfold a QDir instance I can only see a member named d_ptr which references a QDirPrivate object that I cannot inspect. I can also not call other functions of QDir or QFileInfo such as path(), or filePath() at runtime from the debugger. When I try that Visual Studio claims that the address of the function has been optimized away.

Is there anyway to debug this without adding dozens of log statements to the program?

like image 329
lanoxx Avatar asked Jun 26 '17 15:06

lanoxx


2 Answers

UPDATE

After detailing in last answer that it was not possible, today I've found a way to make the debugger show the private (hiding) information.

Note: I'm keeping the old post below because it exposes the whole path I've followed to reach this solution and I think it worth keeping track of mistakes made and wrong conclusions, so nobody else made them again ;).

Suppose you have a QDir d; variable. You can inspect its inner data by making a manual cast in the Watch window:

(Qt5Cored.dll!QDirPrivate*)d.d_ptr.d

Watch window

Unfortunately it doesn't show any useful information since you'll face the same problem for inner members (private data not being printed).

However, from this point you can now modify your .natvis file to show the wanted data on hover:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="QDir">
    <DisplayString>path={((Qt5Cored.dll!QDirPrivate*)d_ptr.d)-&gt;dirEntry.m_filePath}</DisplayString>
  </Type>
</AutoVisualizer>

done!

I haven't been able to print this information on the Watch window (if I add a Expand section to the .natvis file it seems to be omitted), but at least you have this information on debug time.

I suggest you to take a look at the private header files or the Watch window (as described at the beginning of the answer) for the types you want to inspect so you can add the correct data to the .natvis file.

What was wrong about previous analysis?

I think the key point is the manual casting. I mentioned that

dirEntry is part of QDirPrivate, so it is clear that the debugger is not able to inspect it.

It was partially correct. The problem was that the debugger didn't know where to locate QDirPrivate since it is not an exported symbol of Qt; once we've indicated it explicitly (through the (Qt5Cored.dll!QDirPrivate*) cast) it was able to inspect it.


ORIGINAL ANSWER

AFAIK this is not possible for those classes. Unfortunately there are some private structures that are not exported and that seem the debugger cannot view.

Note: I'm working with Qt 5.6.1 so I'm going to use its source code from qt.io repository to match my examples.

Let's take QDir for example: it is defined in qdir.h, which is part of the public API of Qt. In the same file it declares QDirPrivate but its full definition is in qtdir_p.h which is not part of the public API.

Even if symbols are loaded, somehow the definition of this class remains obscure to the debugger. See example below of the .natvis file I've used:

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="QDir">
    <DisplayString>Path: {d_func()-&gt;dirEntry.m_filePath}</DisplayString>
  </Type>
</AutoVisualizer>

But the debugger shows the following when trying to watch QDir d(qApp->applicationDirPath());:

Error: class "QDirPrivate" has no member "dirEntry"

Error while evaluating 'd_func()->dirEntry.m_filePath'

but dirEntry is part of QDirPrivate, so it is clear that the debugger is not able to inspect it. dirEntry is of type QFileSystemEntry, defined in qfilesystementry_p.h (another private class).

Finally, if you take a look at the qt5.natvis file from Qt VS Tools and find corresponding classes in the source code you'll see that all the included classes exposes the definition of the structures used by the .natvis file.


UPDATE

The only public API of such classes are methods, unfortunately, calling functions from debugger is not supported. Citing the response in a MSDN forum from a Microsoft Staff:

Calling a function from the debugger is playing with fire. You are likely to deadlock on cross thread dependencies (even if you don't have any explicit cross thread dependencies, there are shared locks for things like memory allocation). This is why the C++ debugger has no support for implicit funceval.

Actually, if you try to invoke any method like QDir::absolutePath() in the watch window or in the .natvis file you'll see the following error message:

Error: Function QDir::absolutePath has no address, possibly due to compiler optimizations.


NOT SO ELEGANT WORKAROUND

A possible solution would be to use wrapper classes that store those private values. You'd need to replace each object with the wrapper but may help.

Below you'll find a very simple example for QDir (you'd need to complete with required constructors and to save the information you want). Keep in mind that this type of classes in Qt has been designed to be not extensible, so there are no virtual methods (so be aware of reimplementing some of them and to use the object casted to the base class).

class MyQDir : public QDir {
public:
  MyQDir(const QString& path) : QDir(path) {
    m_absolutePath = absolutePath();
  }

private:
  QString m_absolutePath;
};

and following .natvis file to display MyQDir's path on hover:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="MyQDir">
    <DisplayString>Path: {m_absolutePath}</DisplayString>
  </Type>
</AutoVisualizer>

Finally, I think the only solution left would be to print the information to console (qDebug()).


As a side note, this tutorial explains how to write a custom .natvis file. It is for 2015 but I've used it for 2017 flawlessly. I hope it is valid for 2013 too.

like image 90
cbuchart Avatar answered Sep 22 '22 05:09

cbuchart


I have the following extensions to the qt5.natvis which exposes some of the contents of QDir, QFile and QFileInfo, building on an original suggestion above:

<Type Name="QFileInfoPrivate">
    <DisplayString Condition="0 == this">&lt;null&gt;</DisplayString>
    <DisplayString>{fileEntry}</DisplayString>
    <StringView>fileEntry</StringView>
    <Expand>
        <Item Name="QSharedData">*((Qt5Cored.dll!QSharedData *) this)</Item>
        <Item Name="fileEntry">fileEntry</Item>
        <!--
        <Item Name="metaData">metaData</Item>
        <Item Name="fileListsInitialized">fileListsInitialized</Item>
        <Item Name="fileEngine">fileEngine</Item>
        <Item Name="fileNames">fileNames</Item>
        <Item Name="fileOwners">fileOwners</Item>
        <Item Name="cachedFlags">cachedFlags</Item>
        <Item Name="isDefaultConstructed">isDefaultConstructed</Item>
        <Item Name="cache_enabled">cache_enabled</Item>
        <Item Name="fileFlags">fileFlags</Item>
        <Item Name="fileSize">fileSize</Item>
        <Item Name="fileTimes">fileTimes</Item>
        -->
    </Expand>
</Type>


<Type Name="QFileInfo">
    <DisplayString>{*((Qt5Cored.dll!QFileInfoPrivate *) d_ptr.d)}</DisplayString>
    <StringView>*((Qt5Cored.dll!QFileInfoPrivate *) d_ptr.d)</StringView>
    <Expand>
        <Item Name="QFileInfoPrivate">*((Qt5Cored.dll!QFileInfoPrivate *) d_ptr.d)</Item>
    </Expand>
</Type>


<Type Name="QFileSystemEntry">
    <DisplayString Condition="0 == this">&lt;null&gt;</DisplayString>
    <DisplayString>{m_filePath}</DisplayString>
    <StringView>m_filePath</StringView>
    <!--
    <Expand>
        <Item Name="m_filePath">m_filePath</Item>
        <Item Name="m_nativeFilePath">m_nativeFilePath</Item>
        <Item Name="m_lastSeparator">m_lastSeparator</Item>
        <Item Name="m_firstDotInFileName">m_firstDotInFileName</Item>
        <Item Name="m_lastDotInFileName">m_lastDotInFileName</Item>
    </Expand>
    -->
</Type>


<Type Name="QDirPrivate">
    <DisplayString Condition="0 == this">&lt;null&gt;</DisplayString>
    <DisplayString>{dirEntry}</DisplayString>
    <StringView>dirEntry</StringView>
    <Expand>
        <Item Name="QSharedData">*((Qt5Cored.dll!QSharedData *) this)</Item>
        <Item Name="dirEntry">dirEntry</Item>
        <Item Name="nameFilters">nameFilters</Item>
        <Item Name="absoluteDirEntry">absoluteDirEntry</Item>
        <!--
        <Item Name="metaData">metaData</Item>
        <Item Name="fileListsInitialized">fileListsInitialized</Item>
        <Item Name="fileEngine">fileEngine</Item>
        <Item Name="files">files</Item>
        <Item Name="fileInfos">fileInfos</Item>
        <Item Name="sort">sort</Item>
        <Item Name="filters">filters</Item>
        -->
   </Expand>
</Type>


<Type Name="QDir">
    <DisplayString>{*((Qt5Cored.dll!QDirPrivate *) d_ptr.d)}</DisplayString>
    <StringView>*((Qt5Cored.dll!QDirPrivate *) d_ptr.d)</StringView>
    <Expand>
        <Item Name="QDirPrivate">*((Qt5Cored.dll!QDirPrivate *) d_ptr.d)</Item>
    </Expand>
</Type>


<Type Name="QFilePrivate">
    <DisplayString Condition="0 == this">&lt;null&gt;</DisplayString>
    <DisplayString>{fileName}</DisplayString>
    <StringView>fileName</StringView>
    <Expand>
        <Item Name="QFileDevice">*((Qt5Cored.dll!QFileDevice *) this)</Item>
        <Item Name="fileName">fileName</Item>
    </Expand>
</Type>


<Type Name="QFile">
    <DisplayString>{*((Qt5Cored.dll!QFilePrivate *) d_ptr.d)}</DisplayString>
    <StringView>*((Qt5Cored.dll!QFilePrivate *) d_ptr.d)</StringView>
    <Expand>
        <Item Name="QFilePrivate">*((Qt5Cored.dll!QFilePrivate *) d_ptr.d)</Item>
    </Expand>
</Type>
like image 27
Tom Lucas Avatar answered Sep 26 '22 05:09

Tom Lucas