// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * fast ELF file accessor
 * Copyright (C) 2018-2020  David Lamparter for NetDEF, Inc.
 */

/* Note: this wrapper is intended to be used as build-time helper.  While
 * it should be generally correct and proper, there may be the occasional
 * memory leak or SEGV for things that haven't been well-tested.
 *     _
 *    / \    This code is NOT SUITABLE FOR UNTRUSTED ELF FILES.  It's used
 *   / ! \   in FRR to read files created by its own build.  Don't take it out
 *  /_____\  of FRR and use it to parse random ELF files you found somewhere.
 *
 * If you're working with this code (or even reading it), you really need to
 * read a bunch of the ELF specs.  There's no way around it, things in here
 * just represent pieces of ELF pretty much 1:1.  Also, readelf & objdump are
 * your friends.
 *
 * Required reading:
 *   https://refspecs.linuxfoundation.org/elf/elf.pdf
 *   https://refspecs.linuxfoundation.org/elf/x86_64-SysV-psABI.pdf
 * Recommended reading:
 *   https://github.com/ARM-software/abi-aa/releases/download/2020Q4/aaelf64.pdf
 *
 * The core ELF spec is *not* enough, you should read at least one of the
 * processor specific (psABI) docs.  They define what & how relocations work.
 * Luckily we don't need to care about the processor specifics since this only
 * does data relocations, but without looking at the psABI, some things aren't
 * quite clear.
 */

/* the API of this module roughly follows a very small subset of the one
 * provided by the python elfutils package, which unfortunately is painfully
 * slow.
 */

#define PY_SSIZE_T_CLEAN

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Python.h>
#include "structmember.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#if defined(__sun__) && (__SIZEOF_POINTER__ == 4)
/* Solaris libelf bails otherwise ... */
#undef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 32
#endif

#include <elf.h>
#include <libelf.h>
#include <gelf.h>

#include "typesafe.h"
#include "jhash.h"
#include "clippy.h"

static bool debug;

#define debugf(...)                                                            \
	do {                                                                   \
		if (debug)                                                     \
			fprintf(stderr, __VA_ARGS__);                          \
	} while (0)

/* Exceptions */
static PyObject *ELFFormatError;
static PyObject *ELFAccessError;

/* most objects can only be created as return values from one of the methods */
static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
	PyErr_SetString(PyExc_ValueError,
			"cannot create instances of this type");
	return NULL;
}

struct elfreloc;
struct elfsect;

PREDECL_HASH(elfrelocs);

/* ELFFile and ELFSection intentionally share some behaviour, particularly
 * subscript[123:456] access to file data.  This is because relocatables
 * (.o files) do things section-based, but linked executables/libraries do
 * things file-based.  Having the two behave similar allows simplifying the
 * Python code.
 */

/* class ELFFile:
 *
 * overall entry point, instantiated by reading in an ELF file
 */
struct elffile {
	PyObject_HEAD

	char *filename;
	char *mmap, *mmend;
	size_t len;
	Elf *elf;

	/* note from here on there are several instances of
	 *
	 *   GElf_Something *x, _x;
	 *
	 * this is a pattern used by libelf's generic ELF routines; the _x
	 * field is used to create a copy of the ELF structure from the file
	 * with 32/64bit and endianness adjusted.
	 */

	GElf_Ehdr *ehdr, _ehdr;
	Elf_Scn *symtab;
	size_t nsym, symstridx;
	Elf_Data *symdata;

	PyObject **sects;
	size_t n_sect;

	struct elfrelocs_head dynrelocs;

	int elfclass;
	bool bigendian;
	bool has_symbols;
};

/* class ELFSection:
 *
 * note that executables and shared libraries can have their section headers
 * removed, though in practice this is only used as an obfuscation technique.
 */
struct elfsect {
	PyObject_HEAD

	const char *name;
	struct elffile *ef;

	GElf_Shdr _shdr, *shdr;
	Elf_Scn *scn;
	unsigned long idx, len;

	struct elfrelocs_head relocs;
};

/* class ELFReloc:
 *
 * note: relocations in object files (.o) are section-based while relocations
 * in executables and shared libraries are file-based.
 *
 * Whenever accessing something that is a pointer in the ELF file, the Python
 * code needs to check for a relocation;  if the pointer is pointing to some
 * unresolved symbol the file will generally contain 0 bytes.  The relocation
 * will tell what the pointer is actually pointing to.
 *
 * This represents both static (.o file) and dynamic (.so/exec) relocations.
 */
struct elfreloc {
	PyObject_HEAD

	struct elfrelocs_item elfrelocs_item;

	struct elfsect *es;
	struct elffile *ef;

	/* there's also old-fashioned GElf_Rel; we're converting that to
	 * GElf_Rela in elfsect_add_relocations()
	 */
	GElf_Rela _rela, *rela;
	GElf_Sym _sym, *sym;
	size_t symidx;
	const char *symname;

	/* documented below in python docstrings */
	bool symvalid, unresolved, relative;
	unsigned long long st_value;
};

static int elfreloc_cmp(const struct elfreloc *a, const struct elfreloc *b);
static uint32_t elfreloc_hash(const struct elfreloc *reloc);

DECLARE_HASH(elfrelocs, struct elfreloc, elfrelocs_item,
	     elfreloc_cmp, elfreloc_hash);

static Elf_Scn *elf_find_addr(struct elffile *ef, uint64_t addr, size_t *idx);
static PyObject *elffile_secbyidx(struct elffile *w, Elf_Scn *scn, size_t idx);
static PyObject *elfreloc_getsection(PyObject *self, PyObject *args);
static PyObject *elfreloc_getaddend(PyObject *obj, void *closure);

/* --- end of declarations -------------------------------------------------- */

/*
 * class ELFReloc:
 */

static const char elfreloc_doc[] =
	"Represents an ELF relocation record\n"
	"\n"
	"(struct elfreloc * in elf_py.c)";

#define member(name, type, doc)                                                \
	{                                                                      \
		(char *)#name, type, offsetof(struct elfreloc, name), READONLY,\
		(char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)"       \
	}
static PyMemberDef members_elfreloc[] = {
	member(symname, T_STRING,
	       "Name of symbol this relocation refers to.\n"
	       "\n"
	       "Will frequently be `None` in executables and shared libraries."
	),
	member(symvalid, T_BOOL,
	       "Target symbol has a valid type, i.e. not STT_NOTYPE"),
	member(unresolved, T_BOOL,
	       "Target symbol refers to an existing section"),
	member(relative, T_BOOL,
	       "Relocation is a REL (not RELA) record and thus relative."),
	member(st_value, T_ULONGLONG,
	       "Target symbol's value, if known\n\n"
	       "Will be zero for unresolved/external symbols."),
	{}
};
#undef member

static PyGetSetDef getset_elfreloc[] = {
	{ .name = (char *)"r_addend", .get = elfreloc_getaddend, .doc =
		(char *)"Relocation addend value"},
	{}
};

static PyMethodDef methods_elfreloc[] = {
	{"getsection", elfreloc_getsection, METH_VARARGS,
		"Find relocation target's ELF section\n\n"
		"Args: address of relocatee (TODO: fix/remove?)\n"
		"Returns: ELFSection or None\n\n"
		"Not possible if section headers have been stripped."},
	{}
};

static int elfreloc_cmp(const struct elfreloc *a, const struct elfreloc *b)
{
	if (a->rela->r_offset < b->rela->r_offset)
		return -1;
	if (a->rela->r_offset > b->rela->r_offset)
		return 1;
	return 0;
}

static uint32_t elfreloc_hash(const struct elfreloc *reloc)
{
	return jhash(&reloc->rela->r_offset, sizeof(reloc->rela->r_offset),
		     0xc9a2b7f4);
}

static struct elfreloc *elfrelocs_get(struct elfrelocs_head *head,
				      GElf_Addr offset)
{
	struct elfreloc dummy;

	dummy.rela = &dummy._rela;
	dummy.rela->r_offset = offset;
	return elfrelocs_find(head, &dummy);
}

static PyObject *elfreloc_getsection(PyObject *self, PyObject *args)
{
	struct elfreloc *w = (struct elfreloc *)self;
	long data;

	if (!PyArg_ParseTuple(args, "k", &data))
		return NULL;

	if (!w->es)
		Py_RETURN_NONE;

	if (!w->symvalid || w->symidx == 0) {
		size_t idx = 0;
		Elf_Scn *scn;

		data = (w->relative ? data : 0) + w->rela->r_addend;
		scn = elf_find_addr(w->es->ef, data, &idx);
		if (!scn)
			Py_RETURN_NONE;
		return elffile_secbyidx(w->es->ef, scn, idx);
	}
	return elffile_secbyidx(w->es->ef, NULL, w->sym->st_shndx);
}

static PyObject *elfreloc_getaddend(PyObject *obj, void *closure)
{
	struct elfreloc *w = (struct elfreloc *)obj;

	return Py_BuildValue("K", (unsigned long long)w->rela->r_addend);
}

static PyObject *elfreloc_repr(PyObject *arg)
{
	struct elfreloc *w = (struct elfreloc *)arg;

	return PyUnicode_FromFormat("<ELFReloc @%lu %s+%lu>",
				    (unsigned long)w->rela->r_offset,
				    (w->symname && w->symname[0]) ? w->symname
						: "[0]",
				    (unsigned long)w->rela->r_addend);
}

static void elfreloc_free(void *arg)
{
	struct elfreloc *w = arg;

	(void)w;
}

static PyTypeObject typeobj_elfreloc = {
	PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFReloc",
	.tp_basicsize = sizeof(struct elfreloc),
	.tp_flags = Py_TPFLAGS_DEFAULT,
	.tp_doc = elfreloc_doc,
	.tp_new = refuse_new,
	.tp_free = elfreloc_free,
	.tp_repr = elfreloc_repr,
	.tp_members = members_elfreloc,
	.tp_methods = methods_elfreloc,
	.tp_getset = getset_elfreloc,
};

/*
 * class ELFSection:
 */

static const char elfsect_doc[] =
	"Represents an ELF section\n"
	"\n"
	"To access section contents, use subscript notation, e.g.\n"
	"  section[123:456]\n"
	"To read null terminated C strings, replace the end with str:\n"
	"  section[123:str]\n\n"
	"(struct elfsect * in elf_py.c)";

static PyObject *elfsect_getaddr(PyObject *self, void *closure);

#define member(name, type, doc)                                                \
	{                                                                      \
		(char *)#name, type, offsetof(struct elfsect, name), READONLY, \
		(char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)"       \
	}
static PyMemberDef members_elfsect[] = {
	member(name, T_STRING,
	       "Section name, e.g. \".text\""),
	member(idx, T_ULONG,
	       "Section index in file"),
	member(len, T_ULONG,
	       "Section length in bytes"),
	{},
};
#undef member

static PyGetSetDef getset_elfsect[] = {
	{ .name = (char *)"sh_addr", .get = elfsect_getaddr, .doc =
		(char *)"Section virtual address (mapped program view)"},
	{}
};

static PyObject *elfsect_getaddr(PyObject *self, void *closure)
{
	struct elfsect *w = (struct elfsect *)self;

	return Py_BuildValue("K", (unsigned long long)w->shdr->sh_addr);
}


static PyObject *elfsect_getreloc(PyObject *self, PyObject *args)
{
	struct elfsect *w = (struct elfsect *)self;
	struct elfreloc *relw;
	unsigned long offs;
	PyObject *ret;

	if (!PyArg_ParseTuple(args, "k", &offs))
		return NULL;

	relw = elfrelocs_get(&w->relocs, offs + w->shdr->sh_addr);
	if (!relw)
		Py_RETURN_NONE;

	ret = (PyObject *)relw;
	Py_INCREF(ret);
	return ret;
}

static PyMethodDef methods_elfsect[] = {
	{"getreloc", elfsect_getreloc, METH_VARARGS,
		"Check for / get relocation at offset into section\n\n"
		"Args: byte offset into section to check\n"
		"Returns: ELFReloc or None"},
	{}
};

static PyObject *elfsect_subscript(PyObject *self, PyObject *key)
{
	Py_ssize_t start, stop, step, sllen;
	struct elfsect *w = (struct elfsect *)self;
	PySliceObject *slice;
	unsigned long offs, len = ~0UL;

	if (!PySlice_Check(key)) {
		PyErr_SetString(PyExc_IndexError,
				"ELFSection subscript must be slice");
		return NULL;
	}
	slice = (PySliceObject *)key;
	if (PyLong_Check(slice->stop)) {
		if (PySlice_GetIndicesEx(key, w->shdr->sh_size,
					 &start, &stop, &step, &sllen))
			return NULL;

		if (step != 1) {
			PyErr_SetString(PyExc_IndexError,
					"ELFSection subscript slice step must be 1");
			return NULL;
		}
		if ((GElf_Xword)stop > w->shdr->sh_size) {
			PyErr_Format(ELFAccessError,
				     "access (%lu) beyond end of section %lu/%s (%lu)",
				     stop, w->idx, w->name, w->shdr->sh_size);
			return NULL;
		}

		offs = start;
		len = sllen;
	} else {
		if (slice->stop != (void *)&PyUnicode_Type
		    || !PyLong_Check(slice->start)) {
			PyErr_SetString(PyExc_IndexError, "invalid slice");
			return NULL;
		}

		offs = PyLong_AsUnsignedLongLong(slice->start);
		len = ~0UL;
	}

	offs += w->shdr->sh_offset;
	if (offs > w->ef->len) {
		PyErr_Format(ELFAccessError,
			     "access (%lu) beyond end of file (%lu)",
			     offs, w->ef->len);
		return NULL;
	}
	if (len == ~0UL)
		len = strnlen(w->ef->mmap + offs, w->ef->len - offs);

	Py_ssize_t pylen = len;

#if PY_MAJOR_VERSION >= 3
	return Py_BuildValue("y#", w->ef->mmap + offs, pylen);
#else
	return Py_BuildValue("s#", w->ef->mmap + offs, pylen);
#endif
}

static PyMappingMethods mp_elfsect = {
	.mp_subscript = elfsect_subscript,
};

static void elfsect_free(void *arg)
{
	struct elfsect *w = arg;

	(void)w;
}

static PyObject *elfsect_repr(PyObject *arg)
{
	struct elfsect *w = (struct elfsect *)arg;

	return PyUnicode_FromFormat("<ELFSection %s>", w->name);
}

static PyTypeObject typeobj_elfsect = {
	PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFSection",
	.tp_basicsize = sizeof(struct elfsect),
	.tp_flags = Py_TPFLAGS_DEFAULT,
	.tp_doc = elfsect_doc,
	.tp_new = refuse_new,
	.tp_free = elfsect_free,
	.tp_repr = elfsect_repr,
	.tp_as_mapping = &mp_elfsect,
	.tp_members = members_elfsect,
	.tp_methods = methods_elfsect,
	.tp_getset = getset_elfsect,
};

static void elfsect_add_relocations(struct elfsect *w, Elf_Scn *rel,
				    GElf_Shdr *relhdr)
{
	size_t i, entries;
	Elf_Scn *symtab = elf_getscn(w->ef->elf, relhdr->sh_link);
	GElf_Shdr _symhdr, *symhdr = gelf_getshdr(symtab, &_symhdr);
	Elf_Data *symdata = elf_getdata(symtab, NULL);
	Elf_Data *reldata = elf_getdata(rel, NULL);

	entries = relhdr->sh_size / relhdr->sh_entsize;
	for (i = 0; i < entries; i++) {
		struct elfreloc *relw;
		size_t symidx;
		GElf_Rela *rela;
		GElf_Sym *sym;

		relw = (struct elfreloc *)typeobj_elfreloc.tp_alloc(
				&typeobj_elfreloc, 0);
		relw->es = w;

		if (relhdr->sh_type == SHT_REL) {
			GElf_Rel _rel, *erel;

			erel = gelf_getrel(reldata, i, &_rel);
			relw->rela = &relw->_rela;
			relw->rela->r_offset = erel->r_offset;
			relw->rela->r_info = erel->r_info;
			relw->rela->r_addend = 0;
			relw->relative = true;
		} else
			relw->rela = gelf_getrela(reldata, i, &relw->_rela);

		rela = relw->rela;
		if (rela->r_offset < w->shdr->sh_addr
		    || rela->r_offset >= w->shdr->sh_addr + w->shdr->sh_size)
			continue;

		symidx = relw->symidx = GELF_R_SYM(rela->r_info);
		sym = relw->sym = gelf_getsym(symdata, symidx, &relw->_sym);
		if (sym) {
			relw->symname = elf_strptr(w->ef->elf, symhdr->sh_link,
						   sym->st_name);
			relw->symvalid = GELF_ST_TYPE(sym->st_info)
					!= STT_NOTYPE;
			relw->unresolved = sym->st_shndx == SHN_UNDEF;
			relw->st_value = sym->st_value;
		} else {
			relw->symname = NULL;
			relw->symvalid = false;
			relw->unresolved = false;
			relw->st_value = 0;
		}

		debugf("reloc @ %016llx sym %5llu %016llx %s\n",
		       (long long)rela->r_offset, (unsigned long long)symidx,
		       (long long)rela->r_addend, relw->symname);

		elfrelocs_add(&w->relocs, relw);
	}
}

/*
 * bindings & loading code between ELFFile and ELFSection
 */

static PyObject *elfsect_wrap(struct elffile *ef, Elf_Scn *scn, size_t idx,
			      const char *name)
{
	struct elfsect *w;
	size_t i;

	w = (struct elfsect *)typeobj_elfsect.tp_alloc(&typeobj_elfsect, 0);
	if (!w)
		return NULL;

	w->name = name;
	w->ef = ef;
	w->scn = scn;
	w->shdr = gelf_getshdr(scn, &w->_shdr);
	w->len = w->shdr->sh_size;
	w->idx = idx;
	elfrelocs_init(&w->relocs);

	for (i = 0; i < ef->ehdr->e_shnum; i++) {
		Elf_Scn *escn = elf_getscn(ef->elf, i);
		GElf_Shdr _shdr, *shdr = gelf_getshdr(escn, &_shdr);

		if (shdr->sh_type != SHT_RELA && shdr->sh_type != SHT_REL)
			continue;
		if (shdr->sh_info && shdr->sh_info != idx)
			continue;
		elfsect_add_relocations(w, escn, shdr);
	}

	return (PyObject *)w;
}

static Elf_Scn *elf_find_section(struct elffile *ef, const char *name,
		size_t *idx)
{
	size_t i;
	const char *secname;

	for (i = 0; i < ef->ehdr->e_shnum; i++) {
		Elf_Scn *scn = elf_getscn(ef->elf, i);
		GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);

		secname = elf_strptr(ef->elf, ef->ehdr->e_shstrndx,
				     shdr->sh_name);
		if (strcmp(secname, name))
			continue;
		if (idx)
			*idx = i;
		return scn;
	}
	return NULL;
}

static Elf_Scn *elf_find_addr(struct elffile *ef, uint64_t addr, size_t *idx)
{
	size_t i;

	for (i = 0; i < ef->ehdr->e_shnum; i++) {
		Elf_Scn *scn = elf_getscn(ef->elf, i);
		GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);

		/* virtual address is kinda meaningless for TLS sections */
		if (shdr->sh_flags & SHF_TLS)
			continue;
		if (addr < shdr->sh_addr ||
		    addr >= shdr->sh_addr + shdr->sh_size)
			continue;

		if (idx)
			*idx = i;
		return scn;
	}
	return NULL;
}

/*
 * class ELFFile:
 */

static const char elffile_doc[] =
	"Represents an ELF file\n"
	"\n"
	"Args: filename to load\n"
	"\n"
	"To access raw file contents, use subscript notation, e.g.\n"
	"  file[123:456]\n"
	"To read null terminated C strings, replace the end with str:\n"
	"  file[123:str]\n\n"
	"(struct elffile * in elf_py.c)";


#define member(name, type, doc)                                                \
	{                                                                      \
		(char *)#name, type, offsetof(struct elffile, name), READONLY, \
		(char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)"       \
	}
static PyMemberDef members_elffile[] = {
	member(filename, T_STRING,
	       "Original file name as given when opening"),
	member(elfclass, T_INT,
	       "ELF class (architecture bit size)\n\n"
	       "Either 32 or 64, straight integer."),
	member(bigendian, T_BOOL,
	       "ELF file is big-endian\n\n"
	       "All internal ELF structures are automatically converted."),
	member(has_symbols, T_BOOL,
	       "A symbol section is present\n\n"
	       "Note: only refers to .symtab/SHT_SYMTAB section, not DT_SYMTAB"
	),
	{},
};
#undef member

static PyObject *elffile_secbyidx(struct elffile *w, Elf_Scn *scn, size_t idx)
{
	const char *name;
	PyObject *ret;

	if (!scn)
		scn = elf_getscn(w->elf, idx);
	if (!scn || idx >= w->n_sect)
		Py_RETURN_NONE;

	if (!w->sects[idx]) {
		GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);

		name = elf_strptr(w->elf, w->ehdr->e_shstrndx, shdr->sh_name);
		w->sects[idx] = elfsect_wrap(w, scn, idx, name);
	}

	ret = w->sects[idx];
	Py_INCREF(ret);
	return ret;
}

static PyObject *elffile_get_section(PyObject *self, PyObject *args)
{
	const char *name;
	struct elffile *w = (struct elffile *)self;
	Elf_Scn *scn;
	size_t idx = 0;

	if (!PyArg_ParseTuple(args, "s", &name))
		return NULL;

	scn = elf_find_section(w, name, &idx);
	return elffile_secbyidx(w, scn, idx);
}

static PyObject *elffile_get_section_addr(PyObject *self, PyObject *args)
{
	unsigned long long addr;
	struct elffile *w = (struct elffile *)self;
	Elf_Scn *scn;
	size_t idx = 0;

	if (!PyArg_ParseTuple(args, "K", &addr))
		return NULL;

	scn = elf_find_addr(w, addr, &idx);
	return elffile_secbyidx(w, scn, idx);
}

static PyObject *elffile_get_section_idx(PyObject *self, PyObject *args)
{
	unsigned long long idx;
	struct elffile *w = (struct elffile *)self;

	if (!PyArg_ParseTuple(args, "K", &idx))
		return NULL;

	return elffile_secbyidx(w, NULL, idx);
}

static PyObject *elffile_get_symbol(PyObject *self, PyObject *args)
{
	const char *name, *symname;
	struct elffile *w = (struct elffile *)self;
	GElf_Sym _sym, *sym;
	size_t i;

	if (!PyArg_ParseTuple(args, "s", &name))
		return NULL;

	for (i = 0; i < w->nsym; i++) {
		sym = gelf_getsym(w->symdata, i, &_sym);
		if (sym->st_name == 0)
			continue;
		symname = elf_strptr(w->elf, w->symstridx, sym->st_name);
		if (strcmp(symname, name))
			continue;

		PyObject *pysect;
		Elf_Scn *scn = elf_getscn(w->elf, sym->st_shndx);

		if (scn)
			pysect = elffile_secbyidx(w, scn, sym->st_shndx);
		else {
			pysect = Py_None;
			Py_INCREF(pysect);
		}
		return Py_BuildValue("sKN", symname,
				(unsigned long long)sym->st_value, pysect);
	}
	Py_RETURN_NONE;
}

static PyObject *elffile_getreloc(PyObject *self, PyObject *args)
{
	struct elffile *w = (struct elffile *)self;
	struct elfreloc *relw;
	unsigned long offs;
	PyObject *ret;

	if (!PyArg_ParseTuple(args, "k", &offs))
		return NULL;

	relw = elfrelocs_get(&w->dynrelocs, offs);
	if (!relw)
		Py_RETURN_NONE;

	ret = (PyObject *)relw;
	Py_INCREF(ret);
	return ret;
}

static PyObject *elffile_find_note(PyObject *self, PyObject *args)
{
#if defined(HAVE_GELF_GETNOTE) && defined(HAVE_ELF_GETDATA_RAWCHUNK)
	const char *owner;
	const uint8_t *ids;
	GElf_Word id;
	struct elffile *w = (struct elffile *)self;
	size_t i;

	if (!PyArg_ParseTuple(args, "ss", &owner, &ids))
		return NULL;

	if (strlen((char *)ids) != 4) {
		PyErr_SetString(PyExc_ValueError,
				"ELF note ID must be exactly 4-byte string");
		return NULL;
	}
	if (w->bigendian)
		id = (ids[0] << 24) | (ids[1] << 16) | (ids[2] << 8) | ids[3];
	else
		id = (ids[3] << 24) | (ids[2] << 16) | (ids[1] << 8) | ids[0];

	for (i = 0; i < w->ehdr->e_phnum; i++) {
		GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);
		Elf_Data *notedata;
		size_t offset;

		if (phdr->p_type != PT_NOTE)
			continue;

		notedata = elf_getdata_rawchunk(w->elf, phdr->p_offset,
						phdr->p_filesz, ELF_T_NHDR);

		GElf_Nhdr nhdr[1];
		size_t nameoffs, dataoffs;

		offset = 0;
		while ((offset = gelf_getnote(notedata, offset, nhdr,
					      &nameoffs, &dataoffs))) {
			if (phdr->p_offset + nameoffs >= w->len)
				continue;

			const char *name = w->mmap + phdr->p_offset + nameoffs;

			if (strcmp(name, owner))
				continue;
			if (id != nhdr->n_type)
				continue;

			PyObject *s, *e;

			s = PyLong_FromUnsignedLongLong(
				phdr->p_vaddr + dataoffs);
			e = PyLong_FromUnsignedLongLong(
				phdr->p_vaddr + dataoffs + nhdr->n_descsz);
			return PySlice_New(s, e, NULL);
		}
	}
#endif
	Py_RETURN_NONE;
}

#ifdef HAVE_ELF_GETDATA_RAWCHUNK
static bool elffile_virt2file(struct elffile *w, GElf_Addr virt,
			      GElf_Addr *offs)
{
	*offs = 0;

	for (size_t i = 0; i < w->ehdr->e_phnum; i++) {
		GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);

		if (phdr->p_type != PT_LOAD)
			continue;

		if (virt < phdr->p_vaddr
		    || virt >= phdr->p_vaddr + phdr->p_memsz)
			continue;

		if (virt >= phdr->p_vaddr + phdr->p_filesz)
			return false;

		*offs = virt - phdr->p_vaddr + phdr->p_offset;
		return true;
	}

	return false;
}
#endif /* HAVE_ELF_GETDATA_RAWCHUNK */

static PyObject *elffile_subscript(PyObject *self, PyObject *key)
{
	Py_ssize_t start, stop, step;
	PySliceObject *slice;
	struct elffile *w = (struct elffile *)self;
	bool str = false;

	if (!PySlice_Check(key)) {
		PyErr_SetString(PyExc_IndexError,
				"ELFFile subscript must be slice");
		return NULL;
	}
	slice = (PySliceObject *)key;
	stop = -1;
	step = 1;
	if (PyLong_Check(slice->stop)) {
		start = PyLong_AsSsize_t(slice->start);
		if (PyErr_Occurred())
			return NULL;
		if (slice->stop != Py_None) {
			stop = PyLong_AsSsize_t(slice->stop);
			if (PyErr_Occurred())
				return NULL;
		}
		if (slice->step != Py_None) {
			step = PyLong_AsSsize_t(slice->step);
			if (PyErr_Occurred())
				return NULL;
		}
	} else {
		if (slice->stop != (void *)&PyUnicode_Type
		    || !PyLong_Check(slice->start)) {
			PyErr_SetString(PyExc_IndexError, "invalid slice");
			return NULL;
		}

		str = true;
		start = PyLong_AsUnsignedLongLong(slice->start);
	}
	if (step != 1) {
		PyErr_SetString(PyExc_IndexError,
				"ELFFile subscript slice step must be 1");
		return NULL;
	}

	GElf_Addr xstart = start, xstop = stop;

	for (size_t i = 0; i < w->ehdr->e_phnum; i++) {
		GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);

		if (phdr->p_type != PT_LOAD)
			continue;

		if (xstart < phdr->p_vaddr
		    || xstart >= phdr->p_vaddr + phdr->p_memsz)
			continue;
		if (!str && (xstop < phdr->p_vaddr
		    || xstop > phdr->p_vaddr + phdr->p_memsz)) {
			PyErr_Format(ELFAccessError,
				     "access (%llu) beyond end of program header (%llu)",
				     (long long)xstop,
				     (long long)(phdr->p_vaddr +
						 phdr->p_memsz));
			return NULL;
		}

		xstart = xstart - phdr->p_vaddr + phdr->p_offset;

		if (str)
			xstop = strlen(w->mmap + xstart);
		else
			xstop = xstop - phdr->p_vaddr + phdr->p_offset;

		Py_ssize_t pylen = xstop - xstart;

#if PY_MAJOR_VERSION >= 3
		return Py_BuildValue("y#", w->mmap + xstart, pylen);
#else
		return Py_BuildValue("s#", w->mmap + xstart, pylen);
#endif
	};

	return PyErr_Format(ELFAccessError,
			    "virtual address (%llu) not found in program headers",
			    (long long)start);
}

static PyMethodDef methods_elffile[] = {
	{"find_note", elffile_find_note, METH_VARARGS,
		"find specific note entry"},
	{"getreloc", elffile_getreloc, METH_VARARGS,
		"find relocation"},
	{"get_symbol", elffile_get_symbol, METH_VARARGS,
		"find symbol by name"},
	{"get_section", elffile_get_section, METH_VARARGS,
		"find section by name"},
	{"get_section_addr", elffile_get_section_addr, METH_VARARGS,
		"find section by address"},
	{"get_section_idx", elffile_get_section_idx, METH_VARARGS,
		"find section by index"},
	{}
};

static PyObject *elffile_load(PyTypeObject *type, PyObject *args,
			     PyObject *kwds);

static void elffile_free(void *arg)
{
	struct elffile *w = arg;

	elf_end(w->elf);
	munmap(w->mmap, w->len);
	free(w->filename);
}

static PyMappingMethods mp_elffile = {
	.mp_subscript = elffile_subscript,
};

static PyTypeObject typeobj_elffile = {
	PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFFile",
	.tp_basicsize = sizeof(struct elffile),
	.tp_flags = Py_TPFLAGS_DEFAULT,
	.tp_doc = elffile_doc,
	.tp_new = elffile_load,
	.tp_free = elffile_free,
	.tp_as_mapping = &mp_elffile,
	.tp_members = members_elffile,
	.tp_methods = methods_elffile,
};

#ifdef HAVE_ELF_GETDATA_RAWCHUNK
static char *elfdata_strptr(Elf_Data *data, size_t offset)
{
	char *p;

	if (offset >= data->d_size)
		return NULL;

	p = (char *)data->d_buf + offset;
	if (strnlen(p, data->d_size - offset) >= data->d_size - offset)
		return NULL;

	return p;
}

static void elffile_add_dynreloc(struct elffile *w, Elf_Data *reldata,
				 size_t entries, Elf_Data *symdata,
				 Elf_Data *strdata, Elf_Type typ)
{
	size_t i;

	for (i = 0; i < entries; i++) {
		struct elfreloc *relw;
		size_t symidx;
		GElf_Rela *rela;
		GElf_Sym *sym;
		GElf_Addr rel_offs = 0;

		relw = (struct elfreloc *)typeobj_elfreloc.tp_alloc(
				&typeobj_elfreloc, 0);
		relw->ef = w;

		if (typ == ELF_T_REL) {
			GElf_Rel _rel, *rel;
			GElf_Addr offs;

			rel = gelf_getrel(reldata, i, &_rel);
			relw->rela = &relw->_rela;
			relw->rela->r_offset = rel->r_offset;
			relw->rela->r_info = rel->r_info;
			relw->rela->r_addend = 0;
			relw->relative = true;

			/* REL uses the pointer contents itself instead of the
			 * RELA addend field :( ... theoretically this could
			 * be some weird platform specific encoding, but since
			 * we only care about data relocations it should
			 * always be a pointer...
			 */
			if (elffile_virt2file(w, rel->r_offset, &offs)) {
				Elf_Data *ptr;

				/* NB: this endian-converts! */
				ptr = elf_getdata_rawchunk(w->elf, offs,
							   w->elfclass / 8,
							   ELF_T_ADDR);

				if (ptr) {
					char *dst = (char *)&rel_offs;

					/* sigh.  it endian-converts.  but
					 * doesn't size-convert.
					 */
					if (BYTE_ORDER == BIG_ENDIAN &&
					    ptr->d_size < sizeof(rel_offs))
						dst += sizeof(rel_offs) -
						       ptr->d_size;

					memcpy(dst, ptr->d_buf, ptr->d_size);

					relw->relative = false;
					relw->rela->r_addend = rel_offs;
				}
			}
		} else
			relw->rela = gelf_getrela(reldata, i, &relw->_rela);

		rela = relw->rela;
		symidx = relw->symidx = GELF_R_SYM(rela->r_info);
		sym = relw->sym = gelf_getsym(symdata, symidx, &relw->_sym);
		if (sym) {
			if (strdata)
				relw->symname = elfdata_strptr(strdata,
							       sym->st_name);
			relw->symvalid = GELF_ST_TYPE(sym->st_info)
					!= STT_NOTYPE;
			relw->unresolved = sym->st_shndx == SHN_UNDEF;
			relw->st_value = sym->st_value;
		} else {
			relw->symname = NULL;
			relw->symvalid = false;
			relw->unresolved = false;
			relw->st_value = 0;
		}

		if (typ == ELF_T_RELA)
			debugf("dynrela @ %016llx sym %5llu %016llx %s\n",
			       (long long)rela->r_offset,
			       (unsigned long long)symidx,
			       (long long)rela->r_addend, relw->symname);
		else
			debugf("dynrel @ %016llx sym %5llu (%016llx) %s\n",
			       (long long)rela->r_offset,
			       (unsigned long long)symidx,
			       (unsigned long long)rel_offs, relw->symname);

		elfrelocs_add(&w->dynrelocs, relw);
	}

}
#endif /* HAVE_ELF_GETDATA_RAWCHUNK */

/* primary (only, really) entry point to anything in this module */
static PyObject *elffile_load(PyTypeObject *type, PyObject *args,
			      PyObject *kwds)
{
	const char *filename;
	static const char * const kwnames[] = {"filename", NULL};
	struct elffile *w;
	struct stat st;
	int fd, err;

	w = (struct elffile *)typeobj_elffile.tp_alloc(&typeobj_elffile, 0);
	if (!w)
		return NULL;

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", (char **)kwnames,
					 &filename))
		return NULL;

	w->filename = strdup(filename);
	fd = open(filename, O_RDONLY | O_NOCTTY);
	if (fd < 0 || fstat(fd, &st)) {
		PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
		if (fd >= 0)
			close(fd);
		goto out;
	}
	w->len = st.st_size;
	w->mmap = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
	if (!w->mmap) {
		PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
		close(fd);
		goto out;
	}
	close(fd);
	w->mmend = w->mmap + st.st_size;

	if (w->len < EI_NIDENT || memcmp(w->mmap, ELFMAG, SELFMAG)) {
		PyErr_SetString(ELFFormatError, "invalid ELF signature");
		goto out;
	}

	switch (w->mmap[EI_CLASS]) {
	case ELFCLASS32:
		w->elfclass = 32;
		break;
	case ELFCLASS64:
		w->elfclass = 64;
		break;
	default:
		PyErr_SetString(ELFFormatError, "invalid ELF class");
		goto out;
	}
	switch (w->mmap[EI_DATA]) {
	case ELFDATA2LSB:
		w->bigendian = false;
		break;
	case ELFDATA2MSB:
		w->bigendian = true;
		break;
	default:
		PyErr_SetString(ELFFormatError, "invalid ELF byte order");
		goto out;
	}

	w->elf = elf_memory(w->mmap, w->len);
	if (!w->elf)
		goto out_elferr;
	w->ehdr = gelf_getehdr(w->elf, &w->_ehdr);
	if (!w->ehdr)
		goto out_elferr;

	for (size_t i = 0; i < w->ehdr->e_shnum; i++) {
		Elf_Scn *scn = elf_getscn(w->elf, i);
		GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr);

		if (shdr->sh_type == SHT_SYMTAB) {
			w->symtab = scn;
			w->nsym = shdr->sh_size / shdr->sh_entsize;
			w->symdata = elf_getdata(scn, NULL);
			w->symstridx = shdr->sh_link;
			break;
		}
	}
	w->has_symbols = w->symtab && w->symstridx;
	elfrelocs_init(&w->dynrelocs);

#ifdef HAVE_ELF_GETDATA_RAWCHUNK
	for (size_t i = 0; i < w->ehdr->e_phnum; i++) {
		GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr);

		if (phdr->p_type != PT_DYNAMIC)
			continue;

		Elf_Data *dyndata = elf_getdata_rawchunk(w->elf,
				phdr->p_offset, phdr->p_filesz, ELF_T_DYN);

		GElf_Addr dynrela = 0, dynrel = 0, symtab = 0, strtab = 0;
		size_t dynrelasz = 0, dynrelaent = 0;
		size_t dynrelsz = 0, dynrelent = 0;
		size_t strsz = 0;
		GElf_Dyn _dyn, *dyn;

		for (size_t j = 0;; j++) {
			dyn = gelf_getdyn(dyndata, j, &_dyn);

			if (dyn->d_tag == DT_NULL)
				break;

			switch (dyn->d_tag) {
			case DT_SYMTAB:
				symtab = dyn->d_un.d_ptr;
				break;

			case DT_STRTAB:
				strtab = dyn->d_un.d_ptr;
				break;
			case DT_STRSZ:
				strsz = dyn->d_un.d_val;
				break;

			case DT_RELA:
				dynrela = dyn->d_un.d_ptr;
				break;
			case DT_RELASZ:
				dynrelasz = dyn->d_un.d_val;
				break;
			case DT_RELAENT:
				dynrelaent = dyn->d_un.d_val;
				break;

			case DT_REL:
				dynrel = dyn->d_un.d_ptr;
				break;
			case DT_RELSZ:
				dynrelsz = dyn->d_un.d_val;
				break;
			case DT_RELENT:
				dynrelent = dyn->d_un.d_val;
				break;
			}
		}

		GElf_Addr offset;
		Elf_Data *symdata = NULL, *strdata = NULL;

		if (elffile_virt2file(w, symtab, &offset))
			symdata = elf_getdata_rawchunk(w->elf, offset,
						       w->len - offset,
						       ELF_T_SYM);
		if (elffile_virt2file(w, strtab, &offset))
			strdata = elf_getdata_rawchunk(w->elf, offset,
						       strsz, ELF_T_BYTE);

		size_t c;

		if (dynrela && dynrelasz && dynrelaent
		    && elffile_virt2file(w, dynrela, &offset)) {
			Elf_Data *reladata = NULL;

			debugf("dynrela @%llx/%llx+%llx\n", (long long)dynrela,
			       (long long)offset, (long long)dynrelasz);

			reladata = elf_getdata_rawchunk(w->elf, offset,
							dynrelasz, ELF_T_RELA);

			c = dynrelasz / dynrelaent;
			elffile_add_dynreloc(w, reladata, c, symdata, strdata,
					     ELF_T_RELA);
		}

		if (dynrel && dynrelsz && dynrelent
		    && elffile_virt2file(w, dynrel, &offset)) {
			Elf_Data *reldata = NULL;

			debugf("dynrel @%llx/%llx+%llx\n", (long long)dynrel,
			       (long long)offset, (long long)dynrelsz);

			reldata = elf_getdata_rawchunk(w->elf, offset, dynrelsz,
						       ELF_T_REL);

			c = dynrelsz / dynrelent;
			elffile_add_dynreloc(w, reldata, c, symdata, strdata,
					     ELF_T_REL);
		}
	}
#endif
	if (w->ehdr->e_shnum > 0)
		w->sects = calloc(w->ehdr->e_shnum, sizeof(PyObject *));
	w->n_sect = w->ehdr->e_shnum;

	return (PyObject *)w;

out_elferr:
	err = elf_errno();

	PyErr_Format(ELFFormatError, "libelf error %d: %s",
		     err, elf_errmsg(err));
out:
	if (w->elf)
		elf_end(w->elf);
	free(w->filename);
	return NULL;
}

static PyObject *elfpy_debug(PyObject *self, PyObject *args)
{
	int arg;

	if (!PyArg_ParseTuple(args, "p", &arg))
		return NULL;

	debug = arg;

	Py_RETURN_NONE;
}

static PyMethodDef methods_elfpy[] = {
	{"elfpy_debug", elfpy_debug, METH_VARARGS, "switch debuging on/off"},
	{}
};

bool elf_py_init(PyObject *pymod)
{
	if (PyType_Ready(&typeobj_elffile) < 0)
		return false;
	if (PyType_Ready(&typeobj_elfsect) < 0)
		return false;
	if (PyType_Ready(&typeobj_elfreloc) < 0)
		return false;
	if (elf_version(EV_CURRENT) == EV_NONE)
		return false;

#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 5
	PyModule_AddFunctions(pymod, methods_elfpy);
#else
	(void)methods_elfpy;
#endif

#if defined(HAVE_GELF_GETNOTE) && defined(HAVE_ELF_GETDATA_RAWCHUNK)
	PyObject *elf_notes = Py_True;
#else
	PyObject *elf_notes = Py_False;
#endif
	Py_INCREF(elf_notes);
	if (PyModule_AddObject(pymod, "elf_notes", elf_notes))
		Py_DECREF(elf_notes);

	ELFFormatError = PyErr_NewException("_clippy.ELFFormatError",
					    PyExc_ValueError, NULL);
	PyModule_AddObject(pymod, "ELFFormatError", ELFFormatError);
	ELFAccessError = PyErr_NewException("_clippy.ELFAccessError",
					    PyExc_IndexError, NULL);
	PyModule_AddObject(pymod, "ELFAccessError", ELFAccessError);

	Py_INCREF(&typeobj_elffile);
	PyModule_AddObject(pymod, "ELFFile", (PyObject *)&typeobj_elffile);
	Py_INCREF(&typeobj_elfsect);
	PyModule_AddObject(pymod, "ELFSection", (PyObject *)&typeobj_elfsect);
	Py_INCREF(&typeobj_elfreloc);
	PyModule_AddObject(pymod, "ELFReloc", (PyObject *)&typeobj_elfreloc);
	return true;
}
