Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qiling.emu_stop() does not stop the emulator when gdb debugger is attached #1310

Closed
bstee615 opened this issue Feb 6, 2023 · 6 comments
Closed
Assignees

Comments

@bstee615
Copy link

bstee615 commented Feb 6, 2023

Describe the bug
Thank you for your work on this useful framework.

When executing this sample code, Qiling waits for gdb to connect. I attached gdb using target remote 127.0.0.1:9999, then used the stepi command repeatedly until the executable reached the entry of foo. When I use stepi on the entry to foo, then my script's function print_asm calls ql.emu_stop(), which should stop the emulator, but the emulator does not stop - it remains on the same instruction. Because of this, if instead I use the step command to step to the next statement, then gdb hang in an infinite loop.

Sample Code
Qiling script:

from qiling import *
from qiling.const import QL_VERBOSE

def test_gdb(path, rootfs):
    ql = Qiling(path, rootfs, verbose=QL_VERBOSE.OFF)

    # Enable debugger to listen at localhost address, default port 9999
    ql.debugger = True

    ba = ql.loader.images[0].base

    foo_addr = int("0000000000001135", 16)  # start address of foo
    foo_len = int("0000000000000024", 16)  # length of foo

    def print_asm(ql, address, size):
        print("INSTRUCTION:", hex(address), size)
        if address == ba + foo_addr:
            print("REACHED TARGET INSTRUCTION", hex(ba+foo_addr), "END EMU")
            ql.emu_stop()

    ql.hook_code(print_asm, begin=ba + foo_addr, end=ba + foo_addr + foo_len)
    ql.run()

if __name__ == "__main__":
    test_gdb(["./test"], "/")

Log of qiling script output: test_gdb.log

Expected behavior
When the ql.emu_stop() line is executed, emulation will quit and gdb will detach.

Additional context
I ran this test case on the official Docker container qilingframework/qiling:latest with the latest code from the dev branch.

It should be possible to reproduce the bug with any executable by calling Qiling.emu_stop() during execution.
Here is the target program I used:

#include <stdio.h>

void foo(int a) {
    printf("Hello, world %d!\n", a);
}

int main(int argc, char **argv)
{
    int a = 10;
    foo(a);
    return a;
}

Compiled executable (unzip it first): test.zip

@elicn elicn self-assigned this Feb 7, 2023
@elicn
Copy link
Member

elicn commented Feb 8, 2023

Thanks for filing this.

I am having trouble reproducing the problematic behavior; it seems to work as expected (unless I am missing sometrhing here). I took the liberty to simplify your Qiling script a bit and comiled the C code you provided. This is that I got:

#!/usr/bin/env python3

from qiling import Qiling
from qiling.const import QL_VERBOSE

ROOTFS = r'/home/elicn/Downloads/qiling/examples/rootfs/x8664_linux'
ARGV = [fr'{ROOTFS}/bin/gdb-stop']


if __name__ == "__main__":
    ql = Qiling(ARGV, ROOTFS, verbose=QL_VERBOSE.DEFAULT)

    ql.debugger = True

    ba = ql.loader.images[0].base
    foo_begins = ba + 0x1149
    foo_ends = ba + 0x1170

    def print_asm(ql: Qiling, address: int, size: int):
        print(f"INSTRUCTION: {address:08x}")

        if address == foo_ends:
            print("--- TARGET INSTRUCTION REACHED ---")
            ql.emu_stop()

    ql.hook_code(print_asm, begin=ba + 0x1000, end=ba + 0x2000)
    ql.run()

Basically, all instructions between offset 0x1000 and 0x2000 should be printed, where the last instruction to be printed is the last instruction of foo (offset 0x1170). If emulation continues beyond that, we will see instructions at 0x1195 and beyond printed out:
image

Testing it shows that it works fine (note the red dots):
image

Is there something I am missing?

@bstee615
Copy link
Author

bstee615 commented Feb 8, 2023

Thank you for your response. Sorry I didn't include sufficient instructions to reproduce. I recorded a video of how to reproduce the bug here: https://user-images.githubusercontent.com/22305849/217649795-98f72b3b-9dfc-4c38-9049-4afe84de8371.mp4

When I reach the target instruction using stepi or step, the problem is reproduced (execution does not terminate). When I use continue, the execution terminates as expected. Here's the script I ran, which is the same as yours, except I changed the rootfs and the target instruction address for the executable compiled on my platform:

#!/usr/bin/env python3

from qiling import Qiling
from qiling.const import QL_VERBOSE

ROOTFS = r'/'
ARGV = [fr'./test']


if __name__ == "__main__":
    ql = Qiling(ARGV, ROOTFS, verbose=QL_VERBOSE.DEFAULT)

    ql.debugger = True

    ba = ql.loader.images[0].base
    print("BASE ADDRESS:", hex(ba))
    foo_begins = ba + 0x1135
    foo_ends = ba + 0x1158

    def print_asm(ql: Qiling, address: int, size: int):
        print(f"INSTRUCTION: {address:08x}")

        if address == foo_ends:
            print("--- TARGET INSTRUCTION REACHED ---")
            ql.emu_stop()

    ql.hook_code(print_asm, begin=ba + 0x1000, end=ba + 0x2000)
    ql.run()

Log of inferior Python script:

root@d493bf7c8202:~# python test_github_issue.py 
INSTRUCTION: 555555555135
INSTRUCTION: 555555555136
INSTRUCTION: 555555555139
INSTRUCTION: 55555555513d
INSTRUCTION: 555555555140
[=]     gdb> breakpoint hit, stopped at 0x555555555140
[=]     gdb> breakpoint removed from 0x555555555140
[=]     gdb> breakpoint removed from 0x7ffff7de5590
[=]     gdb> breakpoint added at 0x7fffb7e12950
[=]     gdb> breakpoint added at 0x7ffff7de5590
[=]     gdb> stepping 1 instructions from 0x555555555140
INSTRUCTION: 555555555140
[=]     gdb> breakpoint added at 0x555555555140
[=]     gdb> stepping 1 instructions from 0x555555555143
INSTRUCTION: 555555555143
[=]     gdb> stepping 1 instructions from 0x555555555145
INSTRUCTION: 555555555145
[=]     gdb> stepping 1 instructions from 0x55555555514c
INSTRUCTION: 55555555514c
[=]     gdb> stepping 1 instructions from 0x555555555151
INSTRUCTION: 555555555151
[=]     gdb> breakpoint added at 0x555555555156
[=]     gdb> resuming from 0x555555555030
INSTRUCTION: 555555555030
INSTRUCTION: 555555555036
INSTRUCTION: 55555555503b
INSTRUCTION: 555555555020
INSTRUCTION: 555555555026
[=]     fstat(fd = 0x1, buf_ptr = 0x80000000d5d0) = 0x0
[=]     brk(inp = 0x0) = 0x55555555b000
[=]     brk(inp = 0x55555557c000) = 0x55555557c000
Hello, world 10!
[=]     write(fd = 0x1, buf = 0x55555555b2a0, count = 0x11) = 0x11
INSTRUCTION: 555555555156
[=]     gdb> breakpoint hit, stopped at 0x555555555156
[=]     gdb> breakpoint removed from 0x555555555156
[=]     gdb> breakpoint removed from 0x7fffb7e12950
[=]     gdb> breakpoint removed from 0x555555555140
[=]     gdb> breakpoint removed from 0x7ffff7de5590
[=]     gdb> breakpoint added at 0x555555555140
[=]     gdb> breakpoint added at 0x7ffff7de5590
[=]     gdb> stepping 1 instructions from 0x555555555156
INSTRUCTION: 555555555156
INSTRUCTION: 555555555157
INSTRUCTION: 555555555158
--- TARGET INSTRUCTION REACHED ---                            <-- using stepi, execution loops
[=]     gdb> breakpoint removed from 0x555555555140
[=]     gdb> breakpoint removed from 0x7ffff7de5590
[=]     gdb> breakpoint added at 0x555555555140
[=]     gdb> breakpoint added at 0x7ffff7de5590
[=]     gdb> stepping 1 instructions from 0x555555555158
INSTRUCTION: 555555555158
--- TARGET INSTRUCTION REACHED ---                            <-- using stepi, execution loops
[=]     gdb> breakpoint removed from 0x555555555140
[=]     gdb> breakpoint removed from 0x7ffff7de5590
[=]     gdb> breakpoint added at 0x555555555140
[=]     gdb> breakpoint added at 0x7ffff7de5590
[=]     gdb> stepping 1 instructions from 0x555555555158
INSTRUCTION: 555555555158
--- TARGET INSTRUCTION REACHED ---                            <-- using stepi, execution loops
[=]     gdb> breakpoint removed from 0x555555555140
[=]     gdb> breakpoint removed from 0x7ffff7de5590
[=]     gdb> breakpoint added at 0x555555555140
[=]     gdb> breakpoint added at 0x7ffff7de5590
[=]     gdb> resuming from 0x555555555158
INSTRUCTION: 555555555158
--- TARGET INSTRUCTION REACHED ---                            <-- using continue, execution stops as expected.

Log of gdb session:

root@d493bf7c8202:~# gdb -q
(gdb) target remote localhost:9999
(gdb) b foo
Breakpoint 1 at 0x555555555140: file ./test.c, line 4.
(gdb) c
Continuing.

Breakpoint 1, foo (a=10) at ./test.c:4
4           printf("Hello, world %d!\n", a);
(gdb) next
5      }
(gdb) stepi
0x0000555555555158      5       }
(gdb) stepi
0x0000555555555158      5       }
(gdb) stepi
0x0000555555555158      5       }
(gdb) continue
Continuing.
[Inferior 1 (process 42000) exited normally]

@elicn
Copy link
Member

elicn commented Feb 9, 2023

OK, I understand this now and found the reason for this behavior.
Allow me some time to determine the best way to fixi this.

@bstee615
Copy link
Author

Awesome! Thank you for fixing it.

@elicn
Copy link
Member

elicn commented Apr 14, 2023

Hey @bstee615,
The fix has been merged a few weeks ago, and we would appreciate your confirmation on the fix.

@bstee615
Copy link
Author

Wow, this is awesome! Thank you very much.
I will try to reproduce it soon.

@elicn elicn closed this as completed May 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants