xref: /aosp_15_r20/build/bazel/scripts/difftool/diffs/nm.py (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*7594170eSAndroid Build Coastguard Worker#
3*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2022 The Android Open Source Project
4*7594170eSAndroid Build Coastguard Worker#
5*7594170eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*7594170eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*7594170eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*7594170eSAndroid Build Coastguard Worker#
9*7594170eSAndroid Build Coastguard Worker#   http://www.apache.org/licenses/LICENSE-2.0
10*7594170eSAndroid Build Coastguard Worker#
11*7594170eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*7594170eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*7594170eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*7594170eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*7594170eSAndroid Build Coastguard Worker# limitations under the License."""
16*7594170eSAndroid Build Coastguard Worker
17*7594170eSAndroid Build Coastguard Workerimport pathlib
18*7594170eSAndroid Build Coastguard Workerimport re
19*7594170eSAndroid Build Coastguard Workerimport subprocess
20*7594170eSAndroid Build Coastguard Workerfrom diffs.diff import Diff, ExtractInfo
21*7594170eSAndroid Build Coastguard Worker
22*7594170eSAndroid Build Coastguard Workerclass _Symbol:
23*7594170eSAndroid Build Coastguard Worker    """Data structure to hold a symbol as specified by nm
24*7594170eSAndroid Build Coastguard Worker
25*7594170eSAndroid Build Coastguard Worker    Equality of symbols is based on their name and attributes.
26*7594170eSAndroid Build Coastguard Worker
27*7594170eSAndroid Build Coastguard Worker    The self._addr property is excluded from comparisons in this class
28*7594170eSAndroid Build Coastguard Worker    because the location of a symbol in one binary is not a useful
29*7594170eSAndroid Build Coastguard Worker    difference from another binary.
30*7594170eSAndroid Build Coastguard Worker    """
31*7594170eSAndroid Build Coastguard Worker    def __init__(self, name, addr, attr):
32*7594170eSAndroid Build Coastguard Worker        self.name = name
33*7594170eSAndroid Build Coastguard Worker        self._addr = addr
34*7594170eSAndroid Build Coastguard Worker        self.attr = attr
35*7594170eSAndroid Build Coastguard Worker    def __hash__(self):
36*7594170eSAndroid Build Coastguard Worker        return (self.name + self.attr).__hash__()
37*7594170eSAndroid Build Coastguard Worker    def __eq__(self, other):
38*7594170eSAndroid Build Coastguard Worker        return self.name == other.name and self.attr == other.attr
39*7594170eSAndroid Build Coastguard Worker    def __repr__(self):
40*7594170eSAndroid Build Coastguard Worker        return f"{self.name}{{{self.attr}}}"
41*7594170eSAndroid Build Coastguard Worker
42*7594170eSAndroid Build Coastguard Worker
43*7594170eSAndroid Build Coastguard Workerclass NmSymbolDiff(Diff):
44*7594170eSAndroid Build Coastguard Worker  """Compares symbols the symbol table output by nm
45*7594170eSAndroid Build Coastguard Worker
46*7594170eSAndroid Build Coastguard Worker  Example nm output:
47*7594170eSAndroid Build Coastguard Worker  0000000000000140 t GetExceptionSummary
48*7594170eSAndroid Build Coastguard Worker                   U ExpandableStringInitialize
49*7594170eSAndroid Build Coastguard Worker                   U ExpandableStringRelease
50*7594170eSAndroid Build Coastguard Worker  0000000000000cf0 T jniCreateString
51*7594170eSAndroid Build Coastguard Worker
52*7594170eSAndroid Build Coastguard Worker  The first column is the address of the symbol in the binary, the second
53*7594170eSAndroid Build Coastguard Worker  column is an attribute associated with the symbol (see man nm for details),
54*7594170eSAndroid Build Coastguard Worker  and the last column is the demangled symbol name.
55*7594170eSAndroid Build Coastguard Worker  """
56*7594170eSAndroid Build Coastguard Worker  _nm_re = re.compile(r"^(\w+)?\s+([a-zA-Z])\s(\S+)$")
57*7594170eSAndroid Build Coastguard Worker
58*7594170eSAndroid Build Coastguard Worker  def __init__(self, tool: ExtractInfo, tool_name: str):
59*7594170eSAndroid Build Coastguard Worker    self.tool = tool
60*7594170eSAndroid Build Coastguard Worker    self.tool_name = tool_name
61*7594170eSAndroid Build Coastguard Worker
62*7594170eSAndroid Build Coastguard Worker  def _read_symbols(nm_output):
63*7594170eSAndroid Build Coastguard Worker      symbols = set()
64*7594170eSAndroid Build Coastguard Worker      for line in nm_output:
65*7594170eSAndroid Build Coastguard Worker          match = NmSymbolDiff._nm_re.match(line)
66*7594170eSAndroid Build Coastguard Worker          if match:
67*7594170eSAndroid Build Coastguard Worker              symbols.add(_Symbol(match.group(3), match.group(1), match.group(2)))
68*7594170eSAndroid Build Coastguard Worker      return symbols
69*7594170eSAndroid Build Coastguard Worker
70*7594170eSAndroid Build Coastguard Worker  def diff(self, left_path: pathlib.Path, right_path: pathlib.Path) -> list[str]:
71*7594170eSAndroid Build Coastguard Worker    left_nm = subprocess.run(["nm", left_path], capture_output=True, encoding="utf-8").stdout.splitlines()
72*7594170eSAndroid Build Coastguard Worker    right_nm = subprocess.run(["nm", right_path], capture_output=True, encoding="utf-8").stdout.splitlines()
73*7594170eSAndroid Build Coastguard Worker    left_symbols = NmSymbolDiff._read_symbols(left_nm)
74*7594170eSAndroid Build Coastguard Worker    right_symbols = NmSymbolDiff._read_symbols(right_nm)
75*7594170eSAndroid Build Coastguard Worker
76*7594170eSAndroid Build Coastguard Worker    left_only = []
77*7594170eSAndroid Build Coastguard Worker    for s in left_symbols:
78*7594170eSAndroid Build Coastguard Worker        if s not in right_symbols:
79*7594170eSAndroid Build Coastguard Worker          left_only.append(s)
80*7594170eSAndroid Build Coastguard Worker    right_only = []
81*7594170eSAndroid Build Coastguard Worker    for s in right_symbols:
82*7594170eSAndroid Build Coastguard Worker        if s not in left_symbols:
83*7594170eSAndroid Build Coastguard Worker          right_only.append(s)
84*7594170eSAndroid Build Coastguard Worker
85*7594170eSAndroid Build Coastguard Worker    errors = []
86*7594170eSAndroid Build Coastguard Worker    if left_only:
87*7594170eSAndroid Build Coastguard Worker      errors.append(f"symbols in {left_path} not in {right_path}:")
88*7594170eSAndroid Build Coastguard Worker      errors.extend("\t" + str(s) for s in left_only)
89*7594170eSAndroid Build Coastguard Worker    if right_only:
90*7594170eSAndroid Build Coastguard Worker      errors.append(f"symbols in {right_path} not in {left_path}:")
91*7594170eSAndroid Build Coastguard Worker      errors.extend("\t" + str(s) for s in right_only)
92*7594170eSAndroid Build Coastguard Worker
93*7594170eSAndroid Build Coastguard Worker    return errors
94