Merge changes I2c8b84b6,I0ce00cca,I78a83448
* changes:
vndk-abi: Use .dump for unmodified abi-compliance-tool
vndk-abi: Integrate strip debug info
Add Script to strip debug info from dump
diff --git a/scripts/native_heapdump_viewer.py b/scripts/native_heapdump_viewer.py
new file mode 100755
index 0000000..8cda6b6
--- /dev/null
+++ b/scripts/native_heapdump_viewer.py
@@ -0,0 +1,306 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 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.
+
+"""Generates a human-interpretable view of a native heap dump from 'am dumpheap -n'."""
+
+import os
+import os.path
+import re
+import subprocess
+import sys
+
+usage = """
+Usage:
+1. Collect a native heap dump from the device. For example:
+ $ adb shell stop
+ $ adb shell setprop libc.debug.malloc.program app_process
+ $ adb shell setprop libc.debug.malloc.options backtrace=64
+ $ adb shell start
+ (launch and use app)
+ $ adb shell am dumpheap -n <pid> /data/local/tmp/native_heap.txt
+ $ adb pull /data/local/tmp/native_heap.txt
+
+2. Run the viewer:
+ $ python native_heapdump_viewer.py [options] native_heap.txt
+ [--verbose]: verbose output
+ [--html]: interactive html output
+ [--reverse]: reverse the backtraces (start the tree from the leaves)
+ [--symbols SYMBOL_DIR] SYMBOL_DIR is the directory containing the .so files with symbols.
+ Defaults to $ANDROID_PRODUCT_OUT/symbols
+ This outputs a file with lines of the form:
+
+ 5831776 29.09% 100.00% 10532 71b07bc0b0 /system/lib64/libandroid_runtime.so Typeface_createFromArray frameworks/base/core/jni/android/graphics/Typeface.cpp:68
+
+ 5831776 is the total number of bytes allocated at this stack frame, which
+ is 29.09% of the total number of bytes allocated and 100.00% of the parent
+ frame's bytes allocated. 10532 is the total number of allocations at this
+ stack frame. 71b07bc0b0 is the address of the stack frame.
+"""
+
+verbose = False
+html_output = False
+reverse_frames = False
+product_out = os.getenv("ANDROID_PRODUCT_OUT")
+if product_out:
+ symboldir = product_out + "/symbols"
+else:
+ symboldir = "./symbols"
+
+args = sys.argv[1:]
+while len(args) > 1:
+ if args[0] == "--symbols":
+ symboldir = args[1]
+ args = args[2:]
+ elif args[0] == "--verbose":
+ verbose = True
+ args = args[1:]
+ elif args[0] == "--html":
+ html_output = True
+ args = args[1:]
+ elif args[0] == "--reverse":
+ reverse_frames = True
+ args = args[1:]
+ else:
+ print "Invalid option "+args[0]
+ break
+
+if len(args) != 1:
+ print usage
+ exit(0)
+
+native_heap = args[0]
+
+re_map = re.compile("(?P<start>[0-9a-f]+)-(?P<end>[0-9a-f]+) .... (?P<offset>[0-9a-f]+) [0-9a-f]+:[0-9a-f]+ [0-9]+ +(?P<name>.*)")
+
+class Backtrace:
+ def __init__(self, is_zygote, size, frames):
+ self.is_zygote = is_zygote
+ self.size = size
+ self.frames = frames
+
+class Mapping:
+ def __init__(self, start, end, offset, name):
+ self.start = start
+ self.end = end
+ self.offset = offset
+ self.name = name
+
+class FrameDescription:
+ def __init__(self, function, location, library):
+ self.function = function
+ self.location = location
+ self.library = library
+
+
+backtraces = []
+mappings = []
+
+for line in open(native_heap, "r"):
+ parts = line.split()
+ if len(parts) > 7 and parts[0] == "z" and parts[2] == "sz":
+ is_zygote = parts[1] != "1"
+ size = int(parts[3])
+ frames = map(lambda x: int(x, 16), parts[7:])
+ if reverse_frames:
+ frames = list(reversed(frames))
+ backtraces.append(Backtrace(is_zygote, size, frames))
+ continue
+
+ m = re_map.match(line)
+ if m:
+ start = int(m.group('start'), 16)
+ end = int(m.group('end'), 16)
+ offset = int(m.group('offset'), 16)
+ name = m.group('name')
+ mappings.append(Mapping(start, end, offset, name))
+ continue
+
+# Return the mapping that contains the given address.
+# Returns None if there is no such mapping.
+def find_mapping(addr):
+ min = 0
+ max = len(mappings) - 1
+ while True:
+ if max < min:
+ return None
+ mid = (min + max) // 2
+ if mappings[mid].end <= addr:
+ min = mid + 1
+ elif mappings[mid].start > addr:
+ max = mid - 1
+ else:
+ return mappings[mid]
+
+# Resolve address libraries and offsets.
+# addr_offsets maps addr to .so file offset
+# addrs_by_lib maps library to list of addrs from that library
+# Resolved addrs maps addr to FrameDescription
+addr_offsets = {}
+addrs_by_lib = {}
+resolved_addrs = {}
+EMPTY_FRAME_DESCRIPTION = FrameDescription("???", "???", "???")
+for backtrace in backtraces:
+ for addr in backtrace.frames:
+ if addr in addr_offsets:
+ continue
+ mapping = find_mapping(addr)
+ if mapping:
+ addr_offsets[addr] = addr - mapping.start + mapping.offset
+ if not (mapping.name in addrs_by_lib):
+ addrs_by_lib[mapping.name] = []
+ addrs_by_lib[mapping.name].append(addr)
+ else:
+ resolved_addrs[addr] = EMPTY_FRAME_DESCRIPTION
+
+
+# Resolve functions and line numbers
+if html_output == False:
+ print "Resolving symbols using directory %s..." % symboldir
+for lib in addrs_by_lib:
+ sofile = symboldir + lib
+ if os.path.isfile(sofile):
+ file_offset = 0
+ result = subprocess.check_output(["objdump", "-w", "-j", ".text", "-h", sofile])
+ for line in result.split("\n"):
+ splitted = line.split()
+ if len(splitted) > 5 and splitted[1] == ".text":
+ file_offset = int(splitted[5], 16)
+ break
+
+ input_addrs = ""
+ for addr in addrs_by_lib[lib]:
+ input_addrs += "%s\n" % hex(addr_offsets[addr] - file_offset)
+ p = subprocess.Popen(["addr2line", "-C", "-j", ".text", "-e", sofile, "-f"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ result = p.communicate(input_addrs)[0]
+ splitted = result.split("\n")
+ for x in range(0, len(addrs_by_lib[lib])):
+ function = splitted[2*x];
+ location = splitted[2*x+1];
+ resolved_addrs[addrs_by_lib[lib][x]] = FrameDescription(function, location, lib)
+
+ else:
+ if html_output == False:
+ print "%s not found for symbol resolution" % lib
+ fd = FrameDescription("???", "???", lib)
+ for addr in addrs_by_lib[lib]:
+ resolved_addrs[addr] = fd
+
+def addr2line(addr):
+ if addr == "ZYGOTE" or addr == "APP":
+ return FrameDescription("", "", "")
+
+ return resolved_addrs[int(addr, 16)]
+
+class AddrInfo:
+ def __init__(self, addr):
+ self.addr = addr
+ self.size = 0
+ self.number = 0
+ self.children = {}
+
+ def addStack(self, size, stack):
+ self.size += size
+ self.number += 1
+ if len(stack) > 0:
+ child = stack[0]
+ if not (child.addr in self.children):
+ self.children[child.addr] = child
+ self.children[child.addr].addStack(size, stack[1:])
+
+zygote = AddrInfo("ZYGOTE")
+app = AddrInfo("APP")
+
+def display(indent, total, parent_total, node):
+ fd = addr2line(node.addr)
+ print "%9d %6.2f%% %6.2f%% %8d %s%s %s %s %s" % (node.size, 100*node.size/float(total), 100*node.size/float(parent_total), node.number, indent, node.addr, fd.library, fd.function, fd.location)
+ children = sorted(node.children.values(), key=lambda x: x.size, reverse=True)
+ for child in children:
+ display(indent + " ", total, node.size, child)
+
+label_count=0
+def display_html(total, node, extra):
+ global label_count
+ fd = addr2line(node.addr)
+ if verbose:
+ lib = fd.library
+ else:
+ lib = os.path.basename(fd.library)
+ label = "%d %6.2f%% %6d %s%s %s %s" % (node.size, 100*node.size/float(total), node.number, extra, lib, fd.function, fd.location)
+ label = label.replace("&", "&")
+ label = label.replace("'", "'")
+ label = label.replace('"', """)
+ label = label.replace("<", "<")
+ label = label.replace(">", ">")
+ children = sorted(node.children.values(), key=lambda x: x.size, reverse=True)
+ print '<li>'
+ if len(children) > 0:
+ print '<label for="' + str(label_count) + '">' + label + '</label>'
+ print '<input type="checkbox" id="' + str(label_count) + '"/>'
+ print '<ol>'
+ label_count+=1
+ for child in children:
+ display_html(total, child, "")
+ print '</ol>'
+ else:
+ print label
+ print '</li>'
+for backtrace in backtraces:
+ stack = []
+ for addr in backtrace.frames:
+ stack.append(AddrInfo("%x" % addr))
+ stack.reverse()
+ if backtrace.is_zygote:
+ zygote.addStack(backtrace.size, stack)
+ else:
+ app.addStack(backtrace.size, stack)
+
+html_header = """
+<!DOCTYPE html>
+<html><head><style>
+li input {
+ display: none;
+}
+li input:checked + ol > li {
+ display: block;
+}
+li input + ol > li {
+ display: none;
+}
+li {
+ font-family: Roboto Mono,monospace;
+}
+label {
+ font-family: Roboto Mono,monospace;
+ cursor: pointer
+}
+</style></head><body>Native allocation HTML viewer<ol>
+"""
+html_footer = "</ol></body></html>"
+
+if html_output:
+ print html_header
+ display_html(app.size, app, "app ")
+ if zygote.size>0:
+ display_html(zygote.size, zygote, "zygote ")
+ print html_footer
+else:
+ print ""
+ print "%9s %6s %6s %8s %s %s %s %s" % ("BYTES", "%TOTAL", "%PARENT", "COUNT", "ADDR", "LIBRARY", "FUNCTION", "LOCATION")
+ display("", app.size, app.size + zygote.size, app)
+ print ""
+ display("", zygote.size, app.size + zygote.size, zygote)
+ print ""
+
diff --git a/vndk/tools/definition-tool/assets/visual/dep-data.js b/vndk/tools/definition-tool/assets/visual/dep-data.js
new file mode 100644
index 0000000..1d038dc
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/dep-data.js
@@ -0,0 +1,45 @@
+var violatedLibs = {"system.private.bin": [
+ ["/system/bin/systembin", 2],
+ ["/system/bin/otherbin", 1]],
+ "system.private.fwk-only": [
+ ["/system/lib/test.so", 1]],
+ "vendor.private.bin": [
+ ["/vendor/lib/libvendor.so", 2],
+ ["/vendor/lib/vendor_2_lib.so", 1]]};
+var depData = [
+ {"name": "/system/lib/oklib.so",
+ "violate_count": 0,
+ "violates": [],
+ "depends": [],
+ "tag": "system.public.vndk"},
+ {"name": "/system/lib/oklib2.so",
+ "violate_count": 0,
+ "violates": [],
+ "depends": ["/system/lib/oklib.so"],
+ "tag": "system.private.fwk_only"},
+ {"name": "/system/bin/systembin",
+ "violate_count": 2,
+ "violates": ["/vendor/lib/libvendor.so", "/vendor/lib/vendor_2_lib.so"],
+ "depends": ["/system/lib/oklib.so"],
+ "tag": "system.private.bin"},
+ {"name": "/system/bin/otherbin",
+ "violate_count": 1,
+ "violates": ["/vendor/lib/libvendor.so"],
+ "depends": ["/system/lib/oklib2.so"],
+ "tag": "system.private.bin"},
+ {"name": "/system/lib/test.so",
+ "violate_count": 1,
+ "violates": ["/vendor/lib/libvendor.so"],
+ "depends": ["/system/lib/oklib.so"],
+ "tag": "system.private.fwk-only"},
+ {"name": "/vendor/lib/libvendor.so",
+ "violate_count": 2,
+ "violates": ["/system/lib/test.so", "/system/lib/oklib2.so"],
+ "depends": [],
+ "tag": "vendor.private.bin"},
+ {"name": "/vendor/lib/vendor_2_lib.so",
+ "violate_count": 1,
+ "violates": ["/system/lib/test.so"],
+ "depends": [],
+ "tag": "vendor.private.bin"}
+];
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/assets/visual/dep-graph.css b/vndk/tools/definition-tool/assets/visual/dep-graph.css
new file mode 100644
index 0000000..ecee4ff
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/dep-graph.css
@@ -0,0 +1,123 @@
+.node {
+ font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ fill: #bbb;
+ fill-opacity: 0.2;
+}
+
+.node--sys-pri {
+ font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ fill: #ffaaaa;
+ fill-opacity: 0.2;
+}
+
+.node--sys-pub {
+ font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
+ fill: #aaffaa;
+ fill-opacity: 0.2;
+}
+
+.node--source {
+ fill: #2ca02c;
+ fill-opacity: 1;
+}
+
+.node--target {
+ fill: #d62728;
+ fill-opacity: 1;
+}
+
+.node--selected {
+ fill: #ff7000;
+ fill-opacity: 1;
+ font-weight: 700;
+}
+
+.node:hover,
+.node--sys-pri:hover,
+.node--sys-pub:hover {
+ fill: #000;
+ fill-opacity: 1;
+}
+
+.node:hover,
+.node--sys-pri:hover,
+.node--sys-pub:hover,
+.node--source,
+.node--target {
+ font-weight: 700;
+}
+
+.link {
+ stroke: steelblue;
+ stroke-opacity: 0.01;
+ fill: none;
+ pointer-events: none;
+}
+
+.link--violate {
+ stroke: crimson;
+ stroke-opacity: 0.1;
+ stroke-width: 1.0px;
+ fill: none;
+ pointer-events: none;
+}
+
+.link--source,
+.link--target {
+ stroke-opacity: 1;
+ stroke-width: 2.5px;
+}
+
+.link--source {
+ stroke: orange;
+}
+
+.link--target {
+ stroke: #2ca02c;
+}
+
+button.violate {
+ background-color: white;
+ color: #333;
+ cursor: pointer;
+ padding: 5px;
+ width: 100%;
+ border: none;
+ text-align: left;
+ outline: none;
+ font-size: 15px;
+ transition: 0.4s;
+}
+
+button.violate.active,
+button.violate:hover {
+ background-color: #aaa;
+}
+
+.violate-list {
+ background-color: #ddd;
+ padding: 3px;
+ width: 100%;
+ border: none;
+ text-align: left;
+ font-size: 14px;
+}
+
+#violate_list_column {
+ width: 20%;
+ height: 99vh;
+ float: left;
+ overflow: scroll;
+}
+
+#dep_graph_column {
+ width: 80%;
+ height: 99vh;
+ float: right;
+ overflow: scroll;
+}
+
+#reset_btn {
+ position: absolute;
+ margin: 20px;
+}
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/assets/visual/dep-graph.js b/vndk/tools/definition-tool/assets/visual/dep-graph.js
new file mode 100644
index 0000000..89ce76d
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/dep-graph.js
@@ -0,0 +1,251 @@
+(function() {
+ 'use strict';
+
+ let diameter = 1280;
+ let radius = diameter / 2;
+ let innerRadius = radius - 240;
+
+ let cluster = d3.cluster();
+ cluster.size([ 360, innerRadius ]);
+
+ let line = d3.radialLine();
+ line.curve(d3.curveBundle.beta(0.85));
+ line.radius(function(d) { return d.y; });
+ line.angle(function(d) { return d.x / 180 * Math.PI; });
+
+ let link;
+ let node;
+ let selectedNode;
+
+ function init() {
+ let domListCol = document.createElement("div");
+ domListCol.id = "violate_list_column";
+ let domGraphCol = document.createElement("div");
+ domGraphCol.id = "dep_graph_column";
+ let domResetBtn = document.createElement("button");
+ domResetBtn.id = "reset_btn";
+ domResetBtn.innerHTML = "Reset";
+ domGraphCol.appendChild(domResetBtn);
+
+ document.body.appendChild(domListCol);
+ document.body.appendChild(domGraphCol);
+
+ let canvas = d3.select("#dep_graph_column").append("svg");
+ canvas.attr("width", diameter + 200);
+ canvas.attr("height", diameter);
+
+ let svg = canvas.append("g");
+ svg.attr("transform", "translate(" + (radius + 100) + "," + radius + ")");
+
+ link = svg.append("g").selectAll(".link");
+ node = svg.append("g").selectAll(".node");
+
+ showResult(depData, violatedLibs);
+ }
+
+ function showList(depMap, violatedLibs) {
+ function makeTitle(tagName) {
+ let domTitle = document.createElement("div");
+ let domText = document.createElement("h3");
+ domText.innerHTML = tagName;
+ domTitle.appendChild(domText);
+ return domTitle;
+ }
+ function makeButton(libName, count) {
+ let domButton = document.createElement("button");
+ domButton.className = "violate";
+ domButton.innerHTML = libName + " (" + count + ")";
+ domButton.onclick = function() {
+ this.classList.toggle("active");
+ let currentList = this.nextElementSibling;
+ if (currentList.style.display === "block") {
+ currentList.style.display = "none";
+ selectedNode = undefined;
+ resetclicked();
+ } else {
+ currentList.style.display = "block";
+ if (selectedNode) {
+ selectedNode.classList.toggle("active");
+ selectedNode.nextElementSibling.style.display = "none";
+ }
+ selectedNode = domButton;
+ mouseclicked(depMap[libName]);
+ }
+ };
+ return domButton;
+ }
+ function makeList(domList, list)
+ {
+ for (let i = 0; i < list.length; i++) {
+ domList.appendChild(makeButton(list[i][0], list[i][1]));
+ let domDepList = document.createElement("div");
+ let depItem = depMap[list[i][0]];
+ let violates = depItem.data.violates;
+ for (let j = 0; j < violates.length; j++) {
+ let domDepLib = document.createElement("div");
+ let tag = depMap[violates[j]].data.tag;
+ domDepLib.className = "violate-list";
+ domDepLib.innerHTML = violates[j] + " ["
+ + tag.substring(tag.lastIndexOf(".") + 1) + "]";
+ domDepList.appendChild(domDepLib);
+ }
+ domList.appendChild(domDepList);
+ domDepList.style.display = "none";
+ }
+ }
+
+ let domViolatedList = document.getElementById("violate_list_column");
+ if ("vendor.private.bin" in violatedLibs) {
+ let list = violatedLibs["vendor.private.bin"];
+ domViolatedList.appendChild(makeTitle("VENDOR (" + list.length + ")"));
+ makeList(domViolatedList, list);
+ }
+ for (let tag in violatedLibs) {
+ if (tag === "vendor.private.bin")
+ continue;
+ let list = violatedLibs[tag];
+ if (tag === "system.private.bin")
+ tag = "SYSTEM";
+ else
+ tag = tag.substring(tag.lastIndexOf(".") + 1).toUpperCase();
+ domViolatedList.appendChild(makeTitle(tag + " (" + list.length + ")"));
+ makeList(domViolatedList, list);
+ }
+ }
+
+ function showResult(depDumps, violatedLibs) {
+ let root = tagHierarchy(depDumps).sum(function(d) { return 1; });
+ cluster(root);
+
+ let libsDepData = libsDepends(root.leaves());
+ showList(libsDepData[1], violatedLibs);
+ link = link.data(libsDepData[0])
+ .enter()
+ .append("path")
+ .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
+ .attr("class", function(d) { return d.allow ? "link" : "link--violate" })
+ .attr("d", line);
+
+ node = node.data(root.leaves())
+ .enter()
+ .append("text")
+ .attr("class",
+ function(d) {
+ return d.data.parent.parent.parent.key == "system" ?
+ (d.data.parent.parent.key == "system.public" ?
+ "node--sys-pub" :
+ "node--sys-pri") :
+ "node";
+ })
+ .attr("dy", "0.31em")
+ .attr("transform",
+ function(d) {
+ return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" +
+ (d.x < 180 ? "" : "rotate(180)");
+ })
+ .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
+ .text(function(d) { return d.data.key; })
+ .on("click", mouseclicked);
+ document.getElementById("reset_btn").onclick = resetclicked;
+ }
+
+ function resetclicked() {
+ if (selectedNode) {
+ selectedNode.classList.toggle("active");
+ selectedNode.nextElementSibling.style.display = "none";
+ selectedNode = undefined;
+ }
+ link.classed("link--target", false)
+ .classed("link--source", false);
+ node.classed("node--target", false)
+ .classed("node--source", false)
+ .classed("node--selected", false);
+ }
+
+ function mouseclicked(d) {
+ node.each(function(n) { n.target = n.source = false; });
+
+ link.classed("link--target",
+ function(l) {
+ if (l.target === d) {
+ l.source.source = true;
+ return true;
+ } else {
+ return false;
+ }
+ })
+ .classed("link--source",
+ function(l) {
+ if (l.source === d) {
+ l.target.target = true;
+ return true;
+ } else {
+ return false;
+ }
+ })
+ .filter(function(l) { return l.target === d || l.source === d; })
+ .raise();
+
+ node.classed("node--target",
+ function(n) {
+ return n.target;
+ })
+ .classed("node--source",
+ function(n) { return n.source; })
+ .classed("node--selected",
+ function(n) {
+ return n === d;
+ });
+ }
+
+ function tagHierarchy(depDumps) {
+ let map = {};
+
+ function find(name, tag, data) {
+ let node = map[name], i;
+ if (!node) {
+ node = map[name] = data || { name : name, children : [] };
+ if (name.length) {
+ node.parent = find(tag, tag.substring(0, tag.lastIndexOf(".")));
+ node.parent.children.push(node);
+ node.key = name;
+ }
+ }
+ return node;
+ }
+
+ depDumps.forEach(function(d) { find(d.name, d.tag, d); });
+
+ return d3.hierarchy(map[""]);
+ }
+
+ function libsDepends(nodes) {
+ let map = {}, depends = [];
+
+ // Compute a map from name to node.
+ nodes.forEach(function(d) { map[d.data.name] = d; });
+
+ // For each dep, construct a link from the source to target node.
+ nodes.forEach(function(d) {
+ if (d.data.depends)
+ d.data.depends.forEach(function(i) {
+ let l = map[d.data.name].path(map[i]);
+ l.allow = true;
+ depends.push(l);
+ });
+ if (d.data.violates.length) {
+ map[d.data.name].not_allow = true;
+ d.data.violates.forEach(function(i) {
+ map[i].not_allow = true;
+ let l = map[d.data.name].path(map[i]);
+ l.allow = false;
+ depends.push(l);
+ });
+ }
+ });
+
+ return [ depends, map ];
+ }
+
+ window.onload = init;
+})();
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/assets/visual/index.html b/vndk/tools/definition-tool/assets/visual/index.html
new file mode 100644
index 0000000..9a5547f
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/visual/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>VNDK Dependency Graph</title>
+ <link rel="stylesheet" href="dep-graph.css" media="all" />
+ <script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
+ <script type="text/javascript" src="dep-data.js"></script>
+ <script type="text/javascript" src="dep-graph.js"></script>
+ </head>
+
+ <body></body>
+</html>
\ No newline at end of file
diff --git a/vndk/tools/definition-tool/vndk_definition_tool.py b/vndk/tools/definition-tool/vndk_definition_tool.py
index 45c7f36..7c11dce 100755
--- a/vndk/tools/definition-tool/vndk_definition_tool.py
+++ b/vndk/tools/definition-tool/vndk_definition_tool.py
@@ -2339,6 +2339,130 @@
return 0 if num_errors == 0 else 1
+class DepGraphCommand(ELFGraphCommand):
+ def __init__(self):
+ super(DepGraphCommand, self).__init__(
+ 'dep-graph', help='Show the eligible dependencies graph')
+
+ def add_argparser_options(self, parser):
+ super(DepGraphCommand, self).add_argparser_options(parser)
+
+ parser.add_argument('--tag-file', required=True)
+ parser.add_argument(
+ '--output', '-o', help='output directory')
+
+ def _load_tag_file(self, tag_file_path):
+ res = TaggedLibDict(set(), set(), set(), set(), set(), set(), set(),
+ set())
+
+ mapping = {
+ 'll-ndk': res.ll_ndk,
+ 'll-ndk-indirect': res.ndk_indirect,
+ 'sp-ndk': res.sp_ndk,
+ 'sp-ndk-indirect': res.ndk_indirect,
+ 'hl-ndk': res.hl_ndk,
+ 'vndk-sp-hal': res.vndk_sp,
+ 'vndk-sp-both': res.vndk_sp,
+ 'vndk-sp-indirect': res.vndk, # Visible to non-SP-HAL
+ 'vndk': res.vndk,
+ 'vndk-indirect': res.vndk_indirect,
+ 'fwk-only': res.fwk_only,
+ 'remove': set(), # Drop from system lib. Tag as vendor lib.
+ }
+
+ with open(tag_file_path, 'r') as tag_file:
+ csv_reader = csv.reader(tag_file)
+ for lib_name, tag in csv_reader:
+ mapping[tag.lower()].add(os.path.basename(lib_name))
+
+ return res
+
+ def _get_tag_from_lib(self, lib, tags):
+ # Tempororily add the TAG_DEP_TABLE to show permission
+ TAG_DEP_TABLE = {
+ 'vendor': ('ll_ndk', 'sp_ndk', 'vndk_sp', 'vndk', 'vndk_indirect'),
+ }
+
+ tag_hierarchy = []
+ eligible_for_vendor = TAG_DEP_TABLE['vendor']
+ for tag in TAGGED_LIB_DICT_FIELDS:
+ if tag == 'vendor':
+ tag_hierarchy.append('vendor.private.bin')
+ else:
+ pub = 'public' if tag in eligible_for_vendor else 'private'
+ tag_hierarchy.append('system.{}.{}'.format(pub, tag))
+ lib_name = os.path.basename(lib.path)
+ if lib.partition == PT_SYSTEM:
+ for i, lib_set in enumerate(tags):
+ if lib_name in lib_set:
+ return tag_hierarchy[i]
+ return 'system.private.bin'
+ else:
+ return 'vendor.private.bin'
+
+ def _check_if_allowed(self, my_tag, other_tag):
+ my = my_tag.split('.')
+ other = other_tag.split('.')
+ if my[0] == 'system' and other[0] == 'vendor':
+ return False
+ if my[0] == 'vendor' and other[0] == 'system' \
+ and other[1] == 'private':
+ return False
+ return True
+
+ def _get_dep_graph(self, graph, tags):
+ data = []
+ violate_libs = dict()
+ system_libs = graph.lib_pt[PT_SYSTEM].values()
+ vendor_libs = graph.lib_pt[PT_VENDOR].values()
+ for lib in itertools.chain(system_libs, vendor_libs):
+ tag = self._get_tag_from_lib(lib, tags)
+ violate_count = 0
+ lib_item = {
+ 'name': lib.path,
+ 'tag': tag,
+ 'depends': [],
+ 'violates': [],
+ }
+ for dep in lib.deps:
+ if self._check_if_allowed(tag,
+ self._get_tag_from_lib(dep, tags)):
+ lib_item['depends'].append(dep.path)
+ else:
+ lib_item['violates'].append(dep.path)
+ violate_count += 1;
+ lib_item['violate_count'] = violate_count
+ if violate_count > 0:
+ if not tag in violate_libs:
+ violate_libs[tag] = []
+ violate_libs[tag].append((lib.path, violate_count))
+ data.append(lib_item)
+ return data, violate_libs
+
+ def main(self, args):
+ graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
+ args.vendor, args.vendor_dir_as_system,
+ args.load_extra_deps)
+
+ tags = self._load_tag_file(args.tag_file)
+ data, violate_libs = self._get_dep_graph(graph, tags)
+ data.sort(key=lambda lib_item: (lib_item['tag'],
+ lib_item['violate_count']))
+ for libs in violate_libs.values():
+ libs.sort(key=lambda libs: libs[1], reverse=True)
+
+ makedirs(args.output, exist_ok=True)
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ for name in ('index.html', 'dep-graph.js', 'dep-graph.css'):
+ shutil.copyfile(os.path.join(script_dir, 'assets/visual', name),
+ os.path.join(args.output, name))
+ with open(os.path.join(args.output, 'dep-data.js'), 'w') as f:
+ f.write('var violatedLibs = ' + json.dumps(violate_libs) +
+ '\nvar depData = ' + json.dumps(data) + ';')
+
+ return 0
+
+
class VNDKSPCommand(ELFGraphCommand):
def __init__(self):
super(VNDKSPCommand, self).__init__(
@@ -2401,6 +2525,7 @@
register_subcmd(DepsClosureCommand())
register_subcmd(DepsInsightCommand())
register_subcmd(CheckDepCommand())
+ register_subcmd(DepGraphCommand())
register_subcmd(SpLibCommand())
register_subcmd(VNDKSPCommand())