/*
* libkmod - interface to kernel module operations
*
- * Copyright (C) 2011 ProFUSION embedded systems
+ * Copyright (C) 2011-2013 ProFUSION embedded systems
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
+#include <assert.h>
#include <elf.h>
+#include <errno.h>
#include <stdlib.h>
#include <string.h>
-#include <assert.h>
-#include <errno.h>
+
+#include <shared/util.h>
#include "libkmod.h"
-#include "libkmod-private.h"
+#include "libkmod-internal.h"
enum kmod_elf_class {
KMOD_ELF_32 = (1 << 1),
KMOD_ELF_MSB = (1 << 4)
};
-#ifdef WORDS_BIGENDIAN
-static const enum kmod_elf_class native_endianess = KMOD_ELF_MSB;
-#else
-static const enum kmod_elf_class native_endianess = KMOD_ELF_LSB;
-#endif
+/* as defined in module-init-tools */
+struct kmod_modversion32 {
+ uint32_t crc;
+ char name[64 - sizeof(uint32_t)];
+};
+
+struct kmod_modversion64 {
+ uint64_t crc;
+ char name[64 - sizeof(uint64_t)];
+};
struct kmod_elf {
const uint8_t *memory;
uint64_t offset;
uint32_t nameoff; /* offset in strings itself */
} strings;
+ uint16_t machine;
} header;
};
#if defined(ENABLE_LOGGING) && defined(ENABLE_ELFDBG)
#define ELFDBG(elf, ...) \
- _elf_dbg(elf, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);
+ _elf_dbg(elf, __FILE__, __LINE__, __func__, __VA_ARGS__);
static inline void _elf_dbg(const struct kmod_elf *elf, const char *fname, unsigned line, const char *func, const char *fmt, ...)
{
return NULL;
}
return elf_get_mem(elf, elf->header.section.offset +
- idx * elf->header.section.entry_size);
+ (uint64_t)(idx * elf->header.section.entry_size));
}
static inline int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx, uint64_t *offset, uint64_t *size, uint32_t *nameoff)
elf_get_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field))
if (elf->class & KMOD_ELF_32) {
- const Elf32_Shdr *hdr = (const Elf32_Shdr *)p;
+ const Elf32_Shdr *hdr _unused_ = (const Elf32_Shdr *)p;
*size = READV(sh_size);
*offset = READV(sh_offset);
*nameoff = READV(sh_name);
} else {
- const Elf64_Shdr *hdr = (const Elf64_Shdr *)p;
+ const Elf64_Shdr *hdr _unused_ = (const Elf64_Shdr *)p;
*size = READV(sh_size);
*offset = READV(sh_offset);
*nameoff = READV(sh_name);
}
#undef READV
- min_size = *offset + *size;
- if (min_size >= elf->size) {
+ if (addu64_overflow(*offset, *size, &min_size)
+ || min_size > elf->size) {
ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n",
min_size, elf->size);
return -EINVAL;
struct kmod_elf *kmod_elf_new(const void *memory, off_t size)
{
struct kmod_elf *elf;
- size_t hdr_size, shdr_size, min_size;
+ uint64_t min_size;
+ size_t shdrs_size, shdr_size;
int class;
- assert(sizeof(uint16_t) == sizeof(Elf32_Half));
- assert(sizeof(uint16_t) == sizeof(Elf64_Half));
- assert(sizeof(uint32_t) == sizeof(Elf32_Word));
- assert(sizeof(uint32_t) == sizeof(Elf64_Word));
+ assert_cc(sizeof(uint16_t) == sizeof(Elf32_Half));
+ assert_cc(sizeof(uint16_t) == sizeof(Elf64_Half));
+ assert_cc(sizeof(uint32_t) == sizeof(Elf32_Word));
+ assert_cc(sizeof(uint32_t) == sizeof(Elf64_Word));
class = elf_identify(memory, size);
if (class < 0) {
elf->header.section.offset = READV(e_shoff); \
elf->header.section.count = READV(e_shnum); \
elf->header.section.entry_size = READV(e_shentsize); \
- elf->header.strings.section = READV(e_shstrndx)
+ elf->header.strings.section = READV(e_shstrndx); \
+ elf->header.machine = READV(e_machine)
if (elf->class & KMOD_ELF_32) {
- const Elf32_Ehdr *hdr = elf_get_mem(elf, 0);
+ const Elf32_Ehdr *hdr _unused_ = elf_get_mem(elf, 0);
LOAD_HEADER;
- hdr_size = sizeof(Elf32_Ehdr);
shdr_size = sizeof(Elf32_Shdr);
} else {
- const Elf64_Ehdr *hdr = elf_get_mem(elf, 0);
+ const Elf64_Ehdr *hdr _unused_ = elf_get_mem(elf, 0);
LOAD_HEADER;
- hdr_size = sizeof(Elf64_Ehdr);
shdr_size = sizeof(Elf64_Shdr);
}
#undef LOAD_HEADER
elf->header.section.entry_size, shdr_size);
goto invalid;
}
- min_size = hdr_size + shdr_size * elf->header.section.count;
- if (min_size >= elf->size) {
+ shdrs_size = shdr_size * elf->header.section.count;
+ if (addu64_overflow(shdrs_size, elf->header.section.offset, &min_size)
+ || min_size > elf->size) {
ELFDBG(elf, "file is too short to hold sections\n");
goto invalid;
}
return elf->memory;
}
-static int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, const void **buf, size_t *buf_size)
+static int elf_find_section(const struct kmod_elf *elf, const char *section)
+{
+ uint64_t nameslen;
+ const char *names = elf_get_strings_section(elf, &nameslen);
+ uint16_t i;
+
+ for (i = 1; i < elf->header.section.count; i++) {
+ uint64_t off, size;
+ uint32_t nameoff;
+ const char *n;
+ int err = elf_get_section_info(elf, i, &off, &size, &nameoff);
+ if (err < 0)
+ continue;
+ if (nameoff >= nameslen)
+ continue;
+ n = names + nameoff;
+ if (!streq(section, n))
+ continue;
+
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, const void **buf, uint64_t *buf_size)
{
uint64_t nameslen;
const char *names = elf_get_strings_section(elf, &nameslen);
/* array will be allocated with strings in a single malloc, just free *array */
int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array)
{
- size_t i, j, size, count;
+ size_t i, j, count;
+ uint64_t size;
const void *buf;
const char *strings;
char *s, **a;
if (size <= 1)
return 0;
- for (i = 0, count = 0; i < size; i++) {
- if (strings[i] != '\0')
+ for (i = 0, count = 0; i < size; ) {
+ if (strings[i] != '\0') {
+ i++;
continue;
+ }
+
+ while (strings[i] == '\0' && i < size)
+ i++;
count++;
}
a[count] = NULL;
a[0] = s;
- for (i = 0, j = 1; j < count && i < size; i++) {
- if (s[i] != '\0')
+ for (i = 0, j = 1; j < count && i < size; ) {
+ if (s[i] != '\0') {
+ i++;
continue;
+ }
+
+ while (strings[i] == '\0' && i < size)
+ i++;
- a[j] = &s[i + 1];
+ a[j] = &s[i];
j++;
}
/* array will be allocated with strings in a single malloc, just free *array */
int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array)
{
- uint64_t size, secsize, slen, off;
+ size_t off, offcrc, slen;
+ uint64_t size;
struct kmod_modversion *a;
const void *buf;
char *itr;
int i, count, err;
- struct kmod_modversion32 {
- uint32_t crc;
- char name[64 - sizeof(uint32_t)];
- };
- struct kmod_modversion64 {
- uint64_t crc;
- char name[64 - sizeof(uint64_t)];
- };
+#define MODVERSION_SEC_SIZE (sizeof(struct kmod_modversion64))
+
+ assert_cc(sizeof(struct kmod_modversion64) ==
+ sizeof(struct kmod_modversion32));
+
+ if (elf->class & KMOD_ELF_32)
+ offcrc = sizeof(uint32_t);
+ else
+ offcrc = sizeof(uint64_t);
*array = NULL;
err = kmod_elf_get_section(elf, "__versions", &buf, &size);
if (err < 0)
return err;
+
if (buf == NULL || size == 0)
return 0;
- if (elf->class & KMOD_ELF_32)
- secsize = sizeof(struct kmod_modversion32);
- else
- secsize = sizeof(struct kmod_modversion64);
-
- if (size % secsize != 0)
+ if (size % MODVERSION_SEC_SIZE != 0)
return -EINVAL;
- count = size / secsize;
+
+ count = size / MODVERSION_SEC_SIZE;
off = (const uint8_t *)buf - elf->memory;
slen = 0;
- for (i = 0; i < count; i++, off += secsize) {
- const char *symbol;
- if (elf->class & KMOD_ELF_32) {
- struct kmod_modversion32 *mv;
- symbol = elf_get_mem(elf, off + sizeof(mv->crc));
- } else {
- struct kmod_modversion64 *mv;
- symbol = elf_get_mem(elf, off + sizeof(mv->crc));
- }
+
+ for (i = 0; i < count; i++, off += MODVERSION_SEC_SIZE) {
+ const char *symbol = elf_get_mem(elf, off + offcrc);
+
if (symbol[0] == '.')
symbol++;
+
slen += strlen(symbol) + 1;
}
itr = (char *)(a + count);
off = (const uint8_t *)buf - elf->memory;
- for (i = 0; i < count; i++, off += secsize) {
- uint64_t crc;
- const char *symbol;
+
+ for (i = 0; i < count; i++, off += MODVERSION_SEC_SIZE) {
+ uint64_t crc = elf_get_uint(elf, off, offcrc);
+ const char *symbol = elf_get_mem(elf, off + offcrc);
size_t symbollen;
- if (elf->class & KMOD_ELF_32) {
- struct kmod_modversion32 *mv;
- crc = elf_get_uint(elf, off, sizeof(mv->crc));
- symbol = elf_get_mem(elf, off + sizeof(mv->crc));
- } else {
- struct kmod_modversion64 *mv;
- crc = elf_get_uint(elf, off, sizeof(mv->crc));
- symbol = elf_get_mem(elf, off + sizeof(mv->crc));
- }
+
if (symbol[0] == '.')
symbol++;
a[i].crc = crc;
+ a[i].bind = KMOD_SYMBOL_UNDEF;
a[i].symbol = itr;
symbollen = strlen(symbol) + 1;
memcpy(itr, symbol, symbollen);
int kmod_elf_strip_section(struct kmod_elf *elf, const char *section)
{
- uint64_t size, off;
+ uint64_t off, size;
const void *buf;
- int err = kmod_elf_get_section(elf, section, &buf, &size);
- if (err < 0)
- return err;
+ int idx = elf_find_section(elf, section);
+ uint64_t val;
+
+ if (idx < 0)
+ return idx;
+ buf = elf_get_section_header(elf, idx);
off = (const uint8_t *)buf - elf->memory;
-#define WRITEV(field, value) \
- elf_set_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field), value)
if (elf->class & KMOD_ELF_32) {
- const Elf32_Shdr *hdr = buf;
- uint32_t val = ~(uint32_t)SHF_ALLOC;
- return WRITEV(sh_flags, val);
+ off += offsetof(Elf32_Shdr, sh_flags);
+ size = sizeof(((Elf32_Shdr *)buf)->sh_flags);
} else {
- const Elf64_Shdr *hdr = buf;
- uint64_t val = ~(uint64_t)SHF_ALLOC;
- return WRITEV(sh_flags, val);
+ off += offsetof(Elf64_Shdr, sh_flags);
+ size = sizeof(((Elf64_Shdr *)buf)->sh_flags);
}
-#undef WRITEV
+
+ val = elf_get_uint(elf, off, size);
+ val &= ~(uint64_t)SHF_ALLOC;
+
+ return elf_set_uint(elf, off, size, val);
}
int kmod_elf_strip_vermagic(struct kmod_elf *elf)
i += strlen(s);
continue;
}
- s += len;
off = (const uint8_t *)s - elf->memory;
if (elf->changed == NULL) {
ELFDBG(elf, "no vermagic found in .modinfo\n");
return -ENOENT;
}
+
+
+static int kmod_elf_get_symbols_symtab(const struct kmod_elf *elf, struct kmod_modversion **array)
+{
+ uint64_t i, last, size;
+ const void *buf;
+ const char *strings;
+ char *itr;
+ struct kmod_modversion *a;
+ int count, err;
+
+ *array = NULL;
+
+ err = kmod_elf_get_section(elf, "__ksymtab_strings", &buf, &size);
+ if (err < 0)
+ return err;
+ strings = buf;
+ if (strings == NULL || size == 0)
+ return 0;
+
+ /* skip zero padding */
+ while (strings[0] == '\0' && size > 1) {
+ strings++;
+ size--;
+ }
+ if (size <= 1)
+ return 0;
+
+ last = 0;
+ for (i = 0, count = 0; i < size; i++) {
+ if (strings[i] == '\0') {
+ if (last == i) {
+ last = i + 1;
+ continue;
+ }
+ count++;
+ last = i + 1;
+ }
+ }
+ if (strings[i - 1] != '\0')
+ count++;
+
+ *array = a = malloc(size + 1 + sizeof(struct kmod_modversion) * count);
+ if (*array == NULL)
+ return -errno;
+
+ itr = (char *)(a + count);
+ last = 0;
+ for (i = 0, count = 0; i < size; i++) {
+ if (strings[i] == '\0') {
+ size_t slen = i - last;
+ if (last == i) {
+ last = i + 1;
+ continue;
+ }
+ a[count].crc = 0;
+ a[count].bind = KMOD_SYMBOL_GLOBAL;
+ a[count].symbol = itr;
+ memcpy(itr, strings + last, slen);
+ itr[slen] = '\0';
+ itr += slen + 1;
+ count++;
+ last = i + 1;
+ }
+ }
+ if (strings[i - 1] != '\0') {
+ size_t slen = i - last;
+ a[count].crc = 0;
+ a[count].bind = KMOD_SYMBOL_GLOBAL;
+ a[count].symbol = itr;
+ memcpy(itr, strings + last, slen);
+ itr[slen] = '\0';
+ count++;
+ }
+
+ return count;
+}
+
+static inline uint8_t kmod_symbol_bind_from_elf(uint8_t elf_value)
+{
+ switch (elf_value) {
+ case STB_LOCAL:
+ return KMOD_SYMBOL_LOCAL;
+ case STB_GLOBAL:
+ return KMOD_SYMBOL_GLOBAL;
+ case STB_WEAK:
+ return KMOD_SYMBOL_WEAK;
+ default:
+ return KMOD_SYMBOL_NONE;
+ }
+}
+
+static uint64_t kmod_elf_resolve_crc(const struct kmod_elf *elf, uint64_t crc, uint16_t shndx)
+{
+ int err;
+ uint64_t off, size;
+ uint32_t nameoff;
+
+ if (shndx == SHN_ABS || shndx == SHN_UNDEF)
+ return crc;
+
+ err = elf_get_section_info(elf, shndx, &off, &size, &nameoff);
+ if (err < 0) {
+ ELFDBG("Cound not find section index %"PRIu16" for crc", shndx);
+ return (uint64_t)-1;
+ }
+
+ if (crc > (size - sizeof(uint32_t))) {
+ ELFDBG("CRC offset %"PRIu64" is too big, section %"PRIu16" size is %"PRIu64"\n",
+ crc, shndx, size);
+ return (uint64_t)-1;
+ }
+
+ crc = elf_get_uint(elf, off + crc, sizeof(uint32_t));
+ return crc;
+}
+
+/* array will be allocated with strings in a single malloc, just free *array */
+int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array)
+{
+ static const char crc_str[] = "__crc_";
+ static const size_t crc_strlen = sizeof(crc_str) - 1;
+ uint64_t strtablen, symtablen, str_off, sym_off;
+ const void *strtab, *symtab;
+ struct kmod_modversion *a;
+ char *itr;
+ size_t slen, symlen;
+ int i, count, symcount, err;
+
+ err = kmod_elf_get_section(elf, ".strtab", &strtab, &strtablen);
+ if (err < 0) {
+ ELFDBG(elf, "no .strtab found.\n");
+ goto fallback;
+ }
+
+ err = kmod_elf_get_section(elf, ".symtab", &symtab, &symtablen);
+ if (err < 0) {
+ ELFDBG(elf, "no .symtab found.\n");
+ goto fallback;
+ }
+
+ if (elf->class & KMOD_ELF_32)
+ symlen = sizeof(Elf32_Sym);
+ else
+ symlen = sizeof(Elf64_Sym);
+
+ if (symtablen % symlen != 0) {
+ ELFDBG(elf, "unexpected .symtab of length %"PRIu64", not multiple of %"PRIu64" as expected.\n", symtablen, symlen);
+ goto fallback;
+ }
+
+ symcount = symtablen / symlen;
+ count = 0;
+ slen = 0;
+ str_off = (const uint8_t *)strtab - elf->memory;
+ sym_off = (const uint8_t *)symtab - elf->memory + symlen;
+ for (i = 1; i < symcount; i++, sym_off += symlen) {
+ const char *name;
+ uint32_t name_off;
+
+#define READV(field) \
+ elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\
+ sizeof(s->field))
+ if (elf->class & KMOD_ELF_32) {
+ Elf32_Sym *s;
+ name_off = READV(st_name);
+ } else {
+ Elf64_Sym *s;
+ name_off = READV(st_name);
+ }
+#undef READV
+ if (name_off >= strtablen) {
+ ELFDBG(elf, ".strtab is %"PRIu64" bytes, but .symtab entry %d wants to access offset %"PRIu32".\n", strtablen, i, name_off);
+ goto fallback;
+ }
+
+ name = elf_get_mem(elf, str_off + name_off);
+
+ if (strncmp(name, crc_str, crc_strlen) != 0)
+ continue;
+ slen += strlen(name + crc_strlen) + 1;
+ count++;
+ }
+
+ if (count == 0)
+ goto fallback;
+
+ *array = a = malloc(sizeof(struct kmod_modversion) * count + slen);
+ if (*array == NULL)
+ return -errno;
+
+ itr = (char *)(a + count);
+ count = 0;
+ str_off = (const uint8_t *)strtab - elf->memory;
+ sym_off = (const uint8_t *)symtab - elf->memory + symlen;
+ for (i = 1; i < symcount; i++, sym_off += symlen) {
+ const char *name;
+ uint32_t name_off;
+ uint64_t crc;
+ uint8_t info, bind;
+ uint16_t shndx;
+
+#define READV(field) \
+ elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\
+ sizeof(s->field))
+ if (elf->class & KMOD_ELF_32) {
+ Elf32_Sym *s;
+ name_off = READV(st_name);
+ crc = READV(st_value);
+ info = READV(st_info);
+ shndx = READV(st_shndx);
+ } else {
+ Elf64_Sym *s;
+ name_off = READV(st_name);
+ crc = READV(st_value);
+ info = READV(st_info);
+ shndx = READV(st_shndx);
+ }
+#undef READV
+ name = elf_get_mem(elf, str_off + name_off);
+ if (strncmp(name, crc_str, crc_strlen) != 0)
+ continue;
+ name += crc_strlen;
+
+ if (elf->class & KMOD_ELF_32)
+ bind = ELF32_ST_BIND(info);
+ else
+ bind = ELF64_ST_BIND(info);
+
+ a[count].crc = kmod_elf_resolve_crc(elf, crc, shndx);
+ a[count].bind = kmod_symbol_bind_from_elf(bind);
+ a[count].symbol = itr;
+ slen = strlen(name);
+ memcpy(itr, name, slen);
+ itr[slen] = '\0';
+ itr += slen + 1;
+ count++;
+ }
+ return count;
+
+fallback:
+ ELFDBG(elf, "Falling back to __ksymtab_strings!\n");
+ return kmod_elf_get_symbols_symtab(elf, array);
+}
+
+static int kmod_elf_crc_find(const struct kmod_elf *elf, const void *versions, uint64_t versionslen, const char *name, uint64_t *crc)
+{
+ size_t verlen, crclen, off;
+ uint64_t i;
+
+ if (elf->class & KMOD_ELF_32) {
+ struct kmod_modversion32 *mv;
+ verlen = sizeof(*mv);
+ crclen = sizeof(mv->crc);
+ } else {
+ struct kmod_modversion64 *mv;
+ verlen = sizeof(*mv);
+ crclen = sizeof(mv->crc);
+ }
+
+ off = (const uint8_t *)versions - elf->memory;
+ for (i = 0; i < versionslen; i += verlen) {
+ const char *symbol = elf_get_mem(elf, off + i + crclen);
+ if (!streq(name, symbol))
+ continue;
+ *crc = elf_get_uint(elf, off + i, crclen);
+ return i / verlen;
+ }
+
+ ELFDBG(elf, "could not find crc for symbol '%s'\n", name);
+ *crc = 0;
+ return -1;
+}
+
+/* from module-init-tools:elfops_core.c */
+#ifndef STT_REGISTER
+#define STT_REGISTER 13 /* Global register reserved to app. */
+#endif
+
+/* array will be allocated with strings in a single malloc, just free *array */
+int kmod_elf_get_dependency_symbols(const struct kmod_elf *elf, struct kmod_modversion **array)
+{
+ uint64_t versionslen, strtablen, symtablen, str_off, sym_off, ver_off;
+ const void *versions, *strtab, *symtab;
+ struct kmod_modversion *a;
+ char *itr;
+ size_t slen, verlen, symlen, crclen;
+ int i, count, symcount, vercount, err;
+ bool handle_register_symbols;
+ uint8_t *visited_versions;
+ uint64_t *symcrcs;
+
+ err = kmod_elf_get_section(elf, "__versions", &versions, &versionslen);
+ if (err < 0) {
+ versions = NULL;
+ versionslen = 0;
+ verlen = 0;
+ crclen = 0;
+ } else {
+ if (elf->class & KMOD_ELF_32) {
+ struct kmod_modversion32 *mv;
+ verlen = sizeof(*mv);
+ crclen = sizeof(mv->crc);
+ } else {
+ struct kmod_modversion64 *mv;
+ verlen = sizeof(*mv);
+ crclen = sizeof(mv->crc);
+ }
+ if (versionslen % verlen != 0) {
+ ELFDBG(elf, "unexpected __versions of length %"PRIu64", not multiple of %zd as expected.\n", versionslen, verlen);
+ versions = NULL;
+ versionslen = 0;
+ }
+ }
+
+ err = kmod_elf_get_section(elf, ".strtab", &strtab, &strtablen);
+ if (err < 0) {
+ ELFDBG(elf, "no .strtab found.\n");
+ return -EINVAL;
+ }
+
+ err = kmod_elf_get_section(elf, ".symtab", &symtab, &symtablen);
+ if (err < 0) {
+ ELFDBG(elf, "no .symtab found.\n");
+ return -EINVAL;
+ }
+
+ if (elf->class & KMOD_ELF_32)
+ symlen = sizeof(Elf32_Sym);
+ else
+ symlen = sizeof(Elf64_Sym);
+
+ if (symtablen % symlen != 0) {
+ ELFDBG(elf, "unexpected .symtab of length %"PRIu64", not multiple of %"PRIu64" as expected.\n", symtablen, symlen);
+ return -EINVAL;
+ }
+
+ if (versionslen == 0) {
+ vercount = 0;
+ visited_versions = NULL;
+ } else {
+ vercount = versionslen / verlen;
+ visited_versions = calloc(vercount, sizeof(uint8_t));
+ if (visited_versions == NULL)
+ return -ENOMEM;
+ }
+
+ handle_register_symbols = (elf->header.machine == EM_SPARC ||
+ elf->header.machine == EM_SPARCV9);
+
+ symcount = symtablen / symlen;
+ count = 0;
+ slen = 0;
+ str_off = (const uint8_t *)strtab - elf->memory;
+ sym_off = (const uint8_t *)symtab - elf->memory + symlen;
+
+ symcrcs = calloc(symcount, sizeof(uint64_t));
+ if (symcrcs == NULL) {
+ free(visited_versions);
+ return -ENOMEM;
+ }
+
+ for (i = 1; i < symcount; i++, sym_off += symlen) {
+ const char *name;
+ uint64_t crc;
+ uint32_t name_off;
+ uint16_t secidx;
+ uint8_t info;
+ int idx;
+
+#define READV(field) \
+ elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\
+ sizeof(s->field))
+ if (elf->class & KMOD_ELF_32) {
+ Elf32_Sym *s;
+ name_off = READV(st_name);
+ secidx = READV(st_shndx);
+ info = READV(st_info);
+ } else {
+ Elf64_Sym *s;
+ name_off = READV(st_name);
+ secidx = READV(st_shndx);
+ info = READV(st_info);
+ }
+#undef READV
+ if (secidx != SHN_UNDEF)
+ continue;
+
+ if (handle_register_symbols) {
+ uint8_t type;
+ if (elf->class & KMOD_ELF_32)
+ type = ELF32_ST_TYPE(info);
+ else
+ type = ELF64_ST_TYPE(info);
+
+ /* Not really undefined: sparc gcc 3.3 creates
+ * U references when you have global asm
+ * variables, to avoid anyone else misusing
+ * them.
+ */
+ if (type == STT_REGISTER)
+ continue;
+ }
+
+ if (name_off >= strtablen) {
+ ELFDBG(elf, ".strtab is %"PRIu64" bytes, but .symtab entry %d wants to access offset %"PRIu32".\n", strtablen, i, name_off);
+ free(visited_versions);
+ free(symcrcs);
+ return -EINVAL;
+ }
+
+ name = elf_get_mem(elf, str_off + name_off);
+ if (name[0] == '\0') {
+ ELFDBG(elf, "empty symbol name at index %"PRIu64"\n", i);
+ continue;
+ }
+
+ slen += strlen(name) + 1;
+ count++;
+
+ idx = kmod_elf_crc_find(elf, versions, versionslen, name, &crc);
+ if (idx >= 0 && visited_versions != NULL)
+ visited_versions[idx] = 1;
+ symcrcs[i] = crc;
+ }
+
+ if (visited_versions != NULL) {
+ /* module_layout/struct_module are not visited, but needed */
+ ver_off = (const uint8_t *)versions - elf->memory;
+ for (i = 0; i < vercount; i++) {
+ if (visited_versions[i] == 0) {
+ const char *name;
+ name = elf_get_mem(elf, ver_off + i * verlen + crclen);
+ slen += strlen(name) + 1;
+
+ count++;
+ }
+ }
+ }
+
+ if (count == 0) {
+ free(visited_versions);
+ free(symcrcs);
+ *array = NULL;
+ return 0;
+ }
+
+ *array = a = malloc(sizeof(struct kmod_modversion) * count + slen);
+ if (*array == NULL) {
+ free(visited_versions);
+ free(symcrcs);
+ return -errno;
+ }
+
+ itr = (char *)(a + count);
+ count = 0;
+ str_off = (const uint8_t *)strtab - elf->memory;
+ sym_off = (const uint8_t *)symtab - elf->memory + symlen;
+ for (i = 1; i < symcount; i++, sym_off += symlen) {
+ const char *name;
+ uint64_t crc;
+ uint32_t name_off;
+ uint16_t secidx;
+ uint8_t info, bind;
+
+#define READV(field) \
+ elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\
+ sizeof(s->field))
+ if (elf->class & KMOD_ELF_32) {
+ Elf32_Sym *s;
+ name_off = READV(st_name);
+ secidx = READV(st_shndx);
+ info = READV(st_info);
+ } else {
+ Elf64_Sym *s;
+ name_off = READV(st_name);
+ secidx = READV(st_shndx);
+ info = READV(st_info);
+ }
+#undef READV
+ if (secidx != SHN_UNDEF)
+ continue;
+
+ if (handle_register_symbols) {
+ uint8_t type;
+ if (elf->class & KMOD_ELF_32)
+ type = ELF32_ST_TYPE(info);
+ else
+ type = ELF64_ST_TYPE(info);
+
+ /* Not really undefined: sparc gcc 3.3 creates
+ * U references when you have global asm
+ * variables, to avoid anyone else misusing
+ * them.
+ */
+ if (type == STT_REGISTER)
+ continue;
+ }
+
+ name = elf_get_mem(elf, str_off + name_off);
+ if (name[0] == '\0') {
+ ELFDBG(elf, "empty symbol name at index %"PRIu64"\n", i);
+ continue;
+ }
+
+ if (elf->class & KMOD_ELF_32)
+ bind = ELF32_ST_BIND(info);
+ else
+ bind = ELF64_ST_BIND(info);
+ if (bind == STB_WEAK)
+ bind = KMOD_SYMBOL_WEAK;
+ else
+ bind = KMOD_SYMBOL_UNDEF;
+
+ slen = strlen(name);
+ crc = symcrcs[i];
+
+ a[count].crc = crc;
+ a[count].bind = bind;
+ a[count].symbol = itr;
+ memcpy(itr, name, slen);
+ itr[slen] = '\0';
+ itr += slen + 1;
+
+ count++;
+ }
+
+ free(symcrcs);
+
+ if (visited_versions == NULL)
+ return count;
+
+ /* add unvisited (module_layout/struct_module) */
+ ver_off = (const uint8_t *)versions - elf->memory;
+ for (i = 0; i < vercount; i++) {
+ const char *name;
+ uint64_t crc;
+
+ if (visited_versions[i] != 0)
+ continue;
+
+ name = elf_get_mem(elf, ver_off + i * verlen + crclen);
+ slen = strlen(name);
+ crc = elf_get_uint(elf, ver_off + i * verlen, crclen);
+
+ a[count].crc = crc;
+ a[count].bind = KMOD_SYMBOL_UNDEF;
+ a[count].symbol = itr;
+ memcpy(itr, name, slen);
+ itr[slen] = '\0';
+ itr += slen + 1;
+
+ count++;
+ }
+ free(visited_versions);
+ return count;
+}