1#!/usr/bin/env python3
2
3from __future__ import print_function
4import argparse
5import functools
6import re
7import struct
8import sys
9
10parser = argparse.ArgumentParser(description='Generate assembly file to merge into other code.')
11auto_int = functools.update_wrapper(lambda x: int(x, 0), int) # allows hex
12parser.add_argument('--script', dest='script',
13                    required=True,
14                    help='Linker script for extracting symbols')
15parser.add_argument('--bin1', dest='bin1',
16                    required=True,
17                    help='First binary')
18parser.add_argument('--bin2', dest='bin2',
19                    required=True,
20                    help='Second binary')
21parser.add_argument('--gap', dest='gap',
22                    required=True,
23                    type=auto_int,
24                    help='Gap inserted at the start of code section')
25parser.add_argument('--text-diff', dest='text_diff',
26                    required=True,
27                    type=auto_int,
28                    help='Difference between code section start')
29parser.add_argument('--output', dest='output',
30                    help='Output file')
31parser.add_argument('--map', dest='mapfile',
32                    help='Map file (NM) to read for symbols to export')
33parser.add_argument('--exports', dest='exports',
34                    help='Symbols to export')
35parser.add_argument('--section-header', dest='section_header',
36                    default='.section .init.text, "ax", @progbits',
37                    help='Section header declaration')
38parser.add_argument('-v', '--verbose',
39                    action='store_true')
40args = parser.parse_args()
41
42gap = args.gap
43text_diff = args.text_diff
44
45# Parse linker script for external symbols to use.
46# Next regex matches expanded DECLARE_IMPORT lines in linker script.
47symbol_re = re.compile(r'\s+(\S+)\s*=\s*\.\s*\+\s*\((\d+)\s*\*\s*0\s*\)\s*;')
48symbols = {}
49lines = {}
50for line in open(args.script):
51    m = symbol_re.match(line)
52    if not m:
53        continue
54    (name, line_num) = (m.group(1), int(m.group(2)))
55    if line_num == 0:
56        raise Exception("Invalid line number found:\n\t" + line)
57    if line_num in symbols:
58        raise Exception("Symbol with this line already present:\n\t" + line)
59    if name in lines:
60        raise Exception("Symbol with this name already present:\n\t" + name)
61    symbols[line_num] = name
62    lines[name] = line_num
63
64exports = []
65if args.exports is not None:
66    exports = dict([(name, None) for name in args.exports.split(',')])
67
68# Parse mapfile, look for symbols we want to export.
69if args.mapfile is not None:
70    exports["dummy_start"] = None
71    for line in open(args.mapfile):
72        parts = line.split()
73        if len(parts) != 3:
74            continue
75        addr, sym_type, sym = parts
76        if sym_type.upper() == 'T' and sym in exports:
77            exports[sym] = int(addr, 16)
78    if exports["dummy_start"] != 0:
79        raise Exception("dummy_start symbol expected to be present and 0")
80    del exports["dummy_start"]
81
82for (name, addr) in exports.items():
83    if addr is None:
84        raise Exception("Required export symbols %s not found" % name)
85
86file1 = open(args.bin1, 'rb')
87file2 = open(args.bin2, 'rb')
88file1.seek(0, 2)
89size1 = file1.tell()
90file2.seek(0, 2)
91size2 = file2.tell()
92if size1 > size2:
93    file1, file2 = file2, file1
94    size1, size2 = size2, size1
95if size2 != size1 + gap:
96    raise Exception('File sizes do not match')
97del size2
98
99file1.seek(0, 0)
100data1 = file1.read(size1)
101del file1
102file2.seek(gap, 0)
103data2 = file2.read(size1)
104del file2
105
106max_line = max(symbols.keys())
107
108def to_int32(n):
109    '''Convert a number to signed 32 bit integer truncating if needed'''
110    mask = (1 << 32) - 1
111    h = 1 << 31
112    return (n & mask) ^ h - h
113
114i = 0
115references = {}
116internals = 0
117while i <= size1 - 4:
118    n1 = struct.unpack('<I', data1[i:i+4])[0]
119    n2 = struct.unpack('<I', data2[i:i+4])[0]
120    i += 1
121    # The two numbers are the same, no problems
122    if n1 == n2:
123        continue
124    # Try to understand why they are different
125    diff = to_int32(n1 - n2)
126    if diff == -gap: # this is an internal relocation
127        pos = i - 1
128        print("Internal relocation found at position %#x "
129              "n1=%#x n2=%#x diff=%#x" % (pos, n1, n2, diff),
130              file=sys.stderr)
131        i += 3
132        internals += 1
133        if internals >= 10:
134            break
135        continue
136    # This is a relative relocation to a symbol, accepted, code/data is
137    # relocatable.
138    if diff < gap and diff >= gap - max_line:
139        n = gap - diff
140        symbol = symbols.get(n)
141        # check we have a symbol
142        if symbol is None:
143            raise Exception("Cannot find symbol for line %d" % n)
144        pos = i - 1
145        if args.verbose:
146            print('Position %#x %d %s' % (pos, n, symbol), file=sys.stderr)
147        i += 3
148        references[pos] = symbol
149        continue
150    # First byte is the same, move to next byte
151    if diff & 0xff == 0 and i <= size1 - 4:
152       continue
153    # Probably a type of relocation we don't want or support
154    pos = i - 1
155    suggestion = ''
156    symbol = symbols.get(-diff - text_diff)
157    if symbol is not None:
158        suggestion = " Maybe %s is not defined as hidden?" % symbol
159    raise Exception("Unexpected difference found at %#x "
160                    "n1=%#x n2=%#x diff=%#x gap=%#x.%s" % \
161                    (pos, n1, n2, diff, gap, suggestion))
162if internals != 0:
163    raise Exception("Previous relocations found")
164
165def line_bytes(buf, out):
166    '''Output an assembly line with all bytes in "buf"'''
167    # Python 2 compatibility
168    if type(buf) == str:
169        print("\t.byte " + ','.join([str(ord(c)) for c in buf]), file=out)
170    else:
171        print("\t.byte " + ','.join([str(n) for n in buf]), file=out)
172
173def part(start, end, out):
174    '''Output bytes of "data" from "start" to "end"'''
175    while start < end:
176        e = min(start + 16, end)
177        line_bytes(data1[start:e], out)
178        start = e
179
180def reference(pos, out):
181    name = references[pos]
182    n = struct.unpack('<I', data1[pos:pos+4])[0]
183    sign = '+'
184    if n >= (1 << 31):
185        n -= (1 << 32)
186    n += pos
187    if n < 0:
188        n = -n
189        sign = '-'
190    print("\t.hidden %s\n"
191          "\t.long %s %s %#x - ." % (name, name, sign, n),
192          file=out)
193
194def output(out):
195    prev = 0
196    exports_by_addr = {}
197    for (sym, addr) in exports.items():
198        exports_by_addr.setdefault(addr, []).append(sym)
199    positions = list(references.keys())
200    positions += list(exports_by_addr.keys())
201    for pos in sorted(positions):
202        part(prev, pos, out)
203        prev = pos
204        if pos in references:
205            reference(pos, out)
206            prev = pos + 4
207        if pos in exports_by_addr:
208            for sym in exports_by_addr[pos]:
209                print("\t.global %s\n"
210                      "\t.hidden %s\n"
211                      "%s:" % (sym, sym, sym),
212                      file=out)
213    part(prev, size1, out)
214
215out = sys.stdout
216if args.output is not None:
217    out = open(args.output, 'w')
218print('''/*
219 * File autogenerated by combine_two_binaries.py DO NOT EDIT
220 */''', file=out)
221print('\t' + args.section_header, file=out)
222print('obj32_start:', file=out)
223output(out)
224print('\n\t.section .note.GNU-stack,"",@progbits', file=out)
225out.flush()
226