-
Notifications
You must be signed in to change notification settings - Fork 10
Debugger Tutorial
Qore is a multithreaded scripting language designed for logic embedding in the Program class with sandboxing controls. The Qore debugger supports the n:m relationship between threads and Program containers, and allows programmers to control and inspect data in the container by stepping through code, setting breakpoints, running code, handling exceptions, inspecting the call stack, and reading and modifying variables among other things.
The Qore debugger solution has a client-server architecture, which allows the debugging of Qore programs over the network as well as debugging programs run locally.
Because all Qore debugging programs use a consistent command-line client implementation in the DebugCmdLine module, debugging commands are identical whether performing local or remote debugging.
Use the qdbg
program which provides both the debug client and server and takes the script to be debugged as an argument. All following arguments belong to that program. The debug command-line client runs in a separate thread.
The syntax is:
qdbg
[args] qore_script [arg1 arg2 ...]
For example:
qdbg -pno-database test/qore/debug/test-debug-script.q arg1 test2
Remote debugging is supported using the qdbg-server
and qdbq-remote
programs.
-
qdbg-server
provides the debug server -
qdbg-remote
is the command line client to the debug server
Communication is over HTTP using the WebSocket protocol. The WebSocket protocol supports multiple clients connected to the same server, so multiple debug clients can be connected to the same debug server at any one time.
The server (qdbg-server
) requires the listener socket address (ip:port) and URI path plus the Qore source to be run as arguments.
The client (qdbg-remote
) requires the listener socket address (ip:port) and URI path (or optionally a full URL using the ws://
or wss://
scheme) to the debug server as an argument.
The syntax is:
qdbg-server
[args] listener script [arg1 arg2 ...]
qdbg-remote
[args] url
For example:
qdbg-server -l 127.0.0.1:40000 test/qore/debug/test-debug-script.q arg1 test2
qdbg-remote 127.0.0.1:40000
If an SSL-enabled debug server has been implemented in your own program, then the wss://
scheme must be used in the URL as in the following example:
dbg-remote wss://127.0.0.1:40001
The simple shell supports autocomplete via the TAB key, command-line history, help etc.
NOTE: the Qore linenoise module is required for debugger command-line functionality
Abbreviations can be used for any command as long as the abbreviation is unique.
Type help
to get a list of valid commands, session
to get information about the current debugging session.
Start the local debugging session (qdbg
) or connect to the remote server (qdbg-remote
); the following table contains a subset of common commands:
Command | Description |
---|---|
pgm list |
list Program containers |
pgm id
|
set Program context |
pgm add |
set current Program as a debug target |
pgm stop |
tells the debug server to transfer control to the debug client when the current Program executes |
bt |
show thread backtrace for the current Program and thread |
bt all |
show thread backtraces for all Program containers |
frame id
|
set a new stack frame location in the backtrace |
thread list |
show all currently active threads |
thread local |
show local variables in the current stack frame |
thread local var
|
show value of given local variable |
thread local var value
|
set new value for local variable |
go |
release control back to program |
help |
show help text |
To get list of available Program objects use pgm list
command. The result shows pgmId, the script file name, number of threads and info if the Program may be debugged, and if it is currently being debugged. Note that Program objects are by default allowed to be debugged; to disable this (for security reasons for example), use the %no-debugging parse directive (equivalent to PO_NO_DEBUGGING).
qdbg> pgm list
1 : qdbg thr: 2 dbg: 0/0
2 : DebugCmdLine.qm thr: 1 dbg: 0/0
3 : DebugUtil.qm thr: 0 dbg: 0/0
4 : Util.qm thr: 0 dbg: 1/0
5 : DebugProgramControl.qm thr: 2 dbg: 0/0
6*: test-debug-script.q thr: 1 dbg: 1/0
NOTE: As you can see from the above output, each user module has its own Program container, as does the local debugger (
qdbg
) and the program being debugged (test-debug-script.q
).
You can also use pgm search
to search for a particular Program.
For future work with a particular Program object, we need know the pgmId to set up the correct Program context.
qdbg> pgm set 6
remote program context set to "6"
To display current Program context, use
qdbg> pgm
6 :
scriptName : "test-debug-script.q"
scriptPath : "test/qore/debug/test-debug-script.q"
threads : [2]
debugging : True
debugged : True
interrupted : [2]
or
qdbg> pgm get
6
We can inspect parse directives:
qdbg> pgm options
PO_ALLOW_BARE_REFS
PO_ASSUME_LOCAL
PO_NEW_STYLE
NOTE: if PO_NO_DEBUGGING is set, then the debugger cannot attach to the Program container for debugging.
qdbg> pgm set 1
remote program context set to "1"
qdbg> pgm options
PO_NO_TOP_LEVEL_STATEMENTS
PO_ALLOW_BARE_REFS
PO_ASSUME_LOCAL
PO_NEW_STYLE
PO_NO_DEBUGGING
PO_ALLOW_DEBUGGER
We can also get %define values
qdbg> pgm defines
HAVE_ATOMIC_OPERATIONS : True
HAVE_CLOSE_ALL_FD : True
HAVE_DETERMINISTIC_GC : True
HAVE_FILE_LOCKING : True
HAVE_FORK : True
HAVE_GETPPID : True
HAVE_IS_EXECUTABLE : True
...
The Program context also provides info about global variables:
qdbg> pgm global ARGV
list ARGV = [{type: "string", value: "arg1"}, {type: "string", value: "test2"}]
Global variables can be modified as in the following example:
qdbg> pgm global globalString
nothing globalString = null
qdbg> pgm global globalString NEW
ok
qdbg> pgm global globalString
string globalString = "NEW"
parse_to_qore_value() is used to parse non trivial values.
qdbg> pgm global globalAny "(a=(b=(c=x, 2, 3)))"
ok
qdbg> pgm global globalAny
hash globalAny = {a: {b: [{c: "x"}, 2, 3]}}
Currently it is impossible to get/set particular values from hash or list; the entire data structure must be set instead.
The run command starts the Program running again until the next event is triggered, see Runstate.
qdbg> go step
2017-12-05 22:11:54.553140: run/step, pgm: 6, tid: 2
2017-12-05 22:11:54.553834: interrupt/step, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:47, stmtid: 9)
qdbg> go step
2017-12-05 22:11:56.469166: run/step, pgm: 6, tid: 2
2017-12-05 22:11:56.472726: interrupt/funcEnter, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:19, stmtid: 1)
qdbg> go untilreturn
2017-12-05 22:12:18.914171: run/untilreturn, pgm: 6, tid: 2: cmd "run": action null: run "untilreturn"
2017-12-05 22:12:18.914850: interrupt/funcExit, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:19, stmtid: 1)
If the Program is running, then it can be interrupted manually as follows:
qdbg> program stop
ok
qdbg> program stop 2
ok
The user must set the thread to be controlled manually as in the following example:
qdbg> thread set 2
remote thread context set to TID 2
To get the current thread context:
qdbg> thread
TID context: 2
The stack for the current thread context can be inspected as follows:
qdbg> stack
TID 2 call stack:
- 0: TestClass::log() (test/qore/debug/test-debug-script.q:46-46 user)
- 1: main() (test/qore/debug/test-debug-script.q:60-60 user)
- 2: Program::run() (bin/qdbg:126-126 builtin)
- 3: DebugWrapper::background operator() (bin/qdbg:126-126 new-thread)
The debugger can set a particular stack frame and get or set local variable values:
qdbg> frame
1
qdbg> thread local fmt
string fmt = "TestClass::log() %s
"
qdbg> frame 1
frameId set to "1"
qdbg> thread local i
integer i = 0
qdbg> thread local i 99
i: value set to: 99
qdbg> thread local i
integer i = 99
There are special debug variables. The thread debug event
command prints out current event info:
qdbg> thread debug event
type : event
cmd : interrupt
pgmid : 6
tid : 2
stamp : 2017-12-05 21:50:33.377561 Tue +01:00 (CET)
func : funcEnter
runstate : step
frame : 0
statementid : 29
file : "test/qore/debug/test-debug-script.q"
line : 14
The flow variable can change the flow for loops and functions in block/step events; this variable can be examined or set; see Flow control for more information:
qdbg> thread debug flow
none
qdbg> thread debug flow break
qdbg> thread debug flow
break
qdbg> go run
The dismiss variable is available in exception events. If true, then the flow will go on as if the event did not happened:
2017-12-05 22:27:17.300077: interrupt/exception, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:53, stmtid: 16)
qdbg> go step
2017-12-05 22:28:19.637586: run/step, pgm: 6, tid: 2
2017-12-05 22:28:19.640177: run/step, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:55, stmtid: 17)
qdbg> thread local ex
hash ex = {type: {type: "string", value: "System"}, file: {type: "string", value: "test/qore/debug/test-debug-script.q"}, line: {type: "integer", value: 53}, endline: {type: "integer", value: 53}, source: {type: "string", value: ""}, offset: {type: "integer", value: 0}, callstack: {type: "list", value: []}, err: {type: "string", value: "DIVISION-BY-ZERO"}, desc: {type: "string", value: "division by zero found in integer expression"}
Setting dismiss to true skips the exception block.
NOTE: There is a limitation that at this time the exception info is not available
2017-12-05 22:31:14.733864: interrupt/exception, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:53, stmtid: 16)
qdbg> thread debug dismiss true
qdbg> thread debug dismiss
true
qdbg> go step
2017-12-05 22:31:40.521728: run/step, pgm: 6, tid: 2
2017-12-05 22:31:40.522376: interrupt/step, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:57, stmtid: 19)
The thread debug result
command provides get/set access for the value to be returned from the user function or method. It is available in funcEvent only.
2017-12-05 22:24:18.921484: interrupt/funcExit, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:28, stmtid: 20)
qdbg> thread debug result
string result = "F-0"
qdbg> thread debug result F-1
ok
qdbg> thread debug result
string result = "F-1"
Breakpoints can be used to run a Program and until the Program flow reaches a breakpoint. In this case the Program is interrupted; an event is triggered, and control passes to the debugger.
The breakpoint itself consists of a list of statements. The statement is the location in the Qore source code, i.e. the reference is the file name and the source line number. The file name can be omitted when there is only one file.
Breakpoints also have properties to control their behavior as follows:
- id: unique generated identifier
- enable: enable or disable the breakpoint
-
policy: for multithreaded Program objects: the policy is applied with the thread list to select which threads the breakpoint belongs to:
- none: does not provide any thread selection, i.e. the breakpoint is assigned to all threads
- accept: the threads enumerated in the list are assigned to the breakpoint
- reject: the threads not enumerated in the list are assigned to the breakpoint
- thread: list of threads
To create a breakpoint for line 35 and run the Program:
qdbg> break create 35
ok
qdbg> break list
id: 1 pgmId: 6 enabled: true policy: none threads: null
@ 28 test/qore/debug/test-debug-script.q:35-35
qdbg> go run
2017-12-05 22:58:11.538881: run/run, pgm: 6, tid: 2
2017-12-05 22:58:11.542634: interrupt/step, pgm: 6, tid: 2, (test/qore/debug/test-debug-script.q:35, stmtid: 28)
To assign breakpoint for specific function or method add parenthesis with argument types to specify potentially overload function or method:
qdbg> statement assign 1 main()
ok
qdbg> break list
id: 1 pgmId: 6 enabled: true policy: none threads: null
@ 28 test/qore/debug/test-debug-script.q:35-35
@ 3 test/qore/debug/test-debug-script.q:39-57
qdbg> statement assign 1 main(string,int)
cmd "program/6/breakpoint/1/statement/assign" server-side error: RUNTIME-OVERLOAD-ERROR: no variant matching 'main(string, int)' can be found; the following variants are defined:
main()
qdbg> statement assign 1 TestClass::log()
cmd "program/6/breakpoint/1/statement/assign" server-side error: RUNTIME-OVERLOAD-ERROR: no variant matching 'TestClass::log())' can be found; the following variants are defined:
TestClass::log(string fmt)
qdbg> statement assign 1 TestClass::log(string)
ok
qdbg> break list
id: 1 pgmId: 6 enabled: true policy: none threads: null
@ 28 test/qore/debug/test-debug-script.q:35-35
@ 3 test/qore/debug/test-debug-script.q:39-57
@ 29 test/qore/debug/test-debug-script.q:14-14
To activate the breakpoint only for thread 2:
qdbg> break policy 1 accept
ok
qdbg> break thread 1 2
ok
qdbg> break list
id: 1 pgmId: 6 enabled: true policy: accept threads: [2]
@ 28 test/qore/debug/test-debug-script.q:35-35
@ 3 test/qore/debug/test-debug-script.q:39-57
@ 29 test/qore/debug/test-debug-script.q:14-14
To disable the breakpoint:
qdbg> break disable 1
ok
qdbg> break list
id: 1 pgmId: 6 enabled: false policy: accept threads: [2]
@ 28 test/qore/debug/test-debug-script.q:35-35
@ 3 test/qore/debug/test-debug-script.q:39-57
@ 29 test/qore/debug/test-debug-script.q:14-14
Create breakpoint for another module, i.e. program. The program must be allowed for debugging.
qdbg> pgm 4
remote program context set to "4"
qdbg> break create Util::compare_version(string,string)
ok
qdbg> break list
id: 2 pgmId: 4 enabled: true policy: none threads: null
@ 209 /usr/local/share/qore-modules/0.8.13/Util.qm:165-197
To list all breakpoints:
qdbg> break list all
id: 1 pgmId: 6 enabled: false policy: accept threads: [2]
@ 28 test/qore/debug/test-debug-script.q:35-35
@ 3 test/qore/debug/test-debug-script.q:39-57
@ 29 test/qore/debug/test-debug-script.q:14-14
id: 2 pgmId: 4 enabled: true policy: none threads: null
@ 209 /usr/local/share/qore-modules/0.8.13/Util.qm:165-197
To remove a statement from the breakpoint (switching program context is not necessary as the breakpoint is assigned to program):
qdbg> break list all
id: 1 pgmId: 6 enabled: false policy: accept threads: [2]
@ 3 test/qore/debug/test-debug-script.q:39-57
@ 29 test/qore/debug/test-debug-script.q:14-14
id: 2 pgmId: 4 enabled: true policy: none threads: null
@ 209 /usr/local/share/qore-modules/0.8.13/Util.qm:165-197
The help
command also supports code completion and can provide additional information such as command aliases.
qdbg> help
Command root list: break, bt (backtrace, stack), cmd, frame, go, help, history, pgm (program), quit, session, stmt (statement), thread, version
qdbg> help break
break: breakpoint related commands
Subcommand list: create, delete, disable, enable, list, policy, statement, thread
To test a connection with a server use the version command. If the connection has been broken, then there is no host in the output.
qdbg> version
client :
class : DebugCommandLineRemote
host :
class : WebSocketDebugProgramControlHost
The session
command provides an overview about the current session and also offers the possibility of saving the session to an external file and restoring it later.
NOTE: The session data saved includes the pgmId, breakpointId, filename, line number etc, which are only valid for a single process; if the session is restored in a new process, then these values will no longer be valid.
qdbg> session
+ "test-debug-script.q" (program 6, 1 blocked 1 thread, 1 breakpoint (enabled: 0))
- blocked 1 TID: 2
The **cmd**
command sends a raw debug API command to the server. The command argument has the form of a path with parameters included.
qdbg> cmd version
Result: {class: "DebugProgramControlLocal"}
qdbg> cmd program//list
Result: {1: {scriptName: "qdbg", scriptPath: "bin/qdbg", threads: [1, 2], debugging: False}, 2: {scriptName: "DebugCmdLine.qm", scriptPath: "/home/tma/work/qore/qore/qlib/DebugCmdLine.qm", threads: [1], debugging: False}, 3: {scriptName: "DebugUtil.qm", scriptPath: "/home/tma/work/qore/qore/qlib/DebugUtil.qm", threads: [], debugging: False}, 4: {scriptName: "Util.qm", scriptPath: "/usr/local/share/qore-modules/0.8.13/Util.qm", threads: [], debugging: True}, 5: {scriptName: "DebugProgramControl.qm", scriptPath: "/home/tma/work/qore/qore/qlib/DebugProgramControl.qm", threads: [1, 2], debugging: False}, 7: {scriptName: "test-debug-script.q", scriptPath: "test/qore/debug/test-debug-script.q", threads: [2], debugging: True, debugged: True, interrupted: [2]}}
Thread-related commands require parameters not passed via command path.
qdbg> cmd thread/stack
Result: {err: "DEBUG-HANDLER-ERROR", desc: "cannot process command \"thread/stack\""}
qdbg> cmd thread/stack "(tid=2)"
Result: [{function: "interrupt: attach", line: 128, endline: 128, file: "bin/qdbg", source: null, offset: 0, typecode: 0, type: "user", current: True}, {function: "Program::run", line: 128, endline: 128, file: "bin/qdbg", source: null, offset: 0, typecode: 1, type: "builtin"}, {function: "DebugWrapper::background operator", line: 128, endline: 128, file: "bin/qdbg", source: null, offset: 0, typecode: 2, type: "new-thread"}]
The situation is similar when setting a variable value using raw API commands:
qdbg> pgm
7 :
scriptName : "test-debug-script.q"
scriptPath : "test/qore/debug/test-debug-script.q"
threads : [2]
debugging : True
debugged : True
interrupted : [2]
qdbg> cmd program/7/global/globalAny/set "(value=1)"
Result: "ok"
qdbg> cmd program/7/global/globalAny/get
Result: {type: "integer", value: 1}
qdbg> pgm global globalAny
integer globalAny = 1
The server intercepts events at the Qore statement level and passes these events to the debug client. This will happen if a Program object has been explicitly added to list of Program objects being debugged.
When Program objects have been added as debugging targets and have been set to change control to the debugger, then, when they are executed, the client is notified before the next Qore statement is executed in any thread. The Program is interrupted; the client is notified with an attach event, and the server waits for a response that determines the Runstate which controls how the server will continue further processing.
- onAttach: executed once for every thread on the first Qore statement after the Program is registered for debugging
- onBlock: executed when a Qore block is entered. An onStep event is executed for each block statement
- onStep: executed before a statement is executed
- onFunctionEnter: executed when a user function, method, or closure is entered
- onFunctionExit: executed when a user function, method, or closure is exited. If the function returns a value, then the client may inspect/change the result value
- onException: executed when an exception is raised. ExceptionInfo is available and the client may dismiss the exception, i.e. the Program will continue as if the exception had not been raised.
- onDetach: executed when the server is detached from the Program
- detach: debugger detaches from the Program, i.e. the Program is left running and is no longer debugged
- run: the Program is left running but can be interrupted manually, at a breakpoint or when an exception is raised
- step: execute the next Qore statement and break again
- stepover: execute the next Qore statement and break again but skip any procedure calls
- untilreturn: execute until the Program flow leaves the Qore procedure
When debugging in a loop, changing the control flow from the debugger affects how the loop is executed (ex: can be used to break a never-ending loop).