From 183035aff22d13f21bc21dd496aa6e14a5dfa1b7 Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Thu, 12 Oct 2017 13:22:01 +0200 Subject: Initial import --- .gitignore | 3 + README.rst | 14 +++++ linkermapviz/__init__.py | 161 +++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 21 +++++++ 4 files changed, 199 insertions(+) create mode 100644 .gitignore create mode 100644 README.rst create mode 100644 linkermapviz/__init__.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0bc0dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +*.sw? +*.egg-info diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..f638ee6 --- /dev/null +++ b/README.rst @@ -0,0 +1,14 @@ +linkermapviz +============ + +Interactive visualization of GNU ld’s linker map with a tree map. Usage: + +.. code:: bash + + pip install . + gcc -Wl,-Map,output.map -o output input.c + linkermapviz < output.map + +Works best with ``-ffunction-sections -fdata-sections`` and statically linked +code (e.g. for bare-metal embedded systems). + diff --git a/linkermapviz/__init__.py b/linkermapviz/__init__.py new file mode 100644 index 0000000..525dfc1 --- /dev/null +++ b/linkermapviz/__init__.py @@ -0,0 +1,161 @@ +import sys, re, os +from itertools import chain +import squarify + +from bokeh.plotting import figure, show, output_file, ColumnDataSource +from bokeh.models import HoverTool, LabelSet +from bokeh.models.mappers import CategoricalColorMapper +from bokeh.palettes import Category10 +from bokeh.layouts import column + +class Objectfile: + def __init__ (self, section, offset, size, comment): + self.section = section.strip () + self.offset = offset + self.size = size + self.path = (None, None) + if comment: + self.path = re.match (r'^(.+?)(?:\(([^\)]+)\))?$', comment).groups () + self.children = [] + + def __repr__ (self): + return ''.format (self.section, self.offset, self.size, self.path, repr (self.children)) + +def parseSections (fd): + """ + Quick&Dirty parsing for GNU ld’s linker map output, needs LANG=C, because + some messages are localized. + """ + + sections = [] + + # skip until memory map is found + found = False + for l in sys.stdin: + if l.strip () == 'Memory Configuration': + found = True + break + if not found: + return None + + # long section names result in a linebreak afterwards + sectionre = re.compile ('(?P
.+?|.{14,}\n)[ ]+0x(?P[0-9a-f]+)[ ]+0x(?P[0-9a-f]+)(?:[ ]+(?P.+))?\n+', re.I) + subsectionre = re.compile ('[ ]{16}0x(?P[0-9a-f]+)[ ]+(?P.+)\n+', re.I) + s = sys.stdin.read () + pos = 0 + while True: + m = sectionre.match (s, pos) + if not m: + # skip that line + try: + nextpos = s.index ('\n', pos)+1 + pos = nextpos + continue + except ValueError: + break + pos = m.end () + section = m.group ('section') + v = m.group ('offset') + offset = int (v, 16) if v is not None else None + v = m.group ('size') + size = int (v, 16) if v is not None else None + comment = m.group ('comment') + if section != '*default*' and size > 0: + of = Objectfile (section, offset, size, comment) + if section.startswith (' '): + sections[-1].children.append (of) + while True: + m = subsectionre.match (s, pos) + if not m: + break + pos = m.end () + offset, function = m.groups () + offset = int (offset, 16) + if sections and sections[-1].children: + sections[-1].children[-1].children.append ((offset, function)) + else: + sections.append (of) + + return sections + +def main (): + sections = parseSections (sys.stdin) + if sections is None: + print ('start of memory config not found, did you invoke the compiler/linker with LANG=C?') + return + + sectionWhitelist = {'.text', '.data', '.bss'} + plots = [] + whitelistedSections = list (filter (lambda x: x.section in sectionWhitelist, sections)) + allObjects = list (chain (*map (lambda x: x.children, whitelistedSections))) + allFiles = list (set (map (lambda x: os.path.basename (x.path[0]) if x.path[0] else None, allObjects))) + for s in whitelistedSections: + objects = s.children + objects.sort (reverse=True, key=lambda x: x.size) + values = list (map (lambda x: x.size, objects)) + totalsize = sum (values) + + x = 0 + y = 0 + width = 1000 + height = 1000 + values = squarify.normalize_sizes (values, width, height) + rects = squarify.squarify(values, x, y, width, height) + padded_rects = squarify.padded_squarify(values, x, y, width, height) + + # plot with bokeh + output_file('linkermap.html', title='Linker map') + + top = list (map (lambda x: x['y'], padded_rects)) + bottom = list (map (lambda x: x['y']+x['dy'], padded_rects)) + left = list (map (lambda x: x['x'], padded_rects)) + right = list (map (lambda x: x['x']+x['dx'], padded_rects)) + files = list (map (lambda x: os.path.basename (x.path[0]) if x.path[0] else None, objects)) + size = list (map (lambda x: x.size, objects)) + children = list (map (lambda x: ','.join (map (lambda x: x[1], x.children)) if x.children else x.section, objects)) + source = ColumnDataSource(data=dict( + top=top, + bottom=bottom, + left=left, + right=right, + file=files, + size=size, + children=children, + )) + + hover = HoverTool(tooltips=[ + ("size", "@size"), + ("file", "@file"), + ]) + + + p = figure(title='Linker map for section {} ({} bytes)'.format (s.section, totalsize), + plot_width=width, plot_height=height, + tools=[hover,'pan','wheel_zoom','box_zoom','reset'], + x_range=(0, width), y_range=(0, height)) + + p.xaxis.visible = False + p.xgrid.visible = False + p.yaxis.visible = False + p.ygrid.visible = False + + palette = Category10[10] + mapper = CategoricalColorMapper (palette=palette, factors=allFiles) + p.quad (top='top', bottom='bottom', left='left', right='right', source=source, color={'field': 'file', 'transform': mapper}, legend='file') + labels = LabelSet(x='left', y='top', text='children', level='glyph', + x_offset=5, y_offset=5, source=source, render_mode='canvas') + p.add_layout (labels) + labels = LabelSet(x='left', y='top', text='size', level='glyph', + x_offset=5, y_offset=20, source=source, render_mode='canvas') + p.add_layout (labels) + + # set up legend, must be done after plotting + p.legend.location = "top_left" + p.legend.orientation = "horizontal" + + plots.append (p) + show (column (*plots, responsive=True)) + +if __name__ == '__main__': + main () + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0e05383 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +from distutils.core import setup + +setup( + name='linkermapviz', + version='0.1.0', + author='Lars-Dominik Braun', + author_email='lars+linkermapviz@6xq.net', + packages=['linkermapviz'], + license='LICENSE.txt', + description='Visualize GNU ld’s linker map with a tree map.', + install_requires=[ + 'bokeh', + 'squarify', + ], + entry_points={ + 'console_scripts': [ + 'linkermapviz = linkermapviz:main', + ], + }, +) + -- cgit v1.2.3