#!/usr/bin/python3 # Parsing dwarfdump's output to determine whether the location list for the # parameter "b" covers all of the function. The script searches for information # in the input file to determine the [prologue, epilogue) range for the # function, the location list range for "b", and checks that the latter covers # the entirety of the former. import re import sys DebugInfoPattern = r"\.debug_info contents:" DebugLinePattern = r"\.debug_line contents:" ProloguePattern = r"^\s*0x([0-9a-f]+)\s.+prologue_end" EpiloguePattern = r"^\s*0x([0-9a-f]+)\s.+epilogue_begin" FormalPattern = r"^0x[0-9a-f]+:\s+DW_TAG_formal_parameter" LocationPattern = r"DW_AT_location\s+\[DW_FORM_([a-z_]+)\](?:.*0x([a-f0-9]+))" DebugLocPattern = r'\[0x([a-f0-9]+),\s+0x([a-f0-9]+)\) ".text": (.+)$' SeenDebugInfo = False SeenDebugLine = False LocationRanges = None PrologueEnd = None EpilogueBegin = None # The dwarfdump output should contain the DW_AT_location for "b" first, then the # line table which should contain prologue_end and epilogue_begin entries. with open(sys.argv[1], "r") as dwarf_dump_file: dwarf_iter = iter(dwarf_dump_file) for line in dwarf_iter: if not SeenDebugInfo and re.match(DebugInfoPattern, line): SeenDebugInfo = True if not SeenDebugLine and re.match(DebugLinePattern, line): SeenDebugLine = True # Get the range of DW_AT_location for "b". if LocationRanges is None: if match := re.match(FormalPattern, line): # Go until we either find DW_AT_location or reach the end of this entry. location_match = None while location_match is None: if (line := next(dwarf_iter, "")) == "\n": raise RuntimeError( ".debug_info output is missing DW_AT_location for 'b'" ) location_match = re.search(LocationPattern, line) # Variable has whole-scope location, represented by an empty tuple. if location_match.group(1) == "exprloc": LocationRanges = () continue if location_match.group(1) != "sec_offset": raise RuntimeError( f"Unhandled form for DW_AT_location: DW_FORM_{location_match.group(1)}" ) # Variable has location range list. if ( debug_loc_match := re.search(DebugLocPattern, next(dwarf_iter, "")) ) is None: raise RuntimeError(f"Invalid location range list for 'b'") LocationRanges = ( int(debug_loc_match.group(1), 16), int(debug_loc_match.group(2), 16), ) while ( debug_loc_match := re.search(DebugLocPattern, next(dwarf_iter, "")) ) is not None: match_loc_start = int(debug_loc_match.group(1), 16) match_loc_end = int(debug_loc_match.group(2), 16) match_expr = debug_loc_match.group(3) if match_loc_start != LocationRanges[1]: raise RuntimeError( f"Location list for 'b' is discontinuous from [0x{LocationRanges[1]:x}, 0x{match_loc_start:x})" ) if "stack_value" in match_expr: raise RuntimeError( f"Location list for 'b' contains a stack_value expression: {match_expr}" ) LocationRanges = (LocationRanges[0], match_loc_end) # Get the prologue_end address. elif PrologueEnd is None: if match := re.match(ProloguePattern, line): PrologueEnd = int(match.group(1), 16) # Get the epilogue_begin address. elif EpilogueBegin is None: if match := re.match(EpiloguePattern, line): EpilogueBegin = int(match.group(1), 16) break if not SeenDebugInfo: raise RuntimeError(".debug_info section not found.") if not SeenDebugLine: raise RuntimeError(".debug_line section not found.") if LocationRanges is None: raise RuntimeError(".debug_info output is missing parameter 'b'") if PrologueEnd is None: raise RuntimeError(".debug_line output is missing prologue_end") if EpilogueBegin is None: raise RuntimeError(".debug_line output is missing epilogue_begin") if len(LocationRanges) == 2 and ( LocationRanges[0] > PrologueEnd or LocationRanges[1] < EpilogueBegin ): raise RuntimeError( f"""Location list for 'b' does not cover the whole function:") Prologue to Epilogue = [0x{PrologueEnd:x}, 0x{EpilogueBegin:x}) Location range = [0x{LocationRanges[0]:x}, 0x{LocationRanges[1]:x}) """ )