[PATCH 1/4] kallsyms: add support for relative offsets in kallsyms address table

From: Ard Biesheuvel
Date: Wed Jan 20 2016 - 04:06:50 EST


Similar to how relative extables are implemented, it is possible to emit
the kallsyms table in such a way that it contains offsets relative to some
anchor point in the kernel image rather than absolute addresses. The benefit
is that such table entries are no longer subject to dynamic relocation when
the build time and runtime offsets of the kernel image are different. Also,
on 64-bit architectures, it essentially cuts the size of the address table
in half since offsets can typically be expressed in 32 bits.

Since it is useful for some architectures (like x86) to retain the ability
to emit absolute values as well, this patch adds support for both, by
emitting absolute addresses as positive 32-bit values, and addresses
relative to _text as negative values, which are subtracted from the runtime
address of _text to produce the actual address. Positive values are used as
they are found in the table.

Support for the above is enabled by setting CONFIG_KALLSYMS_TEXT_RELATIVE.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx>
---
init/Kconfig | 14 ++++++++
kernel/kallsyms.c | 35 +++++++++++++-----
scripts/kallsyms.c | 38 +++++++++++++++++---
scripts/link-vmlinux.sh | 4 +++
scripts/namespace.pl | 1 +
5 files changed, 79 insertions(+), 13 deletions(-)

diff --git a/init/Kconfig b/init/Kconfig
index 5b86082fa238..73e00b040572 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1427,6 +1427,20 @@ config KALLSYMS_ALL

Say N unless you really need all symbols.

+config KALLSYMS_TEXT_RELATIVE
+ bool
+ help
+ Instead of emitting them as absolute values in the native word size,
+ emit the symbol references in the kallsyms table as 32-bit entries,
+ each containing either an absolute value in the range [0, S32_MAX] or
+ a text relative value in the range [_text, _text + S32_MAX], encoded
+ as negative values.
+
+ On 64-bit builds, this reduces the size of the address table by 50%,
+ but more importantly, it results in entries whose values are build
+ time constants, and no relocation pass is required at runtime to fix
+ up the entries based on the runtime load address of the kernel.
+
config PRINTK
default y
bool "Enable support for printk" if EXPERT
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index 5c5987f10819..e612f7f9e71b 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -38,6 +38,7 @@
* during the second link stage.
*/
extern const unsigned long kallsyms_addresses[] __weak;
+extern const int kallsyms_offsets[] __weak;
extern const u8 kallsyms_names[] __weak;

/*
@@ -176,6 +177,19 @@ static unsigned int get_symbol_offset(unsigned long pos)
return name - kallsyms_names;
}

+static unsigned long kallsyms_sym_address(int idx)
+{
+ if (!IS_ENABLED(CONFIG_KALLSYMS_TEXT_RELATIVE))
+ return kallsyms_addresses[idx];
+
+ /* positive offsets are absolute values */
+ if (kallsyms_offsets[idx] >= 0)
+ return kallsyms_offsets[idx];
+
+ /* negative offsets are relative to _text - 1 */
+ return (unsigned long)_text - 1 - kallsyms_offsets[idx];
+}
+
/* Lookup the address for this symbol. Returns 0 if not found. */
unsigned long kallsyms_lookup_name(const char *name)
{
@@ -187,7 +201,7 @@ unsigned long kallsyms_lookup_name(const char *name)
off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));

if (strcmp(namebuf, name) == 0)
- return kallsyms_addresses[i];
+ return kallsyms_sym_address(i);
}
return module_kallsyms_lookup_name(name);
}
@@ -204,7 +218,7 @@ int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,

for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));
- ret = fn(data, namebuf, NULL, kallsyms_addresses[i]);
+ ret = fn(data, namebuf, NULL, kallsyms_sym_address(i));
if (ret != 0)
return ret;
}
@@ -220,7 +234,10 @@ static unsigned long get_symbol_pos(unsigned long addr,
unsigned long i, low, high, mid;

/* This kernel should never had been booted. */
- BUG_ON(!kallsyms_addresses);
+ if (!IS_ENABLED(CONFIG_KALLSYMS_TEXT_RELATIVE))
+ BUG_ON(!kallsyms_addresses);
+ else
+ BUG_ON(!kallsyms_offsets);

/* Do a binary search on the sorted kallsyms_addresses array. */
low = 0;
@@ -228,7 +245,7 @@ static unsigned long get_symbol_pos(unsigned long addr,

while (high - low > 1) {
mid = low + (high - low) / 2;
- if (kallsyms_addresses[mid] <= addr)
+ if (kallsyms_sym_address(mid) <= addr)
low = mid;
else
high = mid;
@@ -238,15 +255,15 @@ static unsigned long get_symbol_pos(unsigned long addr,
* Search for the first aliased symbol. Aliased
* symbols are symbols with the same address.
*/
- while (low && kallsyms_addresses[low-1] == kallsyms_addresses[low])
+ while (low && kallsyms_sym_address(low-1) == kallsyms_sym_address(low))
--low;

- symbol_start = kallsyms_addresses[low];
+ symbol_start = kallsyms_sym_address(low);

/* Search for next non-aliased symbol. */
for (i = low + 1; i < kallsyms_num_syms; i++) {
- if (kallsyms_addresses[i] > symbol_start) {
- symbol_end = kallsyms_addresses[i];
+ if (kallsyms_sym_address(i) > symbol_start) {
+ symbol_end = kallsyms_sym_address(i);
break;
}
}
@@ -470,7 +487,7 @@ static unsigned long get_ksymbol_core(struct kallsym_iter *iter)
unsigned off = iter->nameoff;

iter->module_name[0] = '\0';
- iter->value = kallsyms_addresses[iter->pos];
+ iter->value = kallsyms_sym_address(iter->pos);

iter->type = kallsyms_get_symbol_type(off);

diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index 8fa81e84e295..07656c102e60 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
+#include <limits.h>

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
@@ -61,6 +62,7 @@ static int all_symbols = 0;
static int absolute_percpu = 0;
static char symbol_prefix_char = '\0';
static unsigned long long kernel_start_addr = 0;
+static int text_relative = 0;

int token_profit[0x10000];

@@ -74,7 +76,7 @@ static void usage(void)
fprintf(stderr, "Usage: kallsyms [--all-symbols] "
"[--symbol-prefix=<prefix char>] "
"[--page-offset=<CONFIG_PAGE_OFFSET>] "
- "< in.map > out.S\n");
+ "[--text-relative] < in.map > out.S\n");
exit(1);
}

@@ -202,6 +204,7 @@ static int symbol_valid(struct sym_entry *s)
*/
static char *special_symbols[] = {
"kallsyms_addresses",
+ "kallsyms_offsets",
"kallsyms_num_syms",
"kallsyms_names",
"kallsyms_markers",
@@ -353,9 +356,34 @@ static void write_src(void)
* .o files. This prevents .tmp_kallsyms.o or any other
* object from referencing them.
*/
- output_label("kallsyms_addresses");
+ if (!text_relative)
+ output_label("kallsyms_addresses");
+ else
+ output_label("kallsyms_offsets");
+
for (i = 0; i < table_cnt; i++) {
- if (!symbol_absolute(&table[i])) {
+ if (text_relative) {
+ long long offset;
+
+ if (symbol_absolute(&table[i])) {
+ offset = table[i].addr;
+ if (offset < 0 || offset > INT_MAX) {
+ fprintf(stderr, "kallsyms failure: "
+ "absolute symbol value %#llx out of range in relative mode\n",
+ table[i].addr);
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ offset = _text - table[i].addr - 1;
+ if (offset < INT_MIN || offset >= 0) {
+ fprintf(stderr, "kallsyms failure: "
+ "relative symbol value %#llx out of range in relative mode\n",
+ table[i].addr);
+ exit(EXIT_FAILURE);
+ }
+ }
+ printf("\t.long\t%#x\n", (int)offset);
+ } else if (!symbol_absolute(&table[i])) {
if (_text <= table[i].addr)
printf("\tPTR\t_text + %#llx\n",
table[i].addr - _text);
@@ -703,7 +731,9 @@ int main(int argc, char **argv)
} else if (strncmp(argv[i], "--page-offset=", 14) == 0) {
const char *p = &argv[i][14];
kernel_start_addr = strtoull(p, NULL, 16);
- } else
+ } else if (strcmp(argv[i], "--text-relative") == 0)
+ text_relative = 1;
+ else
usage();
}
} else if (argc != 1)
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index ba6c34ea5429..e0f957f6a54c 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -90,6 +90,10 @@ kallsyms()
kallsymopt="${kallsymopt} --absolute-percpu"
fi

+ if [ -n "${CONFIG_KALLSYMS_TEXT_RELATIVE}" ]; then
+ kallsymopt="${kallsymopt} --text-relative"
+ fi
+
local aflags="${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} \
${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS}"

diff --git a/scripts/namespace.pl b/scripts/namespace.pl
index a71be6b7cdec..e059ab240364 100755
--- a/scripts/namespace.pl
+++ b/scripts/namespace.pl
@@ -117,6 +117,7 @@ my %nameexception = (
'kallsyms_names' => 1,
'kallsyms_num_syms' => 1,
'kallsyms_addresses'=> 1,
+ 'kallsyms_offsets' => 1,
'__this_module' => 1,
'_etext' => 1,
'_edata' => 1,
--
2.5.0