diff --git a/c2t.py b/c2t.py index b444ee11c..f9e368424 100755 --- a/c2t.py +++ b/c2t.py @@ -5,6 +5,7 @@ stderr ) from os.path import ( + abspath, getmtime, relpath, dirname, @@ -13,6 +14,8 @@ basename ) from os import ( + walk, + remove, makedirs, killpg, setpgrp @@ -68,6 +71,7 @@ pypath ) from debug import ( + create_dwarf_cache, get_elffile_loading, InMemoryELFFile, DWARFInfoCache, @@ -92,6 +96,10 @@ DebugServer, TestBuilder ) +from qemu import ( + qemu_build_dir_by_dwarf, + qemu_target_dir_by_dwarf +) C2T_ERRMSG_FORMAT = "{prog}:\x1b[31m error:\x1b[0m {msg}\n" @@ -767,6 +775,23 @@ def main(): metavar = "N", help = "stop on N-th error, 0 - no stop mode" ) + parser.add_argument("-q", "--qemu", + nargs = "?", + metavar = "executable", + help = "ignore the option in config" + ) + parser.add_argument("-c", "--lcov", + action = "store_true", + help = "estimate TCG frontend coverage by this test session" + ) + parser.add_argument("--coverage-output", + default = "c2t.coverage", + help = "directory for genhtml (lcov) coverage report" + ) + parser.add_argument("-b", "--build", + nargs = "?", + help = "explicitly set QEMU build directory" + ) args = parser.parse_args() @@ -813,6 +838,9 @@ def main(): verify_config_components(config) + if args.qemu: + c2t_cfg.qemu.run.executable = args.qemu + incl, regexp, tests = args.regexps.find_files(C2T_TEST_DIR) if not tests: parser.error("no matches in {dir} with {var} {regexp}".format( @@ -825,6 +853,52 @@ def main(): if jobs < 1: parser.error("wrong number of jobs: %s" % jobs) + lcov = args.lcov + if lcov: + # XXX: Coverage estimation is a monkey style code. But it's better + # than working with lcov manually. + + # Here we looking for TCG frontend build directory because estimation + # of whole Qemu coverage is time consuming and is not a point of c2t. + qemu_exe = c2t_cfg.qemu.run.executable + dic = create_dwarf_cache(qemu_exe) + + build_dir = args.build + if not build_dir: + try: + build_dir = qemu_build_dir_by_dwarf(dic) + except RuntimeError: + print("Use -b option to set QEMU build directory explicitly") + raise + + arch = qemu_exe.split('-')[-1] + if "system" in qemu_exe: + build_dir_suffix = arch + "-softmmu" + else: + build_dir_suffix = arch + "-linux-user" + + target_build_dir = join(build_dir, build_dir_suffix) + + # find TCG frontend build directory + for frontend_build_dir, __, files in walk(target_build_dir): + # TCG frontend build directory normally contains translate.o. + # Name of the directory may vary between qemu version. + if "translate.o" in files: + break + else: + print("c2t cannot find TCG frontend build directory") + return + + # reset previous coverage + lcov = Popen(["lcov", + "--zerocounters", + "--directory", frontend_build_dir + ]) + if lcov.wait(): + # lcov likely print an error message, so we only conclude... + print("c2t stopped because of lcov failure") + return + # creates tests subdirectories if they don't exist for sub_dir in (C2T_TEST_IR_DIR, C2T_TEST_BIN_DIR): if not exists(sub_dir): @@ -833,6 +907,34 @@ def main(): start_cpu_testing(tests, jobs, args.reuse, args.verbose, errors2stop = args.errors ) + + if lcov: + target_dir = qemu_target_dir_by_dwarf(dic.di) + lcov = Popen(["lcov", + "--output-file", "c2t.coverage.info", + "--capture", + "--base-directory", target_dir, + "--no-external", # TCG frontend coverage only + "--directory", frontend_build_dir + ]) + if lcov.wait(): + print("Failed to build coverage report") + return + + genhtml = Popen(["genhtml", + "--output-directory", args.coverage_output, + "c2t.coverage.info" + ]) + if genhtml.wait(): + print("HTML coverage report generation has failed") + else: + # Note, genhtml prints some coverage statistic to stdout + print("HTML coverage report: " + + abspath(join(args.coverage_output, "index.html")) + ) + + remove("c2t.coverage.info") + killpg(0, SIGTERM) diff --git a/c2t/config.py b/c2t/config.py index 02382c9ef..c95e6dc7c 100644 --- a/c2t/config.py +++ b/c2t/config.py @@ -24,10 +24,13 @@ "C2TConfig", "rsp_target qemu gdbserver target_compiler oracle_compiler" ) -Run = namedtuple( - "Run", - "executable args" -) + + +class Run(object): + + def __init__(self, executable, args): + self.executable = executable + self.args = args def get_new_rsp(regs, pc, regsize, little_endian = True): @@ -69,7 +72,8 @@ def __init__(self, run): @property def run_script(self): - return ' '.join(self.run) + run = self.run + return run.executable + " " + run.args class TestBuilder(tuple): @@ -81,4 +85,4 @@ def __new__(cls, *runs): @property def run_script(self): for run in self: - yield ' '.join(run) + yield run.executable + " " + run.args diff --git a/qemu/dwarf_hacks.py b/qemu/dwarf_hacks.py new file mode 100644 index 000000000..6413bbbaf --- /dev/null +++ b/qemu/dwarf_hacks.py @@ -0,0 +1,58 @@ +__all__ = [ + "qemu_build_dir_by_dwarf" + , "qemu_target_dir_by_dwarf" +] + +from os.path import ( + abspath, + dirname +) +from six import ( + PY3 +) +from common import ( + bsep +) + + +def qemu_build_dir_by_dwarf(dia): + # Sometimes trace-root.h is included by its absolute path: + # /path/to/build/directory/./trace-root.h + for cu in dia.di.iter_CUs(): + for f in dia.get_CU_files(cu): + # v--- because of /./ + if len(f) > 2 and f[-1] == b"trace-root.h": + break + else: + continue + break + else: + raise RuntimeError( + "Nothing includes trace-root.h by absolute path" + ) + + f = bsep.join(f) + + if PY3: + # TODO: Is DWARF file name encoding always utf-8? + f = f.decode("utf-8") + + return dirname(abspath(f)) + + +def qemu_target_dir_by_dwarf(dwarf_info): + # There is a convention that TCG frontend (qemu target) has + # translate.c file in its directory. + # Also, there is only one TCG frontend per qemu binary. + for cu in dwarf_info.iter_CUs(): + src_file = cu.get_top_DIE().attributes["DW_AT_name"].value + if src_file.endswith(b"translate.c"): + break + else: + raise RuntimeError("No compile unit for translate.c found") + + if PY3: + # TODO: Is DWARF file name encoding always utf-8? + src_file = src_file.decode("utf-8") + + return dirname(abspath(src_file))