Skip to content

Debugging

xwings edited this page Jul 6, 2025 · 2 revisions

Debugging with Qiling

Qiling Framework provides powerful features for debugging emulated code, allowing you to inspect the machine state, trace execution, and integrate with external debuggers like GDB.

Interactive Debugger (QDB)

Qiling includes its own interactive debugger, QDB, which is similar in concept to GDB. It allows you to pause emulation, inspect state, and control execution flow.

Enabling QDB

To use QDB, you need to enable it in your Qiling script.

from qiling import Qiling
from qiling.debugger import Qdb

if __name__ == "__main__":
    ql = Qiling(['/bin/ls'], 'path/to/rootfs/x8664_linux')

    # Attach the debugger
    debugger = Qdb(ql)

    # Set a breakpoint at the entry point
    entry_point = ql.loader.elf_entry
    debugger.add_bp(entry_point)

    # Run the emulation. It will stop at the breakpoint.
    ql.run()

When the breakpoint is hit, you will be dropped into the QDB interactive shell, where you can use commands like:

  • c: Continue execution.
  • r: Print register values.
  • x/16xb <address>: Examine memory at a given address.
  • s: Step to the next instruction.
  • b <address>: Set a new breakpoint.
  • d <bp_id>: Delete a breakpoint.

GDB Stub

For a more feature-rich debugging experience, Qiling can act as a GDB stub, allowing you to connect a standard GDB client to the emulated process.

How it Works

  1. Start Qiling with the GDB stub enabled. Your Qiling script will listen on a specified TCP port.
  2. Connect your GDB client to that port.
  3. Debug the emulated program using familiar GDB commands.

Enabling the GDB Stub

You can enable the GDB stub with a single line in your Python script.

from qiling import Qiling

if __name__ == "__main__":
    ql = Qiling(['/bin/ls'], 'path/to/rootfs/x8664_linux')

    # Enable the GDB stub on localhost, port 1234
    ql.debugger = "gdb:1234"

    # Run the emulation. It will wait for a GDB client to connect.
    ql.run()

Alternatively, you can use QlTool from the command line:

qltool -f /bin/ls -r path/to/rootfs/x8664_linux -g 1234

Connecting with GDB

From another terminal, launch GDB and connect to the Qiling stub.

# For a more integrated experience, you can point GDB to the executable
# This allows GDB to load symbols and source code.
gdb /bin/ls

(gdb) target remote localhost:1234
(gdb) # You are now connected and can start debugging
(gdb) b main
Breakpoint 1 at 0x4018b0
(gdb) c
Continuing.

Breakpoint 1, 0x00000000004018b0 in main ()
(gdb) info registers
rax            0x4018b0         4199520
rbx            0x0              0
...

Verbose Output and Tracing

For simpler debugging scenarios, you might not need a full debugger. Qiling's verbose logging can provide valuable insights.

Enabling Verbose Modes

You can set the verbosity level during Qiling initialization.

from qiling.const import QL_VERBOSE

# Show basic information like syscalls
ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEFAULT)

# Show detailed instruction-by-instruction tracing
ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG)

# Show syscalls and their parameters
ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DISASM)

This will print detailed information about the emulation process to the console, which can help you quickly identify issues without the overhead of an interactive debugger.

Complete GDB Stub Example

Here is a self-contained script to demonstrate the GDB stub feature. It emulates a simple Linux binary and waits for a GDB client to connect.

Target Binary (hello): A standard "Hello, World!" compiled with gcc -g -o hello hello.c (the -g flag includes debug symbols, which improves the GDB experience).

Qiling Script:

from qiling import Qiling
from qiling.const import QL_VERBOSE

if __name__ == "__main__":
    # Path to the Linux executable and its rootfs
    executable_path = './hello'
    rootfs_path = 'path/to/your/rootfs/x8664_linux'

    ql = Qiling([executable_path], rootfs_path, verbose=QL_VERBOSE.OFF)

    # Enable the GDB stub on localhost, port 1234.
    # You can also specify an address, e.g., "127.0.0.1:1234"
    ql.debugger = "gdb:1234"

    print(f"[*] Starting emulation. Waiting for GDB client to connect on port 1234...")
    print(f"[*] In another terminal, run: gdb {executable_path}")
    print(f"[*] Then, in GDB: target remote :1234")

    # When run(), Qiling will pause and wait for the GDB connection
    # before starting the actual emulation.
    ql.run()

    print("[*] Emulation finished. GDB client disconnected.")

Debugging Workflow

  1. Run the Qiling script. It will print the instructions and then hang, waiting for you.
  2. Open a new terminal.
  3. Launch GDB and point it to your executable so it can load the symbols:
    gdb ./hello
  4. Inside GDB, connect to Qiling:
    (gdb) target remote :1234
    Remote debugging using :1234
    0x00007f73726f6090 in ?? ()
  5. You are now in control. You can set breakpoints, step through code, inspect memory, and use all the familiar GDB commands to debug the program running inside the Qiling emulator.
    (gdb) b main
    Breakpoint 1 at 0x401130: file hello.c, line 5.
    (gdb) c
    Continuing.
    
    Breakpoint 1, main () at hello.c:5
    5           printf("Hello, World!\n");
    (gdb) 
Clone this wiki locally