Allow rom to grow beyond 64K.
[seabios.git] / tools / layoutrom.py
1 #!/usr/bin/env python
2 # Script to analyze code and arrange ld sections.
3 #
4 # Copyright (C) 2008  Kevin O'Connor <kevin@koconnor.net>
5 #
6 # This file may be distributed under the terms of the GNU GPLv3 license.
7
8 import sys
9
10 # Align 'pos' to 'alignbytes' offset
11 def alignpos(pos, alignbytes):
12     mask = alignbytes - 1
13     return (pos + mask) & ~mask
14
15 # LD script headers/trailers
16 COMMONHEADER = """
17 /* DO NOT EDIT!  This is an autogenerated file.  See tools/layoutrom.py. */
18 OUTPUT_FORMAT("elf32-i386")
19 OUTPUT_ARCH("i386")
20 SECTIONS
21 {
22 """
23 COMMONTRAILER = """
24 }
25 """
26
27
28 ######################################################################
29 # 16bit fixed address section fitting
30 ######################################################################
31
32 # Get the maximum start position for a list of sections that end at an
33 # address.
34 def getSectionsStart(sections, endaddr, minalign=1):
35     totspace = 0
36     for size, align, name in sections:
37         if align > minalign:
38             minalign = align
39         totspace = alignpos(totspace, align) + size
40     return (endaddr - totspace) / minalign * minalign
41
42 # Write LD script includes for the given sections
43 def outSections(file, sections):
44     for size, align, name in sections:
45         file.write("*(%s)\n" % (name,))
46
47 # The 16bit code can't exceed 64K of space.
48 MAXPOS = 64*1024
49
50 # Layout the 16bit code.  This ensures sections with fixed offset
51 # requirements are placed in the correct location.  It also places the
52 # 16bit code as high as possible in the f-segment.
53 def doLayout16(sections, outname):
54     textsections = []
55     rodatasections = []
56     datasections = []
57     # fixedsections = [(addr, sectioninfo, extasectionslist), ...]
58     fixedsections = []
59     # canrelocate = [(sectioninfo, list), ...]
60     canrelocate = []
61
62     # Find desired sections.
63     for section in sections:
64         size, align, name = section
65         if name[:11] == '.fixedaddr.':
66             addr = int(name[11:], 16)
67             fixedsections.append((addr, section, []))
68             if align != 1:
69                 print "Error: Fixed section %s has non-zero alignment (%d)" % (
70                     name, align)
71                 sys.exit(1)
72         if name[:6] == '.text.':
73             textsections.append(section)
74             canrelocate.append((section, textsections))
75         if name[:17] == '.rodata.__func__.' or name == '.rodata.str1.1':
76             rodatasections.append(section)
77             #canrelocate.append((section, rodatasections))
78         if name[:8] == '.data16.':
79             datasections.append(section)
80             #canrelocate.append((section, datasections))
81
82     # Find freespace in fixed address area
83     fixedsections.sort()
84     # fixedAddr = [(freespace, sectioninfo), ...]
85     fixedAddr = []
86     for i in range(len(fixedsections)):
87         fixedsectioninfo = fixedsections[i]
88         addr, section, extrasectionslist = fixedsectioninfo
89         if i == len(fixedsections) - 1:
90             nextaddr = MAXPOS
91         else:
92             nextaddr = fixedsections[i+1][0]
93         avail = nextaddr - addr - section[0]
94         fixedAddr.append((avail, fixedsectioninfo))
95
96     # Attempt to fit other sections into fixed area
97     fixedAddr.sort()
98     canrelocate.sort()
99     totalused = 0
100     for freespace, fixedsectioninfo in fixedAddr:
101         fixedaddr, fixedsection, extrasections = fixedsectioninfo
102         addpos = fixedaddr + fixedsection[0]
103         totalused += fixedsection[0]
104         nextfixedaddr = addpos + freespace
105 #        print "Filling section %x uses %d, next=%x, available=%d" % (
106 #            fixedaddr, fixedsection[0], nextfixedaddr, freespace)
107         while 1:
108             canfit = None
109             for fixedaddrinfo in canrelocate:
110                 fitsection, inlist = fixedaddrinfo
111                 fitsize, fitalign, fitname = fitsection
112                 if addpos + fitsize > nextfixedaddr:
113                     # Can't fit and nothing else will fit.
114                     break
115                 fitnextaddr = alignpos(addpos, fitalign) + fitsize
116 #                print "Test %s - %x vs %x" % (
117 #                    fitname, fitnextaddr, nextfixedaddr)
118                 if fitnextaddr > nextfixedaddr:
119                     # This item can't fit.
120                     continue
121                 canfit = (fitnextaddr, fixedaddrinfo)
122             if canfit is None:
123                 break
124             # Found a section that can fit.
125             fitnextaddr, fixedaddrinfo = canfit
126             canrelocate.remove(fixedaddrinfo)
127             fitsection, inlist = fixedaddrinfo
128             inlist.remove(fitsection)
129             extrasections.append(fitsection)
130             addpos = fitnextaddr
131             totalused += fitsection[0]
132 #            print "    Adding %s (size %d align %d) pos=%x avail=%d" % (
133 #                fitsection[2], fitsection[0], fitsection[1]
134 #                , fitnextaddr, nextfixedaddr - fitnextaddr)
135     firstfixed = fixedsections[0][0]
136
137     # Report stats
138     total = MAXPOS-firstfixed
139     slack = total - totalused
140     print ("Fixed space: 0x%x-0x%x  total: %d  slack: %d"
141            "  Percent slack: %.1f%%" % (
142             firstfixed, MAXPOS, total, slack,
143             (float(slack) / total) * 100.0))
144
145     # Find overall start position
146     start16 = getSectionsStart(
147         textsections + rodatasections + datasections, firstfixed)
148
149     # Write header
150     output = open(outname, 'wb')
151     output.write(COMMONHEADER + """
152         .text16 0x%x : {
153                 code16_start = ABSOLUTE(.) ;
154                 freespace_end = . ;
155 """ % start16)
156
157     # Write regular sections
158     outSections(output, textsections)
159     output.write("code16_rodata = . ;\n")
160     outSections(output, rodatasections)
161     outSections(output, datasections)
162
163     # Write fixed sections
164     for addr, section, extrasections in fixedsections:
165         name = section[2]
166         output.write(". = ( 0x%x - code16_start ) ;\n" % (addr,))
167         output.write("*(%s)\n" % (name,))
168         for extrasection in extrasections:
169             output.write("*(%s)\n" % (extrasection[2],))
170
171     # Write trailer
172     output.write("""
173                 code16_end = ABSOLUTE(.) ;
174         }
175
176         /* Discard regular data sections to force a link error if
177          * 16bit code attempts to access data not marked with VAR16
178          */
179         /DISCARD/ : { *(.text*) *(.rodata*) *(.data*) *(.bss*) *(COMMON) }
180 """ + COMMONTRAILER)
181
182     return start16
183
184
185 ######################################################################
186 # 32bit section outputting
187 ######################################################################
188
189 # Return the subset of sections with a given name prefix
190 def getSectionsPrefix(sections, prefix):
191     lp = len(prefix)
192     out = []
193     for size, align, name in sections:
194         if name[:lp] == prefix:
195             out.append((size, align, name))
196     return out
197
198 # Layout the 32bit code.  This places the code as high as possible.
199 def doLayout32(sections, outname, start16):
200     start16 += 0xf0000
201     # Find sections to output
202     textsections = getSectionsPrefix(sections, '.text.')
203     rodatasections = getSectionsPrefix(sections, '.rodata')
204     datasections = getSectionsPrefix(sections, '.data.')
205     bsssections = getSectionsPrefix(sections, '.bss.')
206     start32 = getSectionsStart(
207         textsections + rodatasections + datasections + bsssections, start16, 512)
208
209     # Write sections
210     output = open(outname, 'wb')
211     output.write(COMMONHEADER + """
212         .text32 0x%x : {
213                 code32_start = ABSOLUTE(.) ;
214 """ % start32)
215
216     outSections(output, textsections)
217     output.write("code32_rodata = . ;\n")
218     outSections(output, rodatasections)
219     outSections(output, datasections)
220     outSections(output, bsssections)
221
222     output.write("""
223                 freespace_start = . ;
224                 code32_end = ABSOLUTE(.) ;
225         }
226 """ + COMMONTRAILER)
227
228
229 ######################################################################
230 # Section garbage collection
231 ######################################################################
232
233 # Note required section, and recursively set all referenced sections
234 # as required.
235 def keepsection(name, pri, alt):
236     if name in pri[3]:
237         # Already kept - nothing to do.
238         return
239     pri[3].append(name)
240     relocs = pri[2].get(name)
241     if relocs is None:
242         return
243     # Keep all sections that this section points to
244     for symbol in relocs:
245         addr, section = pri[1].get(symbol, (None, None))
246         if (section is not None and '*' not in section
247             and section[:9] != '.discard.'):
248             keepsection(section, pri, alt)
249             continue
250         # Not in primary sections - it may be a cross 16/32 reference
251         addr, section = alt[1].get(symbol, (None, None))
252         if section is not None and '*' not in section:
253             keepsection(section, alt, pri)
254
255 # Determine which sections are actually referenced and need to be
256 # placed into the output file.
257 def gc(info16, info32):
258     # pri = (sections, symbols, relocs, keep sections)
259     pri = (info16[0], info16[1], info16[2], [])
260     alt = (info32[0], info32[1], info32[2], [])
261     # Start by keeping sections that are globally visible.
262     for size, align, section in info16[0]:
263         if section[:11] == '.fixedaddr.' or '.export.' in section:
264             keepsection(section, pri, alt)
265     # Return sections found.
266     sections16 = []
267     for info in info16[0]:
268         size, align, section = info
269         if section not in pri[3]:
270 #            print "gc16", section
271             continue
272         sections16.append(info)
273     sections32 = []
274     for info in info32[0]:
275         size, align, section = info
276         if section not in alt[3]:
277 #            print "gc32", section
278             continue
279         sections32.append(info)
280     return sections16, sections32
281
282
283 ######################################################################
284 # Startup and input parsing
285 ######################################################################
286
287 # Read in output from objdump
288 def parseObjDump(file):
289     # sections = [(size, align, section), ...]
290     sections = []
291     # symbols[symbol] = section
292     symbols = {}
293     # relocs[section] = [symbol, ...]
294     relocs = {}
295
296     state = None
297     for line in file.readlines():
298         line = line.rstrip()
299         if line == 'Sections:':
300             state = 'section'
301             continue
302         if line == 'SYMBOL TABLE:':
303             state = 'symbol'
304             continue
305         if line[:24] == 'RELOCATION RECORDS FOR [':
306             state = 'reloc'
307             relocsection = line[24:-2]
308             continue
309
310         if state == 'section':
311             try:
312                 idx, name, size, vma, lma, fileoff, align = line.split()
313                 if align[:3] != '2**':
314                     continue
315                 sections.append((int(size, 16), 2**int(align[3:]), name))
316             except:
317                 pass
318             continue
319         if state == 'symbol':
320             try:
321                 section, off, symbol = line[17:].split()
322                 off = int(off, 16)
323                 addr = int(line[:8], 16)
324                 symbols[symbol] = addr, section
325             except:
326                 pass
327             continue
328         if state == 'reloc':
329             try:
330                 off, type, symbol = line.split()
331                 off = int(off, 16)
332                 relocs.setdefault(relocsection, []).append(symbol)
333             except:
334                 pass
335     return sections, symbols, relocs
336
337 def main():
338     # Get output name
339     in16, in32, out16, out32 = sys.argv[1:]
340
341     infile16 = open(in16, 'rb')
342     infile32 = open(in32, 'rb')
343
344     info16 = parseObjDump(infile16)
345     info32 = parseObjDump(infile32)
346
347     sections16, sections32 = gc(info16, info32)
348
349     start16 = doLayout16(sections16, out16)
350     doLayout32(sections32, out32, start16)
351
352 if __name__ == '__main__':
353     main()