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