[LinuxBIOS] lbrelocs - LinuxBIOSv3 re-linking tool

Jordan Crouse jordan.crouse at amd.com
Sat Oct 6 00:42:48 CEST 2007


Okay - based on my previous e-mail, attached is the custom tool that I
wrote to change the relative addresses of the initram binary so that it
can be placed at an arbitrary place in the ROM and still work.

To start with, we ask LD to emit the relocation info for the segment into
the ELF.  The relocation tables list everywhere in the binary where a 
symbol reference is made.  Using that information, we can easily walk
the table and adjust each all the relative offsets so that they will
work from a base address specified on the command line.

This tool just changes the code - a more complete solution would probably
rewrite the entire ELF file so the offset was correct - I think thats
as easy as re-writing the sh_addr for the sections, but I don't know for
sure.  I had to take a crash course in how relocs worked (thanks a million
to  Eric Biderman for arch/i386/boot/compressed/relocs.c in the Linux kernel
- it taught me much, and I borrowed some of the functions for this utility
too).

So, I put this out there as food for thought, and end with some instructions
on how to see it in action (I used the amd/norwich/ mainboard):

Modify the $(LD) command in mainboards/amd/norwich/Makefile to this:

$(Q)$(LD) -R$(obj)/stage0.o --emit-relocs -Ttext 0x0 $(INITRAM_OBJ)
--entry=main -o $(obj)/linuxbios.initram.o

This moves the text to 0x0 (makes the math easier for me to verify), and
emit the relocation tables.

Build the LinuxBIOS image.

Compile the tool, and run it:
lbrelocs -b <base address> linuxbios.initram.o

This command will adjust all the references in the code to the base address.
It will overwrite linuxbios.initram.o, so I recommend saving it off first
if you are going to play around.  Because we're not changing the actual
offsets of the sections, the disassembly will get confused, but you can
still do the math yourself to verify that the calls are correct.

If you are brave and have a geode, you can figure out the right address
to adjust the segment too, copy it to a binary, and rebuild it into a LAR
and see it boot.  

I'm rambling, so I'm going to leave you here.  Comments especially welcome,
if I can do something smarter or better, I'm very interested to hear about
it.  

Jordan

-- 
Jordan Crouse
Systems Software Development Engineer 
Advanced Micro Devices, Inc.
-------------- next part --------------
/* LinuxBIOS post-link fixup script for segments  
 * Copyright (C) 2007, Advanced Micro Devices, Inc.
 *
 * Techniques for reading the ELF file was borrowed from
 * arch/i386/boot/compressed/relocs.c
 * Mainly by Eric Biderman, but also others too.

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* What is happening here? 
 *
 * LinuxBIOSv3 functionality is divided into a series of independent segments,
 * each of which serves a different purpose and are linked independently of
 * each other.  LinuxBIOS also has a number of common symbols that are to 
 * be shared by each segment (such as printk).  The code for these will live
 * in the bootblock (at a known location), but need to be resolved for each
 * block.  This is easily done with the -R command line option for GNU ld.
 *
 * This is not a problem for segments that are compiled to run in memory,
 * since they will be loaded to whatever memory address they were linked for,
 * and all the relative function calls just work.
 *
 * This *is* a problem for segments that need to be executed in place from
 * the ROM (for the segments that run before memory is initialized).  These
 * ROMs are not located at a set location, they may be located anywhere on
 * the ROM (at the option of the LAR tool). 
 *
 * This utility takes an XIP module and adjusts it to run at a specified
 * location.  Thus the final location of the code need not be known at link
 * time.
 *
 * How does this work?  
 *
 * When the ELF is linked, we ask it to emit a series of tables known as
 * relocation tables, which point at all the locations in the the code
 * where we refer to symbols (like calls to functions).  Based on these
 * relocation tables, we can adjust each relative symbol reference to 
 * match the new execution address.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <elf.h>
#include <ctype.h>

/* We shouldn't see ELF headers with more then 64 sections -
the most I've seen to date is 12 */

#define MAXSHDRS 64
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

/* We use this structure to carry around the information for a given
   ELF file, including the mmap variables.  This is a little overkill
   when we read a single ELF file, but it gives us the flexiblity
   to read multiple ELF files if we need to 
*/

struct elffile {
	int fd;
	unsigned char *ptr;
	int size;

	Elf32_Ehdr *header;
	Elf32_Shdr *sects[MAXSHDRS];
	Elf32_Rel  *relocs[MAXSHDRS];
	Elf32_Sym  *symtab[MAXSHDRS];
	char *strtab[MAXSHDRS];
};

/* A verbose flag to increase the blather from printfs() */
static int verbose;

/* Borrowed from relocs.c - I liked the idea - print an error message
 and exit with an error.
*/

static void die(char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	exit(1);
}

/* Borrowed from relocs.c - return name of a section */

static const char *secname(struct elffile *file, unsigned int index)
{
	const char *stab;
	const char *name = "<noname>";

	/* Get the string table for the section headers */
	stab = file->strtab[file->header->e_shstrndx];

	/* If the index matches a real section in the ELF file,
	   then use sh_name to index into the string table above.
	   table.  Otherwise, its a standard section type, and we
	   a standard name
	*/

	if (index < file->header->e_shnum)
		name = stab + file->sects[index]->sh_name;
	else if (index == SHN_ABS)
		name = "ABSOLUTE";
	else if (index == SHN_COMMON)
		name = "COMMON";
	return name;
}
		
/* Borrowed from relocs.c - return name of a symbol */
       
static const char *getname(struct elffile *file, const char *strtab,
			   Elf32_Sym *sym)
{
	const char *name = "<noname>";

	/* If st_name is non zero, then it is an index into the string table
	   passed into the function.  If st_name is not set, then we need to
	   look up the section name instead 
	*/

	if (sym->st_name)
		name = strtab + sym->st_name;
	else
		name = secname(file, file->sects[sym->st_shndx]->sh_name);

	return name;
}

/* Parse the ELF header */

static void getheader(struct elffile *file) 
{	
	file->header = (Elf32_Ehdr *) file->ptr;
	
	if (memcmp(file->header->e_ident, ELFMAG, 4)) 
		die("File is not an ELF\n");

	if (file->header->e_ident[EI_CLASS] != ELFCLASS32)
		die("Not a 32 bit executable\n"); 
	       		
	if (file->header->e_ident[EI_DATA] != ELFDATA2LSB)
		die("Not a LSB ELF executable\n"); 
	
	if (file->header->e_ident[EI_VERSION] != EV_CURRENT)
		die("Unknown ELF version\n"); 

	/* We assume that we are on a LSB system for simplicity now,
	   but this is a stupid assumption and should be fixed 
	*/
}

/* Get the list of sections, and the individual tables from the ELF */

static void getsections(struct elffile *file)
{
	int i;
	int count = file->header->e_shnum;	
	int offset = file->header->e_shoff;

	if (count > MAXSHDRS) 
		die("Too many headers\n");
	
	/* We assume that we are on a LSB system for simplicity now,
	   but this is a stupid assumption and should be fixed 
	*/

	for(i = 0; i < count; i++) {
		file->sects[i] = (Elf32_Shdr *) (file->ptr + offset);
		offset += sizeof(Elf32_Shdr);

		/* We need to individually index the string tables,
		   the symbol tables, and the star of the show, the
		   relocation tables - so when we encounter
		   those, we set up the array pointers accordingly.
		   The mmap()ed file makes this very easy.
		*/

		switch(file->sects[i]->sh_type) {
		case SHT_STRTAB:
			file->strtab[i] = (char *)
				(file->ptr + file->sects[i]->sh_offset);
			break;

		case SHT_SYMTAB:
			file->symtab[i] = (Elf32_Sym *)
				(file->ptr + file->sects[i]->sh_offset);
			break;

		case SHT_REL:
			file->relocs[i] = (Elf32_Rel *)
				(file->ptr + file->sects[i]->sh_offset);
			break;
		}
	}
}
		
/* These are the most important function - this is where we walk
   the reloc tables, and fixup the symbol references.  By the
   time this function ends, the incoming binary should be completely
   ready to go
*/

static void fixupsection(struct elffile *file, int base, int index) 
{	
	int i;
	
	int section = file->sects[index]->sh_link;		
	Elf32_Sym *table = file->symtab[section];

	/* Get the base address of the symbol table */

	unsigned int symbase = file->sects[file->sects[index]->sh_info]->sh_addr;

	/* Get the file offset of the section */
	unsigned int foffset = file->sects[file->sects[index]->sh_info]->sh_offset;

	Elf32_Rel *rel = file->relocs[index];

	/* Walk the relocation table */
	
	for(i = 0; i < file->sects[index]->sh_size / sizeof(*rel); i++) {
		
		/* This is the relative offset of the reloc */
		unsigned int roffset = rel[i].r_offset - symbase;
		
		/* This is the actual location in the file where the reference is */
		unsigned int *fpos = (unsigned int *) (file->ptr + foffset + roffset);
		
		if (ELF32_R_TYPE(rel[i].r_info) == R_386_32) {
			/* For absolute values, we just update the value in place */
			*fpos = (*fpos - symbase) + base;
		}
		else if (ELF32_R_TYPE(rel[i].r_info) == R_386_PC32) {

			unsigned int offset;

			/* Get the actual symbol this entry indexes to */
			Elf32_Sym *sym = &table[ELF32_R_SYM(rel[i].r_info)];

			/* we only worry about the absolute values ("external functions") 
			   everything else should have the right offsets already */

			if (sym->st_shndx != SHN_ABS)
				continue;
					       			
			/* calculate the new relative offset of the symbol */
			offset = sym->st_value - (base + roffset);

			/* Update the value in the file */
			*fpos = 0xfffffffc + offset;
		}
	}
}

static void fixuprelocs(struct elffile *file, int base)
{
	int index;
	
	/* Go through the entire list of sections */

	for(index = 0; index < file->header->e_shnum; index++) {

		/* We only care about the relocation tables */

		if (file->sects[index]->sh_type != SHT_REL)
			continue;
		
		fixupsection(file, base, index);
	}
}

/* Borrowed from reloc.c - pretty print the relocation type */

static const char *reltype(unsigned type)
{
        static const char *type_name[] = {
#define REL_TYPE(X) [X] = #X
                REL_TYPE(R_386_NONE),
                REL_TYPE(R_386_32),
                REL_TYPE(R_386_PC32),
                REL_TYPE(R_386_GOT32),
                REL_TYPE(R_386_PLT32),
                REL_TYPE(R_386_COPY),
                REL_TYPE(R_386_GLOB_DAT),
                REL_TYPE(R_386_JMP_SLOT),
                REL_TYPE(R_386_RELATIVE),
                REL_TYPE(R_386_GOTOFF),
                REL_TYPE(R_386_GOTPC),
#undef REL_TYPE
	};
        const char *name = "UNKNOWN";

        if (type < ARRAY_SIZE(type_name)) {
                name = type_name[type];
        }
        return name;
}

/* This function shows the list of relocations - not useful in normal usage, but its good
   for debugging issues
*/

static void showrelocs(struct elffile *file) {

	int i, j;

	for(i = 0; i < file->header->e_shnum; i++) {
		
		char *sh_strtab;
		Elf32_Sym *sh_symtab;
		unsigned int sec_symtab;
		
		/* We only care about the relocation tables */

		if (file->sects[i]->sh_type != SHT_REL)
			continue;

		/* Get the symbol table index */

		sec_symtab = file->sects[i]->sh_link;

		if (!(file->sects[file->sects[i]->sh_info]->sh_flags & SHF_ALLOC))
			continue;

		/* Get the symbol table and string table */

		sh_symtab = file->symtab[sec_symtab];
		sh_strtab = file->strtab[file->sects[sec_symtab]->sh_link];

		for(j = 0; j < file->sects[i]->sh_size / sizeof(file->relocs[0][0]); j++) {

			Elf32_Rel *rel;
			Elf32_Sym *sym;
			const char *name;

			rel = &file->relocs[i][j];
			sym = &sh_symtab[ELF32_R_SYM(rel->r_info)];
			
			/* Get the name of the symbol */

			name = getname(file, sh_strtab, sym);

			/* Show it */

			printf("%d: %08x %08x %10s %08x %s\n",
			       sym->st_shndx,
			       rel->r_offset,
			       rel->r_info,
			       reltype(ELF32_R_TYPE(rel->r_info)),
			       sym->st_value,
			       name);
		}
	}	
}
	
/* This function opens an elf file, mmaps it, and parses the structures */

static int openelf(const char *filename, struct elffile *file)
{
	struct stat s;

	/* Open the file */

	file->fd = open(filename, O_RDWR);

	/* Get the size */

	if (fstat(file->fd, &s)) {
		fprintf(stderr, "Couldn't stat %s: %m\n", filename);
		close(file->fd);
		return -1;
	}
	
	file->size = s.st_size;

	/* Map the file */

	file->ptr = mmap(0, s.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0);

	if (file->ptr == MAP_FAILED) {
		fprintf(stderr, "Unable to map the file: %m\n");
		close(file->fd);
		return -1;
	}

	/* Parse the ELF bits */
	getheader(file);
	getsections(file);

	return 0;
}

/* This function closes the ELF file (writing back any changes) */

static void closeelf(struct elffile *file)
{
	if (file->fd > 0) {
		munmap(file->ptr, file->size);
		close(file->fd);
	}

	file->fd = 0;
}

struct elffile relocs;
			
int main(int argc, char **argv) 
{
	int frelocs = 0;
	unsigned int base = 0;

	/* Options:
	   -S - specify the symbols table to use when fixing up the relocations 
	   -v - be verbose
	   -r - Show the relocation tables in the image - for debugging
	   -b - Specify the base address for the initram (as a hex value)
	*/

	while(1) {
		signed char ch = getopt(argc, argv, "S:vrlb:");
		if (ch == -1) 
			break;

		switch(ch) {
		case 'b':
			base = strtoul(optarg, 0, 0);
		case 'v':
			verbose = 1;
			break;

		case 'r':
			frelocs=1;
			break;
		}
	} 

	
	if (optind >= argc) {
		printf("Error - you must specify an object file to fixup\n");
		return -1;
	}
	
	if (openelf(argv[optind], &relocs))
		return -1;

	else if (frelocs)
		showrelocs(&relocs);
	else {
		if (verbose)
			printf("Fixing up the object file %s (using base %x)\n", argv[optind], base);

		fixuprelocs(&relocs, base);
	}

	closeelf(&relocs);

	return 0;
}


More information about the coreboot mailing list