/* $Id$ */
/* Copyright (c) 2015-2018 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Devel Asm */
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. */



#include <System.h>
#define ELFSIZE 32
#include "elf.h"

#include "elf.c"


/* ELF32 */
static void _swap_32_ehdr(Elf_Ehdr * ehdr);
static void _swap_32_phdr(Elf_Phdr * phdr);
static void _swap_32_shdr(Elf_Shdr * shdr);


/* ELF32 */
/* elf32_detect */
char const * elf32_detect(AsmFormatPlugin * format, Elf_Ehdr * ehdr)
{
	(void) format;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(ehdr->e_ident[EI_DATA] != elf_arch_native->endian)
		_swap_32_ehdr(ehdr);
	switch(ehdr->e_machine)
	{
		case EM_386:
		case EM_486:
			return "i686";
		case EM_ALPHA:
			return "alpha";
		case EM_ARM:
			return "arm";
		case EM_MIPS:
			return "mips";
		case EM_SPARC:
			return "sparc";
	}
	error_set_code(1, "%s: %s 0x%x", "elf", "Unsupported ELF architecture",
			ehdr->e_machine);
	return NULL;
}


/* destroy_32 */
static int _destroy_32_phdr(AsmFormatPlugin * format, Elf32_Off offset);
static int _destroy_32_shdr(AsmFormatPlugin * format, Elf32_Off offset);

int elf32_destroy(AsmFormatPlugin * format)
{
	int ret = 0;
	AsmFormatPluginHelper * helper = format->helper;
	Elf * elf = format;
	long offset;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(elf32_section(format, ".shstrtab") != 0)
		ret = -1;
	else if(helper->write(helper->format, elf->shstrtab.buf,
				elf->shstrtab.cnt)
			!= (ssize_t)elf->shstrtab.cnt)
		ret = -1;
	else if((offset = helper->seek(helper->format, 0, SEEK_CUR)) < 0)
		ret = -1;
	else if(_destroy_32_phdr(format, offset) != 0
			|| _destroy_32_shdr(format, offset) != 0)
		ret = -1;
	free(elf->shstrtab.buf);
	elf->shstrtab.buf = NULL;
	elf->shstrtab.cnt = 0;
	return ret;
}

static int _destroy_32_phdr(AsmFormatPlugin * format, Elf32_Off offset)
{
	AsmFormatPluginHelper * helper = format->helper;
	Elf * elf = format;
	const ElfArch * ea = elf->arch;
	Elf32_Ehdr hdr;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(elf->es32_cnt == 0)
		return 0;
	if(helper->seek(helper->format, 0, SEEK_SET) != 0)
		return -1;
	if(helper->read(helper->format, &hdr, sizeof(hdr)) != sizeof(hdr))
		return elf_error(format);
	if(ea->endian == ELFDATA2MSB)
	{
		hdr.e_shoff = _htob32(offset);
		hdr.e_shnum = _htob16(elf->es32_cnt + 1);
		hdr.e_shstrndx = _htob16(elf->es32_cnt);
	}
	else
	{
		hdr.e_shoff = _htol32(offset);
		hdr.e_shnum = _htol16(elf->es32_cnt + 1);
		hdr.e_shstrndx = _htol16(elf->es32_cnt);
	}
	if(helper->seek(helper->format, 0, SEEK_SET) != 0)
		return -1;
	if(helper->write(helper->format, &hdr, sizeof(hdr)) != sizeof(hdr))
		return -1;
	return 0;
}

static int _destroy_32_shdr(AsmFormatPlugin * format, Elf32_Off offset)
{
	AsmFormatPluginHelper * helper = format->helper;
	Elf * elf = format;
	const ElfArch * ea = elf->arch;
	Elf32_Word addralign = ea->addralign;
	Elf32_Shdr * es32 = elf->es32;
	Elf32_Shdr hdr;
	int i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(helper->seek(helper->format, 0, SEEK_END) < 0)
		return elf_error(format);
	/* write the undefined section reference */
	memset(&hdr, 0, sizeof(hdr));
	if(ea->endian == ELFDATA2MSB)
	{
		hdr.sh_type = _htob32(SHT_NULL);
		hdr.sh_link = _htob32(SHN_UNDEF);
	}
	else
	{
		hdr.sh_type = _htol32(SHT_NULL);
		hdr.sh_link = _htol32(SHN_UNDEF);
	}
	if(helper->write(helper->format, &hdr, sizeof(hdr)) != sizeof(hdr))
		return -1;
	/* write the sections */
	for(i = 0; i < elf->es32_cnt; i++)
	{
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s() %d\n", __func__, i);
#endif
		if(i + 1 == elf->es32_cnt)
			es32[i].sh_size = offset - es32[i].sh_offset;
		else
			es32[i].sh_size = es32[i + 1].sh_offset
				- es32[i].sh_offset;
		es32[i].sh_offset = (ea->endian == ELFDATA2MSB)
			? _htob32(es32[i].sh_offset)
			: _htol32(es32[i].sh_offset);
		es32[i].sh_size = (ea->endian == ELFDATA2MSB)
			? _htob32(es32[i].sh_size) : _htol32(es32[i].sh_size);
		if(es32[i].sh_type == SHT_PROGBITS)
			es32[i].sh_addralign = (ea->endian == ELFDATA2MSB)
				? _htob32(addralign) : _htol32(addralign);
		es32[i].sh_type = (ea->endian == ELFDATA2MSB)
			? _htob32(es32[i].sh_type) : _htol32(es32[i].sh_type);
		if(helper->write(helper->format, &es32[i], sizeof(Elf32_Shdr))
				!= sizeof(Elf32_Shdr))
			return -1;
	}
	return 0;
}


/* decode_32 */
static int _decode32_shdr(AsmFormatPlugin * format, Elf32_Ehdr * ehdr,
		Elf32_Shdr ** shdr);
static int _decode32_addr(AsmFormatPlugin * format, Elf32_Ehdr * ehdr,
		Elf32_Addr * addr);
static int _decode32_strtab(AsmFormatPlugin * format, Elf32_Shdr * shdr,
		size_t shdr_cnt, uint16_t ndx, char ** strtab,
		size_t * strtab_cnt);
static int _decode32_symtab(AsmFormatPlugin * format, Elf32_Ehdr * ehdr,
		Elf32_Shdr * shdr, size_t shdr_cnt, uint16_t ndx);

int elf32_decode(AsmFormatPlugin * format, int raw)
{
	AsmFormatPluginHelper * helper = format->helper;
	Elf32_Ehdr ehdr;
	Elf32_Shdr * shdr = NULL;
	Elf32_Addr base = 0x0;
	char * shstrtab = NULL;
	size_t shstrtab_cnt = 0;
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__,
			helper->get_filename(helper->format));
#endif
	if(helper->seek(helper->format, 0, SEEK_SET) != 0
			|| helper->read(helper->format, &ehdr, sizeof(ehdr))
			!= sizeof(ehdr))
		return -1;
	if(ehdr.e_ident[EI_DATA] != elf_arch_native->endian)
		_swap_32_ehdr(&ehdr);
	if(_decode32_shdr(format, &ehdr, &shdr) != 0)
		return -1;
	if(_decode32_addr(format, &ehdr, &base) != 0
			|| _decode32_strtab(format, shdr, ehdr.e_shnum,
				ehdr.e_shstrndx, &shstrtab, &shstrtab_cnt)
			!= 0)
	{
		free(shdr);
		return -1;
	}
	for(i = 0; i < ehdr.e_shnum; i++)
		if(shdr[i].sh_type == SHT_SYMTAB)
		{
			/* XXX ignore errors? */
			_decode32_symtab(format, &ehdr, shdr, ehdr.e_shnum, i);
			break;
		}
	for(i = 0; i < ehdr.e_shnum; i++)
	{
		if(shdr[i].sh_name >= shstrtab_cnt)
			continue;
		if((raw || (shdr[i].sh_type == SHT_PROGBITS && shdr[i].sh_flags
						& SHF_EXECINSTR))
				&& helper->set_section(helper->format, i, 0,
					&shstrtab[shdr[i].sh_name],
					shdr[i].sh_offset, shdr[i].sh_size,
					base + shdr[i].sh_offset) == NULL)
			break;
	}
	free(shstrtab);
	free(shdr);
	return (i == ehdr.e_shnum) ? 0 : -1;
}

static int _decode32_shdr(AsmFormatPlugin * format, Elf32_Ehdr * ehdr,
		Elf32_Shdr ** shdr)
{
	AsmFormatPluginHelper * helper = format->helper;
	ssize_t size;
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(ehdr->e_shentsize == 0)
	{
		*shdr = NULL;
		return 0;
	}
	if(ehdr->e_shentsize != sizeof(**shdr))
		return -error_set_code(1, "%s: %s",
				helper->get_filename(helper->format),
				"Invalid section header size");
	if(helper->seek(helper->format, ehdr->e_shoff, SEEK_SET) < 0)
		return -1;
	size = sizeof(**shdr) * ehdr->e_shnum;
	if((*shdr = malloc(size)) == NULL)
		return -elf_error(format);
	if(helper->read(helper->format, *shdr, size) != size)
	{
		free(*shdr);
		return -1;
	}
	if(ehdr->e_ident[EI_DATA] != elf_arch_native->endian)
		for(i = 0; i < ehdr->e_shnum; i++)
			_swap_32_shdr(*shdr + i);
	return 0;
}

static int _decode32_addr(AsmFormatPlugin * format, Elf32_Ehdr * ehdr,
		Elf32_Addr * addr)
{
	AsmFormatPluginHelper * helper = format->helper;
	Elf32_Half i;
	Elf32_Phdr phdr;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(helper->seek(helper->format, ehdr->e_phoff, SEEK_SET) < 0)
		return -1;
	for(i = 0; i < ehdr->e_phnum; i++)
	{
		if(helper->read(helper->format, &phdr, sizeof(phdr))
				!= sizeof(phdr))
			return -1;
		if(ehdr->e_ident[EI_DATA] != elf_arch_native->endian)
			_swap_32_phdr(&phdr);
		if(phdr.p_type == PT_LOAD && phdr.p_flags & (PF_R | PF_X))
		{
			*addr = phdr.p_vaddr;
			return 0;
		}
	}
	*addr = 0x0;
	return 0;
}

static int _decode32_strtab(AsmFormatPlugin * format, Elf32_Shdr * shdr,
		size_t shdr_cnt, uint16_t ndx, char ** strtab,
		size_t * strtab_cnt)
{
	AsmFormatPluginHelper * helper = format->helper;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(ndx >= shdr_cnt)
		return -error_set_code(1, "%s: %s",
				helper->get_filename(helper->format),
				"Unable to read the string table");
	shdr = &shdr[ndx];
	if(shdr->sh_size == 0)
	{
		*strtab = NULL;
		*strtab_cnt = 0;
		return 0;
	}
	if(helper->seek(helper->format, shdr->sh_offset, SEEK_SET) < 0)
		return -1;
	if((*strtab = malloc(shdr->sh_size)) == NULL)
		return -elf_error(format);
	if(helper->read(helper->format, *strtab, shdr->sh_size)
			!= shdr->sh_size)
	{
		free(*strtab);
		return -1;
	}
	*strtab_cnt = shdr->sh_size;
	return 0;
}

static int _decode32_symtab(AsmFormatPlugin * format, Elf32_Ehdr * ehdr,
		Elf32_Shdr * shdr, size_t shdr_cnt, uint16_t ndx)
{
	AsmFormatPluginHelper * helper = format->helper;
	char * strtab = NULL;
	size_t strtab_cnt = 0;
	Elf32_Sym sym;
	size_t i;
	off_t offset;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(ndx >= shdr_cnt || shdr[ndx].sh_entsize != sizeof(sym))
		return -1;
	if(_decode32_strtab(format, shdr, shdr_cnt, shdr[ndx].sh_link, &strtab,
				&strtab_cnt) != 0)
		return -1;
	/* read and process symbols */
	if((offset = helper->seek(helper->format, shdr[ndx].sh_offset,
					SEEK_SET)) < 0
			|| (unsigned long)offset != shdr[ndx].sh_offset)
	{
		free(strtab);
		return -1;
	}
	for(i = 0; i * sizeof(sym) < shdr[ndx].sh_size; i++)
		if(helper->read(helper->format, &sym, sizeof(sym))
				!= sizeof(sym))
			break;
		else if(sym.st_name >= strtab_cnt)
			break;
		else if(ELF32_ST_TYPE(sym.st_info) == STT_FUNC)
		{
			offset = -1;
			if(ehdr->e_type == ET_REL || ehdr->e_type == ET_EXEC
					|| ehdr->e_type == ET_DYN)
				offset = sym.st_value;
			/* record the function */
			helper->set_function(helper->format, i,
					&strtab[sym.st_name], sym.st_value,
					sym.st_size);
		}
	if(i * sizeof(sym) != shdr[ndx].sh_size)
	{
		free(strtab);
		return -1;
	}
	return 0;
}


/* swap_32_ehdr */
static void _swap_32_ehdr(Elf_Ehdr * ehdr)
{
	ehdr->e_type = _bswap16(ehdr->e_type);
	ehdr->e_machine = _bswap16(ehdr->e_machine);
	ehdr->e_version = _bswap32(ehdr->e_version);
	ehdr->e_entry = _bswap32(ehdr->e_entry);
	ehdr->e_phoff = _bswap32(ehdr->e_phoff);
	ehdr->e_shoff = _bswap32(ehdr->e_shoff);
	ehdr->e_flags = _bswap32(ehdr->e_flags);
	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);
}


/* swap_32_phdr */
static void _swap_32_phdr(Elf_Phdr * phdr)
{
	phdr->p_type = _bswap32(phdr->p_type);
	phdr->p_offset = _bswap32(phdr->p_offset);
	phdr->p_vaddr = _bswap32(phdr->p_vaddr);
	phdr->p_paddr = _bswap32(phdr->p_paddr);
	phdr->p_filesz = _bswap32(phdr->p_filesz);
	phdr->p_memsz = _bswap32(phdr->p_memsz);
	phdr->p_flags = _bswap32(phdr->p_flags);
	phdr->p_align = _bswap32(phdr->p_align);
}


/* swap_32_shdr */
static void _swap_32_shdr(Elf_Shdr * shdr)
{
	shdr->sh_name = _bswap32(shdr->sh_name);
	shdr->sh_type = _bswap32(shdr->sh_type);
	shdr->sh_flags = _bswap32(shdr->sh_flags);
	shdr->sh_addr = _bswap32(shdr->sh_addr);
	shdr->sh_offset = _bswap32(shdr->sh_offset);
	shdr->sh_size = _bswap32(shdr->sh_size);
	shdr->sh_link = _bswap32(shdr->sh_link);
	shdr->sh_info = _bswap32(shdr->sh_info);
	shdr->sh_addralign = _bswap32(shdr->sh_addralign);
	shdr->sh_entsize = _bswap32(shdr->sh_entsize);
}
