How does Qt Creator interact with debugger GDB?

You may never notice the interaction between Qt Creator and the GDB debugger in the past. The communication between Qt Creator and GDB is hidden well. You are supposed to only use the variable/watch window during the debug. But sooner or later, you’ll find problems you cannot solve with the debug windows Qt Creator presents to you. For example, you may encounter the problem that Qt Creator cannot launch the debugger engine. Without seeing the logs Qt Creator communicates with the debugger, you’ll never know what’s happening.

There does exist a debugger log window. As I said, it is hidden well. If you are lucky, you may see this windows appears in a short time then disappears again when the debugged program exits. It is also impossible for a newbie to dig the debugger window out. In fact, you can let Qt Creator show the debugger log window only when you are debugging a program and by clicking Window/Views/Debugger Log menu item.

Now you will see the debugger log window which has a left panel and a right panel.

From the debugger log window, you can see a lot of interaction messages between Qt Creator and GDB. These are the commands Qt Creator sends to GDB and the results Qt Creator gets from GDB. I hear you are saying “wait a minute, do you mean those lines like 640-exec-continue 641-thread-info are the commands sent by Qt Creator to GDB and executed by GDB? I never used those commands. I only used commands like br, list, run, continue, print, etc to debug a program in GDB.” Yes, you are right, those are not ordinary GDB CLI commands you are using in your daily debugging work, but GDB MI(Machine oriented text Interface) commands.  When you start GDB with the -i mi option, as Qt Creator does, you’ll get the commands available for you.

gdb -i mi test.exe

The printed messages in the MI mode are very messy because they are not for human readers but for machine parsers. How to decode those inputs and outputs in the debugger log window? Well, the left panel of the debugger log window contains mainly the commands Qt Creator sends to GDB, while the right panel contains both the commands sent and the messages received from GDB. There is a line edit at the bottom of the left panel where you can input debug command(one per time) manually. The MI commands have the form of token-command where token is usually a number. The output of the command will also include the number so the result can be associated with the command easily. But the token can be omitted, for example, -exec-run, -thread-info are both legal GDB MI commands. Most of the common MI commands have their CLI counterparts. For example, -exec-run corresponds to run, -exec-continue corresponds to continue. You can also input ordinary CLI commands in MI mode.

(gdb)
next
&"next\n"
^running
*running,thread-id="all"
(gdb)

The output of a CLI command will include a line beginning with “&” which is followed by the command you enter. The output in MI mode has special character such as &, ^, *, ~ at the beginning of each line which means the line is from specific output channel. The line beginning with ^ is the final status of the command, i.e., done, or error. There may be other outputs between the enter of the command and the final status, even after the final status line. These are called out-of-band messages.

The mainly communications between Qt Creator and GDB are not done by calling GDB MI commands, but through GDB Python API. The python code is mainly in gdbbridge.py and dumper.py which are in c:\Qt5.12.1\Tools\QtCreator\share\qtcreator\debugger\. Qt Creator will import the gdbbridge python module in GdbEngine::setupEngine() in src\plugins\debugger\gdb\gdbengine.cpp then get the theDumper(of class Dumper) object. With the theDumper python object, Qt Creator can retrieve much information from GDB with the python api. In the setupEngine function, you can also find the commands Qt Creator asks GDB to execute to set the debugger up.

show version
show debug-file-directory
set print object on
set breakpoint pending on
set print elements 10000
set unwindonsignal on
set width 0
set height 0
set substitute-path
directory 
set sysroot 
set detach-on-fork off
python sys.path.insert(1,"xxxxxx")
python sys.path.append('xxxxx')
python from gdbbridge import *

After the last line, theDumper is available and the first thing Qt Creator uses it to do is to load the pretty printers.

As I said before, the most used debugger windows are the local and the watcher window. If they are not appeared, you can call them out by clicking Window/Views/Locals(Expressions).

The Locals window shows the values of local variables/parameters of a function when execution stops in the function. The watcher window displays the variables you added to watch their values. After breaking on some line in a function, Qt Creator will call theDumper.fetchVariables to retrieve the values of both the local variables and the watched variables in single shot. Here is an example:

<682python theDumper.fetchVariables({"autoderef":1,"context":"","displaystringlimit":"100","dyntype":1,"expanded":["inspect","local","watch","return"],"fancy":1,"formats":{},"nativemixed":0,"partialvar":"","passexceptions":0,"qobjectnames":1,"resultvarname":"","stringcutoff":"10000","token":682,"typeformats":{},"watchers":[{"exp":"6d7977837273696f6e","iname":"watch.0"}]})

The results are parsed and displayed in the Locals window and the Expressions window, respectively.

Qt Creator calls theDumper.fetchVariables frequently. Even your mouse pointer floats over a variable name in the source code, Qt Creator will call it to retrieve the value of the variable and display it in the popup tool-tip.

The python class Dumper provided by Qt creator eventually calls the gdb provided python function gdb.parse_and_eval(exp) to retrieve the value of an expression from the debugged inferior.  Let’s look into the process in more detail.

Suppose you add a watcher variable by right-clicking a variable in a source file and selecting the “Add Expression Evaluator” context menu item, the variable will appear in the Expressions window, meanwhile the following GDB-MI command is issued:

46python theDumper.fetchVariables({"autoderef":1,"context":"","displaystringlimit":"100","dyntype":1,"expanded":["return","local","inspect","watch"],"fancy":1,"formats":{},"nativemixed":0,"partialvar":"watch.0","passexceptions":0,"qobjectnames":1,"resultvarname":"","stringcutoff":"10000","token":46,"typeformats":{},"watchers":[{"exp":"62","iname":"watch.0"}]})

You can see the watchers parameter of the theDumper.fetchVariables function is an array which contains one expression object:{“exp”:”62″,”iname”:”watch.0″}. The call path is theDumper.fetchVariables ->handleWatches->handleWatch->parseAndEvaluate->nativeParseAndEvaluate->gdb.parse_and_eval.

The returned value of gdb.parse_and_eval is a GDB.Value object, which is converted to an object of class Value provided by Qt Creator in dumper.py module. Then the dumper calls its putItem function to dump the Value object as

address="0x67fc00",numchild="1",type="A",value=""

The values of the fields are just gotten by gdb python api. If you’ve defined a pretty printer the the type(in this case “A”), putItem will call tryPutPrettyItem to get a pretty description about this Value. These fields, plus the fields provided by handleWatch form the description string of the watcher expression:

{iname="watch.0",wname="62",address="0x67fc00",numchild="1",type="A",value="",}

The descriptions of all watcher expressions are put in an array(in this case, there is only one expression):

data=[{iname="watch.0",wname="62",address="0x67fc00",numchild="1",type="A",value="",},]

Apart from the data gotten from GDB, theDumper.fetchVariables also adds other information fields. The final output to Qt Creator would be:

result={token="0",data=[{iname="watch.0",wname="62",address="0x67fc00",numchild="1",type="A",value="",},],typeinfo=[],partial="1",counts={'nostruct-3': 2},timings=[]}\n

Qt Creator will use this result to populate its Expressions window.

You may wonder why value=””? Well, this is because we are retrieving the value of an object of class A. For class objects, the Expressions window only displays its name, address(in the value column) and type. If we were retrieving a basic data type, the value would be the value of the data. You can click the “>” before the name of the object in the Expressions window to expand the object. Then, another theDumper.fetchVariables command is issued. This time, the command would be like:

<55python theDumper.fetchVariables({"autoderef":1,"context":"","displaystringlimit":"100","dyntype":1,"expanded":["return","inspect","local","watch","watch.1"],"fancy":1,"formats":{},"nativemixed":0,"partialvar":"watch.1","passexceptions":0,"qobjectnames":1,"resultvarname":"","stringcutoff":"10000","token":55,"typeformats":{},"watchers":[{"exp":"67","iname":"watch.1"},{"exp":"69","iname":"watch.0"}]})

Although the watchers array only contains the top level watching expressions, the expanded array now contains another entry: watch.1 that corresponds to the object we clicked. The fetchVariables function gets to know this and tries to retrieve the children of watch.1 as well as the top level expressions. The result would be like:

result={token="0",data=[{iname="watch.1",wname="67",address="0x67fbe0",numchild="1",sortable="1",children=[{name="a",numchild="1",address="0x67fbe0",type="QString",valueencoded="utf16",value="3500",},],type="A",value="",},{iname="watch.0",wname="69",address="0x67fbdc",numchild="0",type="int",value="3",},],typeinfo=[],partial="1",counts={'nostruct-3': 2, 'cannotBeQObject': 1, 'q_ptr': 1},timings=[]}\n

Note that, the watch.1 result object now contains a children array which lists its children. In this case, watch.1 has only one child: a that is a QString, and the QString is “5”(utf16 encoded as “3500”).

From the GDB MI command qt creator delivered to GDB, we can see every action in the Expressions window would trigger the refresh of all expressions even we just click one item.

You may also wonder why exp or wname are “67”, “69”? Well, these are the hex representations of ascii code of character “g” and “i”, which are the variable names(expressions) we added to the Expressions window to watch. The naming for local variables and expressions is different. iname(Internal Name) for local variables is local.a, local.b. name is “a”, “b”,…. iname for expressions is watch.0, watch.1, wname(Watch Name) is the same as the exp passed from Qt creator.

 

 

Posted in

Leave a Reply