/* $Id: UWelfviewer.c,v 1.3 2010/10/20 00:12:43 khorben Exp $ */
/* ELF file parser
 * UberWall security team \../. .\../ */
/* Copyright (c) 2010 khorben of UberWall */
/* 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 version 2 of the License.
 *
 * 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 */
/* FIXME:
 * - list the libraries required, RPATH set, etc */



#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#ifdef _LP64
# define ELFSIZE 64
# define bswap(a) bswap64(a)
#else
# define ELFSIZE 32
# define bswap(a) bswap32(a)
#endif
#include <elf.h>
#include "config.h"


/* SI */
/* protected */
/* types */
typedef struct _SI
{
	unsigned long index;
	char const * string;
} SI;


/* prototypes */
static char const * _si_get(SI * si, unsigned long index);


/* UWelfviewer */
/* private */
/* variables */
static SI _si_class[] =
{
	{ ELFCLASSNONE,	"ELFCLASSNONE"	},
	{ ELFCLASS32,	"ELFCLASS32"	},
	{ ELFCLASS64,	"ELFCLASS64"	},
	{ 0,		NULL		}
};

static SI _si_data[] =
{
	{ ELFDATANONE,	"ELFDATANONE"	},
	{ ELFDATA2LSB,	"ELFDATA2LSB"	},
	{ ELFDATA2MSB,	"ELFDATA2MSB"	},
	{ 0,		NULL		}
};

static SI _si_type[] =
{
	{ 0,	"ET_NONE"	},
	{ 1,	"ET_REL"	},
	{ 2,	"ET_EXEC"	},
	{ 3,	"ET_DYN"	},
	{ 4,	"ET_CORE"	},
	{ 0xff00,"ET_LOPROC"	},
	{ 0xffff,"ET_HIPROC"	},
	{ 0,	NULL		}
};

static SI _si_machine[] =
{
	{ 0,	"EM_NONE"	},
	{ 1,	"EM_M32"	},
	{ 2,	"EM_SPARC"	},
	{ 3,	"EM_386"	},
	{ 4,	"EM_68K"	},
	{ 5,	"EM_88K"	},
	{ 7,	"EM_860"	},
	{ 8,	"EM_MIPS"	},
	{ 43,	"EM_SPARCV9"	},
	{ 62,	"EM_X86_64"	},
	{ 0,	NULL		}
};

static SI _si_version[] =
{
	{ 0,	"EV_NONE"	},
	{ 1,	"EV_CURRENT"	},
	{ 0,	NULL		}
};

static SI _si_ptype[] =
{
	{ 0,	"PT_NULL"	},
	{ 1,	"PT_LOAD"	},
	{ 2,	"PT_DYNAMIC"	},
	{ 3,	"PT_INTERP"	},
	{ 4,	"PT_NOTE"	},
	{ 5,	"PT_SHLIB"	},
	{ 6,	"PT_PHDR"	},
	{ 0,	NULL		}
};

static SI _si_dtag[] =
{
	{ 0,		"DT_NULL"		},
	{ 1,		"DT_NEEDED"		},
	{ 2,		"DT_PLTRELSZ"		},
	{ 3,		"DT_PLTGOT"		},
	{ 4,		"DT_HASH"		},
	{ 5,		"DT_STRTAB"		},
	{ 6,		"DT_SYMTAB"		},
	{ 7,		"DT_RELA"		},
	{ 8,		"DT_RELASZ"		},
	{ 9,		"DT_RELAENT"		},
	{ 10,		"DT_STRSZ"		},
	{ 11,		"DT_SYMENT"		},
	{ 12,		"DT_INIT"		},
	{ 13,		"DT_FINI"		},
	{ 14,		"DT_SONAME"		},
	{ 15,		"DT_RPATH"		},
	{ 16,		"DT_SYMBOLIC"		},
	{ 17,		"DT_REL"		},
	{ 18,		"DT_RELSZ"		},
	{ 19,		"DT_RELENT"		},
	{ 20,		"DT_PLTREL"		},
	{ 21,		"DT_DEBUG"		},
	{ 22,		"DT_TEXTREL"		},
	{ 23,		"DT_JMPREL"		},
	{ 24,		"DT_BIND_NOW"		},
	{ 25,		"DT_INIT_ARRAY"		},
	{ 26,		"DT_FINI_ARRAY"		},
	{ 27,		"DT_INIT_ARRAYSZ"	},
	{ 28,		"DT_FINI_ARRAYSZ"	},
	{ 29,		"DT_NUM"		},
	{ 0x60000000,	"DT_LOOS"		},
	{ 0x6ffffff0,	"DT_VERSYM"		},
	{ 0x6ffffffb,	"DT_FLAGS_1"		},
	{ 0x6ffffffc,	"DT_VERDEF"		},
	{ 0x6ffffffd,	"DT_VERDEFNUM"		},
	{ 0x6ffffffe,	"DT_VERNEED"		},
	{ 0x6fffffff,	"DT_VERNEEDNUM"		},
	{ 0x6fffffff,	"DT_HIOS"		},
	{ 0x70000000,	"DT_LOPROC"		},
	{ 0x7fffffff,	"DT_HIPROC"		},
	{ 0,		NULL			}
};

static SI _si_shtype[] =
{
	{ 0,	"SHT_NULL"	},
	{ 1,	"SHT_PROGBITS"	},
	{ 2,	"SHT_SYMTAB"	},
	{ 3,	"SHT_STRTAB"	},
	{ 4,	"SHT_RELA"	},
	{ 5,	"SHT_HASH"	},
	{ 6,	"SHT_DYNAMIC"	},
	{ 7,	"SHT_NOTE"	},
	{ 8,	"SHT_NOBITS"	},
	{ 9,	"SHT_REL"	},
	{ 10,	"SHT_SHLIB"	},
	{ 11,	"SHT_DYNSYM"	},
	{ 0,	NULL		}
};

static SI _si_symtype[] =
{
	{ 0,	"STT_NOTYPE"	},
	{ 1,	"STT_OBJECT"	},
	{ 2,	"STT_FUNC"	},
	{ 3,	"STT_SECTION"	},
	{ 4,	"STT_FILE"	},
	{ 5,	"STT_NUM"	},
	{ 10,	"STT_LOOS"	},
	{ 12,	"STT_HIOS"	},
	{ 13,	"STT_LOPROC"	},
	{ 15,	"STT_HIPROC"	},
	{ 0,	NULL		}
};

static SI _si_rtype[] =
{
	{ 0,	"R_386_NONE"		},
	{ 1,	"R_386_32"		},
	{ 2,	"R_386_PC32"		},
	{ 3,	"R_386_GOT32"		},
	{ 4,	"R_386_PLT32"		},
	{ 5,	"R_386_COPY"		},
	{ 6,	"R_386_GLOB_DAT"	},
	{ 7,	"R_386_JMP_SLOT"	},
	{ 8,	"R_386_RELATIVE"	},
	{ 9,	"R_386_GOTOFF"		},
	{ 9,	"R_SPARC_HI22"		},
	{ 10,	"R_386_GOTPC"		},
	{ 11,	"R_386_32PLT"		},
	{ 19,	"R_SPARC_COPY"		},
	{ 20,	"R_SPARC_GLOB_DAT"	},
	{ 21,	"R_SPARC_JMP_SLOT"	},
	{ 22,	"R_SPARC_RELATIVE"	},
	{ 32,	"R_SPARC_64"		},
	{ 33,	"R_SPARC_OLO10"		},
	{ 46,	"R_SPARC_DISP64"	},
	{ 47,	"R_SPARC_PLT64"		},
	{ 53,	"R_SPARC_REGISTER"	},
	{ 54,	"R_SPARC_UA64"		},
	{ 0,	NULL			}
};


/* prototypes */
static int _UWelfviewer(char const * pathname);
static int _UWelfviewer_do(FILE * fp, char const * pathname);
static int _UWelfviewer_error(char const * format, ...);

static int _usage(void);


/* SI */
/* protected */
/* functions */
static char const * _si_get(SI * si, unsigned long index)
{
	static char buf[32];

	if(si != NULL)
		for(; si->string != NULL; si++)
			if(si->index == index)
				return si->string;
	snprintf(buf, sizeof(buf), "UNKNOWN (%lu)", index);
	return buf;
}


/* UWelfviewer */
/* private */
/* functions */
/* UWelfviewer */
static int _UWelfviewer(char const * pathname)
{
	int ret;
	FILE * fp;

	if((fp = fopen(pathname, "r")) == NULL)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	ret = _UWelfviewer_do(fp, pathname);
	if(fclose(fp) != 0)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	return ret;
}


/* UWelfviewer_error */
static int _UWelfviewer_error(char const * format, ...)
{
	va_list ap;

	fputs(PACKAGE ": ", stderr);
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	fputc('\n', stderr);
	return 1;
}


/* UWelfviewer_do */
/* elf header */
static int _do_ehdr(FILE * fp, char const * pathname, Elf_Ehdr * ehdr);
static void _ehdr_print(Elf_Ehdr * ehdr);
/* program table */
static int _do_phdr(FILE * fp, char const * pathname, Elf_Ehdr * ehdr,
		Elf_Phdr ** phdr, size_t * phdr_cnt);
static void _phdr_print(Elf_Phdr * phdr);
static void _phdr_dyn_print(FILE * fp, Elf_Phdr * phdr);
static char const * _phdr_flags(uint32_t flags);
/* section table */
static int _do_shdr(FILE * fp, char const * pathname, Elf_Ehdr * ehdr,
		Elf_Shdr ** shdr, size_t * shdr_cnt, char ** shstrtab,
		size_t * shstrtab_cnt);
static void _shdr_print(Elf_Shdr * shdr, char const * strtab,
		size_t strtab_cnt);
static char const * _shdr_flags(uint64_t flags);
/* string sections */
static int _do_strtab(FILE * fp, char const * pathname, Elf_Shdr * shdr,
		size_t shdr_cnt, uint16_t ndx, char ** strtab,
		size_t * strtab_cnt);
static char const * _strtab_get(char const * strtab, size_t strtab_cnt,
		unsigned long ndx);
/* symbol table */
static int _do_symtab(FILE * fp, char const * pathname, Elf_Shdr * shdr,
		size_t shdr_cnt, uint16_t ndx, Elf_Sym ** symtab,
		size_t * symtab_cnt, char const * shstrtab,
		size_t shstrtab_cnt);
static void _symtab_print(Elf_Sym * sym, char const * strtab,
		size_t strtab_cnt, Elf_Shdr * shdr, size_t shdr_cnt,
		char const * shstrtab, size_t shstrtab_cnt);
/* relocations */
static int _do_rela(FILE * fp, char const * pathname, Elf_Ehdr * ehdr,
		Elf_Shdr * shdr, size_t shdr_cnt);
static void _rela_print(Elf_Rela * rela, Elf_Sym * symtab, size_t symtab_cnt,
		char const * symstrtab, size_t symstrtab_cnt);

static int _UWelfviewer_do(FILE * fp, char const * pathname)
{
	int ret;
	Elf_Ehdr ehdr;
	Elf_Phdr * phdr = NULL;
	size_t phdr_cnt = 0;
	Elf_Shdr * shdr = NULL;
	size_t shdr_cnt = 0;
	char * shstrtab = NULL;
	size_t shstrtab_cnt = 0;
	uint16_t i;
	Elf_Sym * symtab = NULL;
	size_t symtab_cnt = 0;

	if((ret = _do_ehdr(fp, pathname, &ehdr)) != 0)
		return ret;
	ret |= _do_phdr(fp, pathname, &ehdr, &phdr, &phdr_cnt);
	ret |= _do_shdr(fp, pathname, &ehdr, &shdr, &shdr_cnt, &shstrtab,
			&shstrtab_cnt);
	for(i = 0; i < shdr_cnt; i++)
		if(shdr[i].sh_type == SHT_SYMTAB)
		{
			ret |= _do_symtab(fp, pathname, shdr, shdr_cnt, i,
					&symtab, &symtab_cnt, shstrtab,
					shstrtab_cnt);
			break;
		}
	ret |= _do_rela(fp, pathname, &ehdr, shdr, shdr_cnt);
	free(phdr);
	free(shdr);
	free(shstrtab);
	free(symtab);
	return ret;
}

static int _do_ehdr(FILE * fp, char const * pathname, Elf_Ehdr * ehdr)
{
	if(fread(ehdr, sizeof(*ehdr), 1, fp) != 1)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	if(memcmp(ehdr->e_ident, ELFMAG, sizeof(ELFMAG) - 1) != 0)
		return _UWelfviewer_error("%s: %s", pathname,
				"Not an ELF file");
#if _BYTE_ORDER == _LITTLE_ENDIAN
	if(ehdr->e_ident[EI_DATA] == ELFDATA2MSB)
#else
	if(ehdr->e_ident[EI_DATA] == ELFDATA2LSB)
#endif
	{
		ehdr->e_type = bswap16(ehdr->e_type);
		ehdr->e_machine = bswap16(ehdr->e_machine);
		ehdr->e_version = bswap32(ehdr->e_version);
		ehdr->e_entry = bswap(ehdr->e_entry);
		ehdr->e_phoff = bswap(ehdr->e_phoff);
		ehdr->e_ehsize = bswap16(ehdr->e_ehsize);
		ehdr->e_phentsize = bswap16(ehdr->e_phentsize);
		ehdr->e_phnum = bswap16(ehdr->e_phnum);
		ehdr->e_shentsize = bswap16(ehdr->e_shentsize);
		ehdr->e_shnum = bswap16(ehdr->e_shnum);
		ehdr->e_shstrndx = bswap16(ehdr->e_shstrndx);
	}
	if(ehdr->e_ehsize != sizeof(*ehdr))
		return _UWelfviewer_error("%s: %s", pathname,
				"Unexpected ELF header size");
	_ehdr_print(ehdr);
	return 0;
}

static void _ehdr_print(Elf_Ehdr * ehdr)
{
	printf("ELF %s file for %s, %s %s, version %s\n"
			"Entrypoint: %010p, %u program table entries"
			", %u sections\n",
			_si_get(_si_type, ehdr->e_type),
			_si_get(_si_machine, ehdr->e_machine),
			_si_get(_si_data, ehdr->e_ident[EI_DATA]),
			_si_get(_si_class, ehdr->e_ident[EI_CLASS]),
			_si_get(_si_version, ehdr->e_version),
			(void*)ehdr->e_entry, ehdr->e_phnum, ehdr->e_shnum);
}

static int _do_phdr(FILE * fp, char const * pathname, Elf_Ehdr * ehdr,
		Elf_Phdr ** phdr, size_t * phdr_cnt)
{
	size_t i;

	if(ehdr->e_phnum == 0)
		return 0;
	if(ehdr->e_phentsize != sizeof(**phdr))
		return _UWelfviewer_error("%s: %s", pathname,
				"Unexpected program header size");
	if(fseek(fp, ehdr->e_phoff, SEEK_SET) != 0)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	/* no integer overflow: phentsize and phnum are shorts */
	i = ehdr->e_phentsize * ehdr->e_phnum;
	if((*phdr = malloc(i)) == NULL)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	if(fread(*phdr, sizeof(**phdr), ehdr->e_phnum, fp) != ehdr->e_phnum)
	{
		free(*phdr);
		*phdr = NULL;
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	}
	*phdr_cnt = ehdr->e_phnum;
	printf("\nListing program table at 0x%lx:\n"
			"%-10s %-5s %-10s %-10s %-10s %-10s\n", ehdr->e_phoff,
			"Type", "Flags", "Address", "Offset", "Size",
			"Memory size");
	for(i = 0; i < *phdr_cnt; i++)
		_phdr_print((*phdr) + i);
	for(i = 0; i < *phdr_cnt; i++)
		if((*phdr)[i].p_type == PT_DYNAMIC)
			_phdr_dyn_print(fp, &(*phdr)[i]);
	return 0;
}

static void _phdr_print(Elf_Phdr * phdr)
{
	unsigned long filesz = phdr->p_filesz;
	unsigned long memsz = phdr->p_memsz;

	printf("%-10s %-5s %010p %010p 0x%08lx 0x%08lx\n",
			_si_get(_si_ptype, phdr->p_type),
			_phdr_flags(phdr->p_flags), (void*)phdr->p_vaddr,
			(void*)phdr->p_offset, filesz, memsz);
}

static char const * _phdr_flags(uint32_t flags)
{
	static char buf[4];

	buf[0] = flags & PF_R ? 'r' : '-';
	buf[1] = flags & PF_W ? 'w' : '-';
	buf[2] = flags & PF_X ? 'x' : '-';
	return buf;
}

static void _phdr_dyn_print(FILE * fp, Elf_Phdr * phdr)
{
	size_t i;
	Elf_Dyn dyn;

	if(fseek(fp, phdr->p_offset, SEEK_SET) != 0)
		return; /* XXX report error */
	printf("\nListing dynamic section at 0x%lx:\n"
			"%-12s %-10s\n", phdr->p_offset, "Tag", "Value(s)");
	for(i = 1; i < phdr->p_filesz; i+=sizeof(dyn))
	{
		if(fread(&dyn, sizeof(dyn), 1, fp) != 1)
			continue; /* XXX report error */
		if(dyn.d_tag == DT_NULL)
			break;
		printf("%-12s", _si_get(_si_dtag, dyn.d_tag));
		switch(dyn.d_tag)
		{
			case DT_NEEDED:
			case DT_PLTRELSZ:
			case DT_RELASZ:
			case DT_RELAENT:
			case DT_STRSZ:
			case DT_SYMENT:
			case DT_SONAME:
			case DT_RPATH:
			case DT_RELSZ:
			case DT_RELENT:
			case DT_PLTREL:
				printf(" %-10lu\n", dyn.d_un.d_val);
				break;
			case DT_PLTGOT:
			case DT_HASH:
			case DT_STRTAB:
			case DT_SYMTAB:
			case DT_RELA:
			case DT_INIT:
			case DT_FINI:
			case DT_REL:
			case DT_DEBUG:
			case DT_JMPREL:
				printf(" 0x%08lx\n", dyn.d_un.d_ptr);
				break;
			case DT_SYMBOLIC:
			case DT_TEXTREL:
				printf("\n");
				break;
			case DT_LOPROC:
			case DT_HIPROC:
			default:
				printf(" %-10lu 0x%0lx\n", dyn.d_un.d_val,
						dyn.d_un.d_ptr);
				break;
		}
	}
}

static int _do_shdr(FILE * fp, char const * pathname, Elf_Ehdr * ehdr,
		Elf_Shdr ** shdr, size_t * shdr_cnt, char ** shstrtab,
		size_t * shstrtab_cnt)
{
	size_t i;

	if(ehdr->e_shnum == 0)
		return 0;
	if(ehdr->e_shentsize != sizeof(**shdr))
		return _UWelfviewer_error("%s: %s", pathname,
				"Unexpected section header size");
	if(fseek(fp, ehdr->e_shoff, SEEK_SET) != 0)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	/* no integer overflow: phentsize and phnum are shorts */
	i = ehdr->e_shentsize * ehdr->e_shnum;
	if((*shdr = malloc(i)) == NULL)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	if(fread(*shdr, sizeof(**shdr), ehdr->e_shnum, fp) != ehdr->e_shnum)
	{
		free(*shdr);
		*shdr = NULL;
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	}
	/* load section name string table */
	if(_do_strtab(fp, pathname, *shdr, ehdr->e_shnum, ehdr->e_shstrndx,
				shstrtab, shstrtab_cnt) != 0)
	{
		free(*shdr);
		*shdr = NULL;
		return 1;
	}
	*shdr_cnt = ehdr->e_shnum;
	printf("\nListing sections at 0x%lx:\n"
			"%-19s %-12s %-5s %-10s %-10s %-10s %-4s %-4s %s\n",
			ehdr->e_shoff, "Name", "Type", "Flags", "Address",
			"Offset", "Size", "Link", "Info", "Entsize");
	for(i = 0; i < *shdr_cnt; i++)
		_shdr_print((*shdr) + i, *shstrtab, *shstrtab_cnt);
	return 0;
}

static void _shdr_print(Elf_Shdr * shdr, char const * strtab, size_t strtab_cnt)
{
	unsigned long size = shdr->sh_size;
	unsigned long entsize = shdr->sh_entsize;

	printf("%-19s %-12s %-5s %010p %010p 0x%08lx %-4u %-4u %lu\n",
			_strtab_get(strtab, strtab_cnt, shdr->sh_name),
			_si_get(_si_shtype, shdr->sh_type),
			_shdr_flags(shdr->sh_flags), (void*)shdr->sh_addr,
			(void*)shdr->sh_offset, size, shdr->sh_link,
			shdr->sh_info, entsize);
}

static char const * _shdr_flags(uint64_t flags)
{
	static char buf[5];

	buf[0] = flags & SHF_MASKPROC ? 'm' : '-';
	buf[1] = flags & SHF_ALLOC ? 'a' : '-';
	buf[2] = flags & SHF_WRITE ? 'w' : '-';
	buf[3] = flags & SHF_EXECINSTR ? 'x' : '-';
	return buf;
}

static int _do_strtab(FILE * fp, char const * pathname, Elf_Shdr * shdr,
		size_t shdr_cnt, uint16_t ndx, char ** strtab,
		size_t * strtab_cnt)
{
	if(ndx >= shdr_cnt)
		return 1;
	shdr = &shdr[ndx];
	if(fseek(fp, shdr->sh_offset, SEEK_SET) != 0)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	if((*strtab = malloc(shdr->sh_size)) == NULL)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	if(fread(*strtab, 1, shdr->sh_size, fp) != shdr->sh_size)
	{
		free(*strtab);
		*strtab = NULL;
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	}
	*strtab_cnt = shdr->sh_size;
	return 0;
}

static char const * _strtab_get(char const * strtab, size_t strtab_cnt,
		unsigned long ndx)
{
	static char buf[24];

	if(ndx >= strtab_cnt || strtab == NULL || strtab_cnt == 0
			|| strtab[strtab_cnt - 1] != '\0')
	{
		snprintf(buf, sizeof(buf), "UNKNOWN (%lu)", ndx);
		return buf;
	}
	return &strtab[ndx];
}

static int _do_symtab(FILE * fp, char const * pathname, Elf_Shdr * shdr,
		size_t shdr_cnt, uint16_t ndx, Elf_Sym ** symtab,
		size_t * symtab_cnt, char const * shstrtab, size_t shstrtab_cnt)
{
	size_t i;
	size_t cnt;
	char * strtab = NULL;
	size_t strtab_cnt = 0;

	if(ndx >= shdr_cnt)
		return 1;
	if(shdr[ndx].sh_entsize != sizeof(**symtab))
		return _UWelfviewer_error("%s: %s", pathname,
				"Unexpected symbol size");
	if(shdr[ndx].sh_size % sizeof(**symtab) != 0)
		return _UWelfviewer_error("%s: %s", pathname,
				"Unexpected symbol table size");
	cnt = shdr[ndx].sh_size / sizeof(**symtab);
	if(fseek(fp, shdr[ndx].sh_offset, SEEK_SET) != 0)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	if((*symtab = malloc(shdr[ndx].sh_size)) == NULL)
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	if(fread(*symtab, shdr[ndx].sh_size, 1, fp) != 1)
	{
		free(*symtab);
		*symtab = NULL;
		return _UWelfviewer_error("%s: %s", pathname, strerror(errno));
	}
	if(shstrtab == NULL)
	{
		/* XXX ugly hack: do not list anything */
		*symtab_cnt = cnt;
		return 0;
	}
	/* load symbol table string table */
	if(_do_strtab(fp, pathname, shdr, shdr_cnt, shdr[ndx].sh_link, &strtab,
				&strtab_cnt) != 0)
	{
		free(*symtab);
		*symtab = NULL;
		return 1;
	}
	*symtab_cnt = cnt;
	printf("\nListing symbols at 0x%lx:\n"
			"%-19s %-19s %-11s %-10s %-10s\n", shdr[ndx].sh_offset,
			"Name", "Section", "Type", "Value", "Size");
	for(i = 0; i < cnt; i++)
		_symtab_print((*symtab) + i, strtab, strtab_cnt, shdr, shdr_cnt,
				shstrtab, shstrtab_cnt);
	free(strtab);
	return 0;
}

static void _symtab_print(Elf_Sym * sym, char const * strtab, size_t strtab_cnt,
		Elf_Shdr * shdr, size_t shdr_cnt, char const * shstrtab,
		size_t shstrtab_cnt)
{
	char const * section;
	unsigned long size = sym->st_size;

	if(sym->st_shndx < shdr_cnt)
		section = _strtab_get(shstrtab, shstrtab_cnt,
				shdr[sym->st_shndx].sh_name);
	else
		section = _si_get(NULL, sym->st_shndx);
	printf("%-19s %-19s %-11s %010p 0x%08lx\n",
			_strtab_get(strtab, strtab_cnt, sym->st_name),
			section,
			_si_get(_si_symtype, ELF_ST_TYPE(sym->st_info)),
			(void*)sym->st_value, size);
}

static int _do_rela(FILE * fp, char const * pathname, Elf_Ehdr * ehdr,
		Elf_Shdr * shdr, size_t shdr_cnt)
{
	int ret = 0;
	size_t i;
	size_t j;
	size_t cnt;
	Elf_Rela rela;
	Elf_Sym * symtab = NULL;
	size_t symtab_cnt = 0;
	char * strtab = NULL;
	size_t strtab_cnt = 0;

	for(i = 0; i < shdr_cnt; i++)
	{
		if(shdr[i].sh_type == SHT_RELA)
		{
			if(shdr[i].sh_entsize != sizeof(rela))
			{
				_UWelfviewer_error("%s: %s", pathname,
						"Unexpected relocation size");
				continue;
			}
		}
		else if(shdr[i].sh_type == SHT_REL)
		{
			if(shdr[i].sh_entsize != sizeof(Elf_Rel))
			{
				_UWelfviewer_error("%s: %s", pathname,
						"Unexpected relocation size");
				continue;
			}
		}
		else
			continue;
		if(shdr[i].sh_size % shdr[i].sh_entsize != 0)
		{
			_UWelfviewer_error("%s: %s", pathname,
					"Unexpected relocation section size");
			continue;
		}
		if(fseek(fp, shdr[i].sh_offset, SEEK_SET) != 0)
		{
			ret |= _UWelfviewer_error("%s: %s", pathname, strerror(
						errno));
			continue;
		}
		/* load symbol table */
		if(_do_symtab(fp, pathname, shdr, shdr_cnt, shdr[i].sh_link,
					&symtab, &symtab_cnt, NULL, 0) != 0)
			break;
		/* load symbol table string table */
		if(_do_strtab(fp, pathname, shdr, shdr_cnt,
					shdr[shdr[i].sh_link].sh_link, &strtab,
					&strtab_cnt) != 0)
		{
			free(symtab);
			break;
		}
		cnt = shdr[i].sh_size / shdr[i].sh_entsize;
		printf("\nListing relocations at 0x%lx:\n"
				"%-19s %-10s %-19s %s\n", shdr[i].sh_offset,
				"Symbol", "Offset", "Type", "Addend");
		rela.r_addend = 0;
		for(j = 0; j < cnt; j++)
		{
			if(fread(&rela, shdr[i].sh_entsize, 1, fp) != 1)
			{
				ret |= _UWelfviewer_error("%s: %s", pathname,
						strerror(errno));
				break;
			}
#if _BYTE_ORDER == _LITTLE_ENDIAN
			if(ehdr->e_ident[EI_DATA] == ELFDATA2MSB)
#else
			if(ehdr->e_ident[EI_DATA] == ELFDATA2LSB)
#endif
			{
				rela.r_offset = bswap(rela.r_offset);
				rela.r_info = bswap(rela.r_info);
				rela.r_addend = bswap32(rela.r_addend);
			}
			_rela_print(&rela, symtab, symtab_cnt, strtab,
					strtab_cnt);
		}
		free(strtab);
		free(symtab);
	}
	return 0;
}

static void _rela_print(Elf_Rela * rela, Elf_Sym * symtab, size_t symtab_cnt,
		char const * symstrtab, size_t symstrtab_cnt)
{
	Elf_Sym * sym = NULL;
	char const * name = "";
	unsigned long addend = rela->r_addend;

	if(ELF_R_SYM(rela->r_info) < symtab_cnt)
	{
		sym = &symtab[ELF_R_SYM(rela->r_info)];
		name = _strtab_get(symstrtab, symstrtab_cnt, sym->st_name);
	}
	printf("%-19s %010p %-19s 0x%08lx\n", name,
			(void*)rela->r_offset,
			_si_get(_si_rtype, ELF_R_TYPE(rela->r_info)),
			addend);
}


/* usage */
static int _usage(void)
{
	fputs("Usage: " PACKAGE " filename\n", stderr);
	return 1;
}


/* public */
/* functions */
/* main */
int main(int argc, char * argv[])
{
	int o;

	while((o = getopt(argc, argv, "")) != -1)
		return _usage();
	if(optind + 1 != argc)
		return _usage();
	return _UWelfviewer(argv[optind]) == 0 ? 0 : 2;
}