/*	$OpenBSD: aout_syms.c,v 1.11 2007/07/24 21:11:02 deraadt Exp $	*/
/*
 * Copyright (c) 2002 Federico Schwindt <fgsch@openbsd.org>
 * 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. 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 ``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. 
 */

#include <sys/param.h>
#include <sys/ptrace.h>
#include <sys/mman.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <err.h>

#include <a.out.h>
#include <link.h>

#include "pmdb.h"
#include "symbol.h"

struct aout_symbol_handle {
	struct sym_table	ash_st;
	int		ash_fd;
	char	       *ash_strtab;
	u_int32_t	ash_strsize;
	struct nlist   *ash_symtab;
	int		ash_symsize;
};

#define ASH_TO_ST(ash) (&(ash)->ash_st)
#define ST_TO_ASH(st) ((struct aout_symbol_handle *)(st))

struct sym_table *aout_open(const char *);
void aout_close(struct sym_table *);
char *aout_name_and_off(struct sym_table *, reg, reg *);
int aout_lookup(struct pstate *, const char *, reg *);
void aout_update(struct pstate *);

struct sym_ops aout_sops = {
	aout_open,
	aout_close,
	aout_name_and_off,
	aout_lookup,
	aout_update
};	

int
sym_check_aout(const char *name, struct pstate *ps)
{
	struct exec ahdr;
	int error = 0;
	int fd;

	if ((fd = open(name, O_RDONLY)) < 0)
		return (1);

	if (pread(fd, &ahdr, sizeof(ahdr), 0) != sizeof(ahdr)) {
		error = 1;
	}

	if (!error && N_BADMAG(ahdr)) {
		error = 1;
	}

	close(fd);

	if (!error)
		ps->ps_sops = &aout_sops;

	return (error);
}

struct sym_table *
aout_open(const char *name)
{
	struct aout_symbol_handle *ash;
	struct stat sb;
	u_int32_t symoff, stroff;
	struct exec ahdr;

	if ((ash = malloc(sizeof(*ash))) == NULL) {
		return NULL;
	}

	memset(ash, 0, sizeof(*ash));
	ash->ash_fd = -1;

	if ((ash->ash_fd = open(name, O_RDONLY)) < 0) {
		warn("open(%s)", name);
		goto fail;
	}

	if (pread(ash->ash_fd, &ahdr, sizeof(ahdr), 0) != sizeof(ahdr)) {
		warn("pread(header)");
		goto fail;
	}

	if (N_BADMAG(ahdr)) {
		warnx("Bad magic.");
		goto fail;
	}

	/* Don't go further for stripped files. */
	if (fstat(ash->ash_fd, &sb) < 0 || N_SYMOFF(ahdr) == sb.st_size ||
	    N_STROFF(ahdr) == sb.st_size)
		goto fail;

	symoff = N_SYMOFF(ahdr);
	ash->ash_symsize = ahdr.a_syms;
	stroff = N_STROFF(ahdr);

	if (ahdr.a_syms == 0) {
		warnx("No symbol table");
		goto fail;
	}

	if (pread(ash->ash_fd, &ash->ash_strsize, sizeof(u_int32_t),
	    stroff) != sizeof(u_int32_t)) {
		warn("pread(strsize)");
		goto fail;
	}

	if ((ash->ash_strtab = mmap(NULL, ash->ash_strsize, PROT_READ,
	    MAP_SHARED, ash->ash_fd, stroff)) == MAP_FAILED) {
		warn("mmap(strtab)");
		goto fail;
	}

	if ((ash->ash_symtab = mmap(NULL, ash->ash_symsize, PROT_READ,
	    MAP_SHARED, ash->ash_fd, symoff)) == MAP_FAILED) {
		warn("mmap(symtab)");
		goto fail;
	}

	return (ASH_TO_ST(ash));
fail:

	aout_close(ASH_TO_ST(ash));
	return (NULL);
}

void
aout_close(struct sym_table *st)
{
	struct aout_symbol_handle *ash = ST_TO_ASH(st);

	if (ash->ash_fd != -1)
		close(ash->ash_fd);

	munmap(ash->ash_strtab, ash->ash_strsize);
	munmap(ash->ash_symtab, ash->ash_symsize);
	free(ash);
}

char *
aout_name_and_off(struct sym_table *st, reg pc, reg *offs)
{
	struct aout_symbol_handle *ash = ST_TO_ASH(st);
	struct nlist *s, *bests = NULL;
	int bestoff = 0;
	int nsyms, i;
	char *symn;

#define SYMVAL(S) (unsigned long)((S)->n_value + st->st_offs)

	nsyms = ash->ash_symsize / sizeof(struct nlist);

	bests = NULL;
	for (i = 0; i < nsyms; i++) {
		s = &ash->ash_symtab[i];

		if (s->n_value == 0 ||
		    s->n_un.n_strx == 0)
			continue;

		symn = &ash->ash_strtab[s->n_un.n_strx];
		if (SYMVAL(s) <= pc && SYMVAL(s) > bestoff &&
		    symn[0] != '\0' && strcmp(symn, "gcc2_compiled.")) {
			bests = s;
			bestoff = SYMVAL(s);
		}
	}

	if ((s = bests) == NULL)
		return (NULL);

	*offs = pc - SYMVAL(s);

	return &ash->ash_strtab[s->n_un.n_strx];
}

static struct nlist *
aout_lookup_table(struct aout_symbol_handle *ash, const char *name)
{
	int nsyms, i;
	char *symn;
	struct nlist *s = NULL;

	nsyms = ash->ash_symsize / sizeof(struct nlist);
	for (i = 0; i < nsyms; i++) {
		s = &ash->ash_symtab[i];
		symn = &ash->ash_strtab[s->n_un.n_strx];
		if (strcmp(name, symn) == 0)
			break;
	}

	if (i == nsyms)
		return (NULL);

	return (s);
}

int
aout_lookup(struct pstate *ps, const char *name, reg *res)
{
	struct sym_table *st;
	struct nlist *s = NULL;
	int first = 1;
	char *sname = (char *)name;

restart:
	TAILQ_FOREACH(st, &ps->ps_syms, st_list) {
		if ((s = aout_lookup_table(ST_TO_ASH(st), sname)) != NULL)
			break;
	}

	if (!first)
		free(sname);

	if (s == NULL) {
		if (first) {
			if (asprintf(&sname, "_%s", sname) != -1) {
				first = 0;
				goto restart;
			}
		}

		return (-1);
	}

	*res = s->n_value + st->st_offs;
	return (0);
}

/*
 * Called after execution started so that we can load any dynamic symbols.
 */
void
aout_update(struct pstate *ps)
{
	struct _dynamic dyn;
	struct so_debug sdeb;
	struct section_dispatch_table sdt;
	struct so_map som;
	off_t somp;
	reg addr;
	struct nlist *s;

	if ((s = aout_lookup_table(ST_TO_ASH(ps->ps_sym_exe), "__DYNAMIC")) == NULL) {
		warnx("Can't find __DYNAMIC");
		return;
	}
	addr = s->n_value + ps->ps_sym_exe->st_offs;

	if (process_read(ps, addr, &dyn, sizeof(dyn)) < 0) {
		warn("Can't read __DYNAMIC");
		return;
	}

	if (dyn.d_version != LD_VERSION_BSD) {
		warn("Can't handle __DYNAMIC version %d", dyn.d_version);
		return;
	}

	if (process_read(ps, (off_t)(reg)dyn.d_debug, &sdeb, sizeof(sdeb)) < 0) {
		warn("Can't read __DYNAMIC.d_debug");
		return;
	}

	if (sdeb.dd_version != 0) {
		warn("Can't handle so_debug version %d", sdeb.dd_version);
		return;
	}

	if (process_read(ps, (off_t)(reg)dyn.d_un.d_sdt, &sdt, sizeof(sdt)) < 0) {
		warn("Can't read section dispatch table");
		return;
	}

	somp = (off_t)(reg)sdt.sdt_loaded;
	while (somp) {
		char fname[MAXPATHLEN];
		int i;

		if (process_read(ps, somp, &som, sizeof(som)) < 0) {
			warn("Can't read so_map");
			break;
		}
		somp = (off_t)(reg)som.som_next;
		if (process_read(ps, (off_t)(reg)som.som_path, fname,
		    sizeof(fname)) < 0) {
			warn("Can't read so filename");
			continue;
		}
		
		/* sanity check the file name */
		for (i = 0; i < MAXPATHLEN; i++)
			if (fname[i] == '\0')
				break;
		if (i == MAXPATHLEN) {
			warnx("so filename invalid");
			continue;
		}

		if (st_open(ps, fname, (reg)som.som_addr) == NULL)
			warn("symbol loading failed");
	}
}