#if defined(ELF32BIT)

#define PERBIT(x) x##32
#define ElfPERBIT(x) Elf32_##x
#define ELFPERBIT(x) ELF32_##x
/* 32-bit unsigned integer */
#define Elf32_Uint Elf32_Word

#elif defined(ELF64BIT)

#define PERBIT(x) x##64
#define ElfPERBIT(x) Elf64_##x
#define ELFPERBIT(x) ELF64_##x
/* 64-bit unsigned integer */
#define Elf64_Uint Elf64_Xword

#else
#  error "Undefined ELF word length"
#endif

static void *PERBIT(get_section)(struct elf_file *module,
				 const char *secname,
				 ElfPERBIT(Shdr) **sechdr,
				 unsigned long *secsize)
{
	void *data = module->data;
	unsigned long len = module->len;
	int conv = module->conv;

	ElfPERBIT(Ehdr) *hdr;
	ElfPERBIT(Shdr) *sechdrs;
	ElfPERBIT(Off) e_shoff;
	ElfPERBIT(Half) e_shnum, e_shstrndx;
	ElfPERBIT(Off) secoffset;

	const char *secnames;
	unsigned int i;

	*secsize = 0;

	if (len <= 0 || len < sizeof(*hdr))
		return NULL;

	hdr = data;
	e_shoff = END(hdr->e_shoff, conv);
	e_shnum = END(hdr->e_shnum, conv);
	e_shstrndx = END(hdr->e_shstrndx, conv);

	if (len < e_shoff + e_shnum * sizeof(sechdrs[0]))
		return NULL;

	sechdrs = data + e_shoff;

	if (len < END(sechdrs[e_shstrndx].sh_offset, conv))
		return NULL;

	/* Find section by name; return header, pointer and size. */
	secnames = data + END(sechdrs[e_shstrndx].sh_offset, conv);
	for (i = 1; i < e_shnum; i++) {
		if (streq(secnames + END(sechdrs[i].sh_name, conv), secname)) {
			*secsize = END(sechdrs[i].sh_size, conv);
			secoffset = END(sechdrs[i].sh_offset, conv);
			if (sechdr)
				*sechdr = sechdrs + i;
			if (len < secoffset + *secsize)
				return NULL;
			return data + secoffset;
		}
	}
	return NULL;
}

/* Load the given section: NULL on error. */
static void *PERBIT(load_section)(struct elf_file *module,
				  const char *secname,
				  unsigned long *secsize)
{
	return PERBIT(get_section)(module, secname, NULL, secsize);
}

static struct string_table *PERBIT(load_strings)(struct elf_file *module,
						 const char *secname,
						 struct string_table *tbl)
{
	unsigned long size;
	const char *strings;

	strings = PERBIT(load_section)(module, secname, &size);
	if (strings) {
		if (strings[size-1] != 0) {
			warn("%s may be corrupt; an unterminated string"
			     " was found at the end of section %s\n",
			     module->pathname, secname);
		}
		/* Skip any zero padding. */
		while (!strings[0]) {
			strings++;
			if (size-- <= 1)
				return tbl;
		}
		for (; strings; strings = next_string(strings, &size))
			tbl = NOFAIL(strtbl_add(strings, tbl));
	}
	return tbl;
}

static struct string_table *PERBIT(load_symbols)(struct elf_file *module,
                                                 uint64_t **versions)
{
	struct string_table *symtbl = NULL;

	if (versions) {
		static const char crc[] = "__crc_";
		static const int crc_len = sizeof(crc) - 1;
		unsigned int num_syms, i;
		unsigned long size;
		ElfPERBIT(Sym) *syms;
		char *strings;
		int conv;

		*versions = NULL;
		strings = PERBIT(load_section)(module, ".strtab", &size);
		syms = PERBIT(load_section)(module, ".symtab", &size);
		if (!strings || !syms)
			goto fallback;
		num_syms = size / sizeof(syms[0]);
		*versions = NOFAIL(calloc(sizeof(**versions), num_syms));

		conv = module->conv;
		for (i = 1; i < num_syms; i++) {
			const char *name;
			name = strings + END(syms[i].st_name, conv);
			if (strncmp(name, crc, crc_len) != 0)
				continue;
			name += crc_len;
			symtbl = NOFAIL(strtbl_add(name, symtbl));
			(*versions)[symtbl->cnt - 1] = END(syms[i].st_value,
					conv);
		}
		if (!symtbl) {
			/* Either this module does not export any symbols, or
			 * it was compiled without CONFIG_MODVERSIONS. If the
			 * latter, we will print a warning in load_dep_syms,
			 * so just silently fallback to __ksymtab_strings in
			 * both cases.
			 */
			free(*versions);
			*versions = NULL;
			goto fallback;
		}
		return symtbl;
	}
fallback:
	return PERBIT(load_strings)(module, "__ksymtab_strings", symtbl);
}

static char *PERBIT(get_aliases)(struct elf_file *module, unsigned long *size)
{
	return PERBIT(load_section)(module, ".modalias", size);
}

static char *PERBIT(get_modinfo)(struct elf_file *module, unsigned long *size)
{
	return PERBIT(load_section)(module, ".modinfo", size);
}

#ifndef STT_REGISTER
#define STT_REGISTER    13              /* Global register reserved to app. */
#endif

static struct string_table *PERBIT(load_dep_syms)(struct elf_file *module,
						  struct string_table **types,
						  uint64_t **versions)
{
	unsigned int i, num_syms;
	unsigned int j, num_symvers, versions_size;
	unsigned long size;
	char *strings;
	ElfPERBIT(Sym) *syms;
	ElfPERBIT(Ehdr) *hdr;
	struct PERBIT(modver_info) **symvers;
	int handle_register_symbols;
	struct string_table *names;
	int conv;

	names = NULL;
	*types = NULL;
	symvers = NULL;
	num_symvers = versions_size = 0;

	if (versions) {
		int ok = 1;
		*versions = NULL;
		struct PERBIT(modver_info) *symvers_sec;

		symvers_sec = module->ops->load_section(module, "__versions",
				&size);
		if (!symvers_sec) {
			warn("%s is built without modversions",
					module->pathname);
			ok = 0;
		}
		if (size % sizeof(symvers[0]) != 0) {
			warn("invalid __versions section size in %s",
					module->pathname);
			ok = 0;
		}
		if (ok) {
			num_symvers = size / sizeof(symvers_sec[0]);
			/* symvers is used to keep track of each visited entry.
			 * The table also contains the fake struct_module /
			 * module_layout symbol which we don't want to miss.
			 */
			symvers = NOFAIL(malloc(num_symvers *
						sizeof(symvers[0])));
			for (j = 0; j < num_symvers; j++)
				symvers[j] = &symvers_sec[j];
		} else {
			versions = NULL;
		}
	}

	strings = PERBIT(load_section)(module, ".strtab", &size);
	syms = PERBIT(load_section)(module, ".symtab", &size);
	if (!strings || !syms) {
		warn("Couldn't find symtab and strtab in module %s\n",
		     module->pathname);
		goto out;
	}

	num_syms = size / sizeof(syms[0]);
	hdr = module->data;
	conv = module->conv;
	if (versions) {
		versions_size = num_syms;
		*versions = NOFAIL(calloc(sizeof(**versions), versions_size));
	}

	handle_register_symbols =
		(END(hdr->e_machine, conv) == EM_SPARC ||
		 END(hdr->e_machine, conv) == EM_SPARCV9);

	for (i = 1; i < num_syms; i++) {
		if (END(syms[i].st_shndx, conv) == SHN_UNDEF) {
			/* Look for symbol */
			const char *name;
			int weak;

			name = strings + END(syms[i].st_name, conv);

			/* Not really undefined: sparc gcc 3.3 creates
                           U references when you have global asm
                           variables, to avoid anyone else misusing
                           them. */
			if (handle_register_symbols
			    && (ELFPERBIT(ST_TYPE)(END(syms[i].st_info, conv))
				== STT_REGISTER))
				continue;

			weak = (ELFPERBIT(ST_BIND)(END(syms[i].st_info, conv))
				== STB_WEAK);
			names = NOFAIL(strtbl_add(name, names));
			*types = NOFAIL(strtbl_add(weak ? weak_sym : undef_sym,
				*types));

			if (!versions)
				continue;
			/* Not optimal, but the number of required symbols
			 * is usually not huge and this is only called by
			 * depmod.
			 */
			for (j = 0; j < num_symvers; j++) {
				struct PERBIT(modver_info) *info = symvers[j];

				if (!info)
					continue;
				if (streq(name, info->name)) {
					(*versions)[names->cnt - 1] =
						END(info->crc, conv);
					symvers[j] = NULL;
					break;
				}
			}
		}
	}
	/* add struct_module / module_layout */
	for (j = 0; j < num_symvers; j++) {
		struct PERBIT(modver_info) *info = symvers[j];

		if (!info)
			continue;
		if ((names ? names->cnt : 0) >= versions_size) {
			versions_size++;
			*versions = NOFAIL(realloc(*versions, versions_size));
		}
		names = NOFAIL(strtbl_add(info->name, names));
		*types = NOFAIL(strtbl_add(undef_sym, *types));
		(*versions)[names->cnt - 1] = END(info->crc, conv);
	}
out:
	free(symvers);
	return names;
}

static void *PERBIT(deref_sym)(ElfPERBIT(Ehdr) *hdr,
			       ElfPERBIT(Shdr) *sechdrs,
			       ElfPERBIT(Sym) *sym,
			       unsigned int *secsize,
			       int conv)
{
	/* In BSS?  Happens for empty device tables on
	 * recent GCC versions. */
	if (END(sechdrs[END(sym->st_shndx, conv)].sh_type,conv) == SHT_NOBITS)
		return NULL;

	if (secsize)
		*secsize = END(sym->st_size, conv);
	return (void *)hdr
		+ END(sechdrs[END(sym->st_shndx, conv)].sh_offset, conv)
		+ END(sym->st_value, conv);
}

/* FIXME: Check size, unless we end up using aliases anyway --RR */
static void PERBIT(fetch_tables)(struct elf_file *module,
				 struct module_tables *tables)
{
	unsigned int i;
	unsigned long size;
	char *strings;
	ElfPERBIT(Ehdr) *hdr;
	ElfPERBIT(Sym) *syms;
	ElfPERBIT(Shdr) *sechdrs;
	int conv;

	hdr = module->data;
	conv = module->conv;

	sechdrs = (void *)hdr + END(hdr->e_shoff, conv);
	strings = PERBIT(load_section)(module, ".strtab", &size);
	syms = PERBIT(load_section)(module, ".symtab", &size);

	/* Don't warn again: we already have above */
	if (!strings || !syms)
		return;

	memset(tables, 0x00, sizeof(struct module_tables));

	for (i = 0; i < size / sizeof(syms[0]); i++) {
		char *name = strings + END(syms[i].st_name, conv);

		if (!tables->pci_table && streq(name, "__mod_pci_device_table")) {
			tables->pci_size = PERBIT(PCI_DEVICE_SIZE);
			tables->pci_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, conv);
		}
		else if (!tables->usb_table && streq(name, "__mod_usb_device_table")) {
			tables->usb_size = PERBIT(USB_DEVICE_SIZE);
			tables->usb_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, conv);
		}
		else if (!tables->ccw_table && streq(name, "__mod_ccw_device_table")) {
			tables->ccw_size = PERBIT(CCW_DEVICE_SIZE);
			tables->ccw_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, conv);
		}
		else if (!tables->ieee1394_table && streq(name, "__mod_ieee1394_device_table")) {
			tables->ieee1394_size = PERBIT(IEEE1394_DEVICE_SIZE);
			tables->ieee1394_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
								   NULL, conv);
		}
		else if (!tables->pnp_table && streq(name, "__mod_pnp_device_table")) {
			tables->pnp_size = PERBIT(PNP_DEVICE_SIZE);
			tables->pnp_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							      NULL, conv);
		}
		else if (!tables->pnp_card_table && streq(name, "__mod_pnp_card_device_table")) {
			tables->pnp_card_size = PERBIT(PNP_CARD_DEVICE_SIZE);
			tables->pnp_card_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
								   NULL, conv);
			tables->pnp_card_offset = PERBIT(PNP_CARD_DEVICE_OFFSET);
		}
		else if (!tables->input_table && streq(name, "__mod_input_device_table")) {
			tables->input_size = PERBIT(INPUT_DEVICE_SIZE);
			tables->input_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							        &tables->input_table_size,
							        conv);
		}
		else if (!tables->serio_table && streq(name, "__mod_serio_device_table")) {
			tables->serio_size = PERBIT(SERIO_DEVICE_SIZE);
			tables->serio_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
								NULL, conv);
		}
		else if (!tables->of_table && streq(name, "__mod_of_device_table")) {
			tables->of_size = PERBIT(OF_DEVICE_SIZE);
			tables->of_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
							     NULL, conv);
		}
	}
}

/*
 * strip_section - tell the kernel to ignore the named section
 */
static void PERBIT(strip_section)(struct elf_file *module, const char *secname)
{
	void *p;
	ElfPERBIT(Shdr) *sechdr;
	unsigned long secsize;

	p = PERBIT(get_section)(module, secname, &sechdr, &secsize);
	if (p) {
		ElfPERBIT(Uint) mask;
		mask = ~((ElfPERBIT(Uint))SHF_ALLOC);
		sechdr->sh_flags &= END(mask, module->conv);
	}
}

static int PERBIT(dump_modversions)(struct elf_file *module)
{
	unsigned long secsize;
	struct PERBIT(modver_info) *info;
	int n = 0;

	info = module->ops->load_section(module, "__versions", &secsize);
	if (!info)
		return 0; /* not a kernel module */
	if (secsize % sizeof(*info) != 0)
		return -1; /* invalid section size */

	for (n = 0; n < secsize / sizeof(*info); n++) {
#if defined(ELF32BIT)
		printf("0x%08lx\t%s\n", (unsigned long)
#else /* defined(ELF64BIT) */
		printf("0x%08llx\t%s\n", (unsigned long long)
#endif
			END(info[n].crc, module->conv),
			skip_dot(info[n].name));
	}
	return n;
}

struct module_ops PERBIT(mod_ops) = {
	.load_section	= PERBIT(load_section),
	.load_strings	= PERBIT(load_strings),
	.load_symbols	= PERBIT(load_symbols),
	.load_dep_syms	= PERBIT(load_dep_syms),
	.fetch_tables	= PERBIT(fetch_tables),
	.get_aliases	= PERBIT(get_aliases),
	.get_modinfo	= PERBIT(get_modinfo),
	.strip_section	= PERBIT(strip_section),
	.dump_modvers	= PERBIT(dump_modversions),
};

#undef PERBIT
#undef ElfPERBIT
#undef ELFPERBIT
