/*	$NetBSD: exec_sub.c,v 1.6 2009/03/14 21:04:17 dsl Exp $ */

#include <sys/cdefs.h>

#include "execkern.h"
#include <a.out.h>
#include <sys/param.h>

#ifdef BOOT
void B_PRINT(const unsigned char *p);
#endif

static inline void bzero4(void *ptr, size_t siz);
static void xk_aout(struct execkern_arg *xarg, struct exec *hdr);
static void xk_elf(struct execkern_arg *xarg, Elf32_Ehdr *hdr);

#ifdef LOADBSD
static void DPRINT_SEC(const char *ident,
			    const struct execkern_section *sec);
extern int opt_v;
extern const char *kernel_fn;

static void
DPRINT_SEC(const char *ident, const struct execkern_section *sec)
{

	if (opt_v)
		xwarnx("section (%s): img %p, sz %d, pad %d", ident,
			sec->sec_image, sec->sec_size, sec->sec_pad);
}

#define ERRX(arg)		xerrx arg

#else
#define DPRINT_SEC(ident, sec)	/**/
#define ERRX(arg)		return 1
#endif

/*
 * This code is size-hacked version of
 *
 *	sec->sec_image = (image);
 *	sec->sec_size = (size);
 *	sec->sec_pad = (pad);
 *	DPRINT_SEC((ident), sec);
 *	sec++;
 */
#define SECTION(sec, ident, image, size, pad)	\
	do {					\
		u_long *wp = (void *) sec;	\
		*(void **)wp++ = (image);	\
		*wp++ = (size);			\
		*wp++ = (pad);			\
		DPRINT_SEC((ident), sec);	\
		sec = (void *) wp;		\
	} while (0)

#define SECTION_NOPAD(sec, ident, image, size)	\
		SECTION(sec, (ident), (image), (size), 0)

static inline void
bzero4(void *ptr, size_t siz)
{
	u_long *p;
	u_short s;

	p = ptr;
	s = siz >> 2;

	while (s--)
		*p++ = 0;
}

/*
 * fill in loading information from an a.out executable
 */
static void
xk_aout(struct execkern_arg *xarg, struct exec *hdr)
{
	unsigned u;
	char *s;
	struct execkern_section *sec;

	xarg->entry_addr = hdr->a_entry;
	sec = xarg->sec;

	/* text section and padding between data section */
	s = (void *) (hdr + 1);
	SECTION(sec, "text", s, hdr->a_text, -hdr->a_text & (AOUT_LDPGSZ-1));

	/* data and bss sections */
	s += hdr->a_text;
	SECTION(sec, "data/bss", s, hdr->a_data, hdr->a_bss);

	/* size of symbol table */
	SECTION_NOPAD(sec, "symtab size", &sec[1].sec_size, sizeof(u_long));

	/* symbol table section */
	s += hdr->a_data;
	SECTION_NOPAD(sec, "symbol", s, u = hdr->a_syms);

	/* string table section */
	if (u) {
#ifdef LOADBSD
		if (opt_v)
			xwarnx("symbol table found");
#endif
		s += u;
		SECTION_NOPAD(sec, "string", s, *(u_long *) s);
	}
}

/*
 * fill in loading information from an ELF executable
 */
static void
xk_elf(struct execkern_arg *xarg, Elf32_Ehdr *hdr)
{
	char *top = (void *) hdr;
	struct execkern_section *sec;
	Elf32_Phdr *ph;
	Elf32_Shdr *sh, *sym, *str, *stab, *shstr;
	const char *shstrtab, *shname;
	unsigned u, dpos, pd;
	const char *const shstrtab_new = SHSTRTAB_FAKE;

	xarg->entry_addr = hdr->e_entry;

	/*
	 * text, data, bss
	 */
	ph = (void *) (top + hdr->e_phoff);
	xarg->load_addr = ph->p_vaddr;

	sec = xarg->sec;
	sec->sec_image = top + ph->p_offset;
	sec->sec_size = ph->p_filesz;

	if (hdr->e_phnum != 1) {
		sec->sec_pad = ph[1].p_vaddr - (ph->p_vaddr + ph->p_filesz);
		DPRINT_SEC("program (text)", sec);
		sec++;
		ph++;
		sec->sec_image = top + ph->p_offset;
		sec->sec_size = ph->p_filesz;
	}

	sec->sec_pad = ph->p_memsz - ph->p_filesz;
	DPRINT_SEC("program (data/bss)", sec);
	sec++;

	/*
	 * symbol size
	 */
	xarg->elfsymsiz = 0;		/* no symbol */
	SECTION_NOPAD(sec, "symtab size", &xarg->elfsymsiz, sizeof(int));

	/*
	 * ELF header
	 */
	xarg->ehdr = *hdr;
	xarg->ehdr.e_shstrndx = 0;	/* .shstrtab will be the 1st section */
	SECTION_NOPAD(sec, "ELF header", &xarg->ehdr, sizeof(Elf32_Ehdr));

	sh = (void *) (top + hdr->e_shoff);		/* section header */
	shstr = sh + hdr->e_shstrndx;			/* .shstrtab */
	shstrtab = top + shstr->sh_offset;

	sym = str = stab = 0;
	for (u = 0; sh++, ++u < hdr->e_shnum; ) {
		shname = shstrtab + sh->sh_name;
		if (!strcmp(shname, shstrtab_new + SHNAME_OFF_SYMTAB))
			sym = sh;				/* .symtab */
		if (!strcmp(shname, shstrtab_new + SHNAME_OFF_STRTAB))
			str = sh;				/* .strtab */
		if (!strcmp(shname, shstrtab_new + SHNAME_OFF_STAB))
			stab = sh;				/* .stab */
	}

	if (shstr == 0 || sym == 0 || str == 0)
		xarg->ehdr.e_shnum = 0;		/* no symbol */
	else {
#ifdef LOADBSD
		if (opt_v) {
			xwarnx("symbol table found");
			if (stab)
				xwarnx("debugging information found");
		}
#endif
		xarg->elfsymsiz = 1;		/* has symbol */
		xarg->ehdr.e_shnum = 3;
		xarg->ehdr.e_shoff = sizeof(Elf32_Ehdr);

		SECTION_NOPAD(sec, "section header (shstrtab)",
				shstr, sizeof(Elf32_Shdr));

		SECTION_NOPAD(sec, "section header (symbol)",
				sym, sizeof(Elf32_Shdr));

		SECTION_NOPAD(sec, "section header (string)",
				str, sizeof(Elf32_Shdr));

		dpos = sizeof(Elf32_Ehdr) + sizeof(Elf32_Shdr) * 3;
		u = SIZE_SHSTRTAB_FAKE;

		if (stab) {
			xarg->ehdr.e_shnum++;
			SECTION_NOPAD(sec, "section header (stab)",
					stab, sizeof(Elf32_Shdr));
			dpos += sizeof(Elf32_Shdr);
			u = SIZE_SHSTRTAB_FAKE_WITH_STAB;
		}

		/* new .shstrtab section */
		memcpy(xarg->shstrtab_fake, shstrtab_new, u);
		/*
		 * DDB requires symtab be aligned.
		 */
		pd = -u & ALIGNBYTES;
		SECTION(sec, "shstrtab", &xarg->shstrtab_fake, u, pd);
		shstr->sh_name = SHNAME_OFF_SHSTRTAB;
		shstr->sh_offset = dpos;
		dpos += u + pd;

		SECTION_NOPAD(sec, "symtab",
				top + sym->sh_offset, sym->sh_size);
		sym->sh_name = SHNAME_OFF_SYMTAB;
		sym->sh_offset = dpos;
		dpos += sym->sh_size;

		SECTION_NOPAD(sec, "strtab",
				top + str->sh_offset, str->sh_size);
		str->sh_name = SHNAME_OFF_STRTAB;
		str->sh_offset = dpos;
		dpos += str->sh_size;

		if (stab) {
			SECTION_NOPAD(sec, "stab",
					top + stab->sh_offset, stab->sh_size);
			stab->sh_name = SHNAME_OFF_STAB;
			stab->sh_offset = dpos;
		}
	}
}


int
xk_load(struct execkern_arg *xarg, void *buf, u_long loadaddr)
	/* loadaddr:	 for a.out */
{
	struct exec *ahdr;
	Elf32_Ehdr *ehdr;
	unsigned u;

	/* Unused section entries should be cleared to zero. */
	bzero4(xarg->sec, sizeof xarg->sec);

	xarg->load_addr = loadaddr;

	/*
	 * check exec header
	 */
	ahdr = buf;
	ehdr = buf;

	if (N_GETMAGIC(*ahdr) == NMAGIC) {
		/*
		 * this is an a.out
		 */
#ifdef LOADBSD
		if (opt_v)
			xwarnx("%s: is an a.out", kernel_fn);
#endif
#ifdef BOOT
B_PRINT("This is an a.out\r\n");
#endif

		if ((u = N_GETMID(*ahdr)) != MID_M68K)
			ERRX((1, "%s: Wrong architecture (mid %u)",
					kernel_fn, u));

		/*
		 * fill in loading information
		 */
		xk_aout(xarg, ahdr);

	} else {

		/*
		 * check ELF header
		 */
		if (*(u_int32_t *)&ehdr->e_ident[EI_MAG0] !=
			(ELFMAG0<<24 | ELFMAG1<<16 | ELFMAG2<<8 | ELFMAG3) ||
		    *(u_int16_t *)&ehdr->e_ident[EI_CLASS] !=
			(ELFCLASS32 << 8 | ELFDATA2MSB))
			ERRX((1, "%s: Not an NMAGIC a.out or a 32bit BE ELF",
					kernel_fn));

		/*
		 * this is an ELF
		 */
#ifdef LOADBSD
		if (opt_v)
			xwarnx("%s: is an ELF", kernel_fn);
#endif
#ifdef BOOT
B_PRINT("This is an ELF\r\n");
#endif

		if (ehdr->e_ident[EI_VERSION] != EV_CURRENT ||
		    ehdr->e_version != EV_CURRENT)
			ERRX((1, "%s: Unsupported ELF version", kernel_fn));

		if ((u = ehdr->e_machine) != EM_68K)
			ERRX((1, "%s: Wrong architecture (mid %u)",
					kernel_fn, u));
		if (ehdr->e_type != ET_EXEC)
			ERRX((1, "%s: Not an executable", kernel_fn));
		if ((u = ehdr->e_phnum) != 1 && u != 2)
			ERRX((1, "%s: Wrong number (%u) of loading sections",
					kernel_fn, u));

		/*
		 * fill in loading information
		 */
		xk_elf(xarg, ehdr);
	}

	return 0;
}
