#!/usr/bin/env python3 # # Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.
"""
Checks dwarf CFI (unwinding) information by comparing it to disassembly.
It is only a simple heuristic check of stack pointer adjustments.
Fully inferring CFI from disassembly isnot possible in general. """
import os, re, subprocess, collections, pathlib, bisect, collections, sys from argparse import ArgumentParser from dataclasses import dataclass from functools import cache from pathlib import Path from typing import Any, List, Optional, Set, Tuple, Dict
m = re.search("^architecture: *(.*)$", proc.stdout, re.MULTILINE) assert m, "Can not find ABI of ELF file " + str(lib) assert m.group(1) in ARCHES, "Unknown arch: " + m.group(1) return m.group(1)
results = [] for section in section_re.split(proc.stdout):
files = {m[1]: m[2] for m in filename_re.finditer(section)} ifnot any(f.endswith(".S") for f in files.values()): continue
lines = line_re.findall(section)
results.extend([Source(int(a, 16), files[fn], l, fg) for a, l, fn, fg in lines]) return sorted(filter(lambda line: "end_sequence"notin line.flag, results))
data = re.sub(r"[ \t]+", " ", asm.data)
inst_re = re.compile(r"([0-9a-f]+): +(?:[0-9a-f]{2} +)*(.*)") return [Inst(int(pc, 16), inst) for pc, inst in inst_re.findall(data)]
# PC -> CFA offset (stack size at given PC; None if it not just trivial SP+<integer>)
CfaOffsets = Dict[int, Optional[int]]
def get_dwarf_cfa_offsets(insts: List[Inst], fde: Fde) -> CfaOffsets: """ Get CFA offsets for all instructions from DWARF """
# Parse the CFA offset definitions from the FDE.
sp = SP[arch]
m = re.compile(r"(0x[0-9a-f]+): CFA=(\w*)([^:\n]*)").findall(fde.data)
cfa = collections.deque([(int(a, 0), int(o or"0") if r == sp elseNone) for a, r, o in m]) if all(offset isNonefor add, offset in cfa): # This would create result that never checks anything. raise Error(insts[0].pc, "No trivial CFA offsets. Add function to IGNORE list?")
# Create map from instruction PCs to corresponding CFA offsets.
offset: Optional[int] = INITIAL_OFFSET.get(arch, 0)
result: CfaOffsets = {} for pc, inst in insts: while cfa and cfa[0][0] <= pc:
offset = cfa.popleft()[1]
result[pc] = offset return result
def get_inferred_cfa_offsets(insts: List[Inst]) -> CfaOffsets: """ Get CFA offsets for all instructions from static analysis """
rexprs = get_inst_semantics(arch)
offset: Optional[int] = INITIAL_OFFSET.get(arch, 0)
result: CfaOffsets = {} for pc, inst in insts: # Set current offset for PC, unless branch already set it.
offset = result.setdefault(pc, offset)
# Adjust PC and offset based on the current instruction. for rexpr, adjust_offset, adjust_pc in rexprs:
m = rexpr.fullmatch(inst) if m:
new_offset = offset + adjust_offset(m) if adjust_pc:
new_pc = adjust_pc(m) if insts[0].pc <= new_pc <= insts[-1].pc: if new_pc in result and result[new_pc] != new_offset: raise Error(pc, "Inconsistent branch (old={} new={})"
.format(result[new_pc], new_offset))
result[new_pc] = new_offset else:
offset = new_offset break# First matched pattern wins. return result
for pc, inst in insts: if dwarf_cfa_offsets[pc] isnotNoneand dwarf_cfa_offsets[pc] != inferred_cfa_offsets[pc]: if pc in srcs: # Only report if it maps to source code (not padding or literals). for inst2 in insts:
print("0x{:08x} [{}]: dwarf={} inferred={} {}".format(
inst2.pc, srcs.get(inst2.pc, ""),
str(dwarf_cfa_offsets[inst2.pc]), str(inferred_cfa_offsets[inst2.pc]),
inst2.inst.strip())) raise Error(pc, "DWARF offset does not match inferred offset")
def check_lib(lib: pathlib.Path) -> int: global arch
arch = get_arch(lib)
assert lib.exists()
fdes = get_fde(lib)
asms = collections.deque(get_asm(lib))
srcs = {src.pc: src.file + ":" + src.line for src in get_src(lib)}
seen = set() # Used to verify the we have covered all assembly source lines.
fail = 0 assert srcs, "No sources found"
for fde in fdes: if fde.pc notin srcs: continue# Ignore if it is not hand-written assembly.
# Assembly instructions (one FDE can cover several assembly chunks).
all_insts, name = [], "" while asms and asms[0].pc < fde.end:
asm = asms.popleft() if asm.pc < fde.pc: continue
insts = get_instructions(asm) if any(asm.name.startswith(n) and arch in a for n, a in IGNORE.items()):
seen.update([inst.pc for inst in insts]) continue
all_insts.extend(insts)
name = name or asm.name ifnot all_insts: continue# No assembly
# Compare DWARF data to assembly instructions try:
check_fde(fde, all_insts, srcs) except Error as e:
print("0x{:08x} [{}]: ERROR in {}: {}\n"
.format(e.address, srcs.get(e.address, ""), name, e.message))
fail += 1
seen.update([inst.pc for inst in all_insts]) for pc in sorted(set(srcs.keys()) - seen):
print("ERROR: Missing CFI for {:08x}: {}".format(pc, srcs[pc]))
fail += 1 return fail
def main(argv): """ Check libraries provided on the command line, or use the default build output """
libs = args.srcs ifnot libs:
apex = Path(os.environ["OUT"]) / "symbols/apex/"
libs = list(apex.glob("**/libart.so")) assert libs, "Can not find any libart.so in " + str(apex) for lib in libs:
fail = check_lib(pathlib.Path(lib)) if fail > 0:
print(fail, "ERROR(s) in", str(lib))
sys.exit(1) if args.out:
args.out.write_bytes(b"")
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.