brintos

brintos / llvm-project-archived public Read only

0
0
Text · 17.3 KiB · 405222e Raw
495 lines · python
1#!/usr/bin/env python32#3# This is a tool that works like debug location coverage calculator.4# It parses the llvm-dwarfdump --statistics output by reporting it5# in a more human readable way.6#7 8from __future__ import print_function9import argparse10import os11import sys12from json import loads13from math import ceil14from collections import OrderedDict15from subprocess import Popen, PIPE16 17# This special value has been used to mark statistics that overflowed.18TAINT_VALUE = "tainted"19 20# Initialize the plot.21def init_plot(plt):22    plt.title("Debug Location Statistics", fontweight="bold")23    plt.xlabel("location buckets")24    plt.ylabel("number of variables in the location buckets")25    plt.xticks(rotation=45, fontsize="x-small")26    plt.yticks()27 28 29# Finalize the plot.30def finish_plot(plt):31    plt.legend()32    plt.grid(color="grey", which="major", axis="y", linestyle="-", linewidth=0.3)33    plt.savefig("locstats.png")34    print('The plot was saved within "locstats.png".')35 36 37# Holds the debug location statistics.38class LocationStats:39    def __init__(40        self,41        file_name,42        variables_total,43        variables_total_locstats,44        variables_with_loc,45        variables_scope_bytes_covered,46        variables_scope_bytes,47        variables_coverage_map,48    ):49        self.file_name = file_name50        self.variables_total = variables_total51        self.variables_total_locstats = variables_total_locstats52        self.variables_with_loc = variables_with_loc53        self.scope_bytes_covered = variables_scope_bytes_covered54        self.scope_bytes = variables_scope_bytes55        self.variables_coverage_map = variables_coverage_map56 57    # Get the PC ranges coverage.58    def get_pc_coverage(self):59        if self.scope_bytes_covered == TAINT_VALUE or self.scope_bytes == TAINT_VALUE:60            return TAINT_VALUE61        pc_ranges_covered = int(62            ceil(self.scope_bytes_covered * 100.0) / self.scope_bytes63        )64        return pc_ranges_covered65 66    # Pretty print the debug location buckets.67    def pretty_print(self):68        if self.scope_bytes == 0:69            print("No scope bytes found.")70            return -171 72        pc_ranges_covered = self.get_pc_coverage()73        variables_coverage_per_map = {}74        for cov_bucket in coverage_buckets():75            variables_coverage_per_map[cov_bucket] = None76            if (77                self.variables_coverage_map[cov_bucket] == TAINT_VALUE78                or self.variables_total_locstats == TAINT_VALUE79            ):80                variables_coverage_per_map[cov_bucket] = TAINT_VALUE81            else:82                variables_coverage_per_map[cov_bucket] = int(83                    ceil(self.variables_coverage_map[cov_bucket] * 100.0)84                    / self.variables_total_locstats85                )86 87        print(" =================================================")88        print("            Debug Location Statistics       ")89        print(" =================================================")90        print("     cov%           samples         percentage(~)  ")91        print(" -------------------------------------------------")92        for cov_bucket in coverage_buckets():93            if (94                self.variables_coverage_map[cov_bucket]95                or self.variables_total_locstats == TAINT_VALUE96            ):97                print(98                    "   {0:10}     {1:8}              {2:3}%".format(99                        cov_bucket,100                        self.variables_coverage_map[cov_bucket],101                        variables_coverage_per_map[cov_bucket],102                    )103                )104            else:105                print(106                    "   {0:10}     {1:8d}              {2:3d}%".format(107                        cov_bucket,108                        self.variables_coverage_map[cov_bucket],109                        variables_coverage_per_map[cov_bucket],110                    )111                )112        print(" =================================================")113        print(114            " -the number of debug variables processed: "115            + str(self.variables_total_locstats)116        )117        print(" -PC ranges covered: " + str(pc_ranges_covered) + "%")118 119        # Only if we are processing all the variables output the total120        # availability.121        if self.variables_total and self.variables_with_loc:122            total_availability = None123            if (124                self.variables_total == TAINT_VALUE125                or self.variables_with_loc == TAINT_VALUE126            ):127                total_availability = TAINT_VALUE128            else:129                total_availability = int(130                    ceil(self.variables_with_loc * 100.0) / self.variables_total131                )132            print(" -------------------------------------------------")133            print(" -total availability: " + str(total_availability) + "%")134        print(" =================================================")135 136        return 0137 138    # Draw a plot representing the location buckets.139    def draw_plot(self):140        from matplotlib import pyplot as plt141 142        buckets = range(len(self.variables_coverage_map))143        plt.figure(figsize=(12, 8))144        init_plot(plt)145        plt.bar(146            buckets,147            self.variables_coverage_map.values(),148            align="center",149            tick_label=self.variables_coverage_map.keys(),150            label="variables of {}".format(self.file_name),151        )152 153        # Place the text box with the coverage info.154        pc_ranges_covered = self.get_pc_coverage()155        props = dict(boxstyle="round", facecolor="wheat", alpha=0.5)156        plt.text(157            0.02,158            0.90,159            "PC ranges covered: {}%".format(pc_ranges_covered),160            transform=plt.gca().transAxes,161            fontsize=12,162            verticalalignment="top",163            bbox=props,164        )165 166        finish_plot(plt)167 168    # Compare the two LocationStats objects and draw a plot showing169    # the difference.170    def draw_location_diff(self, locstats_to_compare):171        from matplotlib import pyplot as plt172 173        pc_ranges_covered = self.get_pc_coverage()174        pc_ranges_covered_to_compare = locstats_to_compare.get_pc_coverage()175 176        buckets = range(len(self.variables_coverage_map))177        buckets_to_compare = range(len(locstats_to_compare.variables_coverage_map))178 179        fig = plt.figure(figsize=(12, 8))180        ax = fig.add_subplot(111)181        init_plot(plt)182 183        comparison_keys = list(coverage_buckets())184        ax.bar(185            buckets,186            self.variables_coverage_map.values(),187            align="edge",188            width=0.4,189            label="variables of {}".format(self.file_name),190        )191        ax.bar(192            buckets_to_compare,193            locstats_to_compare.variables_coverage_map.values(),194            color="r",195            align="edge",196            width=-0.4,197            label="variables of {}".format(locstats_to_compare.file_name),198        )199        ax.set_xticks(range(len(comparison_keys)))200        ax.set_xticklabels(comparison_keys)201 202        props = dict(boxstyle="round", facecolor="wheat", alpha=0.5)203        plt.text(204            0.02,205            0.88,206            "{} PC ranges covered: {}%".format(self.file_name, pc_ranges_covered),207            transform=plt.gca().transAxes,208            fontsize=12,209            verticalalignment="top",210            bbox=props,211        )212        plt.text(213            0.02,214            0.83,215            "{} PC ranges covered: {}%".format(216                locstats_to_compare.file_name, pc_ranges_covered_to_compare217            ),218            transform=plt.gca().transAxes,219            fontsize=12,220            verticalalignment="top",221            bbox=props,222        )223 224        finish_plot(plt)225 226 227# Define the location buckets.228def coverage_buckets():229    yield "0%"230    yield "(0%,10%)"231    for start in range(10, 91, 10):232        yield "[{0}%,{1}%)".format(start, start + 10)233    yield "100%"234 235 236# Parse the JSON representing the debug statistics, and create a237# LocationStats object.238def parse_locstats(opts, binary):239    # These will be different due to different options enabled.240    variables_total = None241    variables_total_locstats = None242    variables_with_loc = None243    variables_scope_bytes_covered = None244    variables_scope_bytes = None245    variables_scope_bytes_entry_values = None246    variables_coverage_map = OrderedDict()247 248    # Get the directory of the LLVM tools.249    llvm_dwarfdump_cmd = os.path.join(os.path.dirname(__file__), "llvm-dwarfdump")250    # The statistics llvm-dwarfdump option.251    llvm_dwarfdump_stats_opt = "--statistics"252 253    # Generate the stats with the llvm-dwarfdump.254    subproc = Popen(255        [llvm_dwarfdump_cmd, llvm_dwarfdump_stats_opt, binary],256        stdin=PIPE,257        stdout=PIPE,258        stderr=PIPE,259        universal_newlines=True,260    )261    cmd_stdout, cmd_stderr = subproc.communicate()262 263    # TODO: Handle errors that are coming from llvm-dwarfdump.264 265    # Get the JSON and parse it.266    json_parsed = None267 268    try:269        json_parsed = loads(cmd_stdout)270    except:271        print("error: No valid llvm-dwarfdump statistics found.")272        sys.exit(1)273 274    # TODO: Parse the statistics Version from JSON.275 276    def init_field(name):277        if json_parsed[name] == "overflowed":278            print('warning: "' + name + '" field overflowed.')279            return TAINT_VALUE280        return json_parsed[name]281 282    if opts.only_variables:283        # Read the JSON only for local variables.284        variables_total_locstats = init_field(285            "#local vars processed by location statistics"286        )287        variables_scope_bytes_covered = init_field(288            "sum_all_local_vars(#bytes in parent scope covered" " by DW_AT_location)"289        )290        variables_scope_bytes = init_field("sum_all_local_vars(#bytes in parent scope)")291        if not opts.ignore_debug_entry_values:292            for cov_bucket in coverage_buckets():293                cov_category = (294                    "#local vars with {} of parent scope covered "295                    "by DW_AT_location".format(cov_bucket)296                )297                variables_coverage_map[cov_bucket] = init_field(cov_category)298        else:299            variables_scope_bytes_entry_values = init_field(300                "sum_all_local_vars(#bytes in parent scope "301                "covered by DW_OP_entry_value)"302            )303            if (304                variables_scope_bytes_covered != TAINT_VALUE305                and variables_scope_bytes_entry_values != TAINT_VALUE306            ):307                variables_scope_bytes_covered = (308                    variables_scope_bytes_covered - variables_scope_bytes_entry_values309                )310            for cov_bucket in coverage_buckets():311                cov_category = (312                    "#local vars - entry values with {} of parent scope "313                    "covered by DW_AT_location".format(cov_bucket)314                )315                variables_coverage_map[cov_bucket] = init_field(cov_category)316    elif opts.only_formal_parameters:317        # Read the JSON only for formal parameters.318        variables_total_locstats = init_field(319            "#params processed by location statistics"320        )321        variables_scope_bytes_covered = init_field(322            "sum_all_params(#bytes in parent scope covered " "by DW_AT_location)"323        )324        variables_scope_bytes = init_field("sum_all_params(#bytes in parent scope)")325        if not opts.ignore_debug_entry_values:326            for cov_bucket in coverage_buckets():327                cov_category = (328                    "#params with {} of parent scope covered "329                    "by DW_AT_location".format(cov_bucket)330                )331                variables_coverage_map[cov_bucket] = init_field(cov_category)332        else:333            variables_scope_bytes_entry_values = init_field(334                "sum_all_params(#bytes in parent scope covered " "by DW_OP_entry_value)"335            )336            if (337                variables_scope_bytes_covered != TAINT_VALUE338                and variables_scope_bytes_entry_values != TAINT_VALUE339            ):340                variables_scope_bytes_covered = (341                    variables_scope_bytes_covered - variables_scope_bytes_entry_values342                )343            for cov_bucket in coverage_buckets():344                cov_category = (345                    "#params - entry values with {} of parent scope covered"346                    " by DW_AT_location".format(cov_bucket)347                )348                variables_coverage_map[cov_bucket] = init_field(cov_category)349    else:350        # Read the JSON for both local variables and formal parameters.351        variables_total = init_field("#source variables")352        variables_with_loc = init_field("#source variables with location")353        variables_total_locstats = init_field(354            "#variables processed by location statistics"355        )356        variables_scope_bytes_covered = init_field(357            "sum_all_variables(#bytes in parent scope covered " "by DW_AT_location)"358        )359        variables_scope_bytes = init_field("sum_all_variables(#bytes in parent scope)")360 361        if not opts.ignore_debug_entry_values:362            for cov_bucket in coverage_buckets():363                cov_category = (364                    "#variables with {} of parent scope covered "365                    "by DW_AT_location".format(cov_bucket)366                )367                variables_coverage_map[cov_bucket] = init_field(cov_category)368        else:369            variables_scope_bytes_entry_values = init_field(370                "sum_all_variables(#bytes in parent scope covered "371                "by DW_OP_entry_value)"372            )373            if (374                variables_scope_bytes_covered != TAINT_VALUE375                and variables_scope_bytes_entry_values != TAINT_VALUE376            ):377                variables_scope_bytes_covered = (378                    variables_scope_bytes_covered - variables_scope_bytes_entry_values379                )380            for cov_bucket in coverage_buckets():381                cov_category = (382                    "#variables - entry values with {} of parent scope covered "383                    "by DW_AT_location".format(cov_bucket)384                )385                variables_coverage_map[cov_bucket] = init_field(cov_category)386 387    return LocationStats(388        binary,389        variables_total,390        variables_total_locstats,391        variables_with_loc,392        variables_scope_bytes_covered,393        variables_scope_bytes,394        variables_coverage_map,395    )396 397 398# Parse the program arguments.399def parse_program_args(parser):400    parser.add_argument(401        "--only-variables",402        action="store_true",403        default=False,404        help="calculate the location statistics only for local variables",405    )406    parser.add_argument(407        "--only-formal-parameters",408        action="store_true",409        default=False,410        help="calculate the location statistics only for formal parameters",411    )412    parser.add_argument(413        "--ignore-debug-entry-values",414        action="store_true",415        default=False,416        help="ignore the location statistics on locations with " "entry values",417    )418    parser.add_argument(419        "--draw-plot",420        action="store_true",421        default=False,422        help="show histogram of location buckets generated (requires " "matplotlib)",423    )424    parser.add_argument(425        "--compare",426        action="store_true",427        default=False,428        help="compare the debug location coverage on two files provided, "429        "and draw a plot showing the difference  (requires "430        "matplotlib)",431    )432    parser.add_argument("file_names", nargs="+", type=str, help="file to process")433 434    return parser.parse_args()435 436 437# Verify that the program inputs meet the requirements.438def verify_program_inputs(opts):439    if len(sys.argv) < 2:440        print("error: Too few arguments.")441        return False442 443    if opts.only_variables and opts.only_formal_parameters:444        print("error: Please use just one --only* option.")445        return False446 447    if not opts.compare and len(opts.file_names) != 1:448        print("error: Please specify only one file to process.")449        return False450 451    if opts.compare and len(opts.file_names) != 2:452        print("error: Please specify two files to process.")453        return False454 455    if opts.draw_plot or opts.compare:456        try:457            import matplotlib458        except ImportError:459            print("error: matplotlib not found.")460            return False461 462    return True463 464 465def Main():466    parser = argparse.ArgumentParser()467    opts = parse_program_args(parser)468 469    if not verify_program_inputs(opts):470        parser.print_help()471        sys.exit(1)472 473    binary_file = opts.file_names[0]474    locstats = parse_locstats(opts, binary_file)475 476    if not opts.compare:477        if opts.draw_plot:478            # Draw a histogram representing the location buckets.479            locstats.draw_plot()480        else:481            # Pretty print collected info on the standard output.482            if locstats.pretty_print() == -1:483                sys.exit(0)484    else:485        binary_file_to_compare = opts.file_names[1]486        locstats_to_compare = parse_locstats(opts, binary_file_to_compare)487        # Draw a plot showing the difference in debug location coverage between488        # two files.489        locstats.draw_location_diff(locstats_to_compare)490 491 492if __name__ == "__main__":493    Main()494    sys.exit(0)495