Force a link error if a function is used from the wrong code chunk.
[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 start positions
146     text16_start = getSectionsStart(textsections, firstfixed)
147     data16_start = getSectionsStart(rodatasections + datasections, text16_start)
148
149     # Write header and regular sections
150     output = open(outname, 'wb')
151     output.write(COMMONHEADER + """
152         data16_start = 0x%x ;
153         .data16 data16_start : {
154 """ % data16_start)
155     outSections(output, datasections)
156     output.write("code16_rodata = . ;\n")
157     outSections(output, rodatasections)
158     output.write("""
159         }
160
161         text16_start = 0x%x ;
162         .text16 text16_start : {
163 """ % text16_start)
164     outSections(output, textsections)
165
166     # Write fixed sections
167     for addr, section, extrasections in fixedsections:
168         name = section[2]
169         output.write(". = ( 0x%x - text16_start ) ;\n" % (addr,))
170         output.write("*(%s)\n" % (name,))
171         for extrasection in extrasections:
172             output.write("*(%s)\n" % (extrasection[2],))
173
174     # Write trailer
175     output.write("""
176                 text16_end = ABSOLUTE(.) ;
177         }
178
179         /* Discard regular data sections to force a link error if
180          * 16bit code attempts to access data not marked with VAR16
181          */
182         /DISCARD/ : { *(.text*) *(.rodata*) *(.data*) *(.bss*) *(COMMON) }
183 """ + COMMONTRAILER)
184
185     return data16_start
186
187
188 ######################################################################
189 # 32bit section outputting
190 ######################################################################
191
192 # Return the subset of sections with a given name prefix
193 def getSectionsPrefix(sections, prefix):
194     lp = len(prefix)
195     out = []
196     for size, align, name in sections:
197         if name[:lp] == prefix:
198             out.append((size, align, name))
199     return out
200
201 # Layout the 32bit segmented code.  This places the code as high as possible.
202 def doLayout32seg(sections, outname, endat):
203     # Find sections to output
204     textsections = getSectionsPrefix(sections, '.text.')
205     rodatasections = (getSectionsPrefix(sections, '.rodata.str1.1')
206                       + getSectionsPrefix(sections, '.rodata.__func__.'))
207     datasections = getSectionsPrefix(sections, '.data32seg.')
208     startat = getSectionsStart(
209         textsections + rodatasections + datasections, endat)
210
211     # Write sections
212     output = open(outname, 'wb')
213     output.write(COMMONHEADER + """
214         code32seg_start = 0x%x ;
215         .text32seg code32seg_start : {
216                 freespace_end = . ;
217 """ % startat)
218
219     outSections(output, textsections)
220     output.write("code32seg_rodata = . ;\n")
221     outSections(output, rodatasections)
222     outSections(output, datasections)
223
224     output.write("""
225                 code32seg_end = ABSOLUTE(.) ;
226         }
227         /DISCARD/ : { *(.text*) *(.rodata*) *(.data*) *(.bss*) *(COMMON) }
228 """ + COMMONTRAILER)
229     return startat
230
231 # Layout the 32bit flat code.  This places the code as high as possible.
232 def doLayout32flat(sections, outname, endat):
233     endat += 0xf0000
234     # Find sections to output
235     textsections = getSectionsPrefix(sections, '.text.')
236     rodatasections = getSectionsPrefix(sections, '.rodata')
237     datasections = getSectionsPrefix(sections, '.data.')
238     bsssections = getSectionsPrefix(sections, '.bss.')
239     startat = getSectionsStart(
240         textsections + rodatasections + datasections + bsssections, endat, 512)
241
242     # Write sections
243     output = open(outname, 'wb')
244     output.write(COMMONHEADER + """
245         code32flat_start = 0x%x ;
246         .text32flat code32flat_start : {
247 """ % startat)
248
249     outSections(output, textsections)
250     output.write("code32_rodata = . ;\n")
251     outSections(output, rodatasections)
252     outSections(output, datasections)
253     outSections(output, bsssections)
254
255     output.write("""
256                 freespace_start = . ;
257                 code32flat_end = ABSOLUTE(.) ;
258         }
259 """ + COMMONTRAILER)
260     return startat
261
262
263 ######################################################################
264 # Section garbage collection
265 ######################################################################
266
267 def getSectionsList(info, names):
268     out = []
269     for i in info[0]:
270         size, align, section = i
271         if section not in names:
272 #            print "gc", section
273             continue
274         out.append(i)
275     return out
276
277 # Find and keep the section associated with a symbol (if available).
278 def keepsymbol(symbol, infos, pos):
279     addr, section = infos[pos][1].get(symbol, (None, None))
280     if section is None or '*' in section or section[:9] == '.discard.':
281         return -1
282     keepsection(section, infos, pos)
283     return 0
284
285 # Note required section, and recursively set all referenced sections
286 # as required.
287 def keepsection(name, infos, pos=0):
288     if name in infos[pos][3]:
289         # Already kept - nothing to do.
290         return
291     infos[pos][3].append(name)
292     relocs = infos[pos][2].get(name)
293     if relocs is None:
294         return
295     # Keep all sections that this section points to
296     for symbol in relocs:
297         ret = keepsymbol(symbol, infos, pos)
298         if not ret:
299             continue
300         # Not in primary sections - it may be a cross 16/32 reference
301         ret = keepsymbol(symbol, infos, (pos+1)%3)
302         if not ret:
303             continue
304         ret = keepsymbol(symbol, infos, (pos+2)%3)
305         if not ret:
306             continue
307
308 # Determine which sections are actually referenced and need to be
309 # placed into the output file.
310 def gc(info16, info32seg, info32flat):
311     # infos = ((sections, symbols, relocs, keep sections), ...)
312     infos = ((info16[0], info16[1], info16[2], []),
313              (info32seg[0], info32seg[1], info32seg[2], []),
314              (info32flat[0], info32flat[1], info32flat[2], []))
315     # Start by keeping sections that are globally visible.
316     for size, align, section in info16[0]:
317         if section[:11] == '.fixedaddr.' or '.export.' in section:
318             keepsection(section, infos)
319     # Return sections found.
320     sections16 = getSectionsList(info16, infos[0][3])
321     sections32seg = getSectionsList(info32seg, infos[1][3])
322     sections32flat = getSectionsList(info32flat, infos[2][3])
323     return sections16, sections32seg, sections32flat
324
325
326 ######################################################################
327 # Startup and input parsing
328 ######################################################################
329
330 # Read in output from objdump
331 def parseObjDump(file):
332     # sections = [(size, align, section), ...]
333     sections = []
334     # symbols[symbol] = section
335     symbols = {}
336     # relocs[section] = [symbol, ...]
337     relocs = {}
338
339     state = None
340     for line in file.readlines():
341         line = line.rstrip()
342         if line == 'Sections:':
343             state = 'section'
344             continue
345         if line == 'SYMBOL TABLE:':
346             state = 'symbol'
347             continue
348         if line[:24] == 'RELOCATION RECORDS FOR [':
349             state = 'reloc'
350             relocsection = line[24:-2]
351             continue
352
353         if state == 'section':
354             try:
355                 idx, name, size, vma, lma, fileoff, align = line.split()
356                 if align[:3] != '2**':
357                     continue
358                 sections.append((int(size, 16), 2**int(align[3:]), name))
359             except:
360                 pass
361             continue
362         if state == 'symbol':
363             try:
364                 section, off, symbol = line[17:].split()
365                 off = int(off, 16)
366                 addr = int(line[:8], 16)
367                 symbols[symbol] = addr, section
368             except:
369                 pass
370             continue
371         if state == 'reloc':
372             try:
373                 off, type, symbol = line.split()
374                 off = int(off, 16)
375                 relocs.setdefault(relocsection, []).append(symbol)
376             except:
377                 pass
378     return sections, symbols, relocs
379
380 def main():
381     # Get output name
382     in16, in32seg, in32flat, out16, out32seg, out32flat = sys.argv[1:]
383
384     infile16 = open(in16, 'rb')
385     infile32seg = open(in32seg, 'rb')
386     infile32flat = open(in32flat, 'rb')
387
388     info16 = parseObjDump(infile16)
389     info32seg = parseObjDump(infile32seg)
390     info32flat = parseObjDump(infile32flat)
391
392     sections16, sections32seg, sections32flat = gc(info16, info32seg, info32flat)
393
394     start16 = doLayout16(sections16, out16)
395     start32seg = doLayout32seg(sections32seg, out32seg, start16)
396     doLayout32flat(sections32flat, out32flat, start32seg)
397
398 if __name__ == '__main__':
399     main()