1#!/usr/bin/env python3
2# SPDX-License-Identifier: Apache-2.0
3# -----------------------------------------------------------------------------
4# Copyright 2021-2022 Arm Limited
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may not
7# use this file except in compliance with the License. You may obtain a copy
8# of the License at:
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17# -----------------------------------------------------------------------------
18"""
19The ``astc_dump_binary`` utility provides a wrapper around the ``objdump``
20utility to extract disassembly of specific functions. Currently only matches
21the root name, for sake of command line sanity, so all overloads get dumped.
22
23Using __attribute__((noinline)) can be useful during profiling to stop any
24functions of interest getting inlined once they get too small ...
25"""
26
27
28import argparse
29import re
30import shutil
31import subprocess as sp
32import sys
33
34
35def run_objdump(binary, symbol):
36    """
37    Run objdump on a single binary and extract the range for a given symbol.
38
39    Output is printed to stdout.
40
41    Args:
42        binary (str): The path of the binary file to process.
43        symbol (str): The symbol to match.
44
45    Raises:
46        CalledProcessException: The ``objdump`` subprocess failed for any reason.
47    """
48    args = [
49        "objdump", "-C",
50        "-M", "intel",
51        "--no-show-raw",
52        "-d", "-S",
53        binary
54    ]
55
56    result = sp.run(args, stdout=sp.PIPE, stderr=sp.PIPE,
57                    check=True, universal_newlines=True)
58
59    funcPattern = re.compile(r"^[0-9a-f]{16} <(.*?)\(.*\)>:$")
60
61    funcLines = []
62    funcActive = False
63    lines = result.stdout.splitlines()
64
65    for line in lines:
66        match = funcPattern.match(line)
67        if match:
68            funcName = match.group(1)
69            if funcName == symbol:
70                funcActive = True
71            else:
72                funcActive = False
73
74        if funcActive:
75            funcLines.append(line)
76
77    print("\n".join(funcLines))
78
79def parse_command_line():
80    """
81    Parse the command line.
82
83    Returns:
84        Namespace: The parsed command line container.
85    """
86    parser = argparse.ArgumentParser()
87
88    parser.add_argument("binary", type=argparse.FileType("r"),
89                        help="The new binary to dump")
90
91    parser.add_argument("symbol", type=str,
92                        help="The function name to dump")
93
94    return parser.parse_args()
95
96
97def main():
98    """
99    The main function.
100
101    Returns:
102        int: The process return code.
103    """
104    args = parse_command_line()
105
106    # Preflight - check that size exists. Note that size might still fail at
107    # runtime later, e.g. if the binary is not of the correct format
108    path = shutil.which("objdump")
109    if not path:
110        print("ERROR: The 'objdump' utility is not installed on the PATH")
111        return 1
112
113    # Collect the data
114    try:
115        run_objdump(args.binary.name, args.symbol)
116    except sp.CalledProcessError as ex:
117        print("ERROR: The 'objdump' utility failed")
118        print("       %s" % ex.stderr.strip())
119        return 1
120
121    return 0
122
123
124if __name__ == "__main__":
125    sys.exit(main())
126