1#!/usr/bin/env python
2#
3# Copyright 2004 Matt Mackall <mpm@selenic.com>
4#
5# Inspired by perl Bloat-O-Meter (c) 1997 by Andi Kleen
6#
7# This software may be used and distributed according to the terms
8# of the GNU General Public License, incorporated herein by reference.
9
10import sys, os
11
12def usage():
13    sys.stderr.write("usage: %s [-t] file1 file2 [-- <readelf options>]\n"
14                        % sys.argv[0])
15    sys.stderr.write("\t-t\tShow time spent on parsing/processing\n")
16    sys.stderr.write("\t--\tPass additional parameters to readelf\n")
17    sys.exit(1)
18
19f1, f2 = (None, None)
20flag_timing, dashes = (False, False)
21
22for f in sys.argv[1:]:
23    if f.startswith("-"):
24        if f == "--": # sym_args
25            dashes = True
26            break
27        if f == "-t": # timings
28            flag_timing = True
29    else:
30        if not os.path.exists(f):
31            sys.stderr.write("Error: file '%s' does not exist\n" % f)
32            usage()
33        if f1 is None:
34            f1 = f
35        elif f2 is None:
36            f2 = f
37        else:
38            usage()
39if flag_timing:
40    import time
41if f1 is None or f2 is None:
42    usage()
43
44sym_args = " ".join(sys.argv[3 + flag_timing + dashes:])
45def getsizes(file):
46    sym, alias, lut, section = {}, {}, {}, {}
47    for l in os.popen("readelf -W -S " + file).readlines():
48        x = l.replace("[ ", "[", 1).split()
49        if len(x)<6: continue
50        # Should take these into account too!
51        #if x[1] not in [".text", ".rodata", ".symtab", ".strtab"]: continue
52        if x[1] not in [".rodata"]: continue
53        sym[x[1]] = {"addr" : int(x[3], 16), "size" : int(x[5], 16)}
54        section[x[0][1:-1]] = {"name" : x[1]}
55    for l in os.popen("readelf -W -s %s %s" % (sym_args, file)).readlines():
56        l = l.strip()
57        if not (len(l) and l[0].isdigit() and len(l.split()) == 8):
58            continue
59        num, value, size, typ, bind, vis, ndx, name = l.split()
60        if ndx == "UND": continue # skip undefined
61        if typ in ["SECTION", "FILES"]: continue # skip sections and files
62        if "." in name: name = "static." + name.split(".")[0]
63        value = int(value, 16)
64        size = int(size, 16) if size.startswith('0x') else int(size)
65        if vis != "DEFAULT" and bind != "GLOBAL": # see if it is an alias
66            alias[(value, size)] = {"name" : name}
67        else:
68            sym[name] = {"addr" : value, "size":  size}
69            lut[(value, size)] = 0
70            # If this item is in a known section deduct its size from
71            # the size of the section
72            if ndx in section:
73                sym[section[ndx]["name"]]["size"] -= size
74    for addr, sz in iter(alias.keys()):
75        # If the non-GLOBAL sym has an implementation elsewhere then
76        # it's an alias, disregard it.
77        if not (addr, sz) in lut:
78            # If this non-GLOBAL sym does not have an implementation at
79            # another address, then treat it as a normal symbol.
80            sym[alias[(addr, sz)]["name"]] = {"addr" : addr, "size": sz}
81    return sym
82
83if flag_timing:
84    start_t1 = int(time.time() * 1e9)
85old = getsizes(f1)
86if flag_timing:
87    end_t1 = int(time.time() * 1e9)
88    start_t2 = int(time.time() * 1e9)
89new = getsizes(f2)
90if flag_timing:
91    end_t2 = int(time.time() * 1e9)
92    start_t3 = int(time.time() * 1e9)
93grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0
94delta, common = [], {}
95
96for name in iter(old.keys()):
97    if name in new:
98        common[name] = 1
99
100for name in old:
101    if name not in common:
102        remove += 1
103        sz = old[name]["size"]
104        down += sz
105        delta.append((-sz, name))
106
107for name in new:
108    if name not in common:
109        add += 1
110        sz = new[name]["size"]
111        up += sz
112        delta.append((sz, name))
113
114for name in common:
115        d = new[name].get("size", 0) - old[name].get("size", 0)
116        if d>0: grow, up = grow+1, up+d
117        elif d<0: shrink, down = shrink+1, down-d
118        else:
119            continue
120        delta.append((d, name))
121
122delta.sort()
123delta.reverse()
124if flag_timing:
125    end_t3 = int(time.time() * 1e9)
126
127print("%-48s %7s %7s %+7s" % ("function", "old", "new", "delta"))
128for d, n in delta:
129    if d:
130        old_sz = old.get(n, {}).get("size", "-")
131        new_sz = new.get(n, {}).get("size", "-")
132        print("%-48s %7s %7s %+7d" % (n, old_sz, new_sz, d))
133print("-"*78)
134total="(add/remove: %s/%s grow/shrink: %s/%s up/down: %s/%s)%%sTotal: %s bytes"\
135    % (add, remove, grow, shrink, up, -down, up-down)
136print(total % (" "*(80-len(total))))
137if flag_timing:
138    print("\n%d/%d; %d Parse origin/new; processing nsecs" %
139        (end_t1-start_t1, end_t2-start_t2, end_t3-start_t3))
140    print("total nsecs: %d" % (end_t3-start_t1))
141