/* $Id$ */
/* Copyright (c) 2007-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS System libc */
/* 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT
 * HOLDER OR CONTRIBUTORS 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. */
/* TODO:
 * - call pending dlclose() on exit() */



#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#ifdef DEBUG
# include <stdio.h>
#endif
#include <string.h>
#include <limits.h>
#include <errno.h>
#ifdef _LP64
# define ELFSIZE 64
#else
# define ELFSIZE 32
#endif
#include <dl/elf.h>
#include "endian.h"
#include "dlfcn.h"


/* private */
/* types */
typedef struct _DL
{
	int fd;
	char * path;

	char * text_base;
	size_t text_size;
	char * text_addr;
	char * data_base;
	size_t data_size;
	char * data_addr;

	/* internals */
	Elf_Ehdr ehdr;
	Elf_Shdr * shdr;
	Elf_Sym * symtab;
	size_t symtab_cnt;
} DL;

typedef enum _DLError
{
	DE_E_NO_ERROR = 0,
	/* from errno */
	DE_E_INVAL,
	DE_E_ISDIR,
	DE_E_NOENT,
	DE_E_NOMEM,
	DE_E_NOSYS,
	DE_E_PERM,
	/* for dlfcn */
	DE_INVALID_FORMAT,
	DE_SYMBOL_NOT_FOUND,
	DE_UNKNOWN_ERROR
} DLError;
#define DE_E_FIRST	DE_NO_ERROR
#define DE_E_LAST	DE_E_PERM
#define DE_LAST		DE_UNKNOWN_ERROR
#define DE_COUNT	(DE_LAST + 1)


/* constants */
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define ELFDATA	ELFDATA2MSB
#else
# define ELFDATA	ELFDATA2LSB
#endif


/* variables */
static DLError _dl_errno = DE_E_NO_ERROR;

static long int _dl_page_size = -1;

static Elf_Phdr * _dl_phdr = NULL;
static uint16_t _dl_phentsize = 0;
static uint16_t _dl_phnum = 0;
static void * _dl_str = NULL;


/* prototypes */
static void * _dl_new(char const * pathname);
static void _dl_delete(DL * dl);

/* accessors */
static int _dl_error_set(DLError error, int ret);
static int _dl_error_set_errno(int ret);

/* useful */
static void * _dl_load(DL * dl, off_t offset, size_t size);
static int _dl_strtab(DL * dl, Elf_Word index, char ** strtab,
		size_t * strtab_cnt);
static char const * _dl_strtab_string(char const * strtab, size_t strtab_cnt,
		Elf64_Xword index);
static int _dl_symtab(DL * dl, Elf_Word index, Elf_Word type, Elf_Sym ** symtab,
		size_t * symtab_cnt);


/* functions */
/* dl_new */
static int _new_file(DL * dl, char const * pathname);
static int _new_self(DL * dl);
static int _new_self_dynamic(DL * dl, Elf_Phdr * phdr);
static int _file_read_ehdr(int fd, Elf_Ehdr * ehdr);
static int _file_mmap(DL * dl, Elf_Phdr * phdr, int prot, char ** base,
		size_t * size, char ** addr);
static int _file_prot(unsigned int flags);
static int _file_symbols(DL * dl);
static int _file_relocations(DL * dl);
static void _file_relocations_arch(DL * dl, Elf_Rela * rela,
		char const * strtab, size_t strtab_cnt, Elf_Sym * sym);

static void * _dl_new(char const * pathname)
{
	DL * dl;

	if((dl = malloc(sizeof(*dl))) == NULL)
	{
		_dl_error_set_errno(0);
		return NULL;
	}
	dl->fd = -1;
	dl->path = NULL;
	dl->text_base = NULL;
	dl->text_size = 0;
	dl->text_addr = NULL;
	dl->data_base = NULL;
	dl->data_size = 0;
	dl->data_addr = NULL;
	dl->shdr = NULL;
	dl->symtab = NULL;
	dl->symtab_cnt = 0;
	if((pathname == NULL && _new_self(dl) != 0)
			|| (pathname != NULL && _new_file(dl, pathname) != 0))
	{
		_dl_delete(dl);
		return NULL;
	}
	return dl;
}

static int _new_file(DL * dl, char const * pathname)
{
	Elf_Phdr * phdr;
	size_t i;
	int prot;
	ssize_t len;

	dl->fd = open(pathname, O_RDONLY);
	dl->path = strdup(pathname);
	if(dl->fd < 0 || dl->path == NULL)
		return _dl_error_set_errno(-1);
	/* read the ELF header */
	if(_file_read_ehdr(dl->fd, &dl->ehdr) != 0)
		return -1;
	/* read the program headers */
	if((phdr = _dl_load(dl, dl->ehdr.e_phoff, sizeof(*phdr)
					* dl->ehdr.e_phnum)) == NULL)
		return -1;
	for(i = 0; i < dl->ehdr.e_phnum; i++)
	{
		if(phdr[i].p_type != PT_LOAD)
			continue;
		if(phdr[i].p_filesz > phdr[i].p_memsz)
		{
			free(phdr);
			return _dl_error_set(DE_INVALID_FORMAT, -1);
		}
		prot = _file_prot(phdr[i].p_flags);
		/* enforce W^X */
		if((prot & (PROT_WRITE | PROT_EXEC))
				== (PROT_WRITE | PROT_EXEC))
		{
			free(phdr);
			return _dl_error_set(DE_INVALID_FORMAT, -1);
		}
		if(prot == (PROT_READ | PROT_EXEC))
		{
			if(dl->text_addr != NULL)
			{
				free(phdr);
				return _dl_error_set(DE_INVALID_FORMAT, -1);
			}
			if(_file_mmap(dl, &phdr[i], prot, &dl->text_base,
						&dl->text_size,
						&dl->text_addr) == 0)
				continue;
		}
		else if(phdr[i].p_memsz > 0)
		{
			if(dl->data_addr != NULL)
			{
				free(phdr);
				return _dl_error_set(DE_INVALID_FORMAT, -1);
			}
			if(_file_mmap(dl, &phdr[i], prot, &dl->data_base,
						&dl->data_size,
						&dl->data_addr) == 0)
				continue;
		}
		else
			_dl_error_set(DE_INVALID_FORMAT, -1);
		free(phdr);
		return -1;
	}
	free(phdr);
	/* read the section headers */
	if((len = dl->ehdr.e_shnum * sizeof(*dl->shdr)) < 0) /* XXX relevant? */
		return _dl_error_set(DE_INVALID_FORMAT, -1);
	if((dl->shdr = _dl_load(dl, dl->ehdr.e_shoff, len)) == NULL)
		return -1;
	if(_file_symbols(dl) != 0 || _file_relocations(dl) != 0)
		return -1;
	return 0;
}

static int _new_self(DL * dl)
{
	size_t i;

	if(_dl_phdr == NULL)
		return _dl_error_set(DE_UNKNOWN_ERROR, -1);
	if(_dl_phentsize != sizeof(Elf_Phdr))
		return _dl_error_set(DE_INVALID_FORMAT, -1);
	/* obtain the base address */
	for(i = 0; i < _dl_phnum; i++)
	{
		if(_dl_phdr[i].p_filesz > _dl_phdr[i].p_memsz)
			return _dl_error_set(DE_INVALID_FORMAT, -1);
		if(_dl_phdr[i].p_type != PT_PHDR)
			continue;
		dl->data_addr = (char *)_dl_phdr - _dl_phdr[i].p_vaddr;
		dl->text_addr = (char *)_dl_phdr - _dl_phdr[i].p_vaddr;
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s() addr=%p\n", __func__,
				dl->data_addr);
#endif
		break;
	}
	for(i = 0; i < _dl_phnum; i++)
	{
		switch(_dl_phdr[i].p_type)
		{
			case PT_DYNAMIC:
				if(_new_self_dynamic(dl, &_dl_phdr[i]) != 0)
					return -1;
				return 0;
		}
	}
	return _dl_error_set(DE_INVALID_FORMAT, -1);
}

static int _new_self_dynamic(DL * dl, Elf_Phdr * phdr)
{
	Elf_Dyn * dyn;

	/* FIXME also check for the size */
	for(dyn = (Elf_Dyn *)(dl->data_addr + phdr->p_vaddr);
			dyn->d_tag != DT_NULL; dyn++)
		switch(dyn->d_tag)
		{
			case DT_STRTAB:
#ifdef __linux__
				_dl_str = dyn->d_un.d_ptr;
#else
				_dl_str = dl->data_addr + dyn->d_un.d_ptr;
#endif
				break;
			case DT_STRSZ:
				dl->symtab_cnt = dyn->d_un.d_val;
				break;
			case DT_SYMENT:
				if(dyn->d_un.d_val != sizeof(Elf_Sym))
					return _dl_error_set(DE_INVALID_FORMAT,
							-1);
				break;
			case DT_SYMTAB:
#ifdef __linux__
				dl->symtab = (Elf_Sym *)dyn->d_un.d_ptr;
#else
				dl->symtab = (Elf_Sym *)(dl->data_addr
						+ dyn->d_un.d_ptr);
#endif
				break;
#ifdef DEBUG
			default:
				fprintf(stderr, "DEBUG: %s() %d (0x%x)\n",
						__func__, dyn->d_tag,
						dyn->d_un.d_val);
				break;
#endif
		}
	/* sanity checks */
	if(dl->symtab_cnt != 0 && _dl_str == NULL)
	{
		dl->symtab_cnt = 0;
		return _dl_error_set(DE_INVALID_FORMAT, -1);
	}
	return 0;
}

static int _file_read_ehdr(int fd, Elf_Ehdr * ehdr)
{
	if(read(fd, ehdr, sizeof(*ehdr)) != sizeof(*ehdr))
		return _dl_error_set_errno(-1);
	if(memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0
			|| ehdr->e_ident[EI_CLASS] != ELFCLASS
			|| ehdr->e_ident[EI_DATA] != ELFDATA
			|| ehdr->e_type != ET_DYN
			|| ehdr->e_version != EV_CURRENT
			|| ehdr->e_phnum == 0
			|| ehdr->e_phoff < sizeof(ehdr)
			|| ehdr->e_phentsize != sizeof(Elf_Phdr)
			|| ehdr->e_shnum == 0
			|| ehdr->e_shoff < sizeof(ehdr)
			|| ehdr->e_shentsize != sizeof(Elf_Shdr))
		return _dl_error_set(DE_INVALID_FORMAT, -1);
	return 0;
}

static int _file_mmap(DL * dl, Elf_Phdr * phdr, int prot, char ** base,
		size_t * size, char ** addr)
{
	int flags;
	size_t len;
	off_t offset;

	flags = (prot & PROT_WRITE) ? MAP_PRIVATE : MAP_SHARED;
	len = phdr->p_memsz;
	offset = phdr->p_offset;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: mmap(%p, 0x%lx, %d, %d, %d, 0x%lx)\n", NULL,
			len, prot, flags, dl->fd, offset);
#endif
	if((*base = mmap(NULL, len, prot, flags, dl->fd, offset)) == MAP_FAILED)
		return _dl_error_set_errno(-1);
	*size = len;
	*addr = *base - phdr->p_vaddr;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: base=%p, size=0x%zx, addr=%p\n", *base, *size,
			*addr);
#endif
	/* FIXME will be wrong with page alignment handling */
	memset((*base) + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
	return 0;
}

static int _file_prot(unsigned int flags)
{
	int prot;

	prot = flags & PF_R ? PROT_READ : 0;
	prot |= flags & PF_W ? PROT_WRITE : 0;
	prot |= flags & PF_X ? PROT_EXEC : 0;
	return prot;
}

static int _file_symbols(DL * dl)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	for(i = SHN_UNDEF + 1; i < dl->ehdr.e_shnum; i++)
		if(dl->shdr[i].sh_type == SHT_SYMTAB)
			break;
	if(i == dl->ehdr.e_shnum)
		return 0; /* XXX is this an error? */
	/* FIXME check that there is only one symbol table */
	return _dl_symtab(dl, i, SHT_SYMTAB, &dl->symtab, &dl->symtab_cnt);
}

static int _file_relocations(DL * dl)
{
	size_t i;
	Elf_Shdr * shdr;
	Elf_Rel * rel = NULL;
	size_t j;
	Elf_Rela rela;
	Elf_Sym * symtab;
	size_t symtab_cnt;
	char * strtab;
	size_t strtab_cnt;
	Elf_Sym * sym;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	for(i = SHN_UNDEF + 1; i < dl->ehdr.e_shnum; i++)
	{
		shdr = &dl->shdr[i];
		if((shdr->sh_type != SHT_REL
					|| shdr->sh_entsize != sizeof(*rel))
				&& (shdr->sh_type != SHT_RELA
					|| shdr->sh_entsize != sizeof(rela)))
			continue;
		if((rel = _dl_load(dl, shdr->sh_offset, shdr->sh_size))
				== NULL)
			break;
		if(_dl_symtab(dl, shdr->sh_link, SHT_DYNSYM, &symtab,
					&symtab_cnt) != 0)
			break;
		if(_dl_strtab(dl, dl->shdr[shdr->sh_link].sh_link, &strtab,
					&strtab_cnt) != 0)
		{
			free(symtab);
			break;
		}
		for(j = 0; j < shdr->sh_size; j += shdr->sh_entsize)
		{
			rela.r_addend = 0;
			memcpy(&rela, (char *)rel + j, shdr->sh_entsize);
			if(ELF_R_SYM(rela.r_info) >= symtab_cnt)
				break; /* XXX */
			sym = &symtab[ELF_R_SYM(rela.r_info)];
			_file_relocations_arch(dl, &rela, strtab, strtab_cnt,
					sym);
		}
		free(strtab);
		free(symtab);
	}
	free(rel);
	return 0;
}

static void _file_relocations_arch(DL * dl, Elf_Rela * rela,
		char const * strtab, size_t strtab_cnt, Elf_Sym * sym)
{
#ifndef DEBUG
	(void) strtab;
	(void) strtab_cnt;
	(void) sym;

#endif
#if defined(__amd64__)
	Elf_Addr * addr;

	switch(ELF_R_TYPE(rela->r_info))
	{
		case R_X86_64_NONE:
			break;
		case R_X86_64_RELATIVE:
		case R_X86_64_GLOB_DAT:
			/* FIXME implement */
			break;
		case R_X86_64_JUMP_SLOT:
# ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() Relocating \"%s\""
					" (0x%lx 0x%lx 0x%lx)\n",
					__func__, _dl_strtab_string(strtab,
						strtab_cnt, sym->st_name),
					ELF_R_SYM(rela->r_info),
					rela->r_offset, rela->r_addend);
			fprintf(stderr, "DEBUG: name 0x%lx value 0x%lx\n",
					sym->st_name, sym->st_value);
			fprintf(stderr, "DEBUG: %s() base=0x%lx, addr=0x%lx"
					", offset=0x%lx\n", __func__,
					dl->data_base, dl->data_addr,
					rela->r_offset);
# endif
			addr = dl->data_addr + rela->r_offset;
# ifdef DEBUG
			fprintf(stderr, "*0x%lx = 0x%lx + 0x%lx\n", addr,
					dl->data_base, rela->r_addend);
# endif
			*addr = dl->text_base + rela->r_addend;
			break;
	}
#elif defined(__i386__)
	Elf_Addr * addr;

	switch(ELF_R_TYPE(rela->r_info))
	{
		case R_386_NONE:
			break;
		case R_386_32:
		case R_386_PC32:
			/* FIXME implement */
			break;
		case R_386_RELATIVE:
			addr = (Elf_Addr *)(dl->data_addr + rela->r_offset);
			*addr += dl->data_addr;
			break;
	}
#endif
}


/* dl_delete */
static void _dl_delete(DL * dl)
{
	free(dl->path);
	free(dl->shdr);
	if(dl->fd >= 0)
		free(dl->symtab);
	if(dl->text_base != NULL)
		munmap(dl->text_base, dl->text_size);
	if(dl->data_base != NULL)
		munmap(dl->data_base, dl->data_size);
	if(dl->fd >= 0)
		close(dl->fd);
	free(dl);
}


/* accessors */
/* dl_error_set */
static int _dl_error_set(DLError error, int ret)
{
	_dl_errno = error;
	return ret;
}


/* dl_error_set_errno */
static int _dl_error_set_errno(int ret)
{
	switch(errno)
	{
		case EINVAL:
			_dl_errno = DE_E_INVAL;
			break;
		case EISDIR:
			_dl_errno = DE_E_ISDIR;
			break;
		case ENOENT:
			_dl_errno = DE_E_NOENT;
			break;
		case ENOMEM:
			_dl_errno = DE_E_NOMEM;
			break;
		case ENOSYS:
			_dl_errno = DE_E_NOSYS;
			break;
		case EPERM:
			_dl_errno = DE_E_PERM;
			break;
		default:
			_dl_errno = DE_UNKNOWN_ERROR;
			break;
	}
	return ret;
}


/* useful */
/* dl_load */
static void * _dl_load(DL * dl, off_t offset, size_t size)
{
	void * ret;
	ssize_t ssize;

	if(lseek(dl->fd, offset, SEEK_SET) != offset
			|| (ret = malloc(size)) == NULL)
	{
		_dl_error_set_errno(1);
		return NULL;
	}
	if((ssize = read(dl->fd, ret, size)) <= 0
			|| (size_t)ssize != size)
	{
		free(ret);
		_dl_error_set_errno(1);
		return NULL;
	}
	return ret;
}


/* dl_strtab */
static int _dl_strtab(DL * dl, Elf_Word index, char ** strtab,
		size_t * strtab_cnt)
{
	Elf_Shdr * shdr;

	if(index >= dl->ehdr.e_shnum || dl->shdr[index].sh_type != SHT_STRTAB)
		return -_dl_error_set(DE_INVALID_FORMAT, 1);
	shdr = &dl->shdr[index];
	if((*strtab = _dl_load(dl, shdr->sh_offset, shdr->sh_size)) == NULL)
		return -1;
	if((*strtab)[shdr->sh_size - 1] != '\0')
	{
		free(*strtab);
		return -_dl_error_set(DE_INVALID_FORMAT, 1);
	}
	*strtab_cnt = shdr->sh_size;
	return 0;
}


/* dl_strtab_string */
static char const * _dl_strtab_string(char const * strtab, size_t strtab_cnt,
		Elf64_Xword index)
{
	if(index >= strtab_cnt || strtab == NULL || strtab_cnt == 0)
	{
		_dl_error_set(DE_INVALID_FORMAT, 1);
		return NULL;
	}
	if(index == STN_UNDEF)
		return "";
	return &strtab[index];
}


/* dl_symtab */
static int _dl_symtab(DL * dl, Elf_Word index, Elf_Word type, Elf_Sym ** symtab,
		size_t * symtab_cnt)
{
	Elf_Shdr * shdr;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%p, %u, %u, symtab, symtab_cnt)\n", __func__,
			dl, index, type);
#endif
	if(index >= dl->ehdr.e_shnum)
		return -_dl_error_set(DE_INVALID_FORMAT, 1);
	shdr = &dl->shdr[index];
	if(shdr->sh_type != type || shdr->sh_entsize != sizeof(**symtab))
		return -_dl_error_set(DE_INVALID_FORMAT, 1);
	if((*symtab = _dl_load(dl, shdr->sh_offset, shdr->sh_size)) == NULL)
		return -1;
	*symtab_cnt = shdr->sh_size / shdr->sh_entsize;
	return 0;
}


/* protected */
/* start_dlfcn */
void __start_dlfcn(AuxInfo * auxv)
{
	for(;; auxv++)
		switch(auxv->a_type)
		{
			case AT_NULL:
				return;
			case AT_PAGESZ:
				_dl_page_size = auxv->a_v;
				break;
			case AT_PHDR:
				_dl_phdr = (Elf_Phdr *)auxv->a_v;
				break;
			case AT_PHENT:
				_dl_phentsize = auxv->a_v;
				break;
			case AT_PHNUM:
				_dl_phnum = auxv->a_v;
				break;
			case AT_IGNORE:
			default:
				break;
		}
}


/* public */
/* dlclose */
int __dlclose(void * handle)
{
	DL * dl = handle;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%p)\n", __func__, handle);
#endif
	_dl_delete(dl);
	return 0;
}


/* dlerror */
char * __dlerror(void)
{
	static struct
	{
		DLError error;
		char * string;
	} es[] = {
		{ DE_INVALID_FORMAT,	"Invalid file format"	},
		{ DE_SYMBOL_NOT_FOUND,	"Symbol not found"	},
		{ 0,			"Unknown error"		}
	};
	size_t i;

#if DE_E_FIRST == 0
	if(_dl_errno <= DE_E_LAST)
#else
	if(_dl_errno >= DE_E_FIRST && _dl_errno <= DE_E_LAST)
#endif
		return strerror(errno);
	for(i = 0; es[i].error != 0; i++)
		if(es[i].error == _dl_errno)
			break;
	return es[i].string;
}


/* dlopen */
void * __dlopen(char const * pathname, int mode)
{
	(void) mode;

	if(_dl_page_size < 0 && (_dl_page_size = sysconf(_SC_PAGESIZE)) < 0)
	{
		_dl_error_set_errno(0);
		return NULL;
	}
	return _dl_new(pathname);
}


/* dlsym */
static void * _sym_lookup(DL * dl, char const * name, char const * strings,
		size_t strings_cnt);

void * __dlsym(void * handle, char const * name)
{
	DL * dl = handle;
	void * ret;
	size_t i;
	size_t len;
	Elf_Shdr * shdr;
	char * strings = NULL;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: dlsym(%p, \"%s\")\n", handle, name);
#endif
	if(dl->fd == -1)
	{
		if(dl->symtab_cnt == 0)
		{
			errno = ENOSYS;
			_dl_error_set_errno(0);
			return NULL;
		}
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %p %p\n", dl->text_addr, dl->data_addr);
		ret = _sym_lookup(dl, name, _dl_str, dl->symtab_cnt);
		fprintf(stderr, "DEBUG: %s(\"%s\") => %p\n", __func__, name,
				ret);
		return ret;
#else
		return _sym_lookup(dl, name, _dl_str, dl->symtab_cnt);
#endif
	}
	shdr = dl->shdr;
	for(i = SHN_UNDEF + 1; i < dl->ehdr.e_shnum; i++)
	{
		if(shdr[i].sh_type != SHT_SYMTAB)
			continue;
		/* sanity checks */
		if(shdr[i].sh_entsize != sizeof(Elf_Sym))
		{
			_dl_error_set(DE_INVALID_FORMAT, 0);
			break;
		}
		/* read the complete string section */
		if(_dl_strtab(dl, shdr[i].sh_link, &strings, &len) != 0)
			break;
		ret = _sym_lookup(dl, name, strings, len);
		free(strings);
		if(ret != NULL)
			return ret;
	}
	return NULL;
}

static void * _sym_lookup(DL * dl, char const * name, char const * strtab,
		size_t strtab_cnt)
{
	void * ret = NULL;
	size_t i;
	Elf_Sym * sym;
	char const * p;

	for(i = STN_UNDEF + 1; i < dl->symtab_cnt; i++)
	{
		sym = &dl->symtab[i];
		if((p = _dl_strtab_string(strtab, strtab_cnt, sym->st_name))
				== NULL)
			return NULL;
#ifdef DEBUG
		if(dl->fd == -1)
			fprintf(stderr, "DEBUG: symbol \"%s\"\n", p);
#endif
		if(strcmp(p, name) != 0)
			continue;
		/* found the symbol */
		if(dl->fd != -1 && sym->st_shndx >= dl->ehdr.e_shnum)
		{
			_dl_error_set(DE_INVALID_FORMAT, 0);
			return NULL;
		}
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s() symbol: %s, section: %u, type=%x,"
				" bind=%d, value: 0x%x, size: 0x%x\n",
				__func__, &strtab[sym->st_name], sym->st_shndx,
				ELF_ST_TYPE(sym->st_info),
				ELF_ST_BIND(sym->st_info),
				sym->st_value, sym->st_size);
#endif
		/* FIXME handle only known types */
		if(ELF_ST_TYPE(sym->st_info) == STT_FUNC)
			ret = (void *)(sym->st_value + dl->text_addr);
		else
			ret = (void *)(sym->st_value + dl->data_addr);
		if(ELF_ST_BIND(sym->st_info) != STB_WEAK)
			return ret;
	}
	if(ret == NULL)
		_dl_error_set(DE_SYMBOL_NOT_FOUND, 0);
	return ret;
}
