/* $NetBSD: rmreloc.c,v 1.3 2007/03/04 05:59:07 christos Exp $ */

/*
 * Copyright 1996 John D. Polstra.
 * Copyright 1996 Matt Thomas <matt@3am-software.com>
 * Copyright 2002 Charles M. Hannum <root@ihack.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by John Polstra.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * rmreloc.c - relocate an ELFish RISC OS relocatable module.
 */
/*
 * This code is a heavily hacked version of parts of:
 * lib/libexec/ld.elf_so/headers.c
 * lib/libexec/ld.elf_so/arch/arm/mdreloc.c
 *
 * At present it only deals with DT_REL tables containing R_ARM_NONE
 * and R_ARM_RELATIVE relocations, because those are all that my
 * linker emits.  More can be added as needed.  Note that this has to
 * handle relocating already-relocated code, e.g. after *RMTidy, so
 * most relocations have to reference oldbase, which ld.elf_so just
 * assumes is zero.  There may be a cleverer way to do this.
 */

#include <sys/types.h>
#include <sys/stdint.h>
#include <lib/libsa/stand.h>
#define ELFSIZE 32
#include <sys/exec_elf.h>

#include <riscoscalls.h>

os_error *relocate_self(Elf_Dyn *, void *, void *);

#define assert(x) /* nothing */

/*
 * While relocating ourselves, we must not refer to any global variables.
 * This includes _DYNAMIC -- the startup code finds it for us and passes
 * it to us along with the base address of the module.
 */

typedef struct {
	void *        relocbase;	/* Reloc const = mapbase - *vaddrbase */
	Elf_Dyn        *dynamic;	/* Dynamic section */
	const Elf_Rel  *rel;		/* Relocation entries */
	const Elf_Rel  *rellim;		/* Limit of Relocation entries */
} Obj_Entry;

#define rdbg(x) /* nothing */

/*
 * It is possible for the compiler to emit relocations for unaligned data.
 * We handle this situation with these inlines.
 */
#define	RELOC_ALIGNED_P(x) \
	(((uintptr_t)(x) & (sizeof(void *) - 1)) == 0)

static inline Elf_Addr
load_ptr(void *where)
{
	Elf_Addr res;

	memcpy(&res, where, sizeof(res));

	return (res);
}

static inline void
store_ptr(void *where, Elf_Addr val)
{

	memcpy(where, &val, sizeof(val));
}

static struct os_error bad_reloc = {
	0, "Unhandled ELF redirection"
};

os_error *
relocate_self(Elf_Dyn *dynamic, void *oldbase, void *newbase)
{
	Elf_Dyn *dynp;
	Obj_Entry o = { 0 };
	Obj_Entry *obj; 
	const Elf_Rel *rel;
	Elf_Addr        relsz = 0;

	obj = &o; obj->dynamic = dynamic; obj->relocbase = newbase;

	for (dynp = obj->dynamic; dynp->d_tag != DT_NULL; ++dynp) {
		switch (dynp->d_tag) {
		case DT_REL:
			obj->rel = (const Elf_Rel *)
			    (obj->relocbase + dynp->d_un.d_ptr);
			break;
		case DT_RELSZ:
			relsz = dynp->d_un.d_val;
			break;
		case DT_RELENT:
			assert(dynp->d_un.d_val == sizeof(Elf_Rel));
			break;
		}
	}

	obj->rellim = (const Elf_Rel *)((void *)obj->rel + relsz);

	for (rel = obj->rel; rel < obj->rellim; rel++) {
		Elf_Addr        *where;
		Elf_Addr         tmp;

		where = (Elf_Addr *)(obj->relocbase + rel->r_offset);

		switch (ELF_R_TYPE(rel->r_info)) {
		case R_TYPE(NONE):
			break;

		case R_TYPE(RELATIVE):	/* word32 B + A */
			if (__predict_true(RELOC_ALIGNED_P(where))) {
				tmp = *where + (Elf_Addr)obj->relocbase -
				    (Elf_Addr)oldbase;
				*where = tmp;
			} else {
				tmp = load_ptr(where) +
				    (Elf_Addr)obj->relocbase -
				    (Elf_Addr)oldbase;
				store_ptr(where, tmp);
			}
			rdbg(("RELATIVE in %s --> %p", obj->path,
			    (void *)tmp));
			break;

		default:
			return &bad_reloc;
		}
	}
	return NULL;
}
