elf: add get_symbols()
Similar to module-init-tools load_symbols(), it will try .symtab and
.strtab for symbols starting with __crc_, if they are found their crc
is read from ELF's Elf_Sym::st_value.
If not found, then it will fallback to __ksymtab_strings.
diff --git a/libkmod/libkmod-elf.c b/libkmod/libkmod-elf.c
index eeb17c6..b4d6882 100644
--- a/libkmod/libkmod-elf.c
+++ b/libkmod/libkmod-elf.c
@@ -627,3 +627,195 @@
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].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].symbol = itr;
+ memcpy(itr, strings + last, slen);
+ itr[slen] = '\0';
+ itr += slen + 1;
+ count++;
+ }
+
+ return count;
+}
+
+/* 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;
+
+#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);
+ } else {
+ Elf64_Sym *s;
+ name_off = READV(st_name);
+ crc = READV(st_value);
+ }
+#undef READV
+ name = elf_get_mem(elf, str_off + name_off);
+ if (strncmp(name, crc_str, crc_strlen) != 0)
+ continue;
+ name += crc_strlen;
+
+ a[count].crc = crc;
+ 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);
+}
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 58787f6..089c61e 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -1896,3 +1896,168 @@
list = kmod_list_remove(list);
}
}
+
+struct kmod_module_symbol {
+ uint64_t crc;
+ char symbol[];
+};
+
+static struct kmod_module_symbol *kmod_module_symbols_new(uint64_t crc, const char *symbol)
+{
+ struct kmod_module_symbol *mv;
+ size_t symbollen = strlen(symbol) + 1;
+
+ mv = malloc(sizeof(struct kmod_module_symbol) + symbollen);
+ if (mv == NULL)
+ return NULL;
+
+ mv->crc = crc;
+ memcpy(mv->symbol, symbol, symbollen);
+ return mv;
+}
+
+static void kmod_module_symbol_free(struct kmod_module_symbol *symbol)
+{
+ free(symbol);
+}
+
+/**
+ * kmod_module_get_symbols:
+ * @mod: kmod module
+ * @list: where to return list of module symbols. Use
+ * kmod_module_symbols_get_symbol() and
+ * kmod_module_symbols_get_crc(). Release this list with
+ * kmod_module_symbols_unref_list()
+ *
+ * Get a list of entries in ELF section ".symtab" or "__ksymtab_strings".
+ *
+ * After use, free the @list by calling kmod_module_symbols_free_list().
+ *
+ * Returns: 0 on success or < 0 otherwise.
+ */
+KMOD_EXPORT int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list)
+{
+ struct kmod_file *file;
+ struct kmod_elf *elf;
+ const char *path;
+ const void *mem;
+ struct kmod_modversion *symbols;
+ size_t size;
+ int i, count, ret = 0;
+
+ if (mod == NULL || list == NULL)
+ return -ENOENT;
+
+ assert(*list == NULL);
+
+ path = kmod_module_get_path(mod);
+ if (path == NULL)
+ return -ENOENT;
+
+ file = kmod_file_open(path);
+ if (file == NULL)
+ return -errno;
+
+ size = kmod_file_get_size(file);
+ mem = kmod_file_get_contents(file);
+
+ elf = kmod_elf_new(mem, size);
+ if (elf == NULL) {
+ ret = -errno;
+ goto elf_open_error;
+ }
+
+ count = kmod_elf_get_symbols(elf, &symbols);
+ if (count < 0) {
+ ret = count;
+ goto get_strings_error;
+ }
+
+ for (i = 0; i < count; i++) {
+ struct kmod_module_symbol *mv;
+ struct kmod_list *n;
+
+ mv = kmod_module_symbols_new(symbols[i].crc, symbols[i].symbol);
+ if (mv == NULL) {
+ ret = -errno;
+ kmod_module_symbols_free_list(*list);
+ *list = NULL;
+ goto list_error;
+ }
+
+ n = kmod_list_append(*list, mv);
+ if (n != NULL)
+ *list = n;
+ else {
+ kmod_module_symbol_free(mv);
+ kmod_module_symbols_free_list(*list);
+ *list = NULL;
+ ret = -ENOMEM;
+ goto list_error;
+ }
+ }
+ ret = count;
+
+list_error:
+ free(symbols);
+get_strings_error:
+ kmod_elf_unref(elf);
+elf_open_error:
+ kmod_file_unref(file);
+
+ return ret;
+}
+
+/**
+ * kmod_module_symbols_get_symbol:
+ * @entry: a list entry representing a kmod module symbols
+ *
+ * Get the symbol of a kmod module symbols.
+ *
+ * Returns: the symbol of this kmod module symbols on success or NULL
+ * on failure. The string is owned by the symbols, do not free it.
+ */
+KMOD_EXPORT const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry)
+{
+ struct kmod_module_symbol *symbol;
+
+ if (entry == NULL)
+ return NULL;
+
+ symbol = entry->data;
+ return symbol->symbol;
+}
+
+/**
+ * kmod_module_symbol_get_crc:
+ * @entry: a list entry representing a kmod module symbol
+ *
+ * Get the crc of a kmod module symbol.
+ *
+ * Returns: the crc of this kmod module symbol on success or NULL on
+ * failure. The string is owned by the symbol, do not free it.
+ */
+KMOD_EXPORT uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry)
+{
+ struct kmod_module_symbol *symbol;
+
+ if (entry == NULL)
+ return 0;
+
+ symbol = entry->data;
+ return symbol->crc;
+}
+
+/**
+ * kmod_module_symbols_free_list:
+ * @list: kmod module symbols list
+ *
+ * Release the resources taken by @list
+ */
+KMOD_EXPORT void kmod_module_symbols_free_list(struct kmod_list *list)
+{
+ while (list) {
+ kmod_module_symbol_free(list->data);
+ list = kmod_list_remove(list);
+ }
+}
diff --git a/libkmod/libkmod-private.h b/libkmod/libkmod-private.h
index 4b0d84f..d32b6d7 100644
--- a/libkmod/libkmod-private.h
+++ b/libkmod/libkmod-private.h
@@ -152,6 +152,7 @@
const void *kmod_elf_get_memory(const struct kmod_elf *elf) __must_check __attribute__((nonnull(1)));
int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array) __must_check __attribute__((nonnull(1,2,3)));
int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array) __must_check __attribute__((nonnull(1,2)));
+int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) __must_check __attribute__((nonnull(1,2)));
int kmod_elf_strip_section(struct kmod_elf *elf, const char *section) __must_check __attribute__((nonnull(1,2)));
int kmod_elf_strip_vermagic(struct kmod_elf *elf) __must_check __attribute__((nonnull(1)));
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index 38b81cf..cc3de08 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -150,6 +150,11 @@
uint64_t kmod_module_version_get_crc(const struct kmod_list *entry);
void kmod_module_versions_free_list(struct kmod_list *list);
+int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list);
+const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry);
+uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry);
+void kmod_module_symbols_free_list(struct kmod_list *list);
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
index c61ad7e..bf93729 100644
--- a/libkmod/libkmod.sym
+++ b/libkmod/libkmod.sym
@@ -62,4 +62,11 @@
kmod_module_version_get_symbol;
kmod_module_version_get_crc;
kmod_module_versions_free_list;
-} LIBKMOD_1;
+
+ kmod_module_get_symbols;
+ kmod_module_symbol_get_symbol;
+ kmod_module_symbol_get_crc;
+ kmod_module_symbols_free_list;
+local:
+ *;
+};
diff --git a/test/test-lookup.c b/test/test-lookup.c
index 620237e..70cdb5c 100644
--- a/test/test-lookup.c
+++ b/test/test-lookup.c
@@ -160,6 +160,20 @@
kmod_module_versions_free_list(pre);
}
+ pre = NULL;
+ err = kmod_module_get_symbols(mod, &pre);
+ if (err > 0) {
+ puts("\t\tsymbols:");
+ kmod_list_foreach(d, pre) {
+ const char *symbol;
+ uint64_t crc;
+ symbol = kmod_module_symbol_get_symbol(d);
+ crc = kmod_module_symbol_get_crc(d);
+ printf("\t\t\t%s: %#"PRIx64"\n", symbol, crc);
+ }
+ kmod_module_symbols_free_list(pre);
+ }
+
kmod_module_unref(mod);
}