From patchwork Mon May 28 08:45:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Leo Yan X-Patchwork-Id: 137034 Delivered-To: patch@linaro.org Received: by 2002:a2e:9706:0:0:0:0:0 with SMTP id r6-v6csp2560186lji; Mon, 28 May 2018 01:46:05 -0700 (PDT) X-Google-Smtp-Source: AB8JxZp+STAntEO+tHpumDdrQeHiL8+5/GZMbPQY7ASYbnBD3QN6mtjsFzcM8ZztB0GHzS24JgWv X-Received: by 2002:a17:902:b692:: with SMTP id c18-v6mr12617569pls.307.1527497165433; Mon, 28 May 2018 01:46:05 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1527497165; cv=none; d=google.com; s=arc-20160816; b=mlbmI52fmapARACblj+7OBydv1q2Xb4iYbqIS8kv2j+6aSWcJmTCe+q5JldFo9caWk sXGZc9MEAIuZw/z0mCQtwKaxhGr2dwEWspWoX2aVDJrEgBTCV07vjK91oR0RkdT9jGoH pNFsfkOl1RWOybw+ur7pNEsp1DbiDknHpXK/6C7DV5vq5SyMc4uEPgKYvKcs7QWvoGG5 Wd0FstcyV0hee06JW0Z+qq3qgjC0p0Mc2Q/E5V1B6PobYSfsdS+P9XE0HBB1x1FotEgw iRd/HxHL4KVap5H1/tc9spPHJM0OzjBlCBbmaJa/g/5XYrqn2uVyussmxLB4B6XQJUQH ziQA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=bnFJIkGvsla0t6vZjJXMeOziRo9NX0tsf7NGf5P/aP4=; b=HeEVF1Y028YrIScLoy9oVb/xasnhUuNuSXygRFgyWzNg3bgf88r8wNnwNsuvNx8PbR DdjwbXHENsViChGuofB3kmrr2LdaeKmSnlQ399Q4KXcIFFOhTCeDm1QGjNHf/wCnYIML 5oX2n1ccBIydzPun+At+7TNGcmfOcjn/Db/wHwfqQakQiKuSSVJCWAuB/INcDrY5WP9H CoxHvf5j7KWhj3yR2joQeIuOgUDM1QrY2mNo1zb6Bc2PBE+LMm+PqAKm5UcVy3R4vIva e9OflX8diwJtLTrIZHM1nLGP6DLwSTzw7u8Mf4OFPD80k7C6ngDwUmY1B+c6YcwxLC9R uemA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=NGWVKBQ2; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id g1-v6si23565221pgo.637.2018.05.28.01.46.05; Mon, 28 May 2018 01:46:05 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=NGWVKBQ2; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933159AbeE1IqD (ORCPT + 30 others); Mon, 28 May 2018 04:46:03 -0400 Received: from mail-wr0-f196.google.com ([209.85.128.196]:34929 "EHLO mail-wr0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932106AbeE1Ip6 (ORCPT ); Mon, 28 May 2018 04:45:58 -0400 Received: by mail-wr0-f196.google.com with SMTP id i14-v6so18954770wre.2 for ; Mon, 28 May 2018 01:45:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=bnFJIkGvsla0t6vZjJXMeOziRo9NX0tsf7NGf5P/aP4=; b=NGWVKBQ2uS52XhIR22X9hGAmObXWpn6goO3rnJNZU7lx8Q25QC47d7NXkDLNa49GpQ L01Y9bYUnmM/JIY4pohX+vOlk685oFH21P36XlS9nJd/b4ToSmRNEIcWVAtZqzkbqy9O 6vQRdewujqPOvPsGA4EiCho4tCzUsyvGDgEgs= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=bnFJIkGvsla0t6vZjJXMeOziRo9NX0tsf7NGf5P/aP4=; b=OLiVYRUn9WssfppadlkVj2ycm9NirhGo8ArrXGUEykY99mdBCqd/UsYwJnUQD+QwgU foSBUuY/7WBiHz+YV1voh980rj4Z7USzA5fy2RPLOsPQ6GoWa7C+FEedz39C+hslBUql IJvFQS20Ou9LjNdAV27+lO4obGL0gL5RRm3MTBDIV2wTZeyGZDkPM7IKr6iHFd02hyKr 2VDH10PpbXd+5945j8H8iPgVLEdz1UoJTzFj0pbdcaTPbqF2qerpkCuXjGlLOruH0mzv OOUwvtCkF6v1HVzYgg4PD7OkCT/WXE27Fry1ThL/DclPcvx+ZP6477YypSufym7xfYNN 18dg== X-Gm-Message-State: ALKqPwffl4u9jUkUqFMJI5SiVaHBTuQmljlL6BP1OUcNyoM/IlrxBs1W ygCjGGKELzfpCk1HtRfPQHJS2w== X-Received: by 2002:adf:988c:: with SMTP id w12-v6mr8807873wrb.215.1527497156688; Mon, 28 May 2018 01:45:56 -0700 (PDT) Received: from localhost.localdomain ([45.76.138.171]) by smtp.gmail.com with ESMTPSA id 123-v6sm21783013wmt.19.2018.05.28.01.45.49 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 28 May 2018 01:45:55 -0700 (PDT) From: Leo Yan To: Arnaldo Carvalho de Melo , Mathieu Poirier , Jonathan Corbet , Robert Walker , mike.leach@linaro.org, kim.phillips@arm.co, Tor Jeremiassen Cc: Peter Zijlstra , Ingo Molnar , Alexander Shishkin , Jiri Olsa , Namhyung Kim , linux-arm-kernel@lists.infradead.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, coresight@lists.linaro.org, Leo Yan Subject: [RFT v3 3/4] perf script python: Add script for CoreSight trace disassembler Date: Mon, 28 May 2018 16:45:02 +0800 Message-Id: <1527497103-3593-4-git-send-email-leo.yan@linaro.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1527497103-3593-1-git-send-email-leo.yan@linaro.org> References: <1527497103-3593-1-git-send-email-leo.yan@linaro.org> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This commit adds python script to parse CoreSight tracing event and use command 'objdump' for disassembled lines, finally we can generate readable program execution flow for reviewing tracing data. The script receives CoreSight tracing packet with below format: +------------+------------+------------+ packet(n): | addr | ip | cpu | +------------+------------+------------+ packet(n+1): | addr | ip | cpu | +------------+------------+------------+ packet::ip is the last address of current branch instruction and packet::addr presents the start address of the next coming branch instruction. So for one branch instruction which starts in packet(n), its execution flow starts from packet(n)::addr and it stops at packet(n+1)::ip. As results we need to combine the two continuous packets to generate the instruction range, this is the rationale for the script implementation: [ sample(n)::addr .. sample(n+1)::ip ] Credits to Tor Jeremiassen who have written the script skeleton and provides the ideas for reading symbol file according to build-id, creating memory map for dso and basic packet handling. Mathieu Poirier contributed fixes for build-id and memory map bugs. The detailed development history for this script you can find from [1]. Based on Tor and Mathieu work, the script is updated samples handling for the corrected sample format. Another minor enhancement is to support for without build-id case, the script can parse kernel symbols with option '-k' for vmlinux file path. [1] https://github.com/Linaro/perf-opencsd/commits/perf-opencsd-v4.15/tools/perf/scripts/python/cs-trace-disasm.py Co-authored-by: Tor Jeremiassen Co-authored-by: Mathieu Poirier Signed-off-by: Leo Yan --- tools/perf/scripts/python/arm-cs-trace-disasm.py | 235 +++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 tools/perf/scripts/python/arm-cs-trace-disasm.py -- 2.7.4 diff --git a/tools/perf/scripts/python/arm-cs-trace-disasm.py b/tools/perf/scripts/python/arm-cs-trace-disasm.py new file mode 100644 index 0000000..1239ab4 --- /dev/null +++ b/tools/perf/scripts/python/arm-cs-trace-disasm.py @@ -0,0 +1,235 @@ +# arm-cs-trace-disasm.py: ARM CoreSight Trace Dump With Disassember +# SPDX-License-Identifier: GPL-2.0 +# +# Tor Jeremiassen is original author who wrote script +# skeleton, Mathieu Poirier contributed +# fixes for build-id and memory map; Leo Yan +# updated the packet parsing with new samples format. + +import os +import sys +import re +from subprocess import * +from optparse import OptionParser, make_option + +# Command line parsing + +option_list = [ + # formatting options for the bottom entry of the stack + make_option("-k", "--vmlinux", dest="vmlinux_name", + help="Set path to vmlinux file"), + make_option("-d", "--objdump", dest="objdump_name", + help="Set path to objdump executable file"), + make_option("-v", "--verbose", dest="verbose", + action="store_true", default=False, + help="Enable debugging log") +] + +parser = OptionParser(option_list=option_list) +(options, args) = parser.parse_args() + +if (options.objdump_name == None): + sys.exit("No objdump executable file specified - use -d or --objdump option") + +# Initialize global dicts and regular expression + +build_ids = dict() +mmaps = dict() +disasm_cache = dict() +cpu_data = dict() +disasm_re = re.compile("^\s*([0-9a-fA-F]+):") +disasm_func_re = re.compile("^\s*([0-9a-fA-F]+)\s\<.*\>:") +cache_size = 32*1024 +prev_cpu = -1 + +def parse_buildid(): + global build_ids + + buildid_regex = "([a-fA-f0-9]+)[ \t]([^ \n]+)" + buildid_re = re.compile(buildid_regex) + + results = check_output(["perf", "buildid-list"]).split('\n'); + for line in results: + m = buildid_re.search(line) + if (m == None): + continue; + + id_name = m.group(2) + id_num = m.group(1) + + if (id_name == "[kernel.kallsyms]") : + append = "/kallsyms" + elif (id_name == "[vdso]") : + append = "/vdso" + else: + append = "/elf" + + build_ids[id_name] = os.environ['PERF_BUILDID_DIR'] + \ + "/" + id_name + "/" + id_num + append; + # Replace duplicate slash chars to single slash char + build_ids[id_name] = build_ids[id_name].replace('//', '/', 1) + + if ((options.vmlinux_name == None) and ("[kernel.kallsyms]" in build_ids)): + print "kallsyms cannot be used to dump assembler" + + # Set vmlinux path to replace kallsyms file, if without buildid we still + # can use vmlinux to prase kernel symbols + if ((options.vmlinux_name != None)): + build_ids['[kernel.kallsyms]'] = options.vmlinux_name; + +def parse_mmap(): + global mmaps + + # Check mmap for PERF_RECORD_MMAP and PERF_RECORD_MMAP2 + mmap_regex = "PERF_RECORD_MMAP.* -?[0-9]+/[0-9]+: \[(0x[0-9a-fA-F]+)\((0x[0-9a-fA-F]+)\).*:\s.*\s(\S*)" + mmap_re = re.compile(mmap_regex) + + results = check_output("perf script --show-mmap-events | fgrep PERF_RECORD_MMAP", shell=True).split('\n') + for line in results: + m = mmap_re.search(line) + if (m != None): + if (m.group(3) == '[kernel.kallsyms]_text'): + dso = '[kernel.kallsyms]' + else: + dso = m.group(3) + + start = int(m.group(1),0) + end = int(m.group(1),0) + int(m.group(2),0) + mmaps[dso] = [start, end] + +def find_dso_mmap(addr): + global mmaps + + for key, value in mmaps.items(): + if (addr >= value[0] and addr < value[1]): + return key + + return None + +def read_disam(dso, start_addr, stop_addr): + global mmaps + global build_ids + + addr_range = start_addr + ":" + stop_addr; + + # Don't let the cache get too big, clear it when it hits max size + if (len(disasm_cache) > cache_size): + disasm_cache.clear(); + + try: + disasm_output = disasm_cache[addr_range]; + except: + try: + fname = build_ids[dso]; + except KeyError: + sys.exit("cannot find symbol file for " + dso) + + disasm = [ options.objdump_name, "-d", "-z", + "--start-address="+start_addr, + "--stop-address="+stop_addr, fname ] + + disasm_output = check_output(disasm).split('\n') + disasm_cache[addr_range] = disasm_output; + + return disasm_output + +def dump_disam(dso, start_addr, stop_addr): + for line in read_disam(dso, start_addr, stop_addr): + m = disasm_func_re.search(line) + if (m != None): + print "\t",line + continue + + m = disasm_re.search(line) + if (m == None): + continue; + + print "\t",line + +def dump_packet(sample): + print "Packet = { cpu: 0x%d addr: 0x%x phys_addr: 0x%x ip: 0x%x " \ + "pid: %d tid: %d period: %d time: %d }" % \ + (sample['cpu'], sample['addr'], sample['phys_addr'], \ + sample['ip'], sample['pid'], sample['tid'], \ + sample['period'], sample['time']) + +def trace_begin(): + print 'ARM CoreSight Trace Data Assembler Dump' + parse_buildid() + parse_mmap() + +def trace_end(): + print 'End' + +def trace_unhandled(event_name, context, event_fields_dict): + print ' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())]) + +def process_event(param_dict): + global cache_size + global options + global prev_cpu + + sample = param_dict["sample"] + + if (options.verbose == True): + dump_packet(sample) + + # If period doesn't equal to 1, this packet is for instruction sample + # packet, we need drop this synthetic packet. + if (sample['period'] != 1): + print "Skip synthetic instruction sample" + return + + cpu = format(sample['cpu'], "d"); + + # Initialize CPU data if it's empty, and directly return back + # if this is the first tracing event for this CPU. + if (cpu_data.get(str(cpu) + 'addr') == None): + cpu_data[str(cpu) + 'addr'] = format(sample['addr'], "#x") + prev_cpu = cpu + return + + # The format for packet is: + # + # +------------+------------+------------+ + # sample_prev: | addr | ip | cpu | + # +------------+------------+------------+ + # sample_next: | addr | ip | cpu | + # +------------+------------+------------+ + # + # We need to combine the two continuous packets to get the instruction + # range for sample_prev::cpu: + # + # [ sample_prev::addr .. sample_next::ip ] + # + # For this purose, sample_prev::addr is stored into cpu_data structure + # and read back for 'start_addr' when the new packet comes, and we need + # to use sample_next::ip to calculate 'stop_addr', plusing extra 4 for + # 'stop_addr' is for the sake of objdump so the final assembler dump can + # include last instruction for sample_next::ip. + + start_addr = cpu_data[str(prev_cpu) + 'addr'] + stop_addr = format(sample['ip'] + 4, "#x") + + # Record for previous sample packet + cpu_data[str(cpu) + 'addr'] = format(sample['addr'], "#x") + prev_cpu = cpu + + # Handle CS_ETM_TRACE_ON packet if start_addr=0 and stop_addr=4 + if (int(start_addr, 0) == 0 and int(stop_addr, 0) == 4): + print "CPU%s: CS_ETM_TRACE_ON packet is inserted" % cpu + return + + # Sanity checking dso for start_addr and stop_addr + prev_dso = find_dso_mmap(int(start_addr, 0)) + next_dso = find_dso_mmap(int(stop_addr, 0)) + + # If cannot find dso so cannot dump assembler, bail out + if (prev_dso == None or next_dso == None): + print "Address range [ %s .. %s ]: failed to find dso" % (start_addr, stop_addr) + return + elif (prev_dso != next_dso): + print "Address range [ %s .. %s ]: isn't in same dso" % (start_addr, stop_addr) + return + + dump_disam(prev_dso, start_addr, stop_addr)