Skip to content

Commit

Permalink
gdbstub: implement a softmmu based test
Browse files Browse the repository at this point in the history
This adds a new tests that allows us to test softmmu only features
including watchpoints. To do achieve this we need to:

  - add _exit: labels to the boot codes
  - write a memory.py test case
  - plumb the test case into the build system
  - tweak the run_test script to:
    - re-direct output when asked
    - use socket based connection for all tests
    - add a small pause before connection

Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Message-Id: <20210108224256.2321-6-alex.bennee@linaro.org>
  • Loading branch information
stsquad committed Jan 18, 2021
1 parent 9559150 commit c00506a
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 12 deletions.
36 changes: 27 additions & 9 deletions tests/guest-debug/run-test.py
Expand Up @@ -16,6 +16,7 @@
import shutil
import shlex
import os
from time import sleep
from tempfile import TemporaryDirectory

def get_args():
Expand All @@ -27,10 +28,21 @@ def get_args():
required=True)
parser.add_argument("--test", help="GDB test script",
required=True)
parser.add_argument("--gdb", help="The gdb binary to use", default=None)
parser.add_argument("--gdb", help="The gdb binary to use",
default=None)
parser.add_argument("--output", help="A file to redirect output to")

return parser.parse_args()


def log(output, msg):
if output:
output.write(msg + "\n")
output.flush()
else:
print(msg)


if __name__ == '__main__':
args = get_args()

Expand All @@ -42,18 +54,25 @@ def get_args():
if not args.gdb:
print("We need gdb to run the test")
exit(-1)
if args.output:
output = open(args.output, "w")
else:
output = None

socket_dir = TemporaryDirectory("qemu-gdbstub")
socket_name = os.path.join(socket_dir.name, "gdbstub.socket")

# Launch QEMU with binary
if "system" in args.qemu:
cmd = "%s %s %s -s -S" % (args.qemu, args.qargs, args.binary)
cmd = "%s %s %s -gdb unix:path=%s,server" % (args.qemu,
args.qargs,
args.binary,
socket_name)
else:
cmd = "%s %s -g %s %s" % (args.qemu, args.qargs, socket_name,
args.binary)

print("QEMU CMD: %s" % (cmd))
log(output, "QEMU CMD: %s" % (cmd))
inferior = subprocess.Popen(shlex.split(cmd))

# Now launch gdb with our test and collect the result
Expand All @@ -63,16 +82,15 @@ def get_args():
# disable prompts in case of crash
gdb_cmd += " -ex 'set confirm off'"
# connect to remote
if "system" in args.qemu:
gdb_cmd += " -ex 'target remote localhost:1234'"
else:
gdb_cmd += " -ex 'target remote %s'" % (socket_name)
gdb_cmd += " -ex 'target remote %s'" % (socket_name)
# finally the test script itself
gdb_cmd += " -x %s" % (args.test)

print("GDB CMD: %s" % (gdb_cmd))

result = subprocess.call(gdb_cmd, shell=True);
sleep(1)
log(output, "GDB CMD: %s" % (gdb_cmd))

result = subprocess.call(gdb_cmd, shell=True, stdout=output)

# A negative result is the result of an internal gdb failure like
# a crash. We force a return of 0 so we don't fail the test on
Expand Down
1 change: 1 addition & 0 deletions tests/tcg/aarch64/Makefile.softmmu-target
Expand Up @@ -15,6 +15,7 @@ CRT_PATH=$(AARCH64_SYSTEM_SRC)
LINK_SCRIPT=$(AARCH64_SYSTEM_SRC)/kernel.ld
LDFLAGS=-Wl,-T$(LINK_SCRIPT)
TESTS+=$(AARCH64_TESTS) $(MULTIARCH_TESTS)
EXTRA_RUNS+=$(MULTIARCH_RUNS)
CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC)
LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc

Expand Down
1 change: 1 addition & 0 deletions tests/tcg/aarch64/system/boot.S
Expand Up @@ -197,6 +197,7 @@ __start:
bl main

/* pass return value to sys exit */
_exit:
mov x1, x0
ldr x0, =0x20026 /* ADP_Stopped_ApplicationExit */
stp x0, x1, [sp, #-16]!
Expand Down
1 change: 1 addition & 0 deletions tests/tcg/i386/Makefile.softmmu-target
Expand Up @@ -19,6 +19,7 @@ CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC)
LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc

TESTS+=$(MULTIARCH_TESTS)
EXTRA_RUNS+=$(MULTIARCH_RUNS)

# building head blobs
.PRECIOUS: $(CRT_OBJS)
Expand Down
2 changes: 1 addition & 1 deletion tests/tcg/i386/system/boot.S
Expand Up @@ -76,7 +76,7 @@ _start:
*/
call main

/* output any non-zero result in eax to isa-debug-exit device */
_exit: /* output any non-zero result in eax to isa-debug-exit device */
test %al, %al
jz 1f
out %ax, $0xf4
Expand Down
130 changes: 130 additions & 0 deletions tests/tcg/multiarch/gdbstub/memory.py
@@ -0,0 +1,130 @@
from __future__ import print_function
#
# Test some of the softmmu debug features with the multiarch memory
# test. It is a port of the original vmlinux focused test case but
# using the "memory" test instead.
#
# This is launched via tests/guest-debug/run-test.py
#

import gdb
import sys

failcount = 0


def report(cond, msg):
"Report success/fail of test"
if cond:
print("PASS: %s" % (msg))
else:
print("FAIL: %s" % (msg))
global failcount
failcount += 1


def check_step():
"Step an instruction, check it moved."
start_pc = gdb.parse_and_eval('$pc')
gdb.execute("si")
end_pc = gdb.parse_and_eval('$pc')

return not (start_pc == end_pc)


#
# Currently it's hard to create a hbreak with the pure python API and
# manually matching PC to symbol address is a bit flaky thanks to
# function prologues. However internally QEMU's gdbstub treats them
# the same as normal breakpoints so it will do for now.
#
def check_break(sym_name):
"Setup breakpoint, continue and check we stopped."
sym, ok = gdb.lookup_symbol(sym_name)
bp = gdb.Breakpoint(sym_name, gdb.BP_BREAKPOINT)

gdb.execute("c")

# hopefully we came back
end_pc = gdb.parse_and_eval('$pc')
report(bp.hit_count == 1,
"break @ %s (%s %d hits)" % (end_pc, sym.value(), bp.hit_count))

bp.delete()


def do_one_watch(sym, wtype, text):

wp = gdb.Breakpoint(sym, gdb.BP_WATCHPOINT, wtype)
gdb.execute("c")
report_str = "%s for %s" % (text, sym)

if wp.hit_count > 0:
report(True, report_str)
wp.delete()
else:
report(False, report_str)


def check_watches(sym_name):
"Watch a symbol for any access."

# Should hit for any read
do_one_watch(sym_name, gdb.WP_ACCESS, "awatch")

# Again should hit for reads
do_one_watch(sym_name, gdb.WP_READ, "rwatch")

# Finally when it is written
do_one_watch(sym_name, gdb.WP_WRITE, "watch")


def run_test():
"Run through the tests one by one"

print("Checking we can step the first few instructions")
step_ok = 0
for i in range(3):
if check_step():
step_ok += 1

report(step_ok == 3, "single step in boot code")

# If we get here we have missed some of the other breakpoints.
print("Setup catch-all for _exit")
cbp = gdb.Breakpoint("_exit", gdb.BP_BREAKPOINT)

check_break("main")
check_watches("test_data[128]")

report(cbp.hit_count == 0, "didn't reach backstop")

#
# This runs as the script it sourced (via -x, via run-test.py)
#
try:
inferior = gdb.selected_inferior()
arch = inferior.architecture()
print("ATTACHED: %s" % arch.name())
except (gdb.error, AttributeError):
print("SKIPPING (not connected)", file=sys.stderr)
exit(0)

if gdb.parse_and_eval('$pc') == 0:
print("SKIP: PC not set")
exit(0)

try:
# These are not very useful in scripts
gdb.execute("set pagination off")

# Run the actual tests
run_test()
except (gdb.error):
print("GDB Exception: %s" % (sys.exc_info()[0]))
failcount += 1
pass

# Finally kill the inferior and exit gdb with a count of failures
gdb.execute("kill")
exit(failcount)
19 changes: 18 additions & 1 deletion tests/tcg/multiarch/system/Makefile.softmmu-target
Expand Up @@ -7,8 +7,25 @@
# complications of building.
#

MULTIARCH_SYSTEM_SRC=$(SRC_PATH)/tests/tcg/multiarch/system
MULTIARCH_SRC=$(SRC_PATH)/tests/tcg/multiarch
MULTIARCH_SYSTEM_SRC=$(MULTIARCH_SRC)/system
VPATH+=$(MULTIARCH_SYSTEM_SRC)

MULTIARCH_TEST_SRCS=$(wildcard $(MULTIARCH_SYSTEM_SRC)/*.c)
MULTIARCH_TESTS = $(patsubst $(MULTIARCH_SYSTEM_SRC)/%.c, %, $(MULTIARCH_TEST_SRCS))

ifneq ($(HAVE_GDB_BIN),)
GDB_SCRIPT=$(SRC_PATH)/tests/guest-debug/run-test.py

run-gdbstub-memory: memory
$(call run-test, $@, $(GDB_SCRIPT) \
--gdb $(HAVE_GDB_BIN) \
--qemu $(QEMU) \
--output $<.gdb.out \
--qargs \
"-monitor none -display none -chardev file$(COMMA)path=$<.out$(COMMA)id=output $(QEMU_OPTS)" \
--bin $< --test $(MULTIARCH_SRC)/gdbstub/memory.py, \
"softmmu gdbstub support")

MULTIARCH_RUNS += run-gdbstub-memory
endif
1 change: 1 addition & 0 deletions tests/tcg/x86_64/Makefile.softmmu-target
Expand Up @@ -19,6 +19,7 @@ CFLAGS+=-nostdlib -ggdb -O0 $(MINILIB_INC)
LDFLAGS+=-static -nostdlib $(CRT_OBJS) $(MINILIB_OBJS) -lgcc

TESTS+=$(MULTIARCH_TESTS)
EXTRA_RUNS+=$(MULTIARCH_RUNS)

# building head blobs
.PRECIOUS: $(CRT_OBJS)
Expand Down
2 changes: 1 addition & 1 deletion tests/tcg/x86_64/system/boot.S
Expand Up @@ -124,7 +124,7 @@ _start:
/* don't worry about stack frame, assume everthing is garbage when we return */
call main

/* output any non-zero result in eax to isa-debug-exit device */
_exit: /* output any non-zero result in eax to isa-debug-exit device */
test %al, %al
jz 1f
out %ax, $0xf4
Expand Down

0 comments on commit c00506a

Please sign in to comment.