Back to home page

OSCL-LXR

 
 

    


0001 # SPDX-License-Identifier: GPL-2.0
0002 # arm-cs-trace-disasm.py: ARM CoreSight Trace Dump With Disassember
0003 #
0004 # Author: Tor Jeremiassen <tor@ti.com>
0005 #         Mathieu Poirier <mathieu.poirier@linaro.org>
0006 #         Leo Yan <leo.yan@linaro.org>
0007 #         Al Grant <Al.Grant@arm.com>
0008 
0009 from __future__ import print_function
0010 import os
0011 from os import path
0012 import sys
0013 import re
0014 from subprocess import *
0015 from optparse import OptionParser, make_option
0016 
0017 from perf_trace_context import perf_set_itrace_options, \
0018     perf_sample_insn, perf_sample_srccode
0019 
0020 # Below are some example commands for using this script.
0021 #
0022 # Output disassembly with objdump:
0023 #  perf script -s scripts/python/arm-cs-trace-disasm.py \
0024 #       -- -d objdump -k path/to/vmlinux
0025 # Output disassembly with llvm-objdump:
0026 #  perf script -s scripts/python/arm-cs-trace-disasm.py \
0027 #       -- -d llvm-objdump-11 -k path/to/vmlinux
0028 # Output only source line and symbols:
0029 #  perf script -s scripts/python/arm-cs-trace-disasm.py
0030 
0031 # Command line parsing.
0032 option_list = [
0033     # formatting options for the bottom entry of the stack
0034     make_option("-k", "--vmlinux", dest="vmlinux_name",
0035             help="Set path to vmlinux file"),
0036     make_option("-d", "--objdump", dest="objdump_name",
0037             help="Set path to objdump executable file"),
0038     make_option("-v", "--verbose", dest="verbose",
0039             action="store_true", default=False,
0040             help="Enable debugging log")
0041 ]
0042 
0043 parser = OptionParser(option_list=option_list)
0044 (options, args) = parser.parse_args()
0045 
0046 # Initialize global dicts and regular expression
0047 disasm_cache = dict()
0048 cpu_data = dict()
0049 disasm_re = re.compile("^\s*([0-9a-fA-F]+):")
0050 disasm_func_re = re.compile("^\s*([0-9a-fA-F]+)\s.*:")
0051 cache_size = 64*1024
0052 
0053 glb_source_file_name    = None
0054 glb_line_number     = None
0055 glb_dso         = None
0056 
0057 def get_optional(perf_dict, field):
0058        if field in perf_dict:
0059                return perf_dict[field]
0060        return "[unknown]"
0061 
0062 def get_offset(perf_dict, field):
0063     if field in perf_dict:
0064         return "+%#x" % perf_dict[field]
0065     return ""
0066 
0067 def get_dso_file_path(dso_name, dso_build_id):
0068     if (dso_name == "[kernel.kallsyms]" or dso_name == "vmlinux"):
0069         if (options.vmlinux_name):
0070             return options.vmlinux_name;
0071         else:
0072             return dso_name
0073 
0074     if (dso_name == "[vdso]") :
0075         append = "/vdso"
0076     else:
0077         append = "/elf"
0078 
0079     dso_path = os.environ['PERF_BUILDID_DIR'] + "/" + dso_name + "/" + dso_build_id + append;
0080     # Replace duplicate slash chars to single slash char
0081     dso_path = dso_path.replace('//', '/', 1)
0082     return dso_path
0083 
0084 def read_disam(dso_fname, dso_start, start_addr, stop_addr):
0085     addr_range = str(start_addr) + ":" + str(stop_addr) + ":" + dso_fname
0086 
0087     # Don't let the cache get too big, clear it when it hits max size
0088     if (len(disasm_cache) > cache_size):
0089         disasm_cache.clear();
0090 
0091     if addr_range in disasm_cache:
0092         disasm_output = disasm_cache[addr_range];
0093     else:
0094         start_addr = start_addr - dso_start;
0095         stop_addr = stop_addr - dso_start;
0096         disasm = [ options.objdump_name, "-d", "-z",
0097                "--start-address="+format(start_addr,"#x"),
0098                "--stop-address="+format(stop_addr,"#x") ]
0099         disasm += [ dso_fname ]
0100         disasm_output = check_output(disasm).decode('utf-8').split('\n')
0101         disasm_cache[addr_range] = disasm_output
0102 
0103     return disasm_output
0104 
0105 def print_disam(dso_fname, dso_start, start_addr, stop_addr):
0106     for line in read_disam(dso_fname, dso_start, start_addr, stop_addr):
0107         m = disasm_func_re.search(line)
0108         if m is None:
0109             m = disasm_re.search(line)
0110             if m is None:
0111                 continue
0112         print("\t" + line)
0113 
0114 def print_sample(sample):
0115     print("Sample = { cpu: %04d addr: 0x%016x phys_addr: 0x%016x ip: 0x%016x " \
0116           "pid: %d tid: %d period: %d time: %d }" % \
0117           (sample['cpu'], sample['addr'], sample['phys_addr'], \
0118            sample['ip'], sample['pid'], sample['tid'], \
0119            sample['period'], sample['time']))
0120 
0121 def trace_begin():
0122     print('ARM CoreSight Trace Data Assembler Dump')
0123 
0124 def trace_end():
0125     print('End')
0126 
0127 def trace_unhandled(event_name, context, event_fields_dict):
0128     print(' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())]))
0129 
0130 def common_start_str(comm, sample):
0131     sec = int(sample["time"] / 1000000000)
0132     ns = sample["time"] % 1000000000
0133     cpu = sample["cpu"]
0134     pid = sample["pid"]
0135     tid = sample["tid"]
0136     return "%16s %5u/%-5u [%04u] %9u.%09u  " % (comm, pid, tid, cpu, sec, ns)
0137 
0138 # This code is copied from intel-pt-events.py for printing source code
0139 # line and symbols.
0140 def print_srccode(comm, param_dict, sample, symbol, dso):
0141     ip = sample["ip"]
0142     if symbol == "[unknown]":
0143         start_str = common_start_str(comm, sample) + ("%x" % ip).rjust(16).ljust(40)
0144     else:
0145         offs = get_offset(param_dict, "symoff")
0146         start_str = common_start_str(comm, sample) + (symbol + offs).ljust(40)
0147 
0148     global glb_source_file_name
0149     global glb_line_number
0150     global glb_dso
0151 
0152     source_file_name, line_number, source_line = perf_sample_srccode(perf_script_context)
0153     if source_file_name:
0154         if glb_line_number == line_number and glb_source_file_name == source_file_name:
0155             src_str = ""
0156         else:
0157             if len(source_file_name) > 40:
0158                 src_file = ("..." + source_file_name[-37:]) + " "
0159             else:
0160                 src_file = source_file_name.ljust(41)
0161 
0162             if source_line is None:
0163                 src_str = src_file + str(line_number).rjust(4) + " <source not found>"
0164             else:
0165                 src_str = src_file + str(line_number).rjust(4) + " " + source_line
0166         glb_dso = None
0167     elif dso == glb_dso:
0168         src_str = ""
0169     else:
0170         src_str = dso
0171         glb_dso = dso
0172 
0173     glb_line_number = line_number
0174     glb_source_file_name = source_file_name
0175 
0176     print(start_str, src_str)
0177 
0178 def process_event(param_dict):
0179     global cache_size
0180     global options
0181 
0182     sample = param_dict["sample"]
0183     comm = param_dict["comm"]
0184 
0185     name = param_dict["ev_name"]
0186     dso = get_optional(param_dict, "dso")
0187     dso_bid = get_optional(param_dict, "dso_bid")
0188     dso_start = get_optional(param_dict, "dso_map_start")
0189     dso_end = get_optional(param_dict, "dso_map_end")
0190     symbol = get_optional(param_dict, "symbol")
0191 
0192     if (options.verbose == True):
0193         print("Event type: %s" % name)
0194         print_sample(sample)
0195 
0196     # If cannot find dso so cannot dump assembler, bail out
0197     if (dso == '[unknown]'):
0198         return
0199 
0200     # Validate dso start and end addresses
0201     if ((dso_start == '[unknown]') or (dso_end == '[unknown]')):
0202         print("Failed to find valid dso map for dso %s" % dso)
0203         return
0204 
0205     if (name[0:12] == "instructions"):
0206         print_srccode(comm, param_dict, sample, symbol, dso)
0207         return
0208 
0209     # Don't proceed if this event is not a branch sample, .
0210     if (name[0:8] != "branches"):
0211         return
0212 
0213     cpu = sample["cpu"]
0214     ip = sample["ip"]
0215     addr = sample["addr"]
0216 
0217     # Initialize CPU data if it's empty, and directly return back
0218     # if this is the first tracing event for this CPU.
0219     if (cpu_data.get(str(cpu) + 'addr') == None):
0220         cpu_data[str(cpu) + 'addr'] = addr
0221         return
0222 
0223     # The format for packet is:
0224     #
0225     #         +------------+------------+------------+
0226     #  sample_prev:   |    addr    |    ip      |    cpu     |
0227     #         +------------+------------+------------+
0228     #  sample_next:   |    addr    |    ip      |    cpu     |
0229     #         +------------+------------+------------+
0230     #
0231     # We need to combine the two continuous packets to get the instruction
0232     # range for sample_prev::cpu:
0233     #
0234     #     [ sample_prev::addr .. sample_next::ip ]
0235     #
0236     # For this purose, sample_prev::addr is stored into cpu_data structure
0237     # and read back for 'start_addr' when the new packet comes, and we need
0238     # to use sample_next::ip to calculate 'stop_addr', plusing extra 4 for
0239     # 'stop_addr' is for the sake of objdump so the final assembler dump can
0240     # include last instruction for sample_next::ip.
0241     start_addr = cpu_data[str(cpu) + 'addr']
0242     stop_addr  = ip + 4
0243 
0244     # Record for previous sample packet
0245     cpu_data[str(cpu) + 'addr'] = addr
0246 
0247     # Handle CS_ETM_TRACE_ON packet if start_addr=0 and stop_addr=4
0248     if (start_addr == 0 and stop_addr == 4):
0249         print("CPU%d: CS_ETM_TRACE_ON packet is inserted" % cpu)
0250         return
0251 
0252     if (start_addr < int(dso_start) or start_addr > int(dso_end)):
0253         print("Start address 0x%x is out of range [ 0x%x .. 0x%x ] for dso %s" % (start_addr, int(dso_start), int(dso_end), dso))
0254         return
0255 
0256     if (stop_addr < int(dso_start) or stop_addr > int(dso_end)):
0257         print("Stop address 0x%x is out of range [ 0x%x .. 0x%x ] for dso %s" % (stop_addr, int(dso_start), int(dso_end), dso))
0258         return
0259 
0260     if (options.objdump_name != None):
0261         # It doesn't need to decrease virtual memory offset for disassembly
0262         # for kernel dso, so in this case we set vm_start to zero.
0263         if (dso == "[kernel.kallsyms]"):
0264             dso_vm_start = 0
0265         else:
0266             dso_vm_start = int(dso_start)
0267 
0268         dso_fname = get_dso_file_path(dso, dso_bid)
0269         if path.exists(dso_fname):
0270             print_disam(dso_fname, dso_vm_start, start_addr, stop_addr)
0271         else:
0272             print("Failed to find dso %s for address range [ 0x%x .. 0x%x ]" % (dso, start_addr, stop_addr))
0273 
0274     print_srccode(comm, param_dict, sample, symbol, dso)