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("&", "&amp;")
+    label = label.replace("'", "&apos;")
+    label = label.replace('"', "&quot;")
+    label = label.replace("<", "&lt;")
+    label = label.replace(">", "&gt;")
+    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())