]> git.ipfire.org Git - thirdparty/kmod.git/blobdiff - libkmod/libkmod-elf.c
gitignore: ignore .cache.mk when building modules
[thirdparty/kmod.git] / libkmod / libkmod-elf.c
index 30feb76b1543f6383732eda2ae340d12fb9fe5de..ef4a8a3142a1f48dbbe913b9c8a16ac41d205bf8 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * 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),
@@ -34,11 +35,16 @@ enum kmod_elf_class {
        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;
@@ -57,6 +63,7 @@ struct kmod_elf {
                        uint64_t offset;
                        uint32_t nameoff; /* offset in strings itself */
                } strings;
+               uint16_t machine;
        } header;
 };
 
@@ -64,7 +71,7 @@ struct kmod_elf {
 
 #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, ...)
 {
@@ -211,7 +218,7 @@ static inline const void *elf_get_section_header(const struct kmod_elf *elf, uin
                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)
@@ -231,20 +238,20 @@ static inline int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx,
        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;
@@ -265,13 +272,14 @@ static const char *elf_get_strings_section(const struct kmod_elf *elf, uint64_t
 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) {
@@ -296,16 +304,15 @@ struct kmod_elf *kmod_elf_new(const void *memory, off_t size)
        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
@@ -322,8 +329,9 @@ struct kmod_elf *kmod_elf_new(const void *memory, off_t size)
                       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;
        }
@@ -362,7 +370,32 @@ const void *kmod_elf_get_memory(const struct kmod_elf *elf)
        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);
@@ -395,7 +428,8 @@ static int kmod_elf_get_section(const struct kmod_elf *elf, const char *section,
 /* 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;
@@ -420,9 +454,14 @@ int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char *
        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++;
        }
@@ -442,11 +481,16 @@ int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char *
        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++;
        }
 
@@ -456,50 +500,45 @@ int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char *
 /* 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;
        }
 
@@ -509,23 +548,17 @@ int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion
 
        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);
@@ -537,26 +570,29 @@ int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion
 
 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)
@@ -598,7 +634,6 @@ 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) {
@@ -620,3 +655,560 @@ int kmod_elf_strip_vermagic(struct kmod_elf *elf)
        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;
+}