1/*
2 * Copyright (c) 2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <fcntl.h>
27#include <sys/types.h>
28#include <sys/uio.h>
29#include <unistd.h>
30#include "stuff/bool.h"
31#include "stuff/ofile.h"
32#include "stuff/errors.h"
33#include "stuff/reloc.h"
34#include "coff/base_relocs.h"
35#include "coff/bytesex.h"
36#include "mach-o/x86_64/reloc.h"
37
38/* used by error routines as the name of this program */
39char *progname = NULL;
40
41/* used for debugging this program */
42static enum bool verbose = FALSE;
43
44/* the bytesex of our target object file and of this host machine */
45static enum byte_sex target_byte_sex;
46static enum byte_sex host_byte_sex;
47
48/*
49 * This is the internal structure that we gather the base relocation in from
50 * the Mach-O relocation entries.
51 */
52struct base_reloc {
53    uint64_t addr;
54    uint32_t type;
55};
56struct base_reloc *base_relocs = NULL;
57uint32_t nbase_reloc = 0;
58
59static void process(
60    struct ofile *ofile,
61    char *arch_name,
62    void *cookie);
63static void gather_base_reloc_info(
64    uint32_t addr,
65    struct relocation_info *relocs,
66    uint32_t nreloc,
67    cpu_type_t cpu_type,
68    uint32_t length,
69    int macho_reloc_type,
70    int base_reloc_type);
71static void add_base_reloc(
72    uint64_t addr,
73    uint32_t type);
74static void output_base_relocs(
75    char *out);
76static int cmp_base_relocs(
77    struct base_reloc *x1,
78    struct base_reloc *x2);
79static void usage(
80    void);
81
82/* apple_version is created by the libstuff/Makefile */
83extern char apple_version[];
84char *version = apple_version;
85
86/*
87 * The makerelocs(1) tool makes a file of PECOFF base relocation entries from a
88 * fully linked Mach-O file compiled with dynamic code gen and relocation
89 * entries saved (linked with -r).  The file of PECOFF base relocation entries
90 * then are put in a Mach-O section called .reloc and is then used with
91 * objcopy(1) to convert that Mach-O file into a PECOFF file. The makerelocs(1)
92 * program has the current usage:
93 *
94 *	makerelocs [-v] input_Mach-O output_relocs
95 *
96 * Where the -v flag provides verbose output used to debug the this programs
97 * creation of the PECOFF base relocation entries.
98 *
99 * TODO: This code can be used for the basis to replace the makerelocs(1)
100 * program from the efitools project.  Its current state is that it works for
101 * building the efiboot project and the Bluetooth.efi hardware diag.
102 */
103int
104main(
105int argc,
106char **argv,
107char **envp)
108{
109    int i;
110    char *input, *output;
111
112	progname = argv[0];
113	host_byte_sex = get_host_byte_sex();
114
115	input = NULL;
116	output = NULL;
117
118	for(i = 1; i < argc; i++){
119	    if(strcmp(argv[i], "-v") == 0)
120		verbose = TRUE;
121	    else if(input == NULL)
122		input = argv[i];
123	    else if(output == NULL)
124		output = argv[i];
125	    else
126		usage();
127	}
128	if(input == NULL){
129	    warning("no input file specified");
130	    usage();
131	}
132	if(output == NULL){
133	    warning("no output file specified");
134	    usage();
135	}
136
137	ofile_process(input, NULL, 0, FALSE, FALSE, FALSE, FALSE,
138		      process, NULL);
139	if(errors != 0)
140	    return(EXIT_FAILURE);
141
142	/* create the output file */
143	output_base_relocs(output);
144
145	if(errors == 0)
146	    return(EXIT_SUCCESS);
147	else
148	    return(EXIT_FAILURE);
149}
150
151/*
152 * process() is the routine that gets called by ofile_process() to process the
153 * object file and gather the info to create the base relocation entries.
154 */
155static
156void
157process(
158struct ofile *ofile,
159char *arch_name,
160void *cookie)
161{
162    uint32_t ncmds, i, j;
163    uint64_t addr, first_addr;
164    struct load_command *lc;
165    struct segment_command *sg;
166    struct segment_command_64 *sg64;
167    struct section *s;
168    struct section_64 *s64;
169    enum bool swapped;
170    struct relocation_info *relocs;
171
172    struct symtab_command *st;
173    struct dysymtab_command *dyst;
174    struct nlist *symbols;
175    struct nlist_64 *symbols64;
176
177	st = NULL;
178	dyst = NULL;
179	swapped = host_byte_sex != ofile->object_byte_sex;
180	target_byte_sex = ofile->object_byte_sex;
181	if(ofile->mh != NULL)
182	    ncmds = ofile->mh->ncmds;
183	else
184	    ncmds = ofile->mh64->ncmds;
185
186	lc = ofile->load_commands;
187	for(i = 0; i < ncmds; i++){
188	    if(st == NULL && lc->cmd == LC_SYMTAB){
189		st = (struct symtab_command *)lc;
190	    }
191	    else if(dyst == NULL && lc->cmd == LC_DYSYMTAB){
192		dyst = (struct dysymtab_command *)lc;
193	    }
194	    lc = (struct load_command *)((char *)lc + lc->cmdsize);
195	}
196	/* TODO this would be need to to do checking for undefined symbols */
197	if(ofile->mh != NULL){
198	    symbols = (struct nlist *)(ofile->object_addr + st->symoff);
199	    if(swapped)
200		swap_nlist(symbols, st->nsyms, host_byte_sex);
201	    symbols64 = NULL;
202	}
203	else{
204	    symbols = NULL;
205	    symbols64 = (struct nlist_64 *)(ofile->object_addr +st->symoff);
206	    if(swapped)
207		swap_nlist_64(symbols64, st->nsyms, host_byte_sex);
208	}
209
210	first_addr = 0;
211	lc = ofile->load_commands;
212	for(i = 0; i < ncmds; i++){
213	    if(lc->cmd == LC_SEGMENT){
214		sg = (struct segment_command *)lc;
215		if(first_addr == 0)
216		    first_addr = sg->vmaddr;
217		s = (struct section *)
218		      ((char *)sg + sizeof(struct segment_command));
219		for(j = 0; j < sg->nsects; j++){
220		    relocs = (struct relocation_info *)(ofile->object_addr +
221						        s[j].reloff);
222		    if(swapped)
223			swap_relocation_info(relocs, s[j].nreloc,
224					     host_byte_sex);
225		    if(ofile->mh_cputype == CPU_TYPE_I386)
226			gather_base_reloc_info(s[j].addr, relocs, s[j].nreloc,
227				CPU_TYPE_I386, 2, GENERIC_RELOC_VANILLA,
228				IMAGE_REL_BASED_HIGHLOW);
229		    else if(ofile->mh_cputype == CPU_TYPE_ARM)
230			gather_base_reloc_info(s[j].addr, relocs, s[j].nreloc,
231				CPU_TYPE_ARM, 2, GENERIC_RELOC_VANILLA,
232				IMAGE_REL_BASED_HIGHLOW);
233		    else
234			fatal("unsupported cputype %d", ofile->mh_cputype);
235		    if((s[j].flags & SECTION_TYPE) ==
236			S_NON_LAZY_SYMBOL_POINTERS){
237			for(addr = s[j].addr;
238			    addr < s[j].addr + s[j].size;
239			    addr += 4) {
240			    add_base_reloc(addr, IMAGE_REL_BASED_HIGHLOW);
241			}
242		    }
243		}
244	    }
245	    else if(lc->cmd == LC_SEGMENT_64){
246		sg64 = (struct segment_command_64 *)lc;
247		if(first_addr == 0)
248		    first_addr = sg64->vmaddr;
249		s64 = (struct section_64 *)
250		      ((char *)sg64 + sizeof(struct segment_command_64));
251		for(j = 0; j < sg64->nsects; j++){
252		    relocs = (struct relocation_info *)(ofile->object_addr +
253						        s64[j].reloff);
254		    if(swapped)
255			swap_relocation_info(relocs, s64[j].nreloc,
256					     host_byte_sex);
257		    if(ofile->mh_cputype == CPU_TYPE_X86_64)
258			gather_base_reloc_info(s64[j].addr, relocs,
259			    s64[j].nreloc, CPU_TYPE_X86_64, 3,
260			    X86_64_RELOC_UNSIGNED, IMAGE_REL_BASED_DIR64);
261		    else
262			fatal("unsupported cputype %d", ofile->mh_cputype);
263		    if((s64[j].flags & SECTION_TYPE) ==
264			S_NON_LAZY_SYMBOL_POINTERS){
265			for(addr = s64[j].addr;
266			    addr < s64[j].addr + s64[j].size;
267			    addr += 8) {
268			    add_base_reloc(addr, IMAGE_REL_BASED_DIR64);
269			}
270		    }
271		}
272	    }
273	    lc = (struct load_command *)((char *)lc + lc->cmdsize);
274	}
275	if(dyst != NULL && dyst->nlocrel != 0){
276	    relocs = (struct relocation_info *)(ofile->object_addr +
277						dyst->locreloff);
278	    if(swapped)
279		swap_relocation_info(relocs, dyst->nlocrel, host_byte_sex);
280	    if(ofile->mh_cputype == CPU_TYPE_I386)
281		gather_base_reloc_info(first_addr, relocs, dyst->nlocrel,
282		    CPU_TYPE_I386, 2, GENERIC_RELOC_VANILLA,
283		    IMAGE_REL_BASED_HIGHLOW);
284	    if(ofile->mh_cputype == CPU_TYPE_ARM)
285		gather_base_reloc_info(first_addr, relocs, dyst->nlocrel,
286		    CPU_TYPE_ARM, 2, GENERIC_RELOC_VANILLA,
287		    IMAGE_REL_BASED_HIGHLOW);
288	    else if(ofile->mh_cputype == CPU_TYPE_X86_64)
289		gather_base_reloc_info(first_addr, relocs, dyst->nlocrel,
290		    CPU_TYPE_X86_64, 3, X86_64_RELOC_UNSIGNED,
291		    IMAGE_REL_BASED_DIR64);
292	    else
293		fatal("unsupported cputype %d", ofile->mh_cputype);
294	}
295	if(dyst != NULL && dyst->nextrel != 0)
296	    fatal("input Mach-O file has external relocation entries");
297}
298
299/*
300 * gather_base_reloc_info() is passed the base address for the set of Mach-O
301 * relocation entries. And is passed the cpu_type, length and macho_reloc_type
302 * to look for and the base_reloc_type to create if found.
303 */
304static
305void
306gather_base_reloc_info(
307uint32_t addr,
308struct relocation_info *relocs,
309uint32_t nreloc,
310cpu_type_t cpu_type,
311uint32_t length,
312int macho_reloc_type,
313int base_reloc_type)
314{
315    uint32_t i, r_address, r_pcrel, r_length, r_extern, r_type;
316    struct scattered_relocation_info *sreloc;
317
318	for(i = 0; i < nreloc; i++){
319	    if((relocs[i].r_address & R_SCATTERED) != 0){
320		sreloc = (struct scattered_relocation_info *)(relocs + i);
321		r_address = sreloc->r_address;
322		r_pcrel = sreloc->r_pcrel;
323		r_length = sreloc->r_length;
324		r_type = (enum reloc_type_generic)sreloc->r_type;
325		r_extern = 0;
326	    }
327	    else{
328		r_address = relocs[i].r_address;
329		r_pcrel = relocs[i].r_pcrel;
330		r_length = relocs[i].r_length;
331		r_extern = relocs[i].r_extern;
332		r_type = (enum reloc_type_generic)relocs[i].r_type;
333	    }
334
335	    if(r_extern == 0 && r_pcrel == 0 &&
336	       r_length == length && r_type == macho_reloc_type)
337		add_base_reloc(addr + r_address, base_reloc_type);
338	    else
339	    	; /* TODO add checking and error messages here */
340
341	    if((relocs[i].r_address & R_SCATTERED) == 0){
342		if(reloc_has_pair(cpu_type, relocs[i].r_type))
343		    i++;
344	    }
345	    else{
346		sreloc = (struct scattered_relocation_info *)relocs + i;
347		if(reloc_has_pair(cpu_type, sreloc->r_type))
348		    i++;
349	    }
350	}
351}
352
353/*
354 * add_base_reloc() is passed a addr and a type for a base relocation entry to
355 * add to the list.
356 */
357static
358void
359add_base_reloc(
360uint64_t addr,
361uint32_t type)
362{
363    static int max = 0;
364    struct base_reloc *new_base_relocs;
365
366	if(!max){
367	    max = 128;
368	    base_relocs = (struct base_reloc *)
369			  malloc(max * sizeof(struct base_reloc));
370	}
371	if(nbase_reloc >= max){
372	    new_base_relocs = malloc(2 * max * sizeof(struct base_reloc));
373	    memcpy(new_base_relocs, base_relocs,
374		   max * sizeof(struct base_reloc));
375	    max *= 2;
376	    free(base_relocs);
377	    base_relocs = new_base_relocs;
378	}
379	base_relocs[nbase_reloc].addr = addr;
380        base_relocs[nbase_reloc].type = type;
381	nbase_reloc++;
382}
383
384/*
385 * usage() prints the current usage message and exits indicating failure.
386 */
387static
388void
389usage(
390void)
391{
392	fprintf(stderr, "Usage: %s [-v] input_Mach-O output_relocs\n",
393		progname);
394	exit(EXIT_FAILURE);
395}
396
397/*
398 * The base relocation table in a PECOFF file is divided into blocks. Each
399 * block represents the base relocations for a 4K page. Each block must start
400 * on a 32-bit boundary.  Which is why one "nop" base relocation entry may be
401 * be added as padding in a block.
402 */
403#define MAX_BLOCK_OFFSET 0x1000
404#define BLOCK_MASK (MAX_BLOCK_OFFSET-1)
405
406/*
407 * output_base_relocs() takes the info for the base relocation entries gathered
408 * and outputs the fixup blocks as they would be in a PECOFF file in to the
409 * specified file name.
410 */
411static
412void
413output_base_relocs(
414char *out)
415{
416    int blockcnt;
417    int i, entries;
418    uint64_t base;
419    int size;
420    char *fb;
421    struct base_relocation_block_header *h;
422    struct base_relocation_entry *b;
423    int f;
424    uint32_t offset;
425    enum bool swapped;
426
427	blockcnt = 0;
428	swapped = host_byte_sex != target_byte_sex;
429
430	if(nbase_reloc == 0)
431	    goto done;
432
433	qsort(base_relocs, nbase_reloc, sizeof(struct base_reloc),
434	      (int (*)(const void *, const void *))cmp_base_relocs);
435
436	/*
437	 * The size of the base relocation tables must be a multiple of 4 bytes.
438	 * so we may need to add one relocation entry as padding.  We make this
439	 * fixup block large enought to hold all the base relocation entries.
440	 * But it will be broken up for the base relocation entries for each
441	 * each group that refers to the same 4K page.
442	 */
443	size = sizeof(struct base_relocation_block_header) +
444	       (nbase_reloc + 1) * sizeof(struct base_relocation_entry);
445	fb = malloc(size);
446
447	f = open(out, O_WRONLY|O_CREAT|O_TRUNC, 0644);
448	if(f == -1){
449	    fatal("open output file");
450	}
451
452	entries = 0;
453	base = base_relocs[0].addr & ~BLOCK_MASK;
454	h = (struct base_relocation_block_header *)fb;
455	b = (struct base_relocation_entry *)
456	    (fb + sizeof(struct base_relocation_block_header));
457	for(i = 0; i < nbase_reloc; i++){
458	    offset = base_relocs[i].addr - base;
459	    if(offset >= MAX_BLOCK_OFFSET) {
460		/* add padding if needed */
461		if((entries % 2) != 0){
462		    b[entries].type = IMAGE_REL_BASED_ABSOLUTE;
463		    b[entries].offset = 0;
464		    entries++;
465		}
466		h->page_rva = base;
467		size = sizeof(struct base_relocation_block_header) +
468		       entries * sizeof(struct base_relocation_entry);
469		h->block_size = size;
470		if(swapped){
471		    swap_base_relocation_block_header(h,
472						      target_byte_sex);
473		    swap_base_relocation_entry(b, entries,
474					       target_byte_sex);
475		}
476		// write out the block then start a new one
477		write(f, fb, size);
478
479		entries = 0;
480		blockcnt++;
481		base = base_relocs[i].addr & ~BLOCK_MASK;
482		offset = base_relocs[i].addr - base;
483	    }
484	    b[entries].type = base_relocs[i].type;
485	    b[entries].offset = offset;
486	    entries++;
487	}
488
489	/* add padding if needed */
490	if((entries % 2) != 0){
491	    b[entries].type = IMAGE_REL_BASED_ABSOLUTE;
492	    b[entries].offset = 0;
493	    entries++;
494	}
495	h->page_rva = base;
496	size = sizeof(struct base_relocation_block_header) +
497	       entries * sizeof(struct base_relocation_entry);
498	h->block_size = size;
499	if(swapped){
500	    swap_base_relocation_block_header(h, target_byte_sex);
501	    swap_base_relocation_entry(b, entries, target_byte_sex);
502	}
503	/* write out the last block */
504	write(f, fb, size);
505
506	blockcnt++;
507	close(f);
508done:
509	printf("wrote %d relocations in %d block%s\n", nbase_reloc, blockcnt,
510	       blockcnt == 1 ? "" : "s");
511}
512
513static
514int
515cmp_base_relocs(
516struct base_reloc *x1,
517struct base_reloc *x2)
518{
519	if(x1->addr < x2->addr)
520	    return(-1);
521	if(x1->addr == x2->addr)
522	    return(0);
523	/* x1->addr > x2->addr */
524	    return(1);
525}
526