[efi] Add EFI image format and basic runtime environment
[people/sha0/gpxe.git] / src / util / efilink.c
1 #include <stdint.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <errno.h>
7 #include <bfd.h>
8
9 struct bfd_file {
10         bfd *bfd;
11         asymbol **symtab;
12         long symcount;
13 };
14
15 struct pe_relocs {
16         struct pe_relocs *next;
17         unsigned long start_rva;
18         unsigned int used_relocs;
19         unsigned int total_relocs;
20         uint16_t *relocs;
21 };
22
23 /**
24  * Allocate memory
25  *
26  * @v len               Length of memory to allocate
27  * @ret ptr             Pointer to allocated memory
28  */
29 static void * xmalloc ( size_t len ) {
30         void *ptr;
31
32         ptr = malloc ( len );
33         if ( ! ptr ) {
34                 fprintf ( stderr, "Could not allocate %zd bytes\n", len );
35                 exit ( 1 );
36         }
37
38         return ptr;
39 }
40
41 /**
42  * Generate entry in PE relocation table
43  *
44  * @v pe_reltab         PE relocation table
45  * @v rva               RVA
46  * @v size              Size of relocation entry
47  */
48 static void generate_pe_reloc ( struct pe_relocs **pe_reltab,
49                                 unsigned long rva, size_t size ) {
50         unsigned long start_rva;
51         uint16_t reloc;
52         struct pe_relocs *pe_rel;
53         uint16_t *relocs;
54
55         /* Construct */
56         start_rva = ( rva & ~0xfff );
57         reloc = ( rva & 0xfff );
58         switch ( size ) {
59         case 4:
60                 reloc |= 0x3000;
61                 break;
62         case 2:
63                 reloc |= 0x2000;
64                 break;
65         default:
66                 fprintf ( stderr, "Unsupported relocation size %zd\n", size );
67                 exit ( 1 );
68         }
69
70         /* Locate or create PE relocation table */
71         for ( pe_rel = *pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) {
72                 if ( pe_rel->start_rva == start_rva )
73                         break;
74         }
75         if ( ! pe_rel ) {
76                 pe_rel = xmalloc ( sizeof ( *pe_rel ) );
77                 memset ( pe_rel, 0, sizeof ( *pe_rel ) );
78                 pe_rel->next = *pe_reltab;
79                 *pe_reltab = pe_rel;
80                 pe_rel->start_rva = start_rva;
81         }
82
83         /* Expand relocation list if necessary */
84         if ( pe_rel->used_relocs < pe_rel->total_relocs ) {
85                 relocs = pe_rel->relocs;
86         } else {
87                 pe_rel->total_relocs = ( pe_rel->total_relocs ?
88                                          ( pe_rel->total_relocs * 2 ) : 256 );
89                 relocs = xmalloc ( pe_rel->total_relocs *
90                                    sizeof ( pe_rel->relocs[0] ) );
91                 memset ( relocs, 0,
92                          pe_rel->total_relocs * sizeof ( pe_rel->relocs[0] ) );
93                 memcpy ( relocs, pe_rel->relocs,
94                          pe_rel->used_relocs * sizeof ( pe_rel->relocs[0] ) );
95                 free ( pe_rel->relocs );
96                 pe_rel->relocs = relocs;
97         }
98
99         /* Store relocation */
100         pe_rel->relocs[ pe_rel->used_relocs++ ] = reloc;
101 }
102
103 /**
104  * Calculate size of binary PE relocation table
105  *
106  * @v pe_reltab         PE relocation table
107  * @v buffer            Buffer to contain binary table, or NULL
108  * @ret size            Size of binary table
109  */
110 static size_t output_pe_reltab ( struct pe_relocs *pe_reltab,
111                                  void *buffer ) {
112         struct pe_relocs *pe_rel;
113         unsigned int num_relocs;
114         size_t size;
115         size_t total_size = 0;
116
117         for ( pe_rel = pe_reltab ; pe_rel ; pe_rel = pe_rel->next ) {
118                 num_relocs = ( ( pe_rel->used_relocs + 1 ) & ~1 );
119                 size = ( sizeof ( uint32_t ) /* VirtualAddress */ +
120                          sizeof ( uint32_t ) /* SizeOfBlock */ +
121                          ( num_relocs * sizeof ( uint16_t ) ) );
122                 if ( buffer ) {
123                         *( (uint32_t *) ( buffer + total_size + 0 ) )
124                                 = pe_rel->start_rva;
125                         *( (uint32_t *) ( buffer + total_size + 4 ) ) = size;
126                         memcpy ( ( buffer + total_size + 8 ), pe_rel->relocs,
127                                  ( num_relocs * sizeof ( uint16_t ) ) );
128                 }
129                 total_size += size;
130         }
131
132         return total_size;
133 }
134
135 /**
136  * Read symbol table
137  *
138  * @v bfd               BFD file
139  */
140 static void read_symtab ( struct bfd_file *bfd ) {
141         long symtab_size;
142
143         /* Get symbol table size */
144         symtab_size = bfd_get_symtab_upper_bound ( bfd->bfd );
145         if ( symtab_size < 0 ) {
146                 bfd_perror ( "Could not get symbol table upper bound" );
147                 exit ( 1 );
148         }
149
150         /* Allocate and read symbol table */
151         bfd->symtab = xmalloc ( symtab_size );
152         bfd->symcount = bfd_canonicalize_symtab ( bfd->bfd, bfd->symtab );
153         if ( bfd->symcount < 0 ) {
154                 bfd_perror ( "Cannot read symbol table" );
155                 exit ( 1 );
156         }
157 }
158
159 /**
160  * Read relocation table
161  *
162  * @v bfd               BFD file
163  * @v section           Section
164  * @v symtab            Symbol table
165  * @ret reltab          Relocation table
166  */
167 static arelent ** read_reltab ( struct bfd_file *bfd, asection *section ) {
168         long reltab_size;
169         arelent **reltab;
170         long numrels;
171
172         /* Get relocation table size */
173         reltab_size = bfd_get_reloc_upper_bound ( bfd->bfd, section );
174         if ( reltab_size < 0 ) {
175                 bfd_perror ( "Could not get relocation table upper bound" );
176                 exit ( 1 );
177         }
178
179         /* Allocate and read relocation table */
180         reltab = xmalloc ( reltab_size );
181         numrels = bfd_canonicalize_reloc ( bfd->bfd, section, reltab,
182                                            bfd->symtab );
183         if ( numrels < 0 ) {
184                 bfd_perror ( "Cannot read relocation table" );
185                 exit ( 1 );
186         }
187
188         return reltab;
189 }
190
191
192 /**
193  * Open input BFD file
194  *
195  * @v filename          File name
196  * @ret ibfd            BFD file
197  */
198 static struct bfd_file * open_input_bfd ( const char *filename ) {
199         struct bfd_file *ibfd;
200
201         /* Create BFD file */
202         ibfd = xmalloc ( sizeof ( *ibfd ) );
203         memset ( ibfd, 0, sizeof ( *ibfd ) );
204
205         /* Open the file */
206         ibfd->bfd = bfd_openr ( filename, NULL );
207         if ( ! ibfd->bfd ) {
208                 fprintf ( stderr, "Cannot open %s: ", filename );
209                 bfd_perror ( NULL );
210                 exit ( 1 );
211         }
212
213         /* The call to bfd_check_format() must be present, otherwise
214          * we get a segfault from later BFD calls.
215          */
216         if ( bfd_check_format ( ibfd->bfd, bfd_object ) < 0 ) {
217                 fprintf ( stderr, "%s is not an object file\n", filename );
218                 exit ( 1 );
219         }
220
221         /* Read symbols and relocation entries */
222         read_symtab ( ibfd );
223
224         return ibfd;
225 }
226
227 /**
228  * Open output BFD file
229  *
230  * @v filename          File name
231  * @v ibfd              Input BFD file
232  * @ret obfd            BFD file
233  */
234 static struct bfd_file * open_output_bfd ( const char *filename,
235                                            struct bfd_file *ibfd ) {
236         struct bfd_file *obfd;
237         asection *isection;
238         asection *osection;
239
240         /*
241          * Most of this code is based on what objcopy.c does.
242          *
243          */
244
245         /* Create BFD file */
246         obfd = xmalloc ( sizeof ( *obfd ) );
247         memset ( obfd, 0, sizeof ( *obfd ) );
248
249         /* Open the file */
250         obfd->bfd = bfd_openw ( filename, ibfd->bfd->xvec->name );
251         if ( ! obfd->bfd ) {
252                 fprintf ( stderr, "Cannot open %s: ", filename );
253                 bfd_perror ( NULL );
254                 exit ( 1 );
255         }
256
257         /* Copy per-file data */
258         if ( ! bfd_set_arch_mach ( obfd->bfd, bfd_get_arch ( ibfd->bfd ),
259                                    bfd_get_mach ( ibfd->bfd ) ) ) {
260                 bfd_perror ( "Cannot copy architecture" );
261                 exit ( 1 );
262         }
263         if ( ! bfd_set_format ( obfd->bfd, bfd_get_format ( ibfd->bfd ) ) ) {
264                 bfd_perror ( "Cannot copy format" );
265                 exit ( 1 );
266         }
267         if ( ! bfd_copy_private_header_data ( ibfd->bfd, obfd->bfd ) ) {
268                 bfd_perror ( "Cannot copy private header data" );
269                 exit ( 1 );
270         }
271
272         /* Create sections */
273         for ( isection = ibfd->bfd->sections ; isection ;
274               isection = isection->next ) {
275                 osection = bfd_make_section_anyway ( obfd->bfd,
276                                                      isection->name );
277                 if ( ! osection ) {
278                         bfd_perror ( "Cannot create section" );
279                         exit ( 1 );
280                 }
281                 if ( ! bfd_set_section_flags ( obfd->bfd, osection,
282                                                isection->flags ) ) {
283                         bfd_perror ( "Cannot copy section flags" );
284                         exit ( 1 );
285                 }
286                 if ( ! bfd_set_section_size ( obfd->bfd, osection,
287                                  bfd_section_size ( ibfd->bfd, isection ) ) ) {
288                         bfd_perror ( "Cannot copy section size" );
289                         exit ( 1 );
290                 }
291                 if ( ! bfd_set_section_vma ( obfd->bfd, osection,
292                                   bfd_section_vma ( ibfd->bfd, isection ) ) ) {
293                         bfd_perror ( "Cannot copy section VMA" );
294                         exit ( 1 );
295                 }
296                 osection->lma = bfd_section_lma ( ibfd->bfd, isection );
297                 if ( ! bfd_set_section_alignment ( obfd->bfd, osection,
298                             bfd_section_alignment ( ibfd->bfd, isection ) ) ) {
299                         bfd_perror ( "Cannot copy section alignment" );
300                         exit ( 1 );
301                 }
302                 osection->entsize = isection->entsize;
303                 isection->output_section = osection;
304                 isection->output_offset = 0;
305                 if ( ! bfd_copy_private_section_data ( ibfd->bfd, isection,
306                                                        obfd->bfd, osection ) ){
307                         bfd_perror ( "Cannot copy section private data" );
308                         exit ( 1 );
309                 }
310         }
311
312         /* Copy symbol table */
313         bfd_set_symtab ( obfd->bfd, ibfd->symtab, ibfd->symcount );
314         obfd->symtab = ibfd->symtab;
315
316         return obfd;
317 }
318
319 /**
320  * Copy section from input BFD file to output BFD file
321  *
322  * @v obfd              Output BFD file
323  * @v ibfd              Input BFD file
324  * @v section           Section
325  */
326 static void copy_bfd_section ( struct bfd_file *obfd, struct bfd_file *ibfd,
327                                asection *isection ) {
328         size_t size;
329         void *buf;
330         arelent **reltab;
331         arelent **rel;
332         char *errmsg;
333
334         /* Read in original section */
335         size = bfd_section_size ( ibfd->bfd, isection );
336         if ( ! size )
337                 return;
338         buf = xmalloc ( size );
339         if ( ( ! bfd_get_section_contents ( ibfd->bfd, isection,
340                                             buf, 0, size ) ) ) {
341                 fprintf ( stderr, "Cannot read section %s: ", isection->name );
342                 bfd_perror ( NULL );
343                 exit ( 1 );
344         }
345
346         /* Perform relocations.  We do this here, rather than letting
347          * ld do it for us when creating the input ELF file, so that
348          * we can change symbol values as a result of having created
349          * the .reloc section.
350          */
351         reltab = read_reltab ( ibfd, isection );
352         for ( rel = reltab ; *rel ; rel++ ) {
353                 bfd_perform_relocation ( ibfd->bfd, *rel, buf, isection,
354                                          NULL, &errmsg );
355         }
356         free ( reltab );
357
358         /* Write out modified section */
359         if ( ( ! bfd_set_section_contents ( obfd->bfd,
360                                             isection->output_section,
361                                             buf, 0, size ) ) ) {
362                 fprintf ( stderr, "Cannot write section %s: ",
363                           isection->output_section->name );
364                 bfd_perror ( NULL );
365                 exit ( 1 );
366         }
367
368         free ( buf );
369 }
370
371 /**
372  * Process relocation record
373  *
374  * @v section           Section
375  * @v rel               Relocation entry
376  * @v pe_reltab         PE relocation table to fill in
377  */
378 static void process_reloc ( asection *section, arelent *rel,
379                             struct pe_relocs **pe_reltab ) {
380         reloc_howto_type *howto = rel->howto;
381         asymbol *sym = *(rel->sym_ptr_ptr);
382         unsigned long offset = ( section->lma + rel->address );
383
384         if ( bfd_is_abs_section ( sym->section ) ) {
385                 /* Skip absolute symbols; the symbol value won't
386                  * change when the object is loaded.
387                  */
388         } else if ( strcmp ( howto->name, "R_386_32" ) == 0 ) {
389                 /* Generate a 4-byte PE relocation */
390                 generate_pe_reloc ( pe_reltab, offset, 4 );
391         } else if ( strcmp ( howto->name, "R_386_16" ) == 0 ) {
392                 /* Generate a 2-byte PE relocation */
393                 generate_pe_reloc ( pe_reltab, offset, 2 );
394         } else if ( strcmp ( howto->name, "R_386_PC32" ) == 0 ) {
395                 /* Skip PC-relative relocations; all relative offsets
396                  * remain unaltered when the object is loaded.
397                  */
398         } else {
399                 fprintf ( stderr, "Unrecognised relocation type %s\n",
400                           howto->name );
401                 exit ( 1 );
402         }
403 }
404
405 /**
406  * Create .reloc section
407  *
408  * obfd                 Output BFD file
409  * section              .reloc section in output file
410  * pe_reltab            PE relocation table
411  */
412 static void create_reloc_section ( struct bfd_file *obfd, asection *section,
413                                    struct pe_relocs *pe_reltab ) {
414         size_t raw_size;
415         size_t size;
416         size_t old_size;
417         void *buf;
418         asymbol **sym;
419
420         /* Build binary PE relocation table */
421         raw_size = output_pe_reltab ( pe_reltab, NULL );
422         size = ( ( raw_size + 31 ) & ~31 );
423         buf = xmalloc ( size );
424         memset ( buf, 0, size );
425         output_pe_reltab ( pe_reltab, buf );
426
427         /* Write .reloc section */
428         old_size = bfd_section_size ( obfd->bfd, section );
429         if ( ! bfd_set_section_size ( obfd->bfd, section, size ) ) {
430                 bfd_perror ( "Cannot resize .reloc section" );
431                 exit ( 1 );
432         }
433         if ( ! bfd_set_section_contents ( obfd->bfd, section,
434                                           buf, 0, size ) ) {
435                 bfd_perror ( "Cannot set .reloc section contents" );
436                 exit ( 1 );
437         }
438
439         /* Update symbols pertaining to the relocation directory */
440         for ( sym = obfd->symtab ; *sym ; sym++ ) {
441                 if ( strcmp ( (*sym)->name, "_reloc_memsz" ) == 0 ) {
442                         (*sym)->value = size;
443                 } else if ( strcmp ( (*sym)->name, "_reloc_filesz" ) == 0 ){
444                         (*sym)->value = raw_size;
445                 } else if ( strcmp ( (*sym)->name, "_filesz" ) == 0 ) {
446                         (*sym)->value += ( size - old_size );
447                 }
448         }
449 }
450
451 int main ( int argc, const char *argv[] ) {
452         const char *iname;
453         const char *oname;
454         struct bfd_file *ibfd;
455         struct bfd_file *obfd;
456         asection *section;
457         arelent **reltab;
458         arelent **rel;
459         struct pe_relocs *pe_reltab = NULL;
460         asection *reloc_section;
461
462         /* Initialise libbfd */
463         bfd_init();
464
465         /* Identify intput and output files */
466         if ( argc != 3 ) {
467                 fprintf ( stderr, "Syntax: %s infile outfile\n", argv[0] );
468                 exit ( 1 );
469         }
470         iname = argv[1];
471         oname = argv[2];
472
473         /* Open BFD files */
474         ibfd = open_input_bfd ( iname );
475         obfd = open_output_bfd ( oname, ibfd );
476
477         /* Process relocations in all sections */
478         for ( section = ibfd->bfd->sections ; section ;
479               section = section->next ) {
480                 reltab = read_reltab ( ibfd, section );
481                 for ( rel = reltab ; *rel ; rel++ ) {
482                         process_reloc ( section, *rel, &pe_reltab );
483                 }
484                 free ( reltab );
485         }
486
487         /* Create modified .reloc section */
488         reloc_section = bfd_get_section_by_name ( obfd->bfd, ".reloc" );
489         if ( ! reloc_section ) {
490                 fprintf ( stderr, "Cannot find .reloc section\n" );
491                 exit ( 1 );
492         }
493         create_reloc_section ( obfd, reloc_section, pe_reltab );
494
495         /* Copy other section contents */
496         for ( section = ibfd->bfd->sections ; section ;
497               section = section->next ) {
498                 if ( section->output_section != reloc_section )
499                         copy_bfd_section ( obfd, ibfd, section );
500         }
501
502         /* Write out files and clean up */
503         bfd_close ( obfd->bfd );
504         bfd_close ( ibfd->bfd );
505
506         return 0;
507 }