Summary
-------
This is a significantly simplified version of the original klp-convert tool.
The klp-convert code has never got a proper review and also clean ups
were not easy. The last version was v7, see
https://lore.kernel.org/r/[email protected]
The main change is that the tool does not longer search for the
symbols which would need the livepatch specific relocation entry.
Also klp.symbols file is not longer needed.
Instead, the needed information is appended to the symbol declaration
via a new macro KLP_RELOC_SYMBOL(). It creates symbol with all needed
metadata. For example:
extern char *saved_command_line \
KLP_RELOC_SYMBOL(vmlinux, vmlinux, saved_command_line, 0);
would create symbol
$>readelf -r -W <compiled livepatch module>:
Relocation section '.rela.text' at offset 0x32e60 contains 10 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
[...]
0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.rela.vmlinux.vmlinux.saved_command_line,0 - 4
[...]
The simplified klp-convert tool just transforms symbols
created by KLP_RELOC_SYMBOL() to object specific rela sections
and rela entries which would later be proceed when the livepatch
or the livepatched object is loaded.
For example, klp-convert would replace the above symbols with:
$> readelf -r -W <livepatch_module_proceed_by_klp_convert>
Relocation section '.klp.rela.vmlinux.text' at offset 0x5cb60 contains 1 entry:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.saved_command_line,0 - 4
Note that similar macro was needed also in the original version
to handle more symbols of the same name (sympos).
Given the above, add klp-convert tool; integrate klp-convert tool into
kbuild; add data-structure and macros to enable users to annotate
livepatch source code; make modpost stage compatible with livepatches;
update livepatch-sample and update documentation.
Testing
-------
The patchset selftests build and execute on x86_64, s390x, and ppc64le
for both default config (with added livepatch dependencies) and a larger
SLE-15-ish config.
Summary of changes in this minimal version v2
------------------------
- rebase for v6.9
- cleaned-up SoB chains (suggested by pmladek)
- klp-convert: remove the symbol map auto-resolving solution
- klp-convert: add macro for flagging variables inside a LP src to be resolved by this tool
- klp-convert: code simplification
- selftests: add selftest livepatching function using an external symbol
Previous versions
-----------------
RFC:
https://lore.kernel.org/r/[email protected]/
v2:
https://lore.kernel.org/r/[email protected]/
v3:
https://lore.kernel.org/r/[email protected]/
v4:
https://lore.kernel.org/r/[email protected]/
v5:
(not posted)
https://github.com/joe-lawrence/klp-convert-tree/tree/klp-convert-v5-devel
v6:
https://lore.kernel.org/r/[email protected]/
v7:
https://lore.kernel.org/r/[email protected]/
v1 minimal:
https://lore.kernel.org/r/[email protected]/
From: Josh Poimboeuf <[email protected]>
Define klp prefixes in include/uapi/linux/livepatch.h, and use them for
replacing hard-coded values in kernel/livepatch/core.c.
Signed-off-by: Josh Poimboeuf <[email protected]>
Signed-off-by: Lukas Hruska <[email protected]>
Reviewed-by: Petr Mladek <[email protected]>
Reviewed-by: Marcos Paulo de Souza <[email protected]>
---
MAINTAINERS | 1 +
include/uapi/linux/livepatch.h | 15 +++++++++++++++
kernel/livepatch/core.c | 5 +++--
3 files changed, 19 insertions(+), 2 deletions(-)
create mode 100644 include/uapi/linux/livepatch.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 28e20975c26f..130b8b0bd4f7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12614,6 +12614,7 @@ F: Documentation/ABI/testing/sysfs-kernel-livepatch
F: Documentation/livepatch/
F: arch/powerpc/include/asm/livepatch.h
F: include/linux/livepatch.h
+F: include/uapi/linux/livepatch.h
F: kernel/livepatch/
F: kernel/module/livepatch.c
F: samples/livepatch/
diff --git a/include/uapi/linux/livepatch.h b/include/uapi/linux/livepatch.h
new file mode 100644
index 000000000000..e19430918a07
--- /dev/null
+++ b/include/uapi/linux/livepatch.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+/*
+ * livepatch.h - Kernel Live Patching Core
+ *
+ * Copyright (C) 2016 Josh Poimboeuf <[email protected]>
+ */
+
+#ifndef _UAPI_LIVEPATCH_H
+#define _UAPI_LIVEPATCH_H
+
+#define KLP_RELA_PREFIX ".klp.rela."
+#define KLP_SYM_PREFIX ".klp.sym."
+
+#endif /* _UAPI_LIVEPATCH_H */
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index ecbc9b6aba3a..8bde84d85263 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -20,6 +20,7 @@
#include <linux/completion.h>
#include <linux/memory.h>
#include <linux/rcupdate.h>
+#include <uapi/linux/livepatch.h>
#include <asm/cacheflush.h>
#include "core.h"
#include "patch.h"
@@ -226,7 +227,7 @@ static int klp_resolve_symbols(Elf_Shdr *sechdrs, const char *strtab,
/* Format: .klp.sym.sym_objname.sym_name,sympos */
cnt = sscanf(strtab + sym->st_name,
- ".klp.sym.%55[^.].%511[^,],%lu",
+ KLP_SYM_PREFIX "%55[^.].%511[^,],%lu",
sym_objname, sym_name, &sympos);
if (cnt != 3) {
pr_err("symbol %s has an incorrectly formatted name\n",
@@ -305,7 +306,7 @@ static int klp_write_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
* See comment in klp_resolve_symbols() for an explanation
* of the selected field width value.
*/
- cnt = sscanf(shstrtab + sec->sh_name, ".klp.rela.%55[^.]",
+ cnt = sscanf(shstrtab + sec->sh_name, KLP_RELA_PREFIX "%55[^.]",
sec_objname);
if (cnt != 1) {
pr_err("section %s has an incorrectly formatted name\n",
--
2.45.0
From: Josh Poimboeuf <[email protected]>
Call klp-convert for the livepatch modules after the final linking.
Also update the modpost tool so that it does not warn about unresolved
symbols matching the expected format which will be then resolved by
klp-convert.
Signed-off-by: Josh Poimboeuf <[email protected]>
Signed-off-by: Lukas Hruska <[email protected]>
Reviewed-by: Petr Mladek <[email protected]>
Reviewed-by: Marcos Paulo de Souza <[email protected]>
---
.gitignore | 1 +
Makefile | 10 ++++++----
scripts/Makefile.modfinal | 15 +++++++++++++++
scripts/Makefile.modpost | 5 +++++
scripts/mod/modpost.c | 36 ++++++++++++++++++++++++++++++++++--
scripts/mod/modpost.h | 3 +++
6 files changed, 64 insertions(+), 6 deletions(-)
diff --git a/.gitignore b/.gitignore
index c59dc60ba62e..9b7d492cac99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,6 +70,7 @@ modules.order
/Module.markers
/modules.builtin
/modules.builtin.modinfo
+/modules.livepatch
/modules.nsdeps
#
diff --git a/Makefile b/Makefile
index 967e97878ecd..db06e5db21af 100644
--- a/Makefile
+++ b/Makefile
@@ -1095,6 +1095,7 @@ PHONY += prepare0
export extmod_prefix = $(if $(KBUILD_EXTMOD),$(KBUILD_EXTMOD)/)
export MODORDER := $(extmod_prefix)modules.order
export MODULES_NSDEPS := $(extmod_prefix)modules.nsdeps
+export MODULES_LIVEPATCH := $(extmod_prefix)modules.livepatch
ifeq ($(KBUILD_EXTMOD),)
@@ -1453,8 +1454,8 @@ endif
#
# *.ko are usually independent of vmlinux, but CONFIG_DEBUG_INFO_BTF_MODULES
-# is an exception.
-ifdef CONFIG_DEBUG_INFO_BTF_MODULES
+# and CONFIG_LIVEPATCH are exceptions.
+ifneq ($(or $(CONFIG_DEBUG_INFO_BTF_MODULES),$(CONFIG_LIVEPATCH)),)
KBUILD_BUILTIN := 1
modules: vmlinux
endif
@@ -1477,8 +1478,9 @@ endif # CONFIG_MODULES
# Directories & files removed with 'make clean'
CLEAN_FILES += vmlinux.symvers modules-only.symvers \
modules.builtin modules.builtin.modinfo modules.nsdeps \
- compile_commands.json .thinlto-cache rust/test \
- rust-project.json .vmlinux.objs .vmlinux.export.c
+ modules.livepatch compile_commands.json .thinlto-cache \
+ rust/test rust-project.json .vmlinux.objs \
+ .vmlinux.export.c
# Directories & files removed with 'make mrproper'
MRPROPER_FILES += include/config include/generated \
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index 79fcf2731686..a2a162d204f6 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -14,6 +14,7 @@ include $(srctree)/scripts/Makefile.lib
# find all modules listed in modules.order
modules := $(call read-file, $(MODORDER))
+modules-klp := $(call read-file, $(MODULES_LIVEPATCH))
__modfinal: $(modules:%.o=%.ko)
@:
@@ -60,6 +61,20 @@ endif
targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o)
+# Livepatch
+# ---------------------------------------------------------------------------
+
+%.tmp.ko: %.o %.mod.o FORCE
+ +$(call if_changed,ld_ko_o)
+
+quiet_cmd_klp_convert = KLP $@
+ cmd_klp_convert = scripts/livepatch/klp-convert $< $@
+
+$(modules-klp:%.o=%.ko): %.ko: %.tmp.ko FORCE
+ $(call if_changed,klp_convert)
+
+targets += $(modules-klp:.ko=.tmp.ko)
+
# Add FORCE to the prequisites of a target to force it to be always rebuilt.
# ---------------------------------------------------------------------------
diff --git a/scripts/Makefile.modpost b/scripts/Makefile.modpost
index 739402f45509..3c765c568e34 100644
--- a/scripts/Makefile.modpost
+++ b/scripts/Makefile.modpost
@@ -48,6 +48,7 @@ modpost-args = \
$(if $(KBUILD_MODPOST_WARN),-w) \
$(if $(KBUILD_NSDEPS),-d $(MODULES_NSDEPS)) \
$(if $(CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS)$(KBUILD_NSDEPS),-N) \
+ $(if $(CONFIG_LIVEPATCH),-l $(MODULES_LIVEPATCH)) \
$(if $(findstring 1, $(KBUILD_EXTRA_WARN)),-W) \
-o $@
@@ -145,6 +146,10 @@ $(output-symdump): $(modpost-deps) FORCE
$(call if_changed,modpost)
__modpost: $(output-symdump)
+ifndef CONFIG_LIVEPATCH
+ $(Q)rm -f $(MODULES_LIVEPATCH)
+ $(Q)touch $(MODULES_LIVEPATCH)
+endif
PHONY += FORCE
FORCE:
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index 2f5b91da5afa..15d7d2de653e 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -1654,6 +1654,10 @@ static void read_symbols(const char *modname)
}
}
+ /* Livepatch modules have unresolved symbols resolved by klp-convert */
+ if (get_modinfo(&info, "livepatch"))
+ mod->is_livepatch = true;
+
if (extra_warn && !get_modinfo(&info, "description"))
warn("missing MODULE_DESCRIPTION() in %s\n", modname);
for (sym = info.symtab_start; sym < info.symtab_stop; sym++) {
@@ -1742,10 +1746,18 @@ static void check_exports(struct module *mod)
const char *basename;
exp = find_symbol(s->name);
if (!exp) {
- if (!s->weak && nr_unresolved++ < MAX_UNRESOLVED_REPORTS)
+ if (!s->weak && nr_unresolved++ < MAX_UNRESOLVED_REPORTS) {
+ /*
+ * In case of livepatch module we allow
+ * unresolved symbol with a specific format
+ */
+ if (mod->is_livepatch &&
+ strncmp(s->name, KLP_SYM_RELA, strlen(KLP_SYM_RELA)) == 0)
+ break;
modpost_log(warn_unresolved ? LOG_WARN : LOG_ERROR,
"\"%s\" [%s.ko] undefined!\n",
s->name, mod->name);
+ }
continue;
}
if (exp->module == mod) {
@@ -2178,6 +2190,20 @@ static void write_namespace_deps_files(const char *fname)
free(ns_deps_buf.p);
}
+static void write_livepatch_modules(const char *fname)
+{
+ struct buffer buf = { };
+ struct module *mod;
+
+ list_for_each_entry(mod, &modules, list) {
+ if (mod->is_livepatch)
+ buf_printf(&buf, "%s.o\n", mod->name);
+ }
+
+ write_if_changed(&buf, fname);
+ free(buf.p);
+}
+
struct dump_list {
struct list_head list;
const char *file;
@@ -2189,11 +2215,12 @@ int main(int argc, char **argv)
char *missing_namespace_deps = NULL;
char *unused_exports_white_list = NULL;
char *dump_write = NULL, *files_source = NULL;
+ char *livepatch_modules = NULL;
int opt;
LIST_HEAD(dump_lists);
struct dump_list *dl, *dl2;
- while ((opt = getopt(argc, argv, "ei:MmnT:to:au:WwENd:")) != -1) {
+ while ((opt = getopt(argc, argv, "ei:l:MmnT:to:au:WwENd:")) != -1) {
switch (opt) {
case 'e':
external_module = true;
@@ -2206,6 +2233,9 @@ int main(int argc, char **argv)
case 'M':
module_enabled = true;
break;
+ case 'l':
+ livepatch_modules = optarg;
+ break;
case 'm':
modversions = true;
break;
@@ -2285,6 +2315,8 @@ int main(int argc, char **argv)
if (dump_write)
write_dump(dump_write);
+ if (livepatch_modules)
+ write_livepatch_modules(livepatch_modules);
if (sec_mismatch_count && !sec_mismatch_warn_only)
error("Section mismatches detected.\n"
"Set CONFIG_SECTION_MISMATCH_WARN_ONLY=y to allow them.\n");
diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h
index ee43c7950636..7a82460cc4d6 100644
--- a/scripts/mod/modpost.h
+++ b/scripts/mod/modpost.h
@@ -76,6 +76,8 @@
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#define KLP_SYM_RELA ".klp.sym.rela."
+
void *do_nofail(void *ptr, const char *expr);
struct buffer {
@@ -97,6 +99,7 @@ struct module {
bool is_gpl_compatible;
bool from_dump; /* true if module was loaded from *.symvers */
bool is_vmlinux;
+ bool is_livepatch;
bool seen;
bool has_init;
bool has_cleanup;
--
2.45.0
From: Josh Poimboeuf <[email protected]>
Add a new livepatch sample in samples/livepatch/ to make use of symbols
that must be post-processed to enable load-time relocation resolution.
As the new sample is to be used as an example, it is annotated with
KLP_RELOC_SYMBOL macro.
The livepatch sample updates the function cmdline_proc_show to print the
string referenced by the symbol saved_command_line appended by the
string "livepatch=1".
Signed-off-by: Josh Poimboeuf <[email protected]>
Signed-off-by: Lukas Hruska <[email protected]>
Reviewed-by: Petr Mladek <[email protected]>
---
samples/livepatch/Makefile | 1 +
samples/livepatch/livepatch-extern-symbol.c | 84 +++++++++++++++++++++
2 files changed, 85 insertions(+)
create mode 100644 samples/livepatch/livepatch-extern-symbol.c
diff --git a/samples/livepatch/Makefile b/samples/livepatch/Makefile
index 9f853eeb6140..f2b41f4d6c16 100644
--- a/samples/livepatch/Makefile
+++ b/samples/livepatch/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-fix2.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-demo.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-mod.o
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-busymod.o
+obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-annotated-sample.o
diff --git a/samples/livepatch/livepatch-extern-symbol.c b/samples/livepatch/livepatch-extern-symbol.c
new file mode 100644
index 000000000000..276a43d157b4
--- /dev/null
+++ b/samples/livepatch/livepatch-extern-symbol.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2014 Seth Jennings <[email protected]>
+ */
+
+/*
+ * livepatch-extern-symbol.c - Kernel Live Patching Sample Module
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/livepatch.h>
+
+/*
+ * This (dumb) live patch overrides the function that prints the
+ * kernel boot cmdline when /proc/cmdline is read.
+ *
+ * This livepatch uses the symbol saved_command_line whose relocation
+ * must be resolved during load time. To enable that, this module
+ * must be post-processed by a tool called klp-convert, which embeds
+ * information to be used by the loader to solve the relocation.
+ *
+ * The module is annotated with KLP_RELOC_SYMBOL macros.
+ * These annotations are used by klp-convert to infer that the symbol
+ * saved_command_line is in the object vmlinux.
+ *
+ * Example:
+ *
+ * $ cat /proc/cmdline
+ * <your cmdline>
+ *
+ * $ insmod livepatch-sample.ko
+ * $ cat /proc/cmdline
+ * <your cmdline> livepatch=1
+ *
+ * $ echo 0 > /sys/kernel/livepatch/livepatch_sample/enabled
+ * $ cat /proc/cmdline
+ * <your cmdline>
+ */
+
+extern char *saved_command_line \
+ KLP_RELOC_SYMBOL(vmlinux, vmlinux, saved_command_line);
+
+#include <linux/seq_file.h>
+static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "%s livepatch=1\n", saved_command_line);
+ return 0;
+}
+
+static struct klp_func funcs[] = {
+ {
+ .old_name = "cmdline_proc_show",
+ .new_func = livepatch_cmdline_proc_show,
+ }, { }
+};
+
+static struct klp_object objs[] = {
+ {
+ /* name being NULL means vmlinux */
+ .funcs = funcs,
+ }, { }
+};
+
+static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+};
+
+static int livepatch_init(void)
+{
+ return klp_enable_patch(&patch);
+}
+
+static void livepatch_exit(void)
+{
+}
+
+module_init(livepatch_init);
+module_exit(livepatch_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
--
2.45.0
Add a section to Documentation/livepatch/module-elf-format.rst
describing how klp-convert works for fixing relocations.
Signed-off-by: Lukas Hruska <[email protected]>
Reviewed-by: Petr Mladek <[email protected]>
Reviewed-by: Marcos Paulo de Souza <[email protected]>
---
Documentation/livepatch/module-elf-format.rst | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/Documentation/livepatch/module-elf-format.rst b/Documentation/livepatch/module-elf-format.rst
index a03ed02ec57e..2aa9b11cd806 100644
--- a/Documentation/livepatch/module-elf-format.rst
+++ b/Documentation/livepatch/module-elf-format.rst
@@ -300,3 +300,70 @@ symbol table, and relocation section indices, ELF information is preserved for
livepatch modules and is made accessible by the module loader through
module->klp_info, which is a :c:type:`klp_modinfo` struct. When a livepatch module
loads, this struct is filled in by the module loader.
+
+6. klp-convert tool
+===================
+The livepatch relocation sections might be created using
+scripts/livepatch/klp-convert. It is called automatically during
+the build as part of a module post processing.
+
+The tool is not able to find the symbols and all the metadata
+automatically. Instead, all needed information must already be
+part of rela entry for the given symbol. Such a rela can
+be created easily by using KLP_RELOC_SYMBOL() macro after
+the symbol declaration.
+
+KLP_RELOC_SYMBOL causes that the relocation entries for
+the given symbol will be created in the following format::
+
+ .klp.sym.rela.lp_object.sym_object.sym_name,sympos
+ ^ ^ ^ ^ ^ ^ ^ ^ ^
+ |___________| |_______| |________| |______| |
+ [A] [B] [C] [D] [E]
+
+[A]
+ The symbol name is prefixed with the string ".klp.sym.rela."
+
+[B]
+ The name of the object (i.e. "vmlinux" or name of module) which
+ is livepatched.
+
+[C]
+ The name of the object (i.e. "vmlinux" or name of module) to
+ which the symbol belongs follows immediately after the prefix.
+
+[D]
+ The actual name of the symbol.
+
+[E]
+ The position of the symbol in the object (as according to kallsyms)
+ This is used to differentiate duplicate symbols within the same
+ object. The symbol position is expressed numerically (0, 1, 2...).
+ The symbol position of a unique symbol is 0.
+
+Example:
+--------
+**Livepatch source code:**
+
+::
+
+ extern char *saved_command_line \
+ KLP_RELOC_SYMBOL(vmlinux, vmlinux, saved_command_line, 0);
+
+**`readelf -r -W` output of compiled module:**
+
+::
+
+ Relocation section '.rela.text' at offset 0x32e60 contains 10 entries:
+ Offset Info Type Symbol's Value Symbol's Name + Addend
+ ...
+ 0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.rela.vmlinux.vmlinux.saved_command_line,0 - 4
+ ...
+
+**`readelf -r -W` output of transformed module by klp-convert:**
+
+::
+
+ Relocation section '.klp.rela.vmlinux.text' at offset 0x5cb60 contains 1 entry:
+ Offset Info Type Symbol's Value Symbol's Name + Addend
+ 0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.saved_command_line,0 - 4
--
2.45.0
The test proves that klp-convert works as intended and it is possible to
livepatch a function that use an external symbol.
Signed-off-by: Lukas Hruska <[email protected]>
---
.../testing/selftests/livepatch/functions.sh | 16 +++++-
.../selftests/livepatch/test-extern.sh | 57 +++++++++++++++++++
.../selftests/livepatch/test_modules/Makefile | 2 +
.../livepatch/test_modules/test_klp_extern.c | 51 +++++++++++++++++
.../test_modules/test_klp_extern_hello.c | 36 ++++++++++++
5 files changed, 161 insertions(+), 1 deletion(-)
create mode 100755 tools/testing/selftests/livepatch/test-extern.sh
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_extern.c
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_extern_hello.c
diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing/selftests/livepatch/functions.sh
index fc4c6a016d38..801d55dc06ac 100644
--- a/tools/testing/selftests/livepatch/functions.sh
+++ b/tools/testing/selftests/livepatch/functions.sh
@@ -7,6 +7,7 @@
MAX_RETRIES=600
RETRY_INTERVAL=".1" # seconds
KLP_SYSFS_DIR="/sys/kernel/livepatch"
+MODULE_SYSFS_DIR="/sys/module"
# Kselftest framework requirement - SKIP code is 4
ksft_skip=4
@@ -299,7 +300,7 @@ function check_result {
result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \
grep -e 'livepatch:' -e 'test_klp' | \
grep -v '\(tainting\|taints\) kernel' | \
- sed 's/^\[[ 0-9.]*\] //')
+ sed 's/^\[[ 0-9.]*\] //' | sed 's/^test_klp_log: //')
if [[ "$expect" == "$result" ]] ; then
echo "ok"
@@ -344,3 +345,16 @@ function check_sysfs_value() {
die "Unexpected value in $path: $expected_value vs. $value"
fi
}
+
+# read_module_param_value(modname, param) - read module parameter value
+# modname - livepatch module creating the sysfs interface
+# param - parameter name
+function read_module_param() {
+ local mod="$1"; shift
+ local param="$1"; shift
+
+ local path="$MODULE_SYSFS_DIR/$mod/parameters/$param"
+
+ log "% echo \"$mod/parameters/$param: \$(cat $path)\""
+ log "$mod/parameters/$param: $(cat $path)"
+}
diff --git a/tools/testing/selftests/livepatch/test-extern.sh b/tools/testing/selftests/livepatch/test-extern.sh
new file mode 100755
index 000000000000..3dde6cabb07c
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test-extern.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2024 Lukas Hruska <[email protected]>
+
+. $(dirname $0)/functions.sh
+
+MOD_LIVEPATCH=test_klp_extern
+MOD_HELLO=test_klp_extern_hello
+PARAM_HELLO=hello
+
+setup_config
+
+# - load a module to be livepatched
+# - load a livepatch that modifies the output from 'hello' parameter
+# of the previously loaded module and verify correct behaviour
+# - unload the livepatch and make sure the patch was removed
+# - unload the module that was livepatched
+
+start_test "livepatch with external symbol"
+
+load_mod $MOD_HELLO
+
+read_module_param $MOD_HELLO $PARAM_HELLO
+
+load_lp $MOD_LIVEPATCH
+
+read_module_param $MOD_HELLO $PARAM_HELLO
+
+disable_lp $MOD_LIVEPATCH
+unload_lp $MOD_LIVEPATCH
+
+read_module_param $MOD_HELLO $PARAM_HELLO
+
+unload_mod $MOD_HELLO
+
+check_result "% insmod test_modules/$MOD_HELLO.ko
+% echo \"$MOD_HELLO/parameters/$PARAM_HELLO: \$(cat /sys/module/$MOD_HELLO/parameters/$PARAM_HELLO)\"
+$MOD_HELLO/parameters/$PARAM_HELLO: Hello from kernel module.
+% insmod test_modules/$MOD_LIVEPATCH.ko
+livepatch: enabling patch '$MOD_LIVEPATCH'
+livepatch: '$MOD_LIVEPATCH': initializing patching transition
+livepatch: '$MOD_LIVEPATCH': starting patching transition
+livepatch: '$MOD_LIVEPATCH': completing patching transition
+livepatch: '$MOD_LIVEPATCH': patching complete
+% echo \"$MOD_HELLO/parameters/$PARAM_HELLO: \$(cat /sys/module/$MOD_HELLO/parameters/$PARAM_HELLO)\"
+$MOD_HELLO/parameters/$PARAM_HELLO: Hello from livepatched module.
+% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
+livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
+livepatch: '$MOD_LIVEPATCH': starting unpatching transition
+livepatch: '$MOD_LIVEPATCH': completing unpatching transition
+livepatch: '$MOD_LIVEPATCH': unpatching complete
+% rmmod $MOD_LIVEPATCH
+% echo \"$MOD_HELLO/parameters/$PARAM_HELLO: \$(cat /sys/module/$MOD_HELLO/parameters/$PARAM_HELLO)\"
+$MOD_HELLO/parameters/$PARAM_HELLO: Hello from kernel module.
+% rmmod $MOD_HELLO"
+
+exit 0
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index e6e638c4bcba..0d6df14787da 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -6,6 +6,8 @@ obj-m += test_klp_atomic_replace.o \
test_klp_callbacks_demo.o \
test_klp_callbacks_demo2.o \
test_klp_callbacks_mod.o \
+ test_klp_extern.o \
+ test_klp_extern_hello.o \
test_klp_livepatch.o \
test_klp_state.o \
test_klp_state2.o \
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_extern.c b/tools/testing/selftests/livepatch/test_modules/test_klp_extern.c
new file mode 100644
index 000000000000..2a88ae289668
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_extern.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2024 Lukas Hruska <[email protected]>
+
+#define pr_fmt(fmt) "test_klp_extern_hello: " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/livepatch.h>
+
+extern const char *hello_msg \
+ KLP_RELOC_SYMBOL(test_klp_extern_hello, test_klp_extern_hello, hello_msg);
+
+static int hello_get(char *buffer, const struct kernel_param *kp)
+{
+ return sysfs_emit(buffer, "%s livepatched module.\n", hello_msg);
+}
+
+static struct klp_func funcs[] = {
+ {
+ .old_name = "hello_get",
+ .new_func = hello_get,
+ }, { }
+};
+
+static struct klp_object objs[] = {
+ {
+ .name = "test_klp_extern_hello",
+ .funcs = funcs,
+ }, { }
+};
+
+static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+};
+
+static int test_klp_extern_init(void)
+{
+ return klp_enable_patch(&patch);
+}
+
+static void test_klp_extern_exit(void)
+{
+}
+
+module_init(test_klp_extern_init);
+module_exit(test_klp_extern_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
+MODULE_AUTHOR("Lukas Hruska <[email protected]>");
+MODULE_DESCRIPTION("Livepatch test: external symbol relocation");
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_extern_hello.c b/tools/testing/selftests/livepatch/test_modules/test_klp_extern_hello.c
new file mode 100644
index 000000000000..58ce4e655eee
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_extern_hello.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2024 Lukas Hruska <[email protected]>
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+const char *hello_msg = "Hello from";
+
+static int hello_get(char *buffer, const struct kernel_param *kp)
+{
+ return sysfs_emit(buffer, "%s kernel module.\n", hello_msg);
+}
+
+static const struct kernel_param_ops hello_ops = {
+ .get = hello_get
+};
+
+module_param_cb(hello, &hello_ops, NULL, 0400);
+MODULE_PARM_DESC(hello, "Read only parameter greeting the reader.");
+
+static int test_klp_extern_hello_init(void)
+{
+ return 0;
+}
+
+static void test_klp_extern_hello_exit(void)
+{
+}
+
+module_init(test_klp_extern_hello_init);
+module_exit(test_klp_extern_hello_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lukas Hruska <[email protected]>");
+MODULE_DESCRIPTION("Livepatch test: external symbol relocation - test module");
--
2.45.0
Livepatches need to access external symbols which can't be handled
by the normal relocation mechanism. It is needed for two types
of symbols:
+ Symbols which can be local for the original livepatched function.
The alternative implementation in the livepatch sees them
as external symbols.
+ Symbols in modules which are exported via EXPORT_SYMBOL*(). They
must be handled special way otherwise the livepatch module would
depend on the livepatched one. Loading such livepatch would cause
loading the other module as well.
The address of these symbols can be found via kallsyms. Or they can
be relocated using livepatch specific relocation sections as specified
in Documentation/livepatch/module-elf-format.txt.
Currently, there is no trivial way to embed the required information as
requested in the final livepatch elf object. klp-convert solves this
problem by using annotations in the elf object to convert the relocation
accordingly to the specification, enabling it to be handled by the
livepatch loader.
Given the above, create scripts/livepatch to hold tools developed for
livepatches and add source files for klp-convert there.
Allow to annotate such external symbols in the livepatch by a macro
KLP_RELOC_SYMBOL(). It will create symbol with all needed
metadata. For example:
extern char *saved_command_line \
KLP_RELOC_SYMBOL(vmlinux, vmlinux, saved_command_line, 0);
would create symbol
$>readelf -r -W <compiled livepatch module>:
Relocation section '.rela.text' at offset 0x32e60 contains 10 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
[...]
0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.rela.vmlinux.vmlinux.saved_command_line,0 - 4
[...]
Also add scripts/livepatch/klp-convert. The tool transforms symbols
created by KLP_RELOC_SYMBOL() to object specific rela sections
and rela entries which would later be proceed when the livepatch
or the livepatched object is loaded.
For example, klp-convert would replace the above symbol with:
$> readelf -r -W <livepatch_module_proceed_by_klp_convert>
Relocation section '.klp.rela.vmlinux.text' at offset 0x5cb60 contains 1 entry:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.saved_command_line,0 - 4
klp-convert relies on libelf and on a list implementation. Add files
scripts/livepatch/elf.c and scripts/livepatch/elf.h, which are a libelf
interfacing layer and scripts/livepatch/list.h, which is a list
implementation.
Update Makefiles to correctly support the compilation of the new tool,
update MAINTAINERS file and add a .gitignore file.
[[email protected]: initial version]
Signed-off-by: Josh Poimboeuf <[email protected]>
[[email protected]: clean-up and fixes]
Signed-off-by: Joe Lawrence <[email protected]>
[[email protected]: klp-convert code, minimal approach]
Signed-off-by: Lukas Hruska <[email protected]>
Reviewed-by: Marcos Paulo de Souza <[email protected]>
---
MAINTAINERS | 1 +
include/linux/livepatch.h | 19 +
scripts/Makefile | 1 +
scripts/livepatch/.gitignore | 1 +
scripts/livepatch/Makefile | 5 +
scripts/livepatch/elf.c | 817 ++++++++++++++++++++++++++++++++
scripts/livepatch/elf.h | 73 +++
scripts/livepatch/klp-convert.c | 284 +++++++++++
scripts/livepatch/klp-convert.h | 23 +
scripts/livepatch/list.h | 391 +++++++++++++++
10 files changed, 1615 insertions(+)
create mode 100644 scripts/livepatch/.gitignore
create mode 100644 scripts/livepatch/Makefile
create mode 100644 scripts/livepatch/elf.c
create mode 100644 scripts/livepatch/elf.h
create mode 100644 scripts/livepatch/klp-convert.c
create mode 100644 scripts/livepatch/klp-convert.h
create mode 100644 scripts/livepatch/list.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 130b8b0bd4f7..d2facc1f4e15 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12618,6 +12618,7 @@ F: include/uapi/linux/livepatch.h
F: kernel/livepatch/
F: kernel/module/livepatch.c
F: samples/livepatch/
+F: scripts/livepatch/
F: tools/testing/selftests/livepatch/
LLC (802.2)
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index 9b9b38e89563..83bbcd1c43fd 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -235,6 +235,25 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
unsigned int symindex, unsigned int secindex,
const char *objname);
+/**
+ * KLP_RELOC_SYMBOL_POS - define relocation for external symbols
+ *
+ * @LP_OBJ_NAME: name of the livepatched object where the symbol is needed
+ * @SYM_OBJ_NAME: name of the object where the symbol exists
+ * @SYM_NAME: symbol name
+ * @SYM_POS: position of the symbol in SYM_OBJ when there are more
+ * symbols of the same name.
+ *
+ * Use for annotating external symbols used in livepatches which are
+ * not exported in vmlinux or are in livepatched modules, see
+ * Documentation/livepatch/module-elf-format.rst
+ */
+#define KLP_RELOC_SYMBOL_POS(LP_OBJ_NAME, SYM_OBJ_NAME, SYM_NAME, SYM_POS) \
+ asm("\".klp.sym.rela." #LP_OBJ_NAME "." #SYM_OBJ_NAME "." #SYM_NAME "," #SYM_POS "\"")
+
+#define KLP_RELOC_SYMBOL(LP_OBJ_NAME, SYM_OBJ_NAME, SYM_NAME) \
+ KLP_RELOC_SYMBOL_POS(LP_OBJ_NAME, SYM_OBJ_NAME, SYM_NAME, 0)
+
#else /* !CONFIG_LIVEPATCH */
static inline int klp_module_coming(struct module *mod) { return 0; }
diff --git a/scripts/Makefile b/scripts/Makefile
index bc90520a5426..dfb1870551a6 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -55,6 +55,7 @@ targets += module.lds
subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins
subdir-$(CONFIG_MODVERSIONS) += genksyms
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
+subdir-$(CONFIG_LIVEPATCH) += livepatch
# Let clean descend into subdirs
subdir- += basic dtc gdb kconfig mod
diff --git a/scripts/livepatch/.gitignore b/scripts/livepatch/.gitignore
new file mode 100644
index 000000000000..dc22fe4b6a5b
--- /dev/null
+++ b/scripts/livepatch/.gitignore
@@ -0,0 +1 @@
+klp-convert
diff --git a/scripts/livepatch/Makefile b/scripts/livepatch/Makefile
new file mode 100644
index 000000000000..71dce0f3e893
--- /dev/null
+++ b/scripts/livepatch/Makefile
@@ -0,0 +1,5 @@
+hostprogs-always-y := klp-convert
+
+klp-convert-objs := klp-convert.o elf.o
+
+HOSTLDLIBS_klp-convert := -lelf
diff --git a/scripts/livepatch/elf.c b/scripts/livepatch/elf.c
new file mode 100644
index 000000000000..c649268a4a55
--- /dev/null
+++ b/scripts/livepatch/elf.c
@@ -0,0 +1,817 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * elf.c - ELF access library
+ *
+ * Adapted from kpatch (https://github.com/dynup/kpatch):
+ * Copyright (C) 2013-2016 Josh Poimboeuf <[email protected]>
+ * Copyright (C) 2014 Seth Jennings <[email protected]>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "elf.h"
+
+#define WARN(format, ...) \
+ fprintf(stderr, "%s: " format "\n", elf->name, ##__VA_ARGS__)
+
+/*
+ * Fallback for systems without this "read, mmaping if possible" cmd.
+ */
+#ifndef ELF_C_READ_MMAP
+#define ELF_C_READ_MMAP ELF_C_READ
+#endif
+
+bool is_rela_section(struct section *sec)
+{
+ return (sec->sh.sh_type == SHT_RELA);
+}
+
+struct section *find_section_by_name(struct elf *elf, const char *name)
+{
+ struct section *sec;
+
+ list_for_each_entry(sec, &elf->sections, list)
+ if (!strcmp(sec->name, name))
+ return sec;
+
+ return NULL;
+}
+
+static struct section *find_section_by_index(struct elf *elf,
+ int idx)
+{
+ struct section *sec;
+
+ list_for_each_entry(sec, &elf->sections, list)
+ if (sec->idx == idx)
+ return sec;
+
+ return NULL;
+}
+
+static struct symbol *find_symbol_by_index(struct elf *elf, unsigned int idx)
+{
+ struct symbol *sym;
+
+ list_for_each_entry(sym, &elf->symbols, list)
+ if (sym->idx == idx)
+ return sym;
+
+ return NULL;
+}
+
+static int read_sections(struct elf *elf)
+{
+ Elf_Scn *s = NULL;
+ struct section *sec;
+ size_t shstrndx, sections_nr;
+ size_t i;
+
+ if (elf_getshdrnum(elf->elf, §ions_nr)) {
+ perror("elf_getshdrnum");
+ return -1;
+ }
+
+ if (elf_getshdrstrndx(elf->elf, &shstrndx)) {
+ perror("elf_getshdrstrndx");
+ return -1;
+ }
+
+ for (i = 0; i < sections_nr; i++) {
+ sec = calloc(1, sizeof(*sec));
+ if (!sec) {
+ perror("calloc");
+ return -1;
+ }
+
+ INIT_LIST_HEAD(&sec->relas);
+
+ list_add_tail(&sec->list, &elf->sections);
+
+ s = elf_getscn(elf->elf, i);
+ if (!s) {
+ perror("elf_getscn");
+ return -1;
+ }
+
+ sec->idx = elf_ndxscn(s);
+
+ if (!gelf_getshdr(s, &sec->sh)) {
+ perror("gelf_getshdr");
+ return -1;
+ }
+
+ sec->name = elf_strptr(elf->elf, shstrndx, sec->sh.sh_name);
+ if (!sec->name) {
+ perror("elf_strptr");
+ return -1;
+ }
+
+ sec->elf_data = elf_getdata(s, NULL);
+ if (!sec->elf_data) {
+ perror("elf_getdata");
+ return -1;
+ }
+
+ if (sec->elf_data->d_off != 0 ||
+ sec->elf_data->d_size != sec->sh.sh_size) {
+ WARN("unexpected data attributes for %s", sec->name);
+ return -1;
+ }
+
+ sec->data = sec->elf_data->d_buf;
+ sec->size = sec->elf_data->d_size;
+ }
+
+ /* sanity check, one more call to elf_nextscn() should return NULL */
+ if (elf_nextscn(elf->elf, s)) {
+ WARN("section entry mismatch");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int read_symbols(struct elf *elf)
+{
+ struct section *symtab;
+ struct symbol *sym;
+ int symbols_nr, i;
+
+ symtab = find_section_by_name(elf, ".symtab");
+ if (!symtab) {
+ WARN("missing symbol table");
+ return -1;
+ }
+
+ symbols_nr = symtab->sh.sh_size / symtab->sh.sh_entsize;
+
+ for (i = 0; i < symbols_nr; i++) {
+ sym = calloc(1, sizeof(*sym));
+ if (!sym) {
+ perror("calloc");
+ return -1;
+ }
+
+ sym->idx = i;
+
+ if (!gelf_getsym(symtab->elf_data, i, &sym->sym)) {
+ perror("gelf_getsym");
+ goto err;
+ }
+
+ sym->name = elf_strptr(elf->elf, symtab->sh.sh_link,
+ sym->sym.st_name);
+ if (!sym->name) {
+ perror("elf_strptr");
+ goto err;
+ }
+
+ sym->type = GELF_ST_TYPE(sym->sym.st_info);
+ sym->bind = GELF_ST_BIND(sym->sym.st_info);
+
+ if (sym->sym.st_shndx > SHN_UNDEF &&
+ sym->sym.st_shndx < SHN_LORESERVE) {
+ sym->sec = find_section_by_index(elf,
+ sym->sym.st_shndx);
+ if (!sym->sec) {
+ WARN("couldn't find section for symbol %s",
+ sym->name);
+ goto err;
+ }
+ if (sym->type == STT_SECTION) {
+ sym->name = sym->sec->name;
+ sym->sec->sym = sym;
+ }
+ }
+
+ sym->offset = sym->sym.st_value;
+ sym->size = sym->sym.st_size;
+
+ list_add_tail(&sym->list, &elf->symbols);
+ }
+
+ return 0;
+
+err:
+ free(sym);
+ return -1;
+}
+
+static int read_relas(struct elf *elf)
+{
+ struct section *sec;
+ struct rela *rela;
+ int relas_nr, i;
+ unsigned int symndx;
+
+ list_for_each_entry(sec, &elf->sections, list) {
+ if (sec->sh.sh_type != SHT_RELA)
+ continue;
+
+ sec->base = find_section_by_name(elf, sec->name + 5);
+ if (!sec->base) {
+ WARN("can't find base section for rela section %s",
+ sec->name);
+ return -1;
+ }
+
+ sec->base->rela = sec;
+
+ relas_nr = sec->sh.sh_size / sec->sh.sh_entsize;
+ for (i = 0; i < relas_nr; i++) {
+ rela = calloc(1, sizeof(*rela));
+ if (!rela) {
+ perror("calloc");
+ return -1;
+ }
+
+ if (!gelf_getrela(sec->elf_data, i, &rela->rela)) {
+ perror("gelf_getrela");
+ return -1;
+ }
+
+ rela->type = GELF_R_TYPE(rela->rela.r_info);
+ rela->addend = rela->rela.r_addend;
+ rela->offset = rela->rela.r_offset;
+ symndx = GELF_R_SYM(rela->rela.r_info);
+ rela->sym = find_symbol_by_index(elf, symndx);
+ if (!rela->sym) {
+ WARN("can't find rela entry symbol %u for %s",
+ symndx, sec->name);
+ return -1;
+ }
+
+ list_add_tail(&rela->list, &sec->relas);
+ }
+ }
+
+ return 0;
+}
+
+struct section *create_rela_section(struct elf *elf, const char *name,
+ struct section *base)
+{
+ struct section *sec;
+
+ sec = calloc(1, sizeof(*sec));
+ if (!sec) {
+ WARN("calloc failed");
+ return NULL;
+ }
+ INIT_LIST_HEAD(&sec->relas);
+
+ sec->base = base;
+ sec->name = strdup(name);
+ if (!sec->name) {
+ WARN("strdup failed");
+ return NULL;
+ }
+ sec->sh.sh_name = ~0;
+ sec->sh.sh_type = SHT_RELA;
+
+ if (elf->elf_class == ELFCLASS32) {
+ sec->sh.sh_entsize = sizeof(Elf32_Rela);
+ sec->sh.sh_addralign = 4;
+ } else {
+ sec->sh.sh_entsize = sizeof(Elf64_Rela);
+ sec->sh.sh_addralign = 8;
+ }
+ sec->sh.sh_flags = SHF_ALLOC;
+
+ sec->elf_data = calloc(1, sizeof(*sec->elf_data));
+ if (!sec->elf_data) {
+ WARN("calloc failed");
+ return NULL;
+ }
+ sec->elf_data->d_type = ELF_T_RELA;
+
+ list_add_tail(&sec->list, &elf->sections);
+
+ return sec;
+}
+
+static int update_shstrtab(struct elf *elf)
+{
+ struct section *shstrtab, *sec;
+ size_t orig_size, new_size = 0, offset, len;
+ char *buf;
+
+ shstrtab = find_section_by_name(elf, ".shstrtab");
+ if (!shstrtab) {
+ WARN("can't find .shstrtab");
+ return -1;
+ }
+
+ orig_size = new_size = shstrtab->size;
+
+ list_for_each_entry(sec, &elf->sections, list) {
+ if (sec->sh.sh_name != ~0U)
+ continue;
+ new_size += strlen(sec->name) + 1;
+ }
+
+ if (new_size == orig_size)
+ return 0;
+
+ buf = malloc(new_size);
+ if (!buf) {
+ WARN("malloc failed");
+ return -1;
+ }
+ memcpy(buf, (void *)shstrtab->data, orig_size);
+
+ offset = orig_size;
+ list_for_each_entry(sec, &elf->sections, list) {
+ if (sec->sh.sh_name != ~0U)
+ continue;
+ sec->sh.sh_name = offset;
+ len = strlen(sec->name) + 1;
+ memcpy(buf + offset, sec->name, len);
+ offset += len;
+ }
+
+ shstrtab->elf_data->d_buf = shstrtab->data = buf;
+ shstrtab->elf_data->d_size = shstrtab->size = new_size;
+ shstrtab->sh.sh_size = new_size;
+
+ return 1;
+}
+
+static void free_shstrtab(struct elf *elf)
+{
+ struct section *shstrtab;
+
+ shstrtab = find_section_by_name(elf, ".shstrtab");
+ if (!shstrtab)
+ return;
+
+ free(shstrtab->elf_data->d_buf);
+}
+
+static int update_strtab(struct elf *elf)
+{
+ struct section *strtab;
+ struct symbol *sym;
+ size_t orig_size, new_size = 0, offset, len;
+ char *buf;
+
+ strtab = find_section_by_name(elf, ".strtab");
+ if (!strtab) {
+ WARN("can't find .strtab");
+ return -1;
+ }
+
+ orig_size = new_size = strtab->size;
+
+ list_for_each_entry(sym, &elf->symbols, list) {
+ if (sym->sym.st_name != ~0U)
+ continue;
+ new_size += strlen(sym->name) + 1;
+ }
+
+ if (new_size == orig_size)
+ return 0;
+
+ buf = malloc(new_size);
+ if (!buf) {
+ WARN("malloc failed");
+ return -1;
+ }
+ memcpy(buf, (void *)strtab->data, orig_size);
+
+ offset = orig_size;
+ list_for_each_entry(sym, &elf->symbols, list) {
+ if (sym->sym.st_name != ~0U)
+ continue;
+ sym->sym.st_name = offset;
+ len = strlen(sym->name) + 1;
+ memcpy(buf + offset, sym->name, len);
+ offset += len;
+ }
+
+ strtab->elf_data->d_buf = strtab->data = buf;
+ strtab->elf_data->d_size = strtab->size = new_size;
+ strtab->sh.sh_size = new_size;
+
+ return 1;
+}
+
+static void free_strtab(struct elf *elf)
+{
+ struct section *strtab;
+
+ strtab = find_section_by_name(elf, ".strtab");
+ if (!strtab)
+ return;
+
+ if (strtab->elf_data)
+ free(strtab->elf_data->d_buf);
+}
+
+static int update_symtab(struct elf *elf)
+{
+ struct section *symtab, *sec;
+ struct symbol *sym;
+ char *buf;
+ size_t size;
+ int offset = 0, nr_locals = 0, idx, nr_syms;
+
+ idx = 0;
+ list_for_each_entry(sec, &elf->sections, list)
+ sec->idx = idx++;
+
+ idx = 0;
+ list_for_each_entry(sym, &elf->symbols, list) {
+ sym->idx = idx++;
+ if (sym->sec)
+ sym->sym.st_shndx = sym->sec->idx;
+ }
+ nr_syms = idx;
+
+ symtab = find_section_by_name(elf, ".symtab");
+ if (!symtab) {
+ WARN("can't find symtab");
+ return -1;
+ }
+
+ symtab->sh.sh_link = find_section_by_name(elf, ".strtab")->idx;
+
+ /* create new symtab buffer */
+ if (elf->elf_class == ELFCLASS32)
+ size = nr_syms * sizeof(Elf32_Sym);
+ else
+ size = nr_syms * sizeof(Elf64_Sym);
+ buf = calloc(1, size);
+ if (!buf) {
+ WARN("calloc failed");
+ return -1;
+ }
+
+ offset = 0;
+ list_for_each_entry(sym, &elf->symbols, list) {
+
+ if (elf->elf_class == ELFCLASS32) {
+ /* Manually convert to 32-bit Elf32_Sym */
+ Elf32_Sym sym32;
+
+ sym32.st_name = sym->sym.st_name;
+ sym32.st_info = sym->sym.st_info;
+ sym32.st_other = sym->sym.st_other;
+ sym32.st_shndx = sym->sym.st_shndx;
+ sym32.st_value = sym->sym.st_value;
+ sym32.st_size = sym->sym.st_size;
+ memcpy(buf + offset, &sym32, sizeof(Elf32_Sym));
+ } else {
+ /* Existing 64-bit GElf_Syms are fine */
+ memcpy(buf + offset, &sym->sym, sizeof(Elf64_Sym));
+ }
+
+ offset += symtab->sh.sh_entsize;
+
+ if (sym->bind == STB_LOCAL)
+ nr_locals++;
+ }
+
+ symtab->elf_data->d_buf = symtab->data = buf;
+ symtab->elf_data->d_size = symtab->size = size;
+ symtab->sh.sh_size = size;
+
+ /* update symtab section header */
+ symtab->sh.sh_info = nr_locals;
+
+ return 1;
+}
+
+static void free_symtab(struct elf *elf)
+{
+ struct section *symtab;
+
+ symtab = find_section_by_name(elf, ".symtab");
+ if (!symtab)
+ return;
+
+ free(symtab->elf_data->d_buf);
+}
+
+static int update_relas(struct elf *elf)
+{
+ struct section *sec, *symtab;
+ struct rela *rela;
+ int nr_relas, idx, size;
+ void *relas;
+
+ symtab = find_section_by_name(elf, ".symtab");
+
+ list_for_each_entry(sec, &elf->sections, list) {
+ if (!is_rela_section(sec))
+ continue;
+
+ sec->sh.sh_link = symtab->idx;
+ if (sec->base)
+ sec->sh.sh_info = sec->base->idx;
+
+ nr_relas = 0;
+ list_for_each_entry(rela, &sec->relas, list)
+ nr_relas++;
+
+ if (elf->elf_class == ELFCLASS32)
+ size = nr_relas * sizeof(Elf32_Rela);
+ else
+ size = nr_relas * sizeof(Elf64_Rela);
+
+ relas = malloc(size);
+ if (!relas) {
+ WARN("malloc failed");
+ return -1;
+ }
+
+ sec->elf_data->d_buf = sec->data = relas;
+ sec->elf_data->d_size = sec->size = size;
+ sec->sh.sh_size = size;
+
+ idx = 0;
+ list_for_each_entry(rela, &sec->relas, list) {
+ if (elf->elf_class == ELFCLASS32) {
+ Elf32_Rela *relas32 = relas;
+
+ relas32[idx].r_offset = rela->offset;
+ relas32[idx].r_addend = rela->addend;
+ relas32[idx].r_info = ELF32_R_INFO(rela->sym->idx,
+ rela->type);
+ } else {
+ Elf64_Rela *relas64 = relas;
+
+ relas64[idx].r_offset = rela->offset;
+ relas64[idx].r_addend = rela->addend;
+ relas64[idx].r_info = ELF64_R_INFO(rela->sym->idx,
+ rela->type);
+ }
+ idx++;
+ }
+ }
+
+ return 1;
+}
+
+static void update_groups(struct elf *elf)
+{
+ struct section *sec, *symtab;
+
+ symtab = find_section_by_name(elf, ".symtab");
+
+ list_for_each_entry(sec, &elf->sections, list) {
+ if (sec->sh.sh_type == SHT_GROUP)
+ sec->sh.sh_link = symtab->idx;
+ }
+}
+
+static void free_relas(struct elf *elf)
+{
+ struct section *sec, *symtab;
+
+ symtab = find_section_by_name(elf, ".symtab");
+ if (!symtab)
+ return;
+
+ list_for_each_entry(sec, &elf->sections, list) {
+ if (!is_rela_section(sec))
+ continue;
+
+ free(sec->elf_data->d_buf);
+ }
+}
+
+static int write_file(struct elf *elf, const char *file)
+{
+ int fd;
+ Elf *e;
+ GElf_Ehdr eh, ehout;
+ Elf_Scn *scn;
+ Elf_Data *data;
+ GElf_Shdr sh;
+ struct section *sec;
+
+ fd = creat(file, 0664);
+ if (fd == -1) {
+ WARN("couldn't create %s", file);
+ return -1;
+ }
+
+ e = elf_begin(fd, ELF_C_WRITE, NULL);
+ if (!e) {
+ WARN("elf_begin failed");
+ return -1;
+ }
+
+ if (!gelf_newehdr(e, gelf_getclass(elf->elf))) {
+ WARN("gelf_newehdr failed");
+ return -1;
+ }
+
+ if (!gelf_getehdr(e, &ehout)) {
+ WARN("gelf_getehdr failed");
+ return -1;
+ }
+
+ if (!gelf_getehdr(elf->elf, &eh)) {
+ WARN("gelf_getehdr failed");
+ return -1;
+ }
+
+ memset(&ehout, 0, sizeof(ehout));
+ ehout.e_ident[EI_DATA] = eh.e_ident[EI_DATA];
+ ehout.e_machine = eh.e_machine;
+ ehout.e_flags = eh.e_flags;
+ ehout.e_type = eh.e_type;
+ ehout.e_version = EV_CURRENT;
+ ehout.e_shstrndx = find_section_by_name(elf, ".shstrtab")->idx;
+
+ list_for_each_entry(sec, &elf->sections, list) {
+ if (!sec->idx)
+ continue;
+ scn = elf_newscn(e);
+ if (!scn) {
+ WARN("elf_newscn failed");
+ return -1;
+ }
+
+ data = elf_newdata(scn);
+ if (!data) {
+ WARN("elf_newdata failed");
+ return -1;
+ }
+
+ if (!elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY)) {
+ WARN("elf_flagdata failed");
+ return -1;
+ }
+
+ data->d_type = sec->elf_data->d_type;
+ data->d_buf = sec->elf_data->d_buf;
+ data->d_size = sec->elf_data->d_size;
+
+ if (!gelf_getshdr(scn, &sh)) {
+ WARN("gelf_getshdr failed");
+ return -1;
+ }
+
+ sh = sec->sh;
+
+ if (!gelf_update_shdr(scn, &sh)) {
+ WARN("gelf_update_shdr failed");
+ return -1;
+ }
+ }
+
+ if (!gelf_update_ehdr(e, &ehout)) {
+ WARN("gelf_update_ehdr failed");
+ return -1;
+ }
+
+ if (elf_update(e, ELF_C_WRITE) < 0) {
+ fprintf(stderr, "%s\n", elf_errmsg(-1));
+ WARN("elf_update failed");
+ return -1;
+ }
+
+ elf_end(e);
+
+ return 0;
+}
+
+int elf_write_file(struct elf *elf, const char *file)
+{
+ int ret_shstrtab = 0;
+ int ret_strtab = 0;
+ int ret_symtab = 0;
+ int ret_relas = 0;
+ int ret;
+
+ ret_shstrtab = update_shstrtab(elf);
+ if (ret_shstrtab < 0) {
+ ret = ret_shstrtab;
+ goto out;
+ }
+
+ ret_strtab = update_strtab(elf);
+ if (ret_strtab < 0) {
+ ret = ret_strtab;
+ goto out;
+ }
+
+ ret_symtab = update_symtab(elf);
+ if (ret_symtab < 0) {
+ ret = ret_symtab;
+ goto out;
+ }
+
+ ret_relas = update_relas(elf);
+ if (ret_relas < 0) {
+ ret = ret_relas;
+ goto out;
+ }
+
+ update_groups(elf);
+
+ ret = write_file(elf, file);
+ if (ret)
+ return ret;
+
+out:
+ if (ret_relas > 0)
+ free_relas(elf);
+ if (ret_symtab > 0)
+ free_symtab(elf);
+ if (ret_strtab > 0)
+ free_strtab(elf);
+ if (ret_shstrtab > 0)
+ free_shstrtab(elf);
+
+ return ret;
+}
+
+struct elf *elf_open(const char *name)
+{
+ struct elf *elf;
+
+ elf_version(EV_CURRENT);
+
+ elf = calloc(1, sizeof(*elf));
+ if (!elf) {
+ perror("calloc");
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&elf->sections);
+ INIT_LIST_HEAD(&elf->symbols);
+
+ elf->fd = open(name, O_RDONLY);
+ if (elf->fd == -1) {
+ perror("open");
+ goto err;
+ }
+
+ elf->elf = elf_begin(elf->fd, ELF_C_READ_MMAP, NULL);
+ if (!elf->elf) {
+ perror("elf_begin");
+ goto err;
+ }
+
+ if (!gelf_getehdr(elf->elf, &elf->ehdr)) {
+ perror("gelf_getehdr");
+ goto err;
+ }
+
+ elf->elf_class = gelf_getclass(elf->elf);
+ if ((elf->elf_class != ELFCLASS32) && (elf->elf_class != ELFCLASS64)) {
+ WARN("invalid elf class");
+ goto err;
+ }
+
+ if (read_sections(elf))
+ goto err;
+
+ if (read_symbols(elf))
+ goto err;
+
+ if (read_relas(elf))
+ goto err;
+
+ return elf;
+
+err:
+ elf_close(elf);
+ return NULL;
+}
+
+void elf_close(struct elf *elf)
+{
+ struct section *sec, *tmpsec;
+ struct symbol *sym, *tmpsym;
+ struct rela *rela, *tmprela;
+
+ list_for_each_entry_safe(sym, tmpsym, &elf->symbols, list) {
+ list_del(&sym->list);
+ free(sym);
+ }
+ list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) {
+ list_for_each_entry_safe(rela, tmprela, &sec->relas, list) {
+ list_del(&rela->list);
+ free(rela);
+ }
+ list_del(&sec->list);
+ free(sec);
+ }
+ if (elf->fd > 0)
+ close(elf->fd);
+ if (elf->elf)
+ elf_end(elf->elf);
+ free(elf);
+}
diff --git a/scripts/livepatch/elf.h b/scripts/livepatch/elf.h
new file mode 100644
index 000000000000..784cf42b01bf
--- /dev/null
+++ b/scripts/livepatch/elf.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2015-2016 Josh Poimboeuf <[email protected]>
+ */
+
+#ifndef _KLP_POST_ELF_H
+#define _KLP_POST_ELF_H
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <gelf.h>
+#include "list.h"
+
+#ifdef LIBELF_USE_DEPRECATED
+# define elf_getshdrnum elf_getshnum
+# define elf_getshdrstrndx elf_getshstrndx
+#endif
+
+struct section {
+ struct list_head list;
+ GElf_Shdr sh;
+ struct section *base, *rela;
+ struct list_head relas;
+ struct symbol *sym;
+ Elf_Data *elf_data;
+ char *name;
+ int idx;
+ void *data;
+ unsigned int size;
+};
+
+struct symbol {
+ struct list_head list;
+ GElf_Sym sym;
+ struct section *sec;
+ char *name;
+ unsigned int idx;
+ unsigned char bind, type;
+ unsigned long offset;
+ unsigned int size;
+};
+
+struct rela {
+ struct list_head list;
+ GElf_Rela rela;
+ struct symbol *sym;
+ unsigned int type;
+ unsigned long offset;
+ int addend;
+};
+
+struct elf {
+ Elf *elf;
+ GElf_Ehdr ehdr;
+ int fd;
+ char *name;
+ int elf_class;
+ struct list_head sections;
+ struct list_head symbols;
+};
+
+
+struct elf *elf_open(const char *name);
+bool is_rela_section(struct section *sec);
+struct section *find_section_by_name(struct elf *elf, const char *name);
+struct section *create_rela_section(struct elf *elf, const char *name,
+ struct section *base);
+
+void elf_close(struct elf *elf);
+int elf_write_file(struct elf *elf, const char *file);
+
+
+#endif /* _KLP_POST_ELF_H */
diff --git a/scripts/livepatch/klp-convert.c b/scripts/livepatch/klp-convert.c
new file mode 100644
index 000000000000..203802be334b
--- /dev/null
+++ b/scripts/livepatch/klp-convert.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2016 Josh Poimboeuf <[email protected]>
+ * Copyright (C) 2017 Joao Moreira <[email protected]>
+ * Copyright (C) 2023 Lukas Hruska <[email protected]>
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "elf.h"
+#include "list.h"
+#include "klp-convert.h"
+
+#define KSYM_NAME_LEN 512
+
+#define safe_snprintf(var, size, format, args...) \
+ ({ \
+ int __ret; \
+ \
+ __ret = snprintf(var, size, format, ##args); \
+ __ret < 0 || (size_t)__ret >= size; \
+ })
+
+/*
+ * Formats name of klp rela symbol based on another given section (@oldsec)
+ * and object (@obj_name) name, then returns it
+ */
+static char *alloc_klp_rela_name(struct section *oldsec,
+ char *lp_obj_name, struct elf *klp_elf)
+{
+ char *klp_rela_name;
+ unsigned int length;
+ int err;
+
+ /*
+ * Format: .klp.rela.sec_objname.section_name
+ * Note: ".section_name" comes from oldsec->base->name
+ * including the dot.
+ */
+ length = strlen(KLP_RELA_PREFIX) + strlen(lp_obj_name)
+ + strlen(oldsec->base->name) + 1;
+
+ klp_rela_name = calloc(1, length);
+ if (!klp_rela_name) {
+ WARN("Memory allocation failed (%s%s%s)\n", KLP_RELA_PREFIX,
+ lp_obj_name, oldsec->base->name);
+ return NULL;
+ }
+
+ err = safe_snprintf(klp_rela_name, length, KLP_RELA_PREFIX "%s%s",
+ lp_obj_name, oldsec->base->name);
+ if (err) {
+ WARN("Length error (%s)", klp_rela_name);
+ free(klp_rela_name);
+ return NULL;
+ }
+
+ return klp_rela_name;
+}
+
+static int calc_digits(int num)
+{
+ int count = 0;
+
+ /* It takes a digit to represent zero */
+ if (!num)
+ return 1;
+
+ while (num != 0) {
+ num /= 10;
+ count++;
+ }
+
+ return count;
+}
+
+/* Converts rela symbol names */
+static bool convert_symbol(struct symbol *s)
+{
+ char lp_obj_name[MODULE_NAME_LEN];
+ char sym_obj_name[MODULE_NAME_LEN];
+ char sym_name[KSYM_NAME_LEN];
+ char *klp_sym_name;
+ unsigned long sym_pos;
+ int poslen;
+ unsigned int length;
+
+ static_assert(MODULE_NAME_LEN >= 56 && KSYM_NAME_LEN == 512,
+ "Update limit in the below sscanf()");
+
+ if (sscanf(s->name, KLP_SYM_RELA_PREFIX "%55[^.].%55[^.].%511[^,],%lu",
+ lp_obj_name, sym_obj_name, sym_name, &sym_pos) != 4) {
+ WARN("Invalid format of symbol (%s)\n", s->name);
+ return false;
+ }
+
+ poslen = calc_digits(sym_pos);
+
+ length = strlen(KLP_SYM_PREFIX) + strlen(sym_obj_name)
+ + strlen(sym_name) + sizeof(poslen) + 3;
+
+ klp_sym_name = calloc(1, length);
+ if (!klp_sym_name) {
+ WARN("Memory allocation failed (%s%s.%s,%lu)\n", KLP_SYM_PREFIX,
+ sym_obj_name, sym_name, sym_pos);
+ return false;
+ }
+
+ if (safe_snprintf(klp_sym_name, length, KLP_SYM_PREFIX "%s.%s,%lu",
+ sym_obj_name, sym_name, sym_pos)) {
+
+ WARN("Length error (%s%s.%s,%lu)", KLP_SYM_PREFIX,
+ sym_obj_name, sym_name, sym_pos);
+ free(klp_sym_name);
+ return false;
+ }
+
+ s->name = klp_sym_name;
+ s->sec = NULL;
+ s->sym.st_name = -1;
+ s->sym.st_shndx = SHN_LIVEPATCH;
+
+ return true;
+}
+
+/* Checks if a symbols was already converted */
+static bool is_converted_symbol(struct symbol *sym)
+{
+ return sym->sym.st_shndx == SHN_LIVEPATCH;
+}
+
+/*
+ * Finds or creates a klp rela section based on another given section (@oldsec)
+ * and rela's symbol name (@rela), then returns it
+ */
+static struct section *get_or_create_klp_rela_section(struct section *oldsec, struct rela *rela,
+ struct elf *klp_elf)
+{
+ char *klp_rela_name;
+ char lp_obj_name[MODULE_NAME_LEN];
+ struct section *sec;
+
+ if (sscanf(rela->sym->name, KLP_SYM_RELA_PREFIX "%55[^.]", lp_obj_name) != 1) {
+ WARN("Invalid relocation symbol name.\n");
+ return NULL;
+ }
+
+ klp_rela_name = alloc_klp_rela_name(oldsec, lp_obj_name, klp_elf);
+ if (!klp_rela_name) {
+ WARN("Can't create or access klp.rela section (%s%s)\n",
+ lp_obj_name, oldsec->base->name);
+ return NULL;
+ }
+
+ sec = find_section_by_name(klp_elf, klp_rela_name);
+ if (!sec)
+ sec = create_rela_section(klp_elf, klp_rela_name, oldsec->base);
+
+ if (sec)
+ sec->sh.sh_flags |= SHF_RELA_LIVEPATCH;
+
+ free(klp_rela_name);
+ return sec;
+}
+
+static void move_rela(struct rela *r, struct section *rela_sec)
+{
+ /* Move the rela into newly created klp rela section */
+ list_del(&r->list);
+ list_add_tail(&r->list, &rela_sec->relas);
+}
+
+static bool is_klp_sym_rela_symbol(struct symbol *sym)
+{
+ int len;
+
+ /* skip index 0 which serves as the undefined symbol index */
+ if (!sym->idx)
+ return false;
+
+ len = strlen(KLP_SYM_RELA_PREFIX);
+ /*
+ * we want to resolve only symbols with format:
+ * .klp.sym.rela.<lp-obj-name>.<foo-providing-obj-name>.foo,0
+ */
+ return strncmp(sym->name, KLP_SYM_RELA_PREFIX, len) == 0;
+}
+
+/* Checks if a section is a klp rela section */
+static bool is_klp_rela_section(struct section *sec)
+{
+ if (!is_rela_section(sec))
+ return false;
+
+ int len = strlen(KLP_RELA_PREFIX);
+
+ return strncmp(sec->name, KLP_RELA_PREFIX, len) == 0;
+}
+
+/*
+ * Frees the new names and rela sections as created by
+ * get_or_create_klp_rela_section(), and convert_symbol()
+ */
+static void free_converted_resources(struct elf *klp_elf)
+{
+ struct symbol *sym;
+ struct section *sec;
+
+ list_for_each_entry(sym, &klp_elf->symbols, list) {
+ if (is_converted_symbol(sym))
+ free(sym->name);
+ }
+
+ list_for_each_entry(sec, &klp_elf->sections, list) {
+ if (is_klp_rela_section(sec)) {
+ free(sec->elf_data);
+ free(sec->name);
+ }
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ const char *klp_in_module, *klp_out_module;
+ struct rela *rela, *tmprela;
+ struct section *sec, *rela_sec;
+ struct elf *klp_elf;
+ struct symbol *sym;
+
+ if (argc != 3) {
+ WARN("Usage: %s <input.ko> <output.ko>", argv[0]);
+ return -1;
+ }
+
+ klp_in_module = argv[1];
+ klp_out_module = argv[2];
+
+ klp_elf = elf_open(klp_in_module);
+ if (!klp_elf) {
+ WARN("Unable to read elf file %s\n", klp_in_module);
+ return -1;
+ }
+
+ list_for_each_entry(sec, &klp_elf->sections, list) {
+ /* skip newly created sections */
+ if (is_klp_rela_section(sec))
+ continue;
+
+ list_for_each_entry_safe(rela, tmprela, &sec->relas, list) {
+ if (!is_klp_sym_rela_symbol(rela->sym))
+ continue;
+
+ rela_sec = get_or_create_klp_rela_section(sec, rela, klp_elf);
+ if (!rela_sec) {
+ WARN("Unable to convert relocation: %s",
+ rela->sym->name);
+ return -1;
+ }
+ /* rela needs to be moved to newly created section */
+ move_rela(rela, rela_sec);
+ }
+ }
+
+ /* Rename symbols */
+ list_for_each_entry(sym, &klp_elf->symbols, list) {
+ if (!is_klp_sym_rela_symbol(sym))
+ continue;
+ if (!convert_symbol(sym)) {
+ WARN("Unable to convert symbol name (%s)\n",
+ sym->name);
+ return -1;
+ }
+ }
+
+ if (elf_write_file(klp_elf, klp_out_module))
+ return -1;
+
+ free_converted_resources(klp_elf);
+ elf_close(klp_elf);
+
+ return 0;
+}
diff --git a/scripts/livepatch/klp-convert.h b/scripts/livepatch/klp-convert.h
new file mode 100644
index 000000000000..416b86e46598
--- /dev/null
+++ b/scripts/livepatch/klp-convert.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2016 Josh Poimboeuf <[email protected]>
+ * Copyright (C) 2017 Joao Moreira <[email protected]>
+ *
+ */
+
+#define SHN_LIVEPATCH 0xff20
+#define SHF_RELA_LIVEPATCH 0x00100000
+#define MODULE_NAME_LEN (64 - sizeof(GElf_Addr))
+#define WARN(format, ...) \
+ fprintf(stderr, "klp-convert: " format "\n", ##__VA_ARGS__)
+
+/*
+ * klp-convert uses macros and structures defined in the linux sources
+ * package (see include/uapi/linux/livepatch.h). To prevent the
+ * dependency when building locally, they are defined below. Also notice
+ * that these should match the definitions from the targeted kernel.
+ */
+
+#define KLP_RELA_PREFIX ".klp.rela."
+#define KLP_SYM_RELA_PREFIX ".klp.sym.rela."
+#define KLP_SYM_PREFIX ".klp.sym."
diff --git a/scripts/livepatch/list.h b/scripts/livepatch/list.h
new file mode 100644
index 000000000000..4d429120fabf
--- /dev/null
+++ b/scripts/livepatch/list.h
@@ -0,0 +1,391 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+#define WRITE_ONCE(a, b) (a = b)
+#define READ_ONCE(a) a
+
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({ \
+ const typeof(((type *)0)->member) * __mptr = (ptr); \
+ (type *)((char *)__mptr - offsetof(type, member)); })
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+ WRITE_ONCE(list->next, list);
+ list->prev = list;
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ WRITE_ONCE(prev->next, new);
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head *prev, struct list_head *next)
+{
+ next->prev = prev;
+ WRITE_ONCE(prev->next, next);
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty() on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void __list_del_entry(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+}
+
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_is_last - tests whether @list is the last entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_last(const struct list_head *list,
+ const struct list_head *head)
+{
+ return list->next == head;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return READ_ONCE(head->next) == head;
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/**
+ * list_last_entry - get the last element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_last_entry(ptr, type, member) \
+ list_entry((ptr)->prev, type, member)
+
+/**
+ * list_first_entry_or_null - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_first_entry_or_null(ptr, type, member) \
+ (!list_empty(ptr) ? list_first_entry(ptr, type, member) : NULL)
+
+/**
+ * list_next_entry - get the next element in list
+ * @pos: the type * to cursor
+ * @member: the name of the list_head within the struct.
+ */
+#define list_next_entry(pos, member) \
+ list_entry((pos)->member.next, typeof(*(pos)), member)
+
+/**
+ * list_prev_entry - get the prev element in list
+ * @pos: the type * to cursor
+ * @member: the name of the list_head within the struct.
+ */
+#define list_prev_entry(pos, member) \
+ list_entry((pos)->member.prev, typeof(*(pos)), member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev_safe - iterate over a list backwards safe against removal
+ of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_prev_safe(pos, n, head) \
+ for (pos = (head)->prev, n = pos->prev; \
+ pos != (head); \
+ pos = n, n = pos->prev)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_last_entry(head, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use in
+ list_for_each_entry_continue()
+ * @pos: the type * to use as a start point
+ * @head: the head of the list
+ * @member: the name of the list_head within the struct.
+ *
+ * Prepares a pos entry for use as a start point in
+ list_for_each_entry_continue().
+ */
+#define list_prepare_entry(pos, head, member) \
+ ((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - continue iteration over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Continue to iterate over list of given type, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue(pos, head, member) \
+ for (pos = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_continue_reverse - iterate backwards from the given point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Start to iterate over list of given type backwards, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue_reverse(pos, head, member) \
+ for (pos = list_prev_entry(pos, member); \
+ &pos->member != (head); \
+ pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_from - iterate over list of given type from the current
+ point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from(pos, head, member) \
+ for (; &pos->member != (head); \
+ pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against
+ removal of list entry
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member), \
+ n = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_continue - continue list iteration safe against
+ * removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing after current point,
+ * safe against removal of list entry.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member) \
+ for (pos = list_next_entry(pos, member), \
+ n = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_from - iterate over list from current point safe
+ * against removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate over list of given type from current point, safe against
+ * removal of list entry.
+ */
+#define list_for_each_entry_safe_from(pos, n, head, member) \
+ for (n = list_next_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_reverse - iterate backwards over list safe against
+ * removal
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, safe against removal
+ * of list entry.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member) \
+ for (pos = list_last_entry(head, typeof(*pos), member), \
+ n = list_prev_entry(pos, member); \
+ &pos->member != (head); \
+ pos = n, n = list_prev_entry(n, member))
+
+/**
+ * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
+ * @pos: the loop cursor used in the list_for_each_entry_safe loop
+ * @n: temporary storage used in list_for_each_entry_safe
+ * @member: the name of the list_head within the struct.
+ *
+ * list_safe_reset_next is not safe to use in general if the list may be
+ * modified concurrently (eg. the lock is dropped in the loop body). An
+ * exception to this is if the cursor element (pos) is pinned in the list,
+ * and list_safe_reset_next is called after re-taking the lock and before
+ * completing the current iteration of the loop body.
+ */
+#define list_safe_reset_next(pos, n, member) \
+ (n = list_next_entry(pos, member))
+
+#endif
--
2.45.0
On Thu 2024-05-16 15:30:05, Lukas Hruska wrote:
> Livepatches need to access external symbols which can't be handled
> by the normal relocation mechanism. It is needed for two types
> of symbols:
>
> + Symbols which can be local for the original livepatched function.
> The alternative implementation in the livepatch sees them
> as external symbols.
>
> + Symbols in modules which are exported via EXPORT_SYMBOL*(). They
> must be handled special way otherwise the livepatch module would
> depend on the livepatched one. Loading such livepatch would cause
> loading the other module as well.
>
> The address of these symbols can be found via kallsyms. Or they can
> be relocated using livepatch specific relocation sections as specified
> in Documentation/livepatch/module-elf-format.txt.
>
> --- /dev/null
> +++ b/scripts/livepatch/klp-convert.c
> +/* Converts rela symbol names */
> +static bool convert_symbol(struct symbol *s)
> +{
> + char lp_obj_name[MODULE_NAME_LEN];
> + char sym_obj_name[MODULE_NAME_LEN];
> + char sym_name[KSYM_NAME_LEN];
> + char *klp_sym_name;
> + unsigned long sym_pos;
> + int poslen;
> + unsigned int length;
> +
> + static_assert(MODULE_NAME_LEN >= 56 && KSYM_NAME_LEN == 512,
> + "Update limit in the below sscanf()");
> +
> + if (sscanf(s->name, KLP_SYM_RELA_PREFIX "%55[^.].%55[^.].%511[^,],%lu",
> + lp_obj_name, sym_obj_name, sym_name, &sym_pos) != 4) {
> + WARN("Invalid format of symbol (%s)\n", s->name);
> + return false;
> + }
> +
> + poslen = calc_digits(sym_pos);
> +
> + length = strlen(KLP_SYM_PREFIX) + strlen(sym_obj_name)
> + + strlen(sym_name) + sizeof(poslen) + 3;
There should be "poslen" instead of "sizeof(poslen)".
> +
> + klp_sym_name = calloc(1, length);
> + if (!klp_sym_name) {
> + WARN("Memory allocation failed (%s%s.%s,%lu)\n", KLP_SYM_PREFIX,
> + sym_obj_name, sym_name, sym_pos);
> + return false;
> + }
> +
> + if (safe_snprintf(klp_sym_name, length, KLP_SYM_PREFIX "%s.%s,%lu",
> + sym_obj_name, sym_name, sym_pos)) {
> +
> + WARN("Length error (%s%s.%s,%lu)", KLP_SYM_PREFIX,
> + sym_obj_name, sym_name, sym_pos);
> + free(klp_sym_name);
> + return false;
> + }
> +
> + s->name = klp_sym_name;
> + s->sec = NULL;
> + s->sym.st_name = -1;
> + s->sym.st_shndx = SHN_LIVEPATCH;
> +
> + return true;
> +}
> --- /dev/null
> +++ b/scripts/livepatch/klp-convert.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2016 Josh Poimboeuf <[email protected]>
> + * Copyright (C) 2017 Joao Moreira <[email protected]>
> + *
> + */
> +
> +#define SHN_LIVEPATCH 0xff20
> +#define SHF_RELA_LIVEPATCH 0x00100000
> +#define MODULE_NAME_LEN (64 - sizeof(GElf_Addr))
> +#define WARN(format, ...) \
> + fprintf(stderr, "klp-convert: " format "\n", ##__VA_ARGS__)
Nit: I would remove "\n" here and add it to all callers. Half of the
callers already have it ;-)
> +
> +/*
> + * klp-convert uses macros and structures defined in the linux sources
> + * package (see include/uapi/linux/livepatch.h). To prevent the
> + * dependency when building locally, they are defined below. Also notice
> + * that these should match the definitions from the targeted kernel.
> + */
> +
> +#define KLP_RELA_PREFIX ".klp.rela."
> +#define KLP_SYM_RELA_PREFIX ".klp.sym.rela."
> +#define KLP_SYM_PREFIX ".klp.sym."
Otherwise, it looks good.
Best Regards,
Petr
On Thu 2024-05-16 15:30:07, Lukas Hruska wrote:
> From: Josh Poimboeuf <[email protected]>
>
> Add a new livepatch sample in samples/livepatch/ to make use of symbols
> that must be post-processed to enable load-time relocation resolution.
> As the new sample is to be used as an example, it is annotated with
> KLP_RELOC_SYMBOL macro.
>
> The livepatch sample updates the function cmdline_proc_show to print the
> string referenced by the symbol saved_command_line appended by the
> string "livepatch=1".
>
> Signed-off-by: Josh Poimboeuf <[email protected]>
> Signed-off-by: Lukas Hruska <[email protected]>
> Reviewed-by: Petr Mladek <[email protected]>
> ---
> samples/livepatch/Makefile | 1 +
> samples/livepatch/livepatch-extern-symbol.c | 84 +++++++++++++++++++++
> 2 files changed, 85 insertions(+)
> create mode 100644 samples/livepatch/livepatch-extern-symbol.c
>
> diff --git a/samples/livepatch/Makefile b/samples/livepatch/Makefile
> index 9f853eeb6140..f2b41f4d6c16 100644
> --- a/samples/livepatch/Makefile
> +++ b/samples/livepatch/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-shadow-fix2.o
> obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-demo.o
> obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-mod.o
> obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-callbacks-busymod.o
> +obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-annotated-sample.o
It seems that the sample has been renamed without updating
the Makefile rule. There should be:
+obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch-extern-symbol.o
Otherwise, I get:
make[4]: *** No rule to make target 'samples/livepatch/livepatch-annotated-sample.o', needed by 'samples/livepatch/'. Stop.
make[3]: *** [scripts/Makefile.build:485: samples/livepatch] Error 2
make[2]: *** [scripts/Makefile.build:485: samples] Error 2
make[1]: *** [/prace/kernel/linux/Makefile:1921: .] Error 2
make: *** [Makefile:240: __sub-make] Error 2
> diff --git a/samples/livepatch/livepatch-extern-symbol.c b/samples/livepatch/livepatch-extern-symbol.c
> new file mode 100644
> index 000000000000..276a43d157b4
> --- /dev/null
> +++ b/samples/livepatch/livepatch-extern-symbol.c
> @@ -0,0 +1,84 @@
The test module works as expected after fixing the Makefile.
Best Regards,
Petr
On Thu 2024-05-16 15:30:09, Lukas Hruska wrote:
> The test proves that klp-convert works as intended and it is possible to
> livepatch a function that use an external symbol.
>
> Signed-off-by: Lukas Hruska <[email protected]>
> --- a/tools/testing/selftests/livepatch/functions.sh
> +++ b/tools/testing/selftests/livepatch/functions.sh
> @@ -7,6 +7,7 @@
> MAX_RETRIES=600
> RETRY_INTERVAL=".1" # seconds
> KLP_SYSFS_DIR="/sys/kernel/livepatch"
> +MODULE_SYSFS_DIR="/sys/module"
>
> # Kselftest framework requirement - SKIP code is 4
> ksft_skip=4
> @@ -299,7 +300,7 @@ function check_result {
> result=$(dmesg | awk -v last_dmesg="$LAST_DMESG" 'p; $0 == last_dmesg { p=1 }' | \
> grep -e 'livepatch:' -e 'test_klp' | \
> grep -v '\(tainting\|taints\) kernel' | \
> - sed 's/^\[[ 0-9.]*\] //')
> + sed 's/^\[[ 0-9.]*\] //' | sed 's/^test_klp_log: //')
The prefix "test_klp_log:" is not used anywhere. It seems that this
change is not needed in the final version.
>
> if [[ "$expect" == "$result" ]] ; then
> echo "ok"
Otherwise, it looks and works nice. With the hunk removed:
Reviewed-by: Petr Mladek <[email protected]>
Tested-by: Petr Mladek <[email protected]>
Best Regards,
Petr
Hi Lukas,
kernel test robot noticed the following build errors:
[auto build test ERROR on masahiroy-kbuild/fixes]
[also build test ERROR on shuah-kselftest/next shuah-kselftest/fixes linus/master v6.9]
[cannot apply to masahiroy-kbuild/for-next next-20240517]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Lukas-Hruska/livepatch-Add-klp-convert-tool/20240516-223103
base: https://git.kernel.org/pub/scm/linux/kernel/git/masahiroy/linux-kbuild.git fixes
patch link: https://lore.kernel.org/r/20240516133009.20224-7-lhruska%40suse.cz
patch subject: [PATCH v2 6/6] selftests: livepatch: Test livepatching function using an external symbol
compiler: clang version 18.1.5 (https://github.com/llvm/llvm-project 617a15a9eac96088ae5e9134248d8236e34b91b1)
reproduce:
$ clang --version
clang version 18.1.5 (git://gitmirror/llvm_project 617a15a9eac96088ae5e9134248d8236e34b91b1)
Target: x86_64-unknown-linux-gnu
$ cd linux
# download the attached config file and save it as .config
$ make ARCH=x86_64 LLVM=1 -j$(nproc) olddefconfig
$ make ARCH=x86_64 LLVM=1 -j$(nproc)
$ export KDIR=$PWD
$ make ARCH=x86_64 LLVM=1 -j$(nproc) -C tools/testing/selftests/livepatch
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/r/[email protected]/
All errors (new ones prefixed by >>, old ones prefixed by <<):
make: Entering directory 'tools/testing/selftests/livepatch'
make -C test_modules
make[1]: Entering directory 'tools/testing/selftests/livepatch/test_modules'
make -C /root/linux modules KBUILD_EXTMOD=tools/testing/selftests/livepatch/test_modules
make[2]: Entering directory '/root/linux'
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_busy.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_mod.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_extern.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_extern_hello.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_state.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_state2.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_state3.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_shadow_vars.o
CC [M] tools/testing/selftests/livepatch/test_modules/test_klp_syscall.o
MODPOST tools/testing/selftests/livepatch/test_modules/Module.symvers
>> ERROR: modpost: "".klp.sym.rela.test_klp_extern_hello.test_klp_extern_hello.hello_msg,0"" [tools/testing/selftests/livepatch/test_modules/test_klp_extern.ko] undefined!
make[4]: *** [scripts/Makefile.modpost:146: tools/testing/selftests/livepatch/test_modules/Module.symvers] Error 1
make[3]: *** [Makefile:1873: modpost] Error 2
make[2]: *** [Makefile:240: __sub-make] Error 2
make[2]: Leaving directory 'linux'
make[1]: *** [Makefile:21: modules] Error 2
make[1]: Leaving directory 'tools/testing/selftests/livepatch/test_modules'
make: *** [../lib.mk:107: gen_mods_dir] Error 2
make: Leaving directory 'tools/testing/selftests/livepatch'
FYI, this build error is captured when using clang compiler, while
there is no such error when using gcc, so this may be a
compiler specific issue.
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
From: [email protected]
On Thu, 16 May 2024 15:30:03 +0200 Lukas Hruska <[email protected]> wrote:
> Summary
> -------
>
> This is a significantly simplified version of the original klp-convert tool.
> The klp-convert code has never got a proper review and also clean ups
> were not easy. The last version was v7, see
> https://lore.kernel.org/r/[email protected]
>
> The main change is that the tool does not longer search for the
> symbols which would need the livepatch specific relocation entry.
> Also klp.symbols file is not longer needed.
>
> Instead, the needed information is appended to the symbol declaration
> via a new macro KLP_RELOC_SYMBOL(). It creates symbol with all needed
> metadata. For example:
>
> extern char *saved_command_line \
> KLP_RELOC_SYMBOL(vmlinux, vmlinux, saved_command_line, 0);
>
> would create symbol
>
> $>readelf -r -W <compiled livepatch module>:
> Relocation section '.rela.text' at offset 0x32e60 contains 10 entries:
> Offset Info Type Symbol's Value Symbol's Name + Addend
> [...]
> 0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.rela.vmlinux.vmlinux.saved_command_line,0 - 4
> [...]
>
>
> The simplified klp-convert tool just transforms symbols
> created by KLP_RELOC_SYMBOL() to object specific rela sections
> and rela entries which would later be proceed when the livepatch
> or the livepatched object is loaded.
>
> For example, klp-convert would replace the above symbols with:
>
> $> readelf -r -W <livepatch_module_proceed_by_klp_convert>
> Relocation section '.klp.rela.vmlinux.text' at offset 0x5cb60 contains 1 entry:
> Offset Info Type Symbol's Value Symbol's Name + Addend
> 0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.saved_command_line,0 - 4
>
>
> Note that similar macro was needed also in the original version
> to handle more symbols of the same name (sympos).
>
> Given the above, add klp-convert tool; integrate klp-convert tool into
> kbuild; add data-structure and macros to enable users to annotate
> livepatch source code; make modpost stage compatible with livepatches;
> update livepatch-sample and update documentation.
>
>
> Testing
> -------
>
> The patchset selftests build and execute on x86_64, s390x, and ppc64le
> for both default config (with added livepatch dependencies) and a larger
> SLE-15-ish config.
>
>
> Summary of changes in this minimal version v2
> ------------------------
>
> - rebase for v6.9
> - cleaned-up SoB chains (suggested by pmladek)
> - klp-convert: remove the symbol map auto-resolving solution
> - klp-convert: add macro for flagging variables inside a LP src to be resolved by this tool
> - klp-convert: code simplification
> - selftests: add selftest livepatching function using an external symbol
Thanks for sending this new version Lukas! It currently fails to apply on
current Linux master, but the conflict is very simple to address (attached
patch).
Joe, Josh, other people, can you also take a look in the patchset? It would be
good we can move this forward.
Thanks,
Marcos
commit 1a1cf8b0967c26857b17e8ceb02f6a1bd854667d
Author: Marcos Paulo de Souza <[email protected]>
Date: Wed May 29 10:18:38 2024 -0300
Solve merge problem
Signed-off-by: Marcos Paulo de Souza <[email protected]>
diff --git a/Makefile b/Makefile
index f975b6396328..579dfb46e691 100644
--- a/Makefile
+++ b/Makefile
@@ -1491,7 +1491,7 @@ endif # CONFIG_MODULES
# Directories & files removed with 'make clean'
CLEAN_FILES += vmlinux.symvers modules-only.symvers \
modules.builtin modules.builtin.modinfo modules.nsdeps \
- compile_commands.json rust/test \
+ compile_commands.json .thinlto-cache rust/test \
rust-project.json .vmlinux.objs .vmlinux.export.c
# Directories & files removed with 'make mrproper'
>
> Previous versions
> -----------------
>
> RFC:
> https://lore.kernel.org/r/[email protected]/
> v2:
> https://lore.kernel.org/r/[email protected]/
> v3:
> https://lore.kernel.org/r/[email protected]/
> v4:
> https://lore.kernel.org/r/[email protected]/
> v5:
> (not posted)
> https://github.com/joe-lawrence/klp-convert-tree/tree/klp-convert-v5-devel
> v6:
> https://lore.kernel.org/r/[email protected]/
> v7:
> https://lore.kernel.org/r/[email protected]/
> v1 minimal:
> https://lore.kernel.org/r/[email protected]/
Hi Lukas,
As mentioned offlist, reviewing and testing this is on my TODO list, but
here are some early notes ...
On Thu, May 16, 2024 at 03:30:05PM +0200, Lukas Hruska wrote:
> Livepatches need to access external symbols which can't be handled
> by the normal relocation mechanism. It is needed for two types
> of symbols:
>
> + Symbols which can be local for the original livepatched function.
> The alternative implementation in the livepatch sees them
> as external symbols.
>
> + Symbols in modules which are exported via EXPORT_SYMBOL*(). They
> must be handled special way otherwise the livepatch module would
> depend on the livepatched one. Loading such livepatch would cause
> loading the other module as well.
>
> The address of these symbols can be found via kallsyms. Or they can
> be relocated using livepatch specific relocation sections as specified
> in Documentation/livepatch/module-elf-format.txt.
>
> Currently, there is no trivial way to embed the required information as
> requested in the final livepatch elf object. klp-convert solves this
> problem by using annotations in the elf object to convert the relocation
> accordingly to the specification, enabling it to be handled by the
> livepatch loader.
>
> Given the above, create scripts/livepatch to hold tools developed for
> livepatches and add source files for klp-convert there.
>
> Allow to annotate such external symbols in the livepatch by a macro
> KLP_RELOC_SYMBOL(). It will create symbol with all needed
> metadata. For example:
>
> extern char *saved_command_line \
> KLP_RELOC_SYMBOL(vmlinux, vmlinux, saved_command_line, 0);
Nit: should this be KLP_RELOC_SYMBOL_POS if it including the 0 position?
(Or KLP_RELOC_SYMBOL and omit the implied 0-th position.)
> diff --git a/scripts/livepatch/elf.c b/scripts/livepatch/elf.c
> --- /dev/null
> +++ b/scripts/livepatch/elf.c
> +static int update_shstrtab(struct elf *elf)
> +{
> + struct section *shstrtab, *sec;
> + size_t orig_size, new_size = 0, offset, len;
> + char *buf;
> +
> + shstrtab = find_section_by_name(elf, ".shstrtab");
> + if (!shstrtab) {
> + WARN("can't find .shstrtab");
> + return -1;
> + }
> +
> + orig_size = new_size = shstrtab->size;
> +
> + list_for_each_entry(sec, &elf->sections, list) {
> + if (sec->sh.sh_name != ~0U)
> + continue;
> + new_size += strlen(sec->name) + 1;
> + }
> +
> + if (new_size == orig_size)
> + return 0;
> +
> + buf = malloc(new_size);
> + if (!buf) {
> + WARN("malloc failed");
> + return -1;
> + }
> + memcpy(buf, (void *)shstrtab->data, orig_size);
While reviewing klp-convert v7 [1], Alexey Dobriyan notes here,
"This code is called realloc(). :-)"
[1] https://lore.kernel.org/live-patching/4ce29654-4e1e-4680-9c25-715823ff5e02@p183/
> +static int update_strtab(struct elf *elf)
> +{
> + struct section *strtab;
> + struct symbol *sym;
> + size_t orig_size, new_size = 0, offset, len;
> + char *buf;
> +
> + strtab = find_section_by_name(elf, ".strtab");
> + if (!strtab) {
> + WARN("can't find .strtab");
> + return -1;
> + }
> +
> + orig_size = new_size = strtab->size;
> +
> + list_for_each_entry(sym, &elf->symbols, list) {
> + if (sym->sym.st_name != ~0U)
> + continue;
> + new_size += strlen(sym->name) + 1;
> + }
> +
> + if (new_size == orig_size)
> + return 0;
> +
> + buf = malloc(new_size);
> + if (!buf) {
> + WARN("malloc failed");
> + return -1;
> + }
> + memcpy(buf, (void *)strtab->data, orig_size);
I think Alexey's realloc suggestion would apply here, too.
> +static int write_file(struct elf *elf, const char *file)
> +{
> + int fd;
> + Elf *e;
> + GElf_Ehdr eh, ehout;
> + Elf_Scn *scn;
> + Elf_Data *data;
> + GElf_Shdr sh;
> + struct section *sec;
> +
> + fd = creat(file, 0664);
> + if (fd == -1) {
> + WARN("couldn't create %s", file);
> + return -1;
> + }
> +
> + e = elf_begin(fd, ELF_C_WRITE, NULL);
Alexy also found an ELF coding bug:
"elf_end() doesn't close descriptor, so there is potentially corrupted
data. There is no unlink() call if writes fail as well."
> +void elf_close(struct elf *elf)
> +{
> + struct section *sec, *tmpsec;
> + struct symbol *sym, *tmpsym;
> + struct rela *rela, *tmprela;
> +
> + list_for_each_entry_safe(sym, tmpsym, &elf->symbols, list) {
> + list_del(&sym->list);
> + free(sym);
> + }
> + list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) {
> + list_for_each_entry_safe(rela, tmprela, &sec->relas, list) {
> + list_del(&rela->list);
> + free(rela);
> + }
> + list_del(&sec->list);
> + free(sec);
> + }
> + if (elf->fd > 0)
> + close(elf->fd);
Alexy found another ELF coding bug here:
"Techically, it is "fd >= 0"."
I had coded fixes for these in a v8-devel that I never finished. It
shouldn't be too hard to fix these up in the minimal version of the
patchset, but lmk if you'd like a patch.
That's all for now. My plan is to try and turn off kpatch-build's
klp-relocation code and see how passing through to klp-convert fares.
That would give us a good comparison of real-world examples that need to
be handled and tested.
--
Joe
On Thu, May 16, 2024 at 03:30:05PM +0200, Lukas Hruska wrote:
> Livepatches need to access external symbols which can't be handled
> by the normal relocation mechanism. It is needed for two types
> of symbols:
>
> + Symbols which can be local for the original livepatched function.
> The alternative implementation in the livepatch sees them
> as external symbols.
>
> + Symbols in modules which are exported via EXPORT_SYMBOL*(). They
> must be handled special way otherwise the livepatch module would
> depend on the livepatched one. Loading such livepatch would cause
> loading the other module as well.
>
> The address of these symbols can be found via kallsyms. Or they can
> be relocated using livepatch specific relocation sections as specified
> in Documentation/livepatch/module-elf-format.txt.
>
> Currently, there is no trivial way to embed the required information as
> requested in the final livepatch elf object. klp-convert solves this
> problem by using annotations in the elf object to convert the relocation
> accordingly to the specification, enabling it to be handled by the
> livepatch loader.
>
> Given the above, create scripts/livepatch to hold tools developed for
> livepatches and add source files for klp-convert there.
>
> Allow to annotate such external symbols in the livepatch by a macro
> KLP_RELOC_SYMBOL(). It will create symbol with all needed
> metadata. For example:
>
> extern char *saved_command_line \
> KLP_RELOC_SYMBOL(vmlinux, vmlinux, saved_command_line, 0);
>
> would create symbol
>
> $>readelf -r -W <compiled livepatch module>:
> Relocation section '.rela.text' at offset 0x32e60 contains 10 entries:
> Offset Info Type Symbol's Value Symbol's Name + Addend
> [...]
> 0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.rela.vmlinux.vmlinux.saved_command_line,0 - 4
> [...]
>
> Also add scripts/livepatch/klp-convert. The tool transforms symbols
> created by KLP_RELOC_SYMBOL() to object specific rela sections
> and rela entries which would later be proceed when the livepatch
> or the livepatched object is loaded.
>
> For example, klp-convert would replace the above symbol with:
>
> $> readelf -r -W <livepatch_module_proceed_by_klp_convert>
> Relocation section '.klp.rela.vmlinux.text' at offset 0x5cb60 contains 1 entry:
> Offset Info Type Symbol's Value Symbol's Name + Addend
> 0000000000000068 0000003c00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.saved_command_line,0 - 4
>
> klp-convert relies on libelf and on a list implementation. Add files
> scripts/livepatch/elf.c and scripts/livepatch/elf.h, which are a libelf
> interfacing layer and scripts/livepatch/list.h, which is a list
> implementation.
>
> Update Makefiles to correctly support the compilation of the new tool,
> update MAINTAINERS file and add a .gitignore file.
>
> [[email protected]: initial version]
> Signed-off-by: Josh Poimboeuf <[email protected]>
> [[email protected]: clean-up and fixes]
> Signed-off-by: Joe Lawrence <[email protected]>
> [[email protected]: klp-convert code, minimal approach]
> Signed-off-by: Lukas Hruska <[email protected]>
> Reviewed-by: Marcos Paulo de Souza <[email protected]>
> ---
> MAINTAINERS | 1 +
> include/linux/livepatch.h | 19 +
> scripts/Makefile | 1 +
> scripts/livepatch/.gitignore | 1 +
> scripts/livepatch/Makefile | 5 +
> scripts/livepatch/elf.c | 817 ++++++++++++++++++++++++++++++++
> scripts/livepatch/elf.h | 73 +++
> scripts/livepatch/klp-convert.c | 284 +++++++++++
> scripts/livepatch/klp-convert.h | 23 +
> scripts/livepatch/list.h | 391 +++++++++++++++
> 10 files changed, 1615 insertions(+)
> create mode 100644 scripts/livepatch/.gitignore
> create mode 100644 scripts/livepatch/Makefile
> create mode 100644 scripts/livepatch/elf.c
> create mode 100644 scripts/livepatch/elf.h
> create mode 100644 scripts/livepatch/klp-convert.c
> create mode 100644 scripts/livepatch/klp-convert.h
> create mode 100644 scripts/livepatch/list.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 130b8b0bd4f7..d2facc1f4e15 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12618,6 +12618,7 @@ F: include/uapi/linux/livepatch.h
> F: kernel/livepatch/
> F: kernel/module/livepatch.c
> F: samples/livepatch/
> +F: scripts/livepatch/
> F: tools/testing/selftests/livepatch/
>
> LLC (802.2)
> diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
> index 9b9b38e89563..83bbcd1c43fd 100644
> --- a/include/linux/livepatch.h
> +++ b/include/linux/livepatch.h
> @@ -235,6 +235,25 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
> unsigned int symindex, unsigned int secindex,
> const char *objname);
>
> +/**
> + * KLP_RELOC_SYMBOL_POS - define relocation for external symbols
> + *
> + * @LP_OBJ_NAME: name of the livepatched object where the symbol is needed
> + * @SYM_OBJ_NAME: name of the object where the symbol exists
> + * @SYM_NAME: symbol name
> + * @SYM_POS: position of the symbol in SYM_OBJ when there are more
> + * symbols of the same name.
> + *
> + * Use for annotating external symbols used in livepatches which are
> + * not exported in vmlinux or are in livepatched modules, see
> + * Documentation/livepatch/module-elf-format.rst
> + */
> +#define KLP_RELOC_SYMBOL_POS(LP_OBJ_NAME, SYM_OBJ_NAME, SYM_NAME, SYM_POS) \
> + asm("\".klp.sym.rela." #LP_OBJ_NAME "." #SYM_OBJ_NAME "." #SYM_NAME "," #SYM_POS "\"")
^^^
I think I found a potential bug, or at least compatiblity problem with
including a comma "," character in this symbol format and older versions
of the GNU assembler. The good news is that other delimiter characters
like "." or "#" seem to work out fine.
If you want to reproduce, you'll need a version of `as` like binutils
2.36.1 and try building the samples/livepatch/livepatch-extern-symbol.ko
and you should get an error like:
Assembler messages:
Warning: missing closing '"'
Warning: missing closing '"'
Error: too many memory references for `movq'
If you want to retrace my adventure, here are my steps:
1) Clone klp-convert-tree repo branch containing this patchset +
Petr's review comments + a few helpful things for klp-convert
development:
$ git clone \
--single-branch --branch=klp-convert-minimal-v1-review --depth=9 \
https://github.com/joe-lawrence/klp-convert-tree.git
[ ... snip ... ]
$ cd klp-convert-tree
2) Override .cross-dev defaults:
$ export BUILD_ARCHES=x86_64
$ export COMPILER=gcc-11.1.0
$ export URL=https://cdn.kernel.org/pub/tools/crosstool/files/bin/x86_64/
$ export OUTDIR_PREFIX=$(pwd)/build
$ export COMPILER_INSTALL_PATH=$(pwd)/cross-compiler
3) Setup x86_64 default .config (this will download and install the
gcc-11.1.0 compiler from cdn.kernel.org):
$ ./cross-dev make defconfig
x86_64 : make defconfig ...
Compiler will be installed in /root/klp-convert-tree/cross-compiler
[ ... snip ... ]
4) Add kernel livepatching configuration options:
$ ./cross-dev klp-config
Configuring x86_64 ...
[ ... snip ... ]
$ grep LIVEPATCH "$OUTDIR_PREFIX"-x86_64/.config
CONFIG_HAVE_LIVEPATCH=y
CONFIG_LIVEPATCH=y
CONFIG_SAMPLE_LIVEPATCH=m
5) Run the cross-compiler build until it hits a build error on
livepatch-extern-symbol.ko:
$ ./cross-dev make -j$(nproc)
[ ... snip ... ]
make: Target '__all' not remade because of errors.
[ x86_64 : make -j48 = FAIL ]
6) With pre-requisites already built, retry the external symbol sample
and add -save-temps to the KCFLAGS to keep the generated assembly file:
$ KCFLAGS="-save-temps=obj" ./cross-dev make samples/livepatch/livepatch-extern-symbol.ko
[ ... snip ... ]
samples/livepatch/livepatch-extern-symbol.s: Assembler messages:
samples/livepatch/livepatch-extern-symbol.s:103: Warning: missing closing '"'
samples/livepatch/livepatch-extern-symbol.s:103: Warning: missing closing '"'
samples/livepatch/livepatch-extern-symbol.s:103: Error: too many memory references for `movq'
[ ... snip ... ]
7) Which line is that?
$ awk 'NR==103' "$OUTDIR_PREFIX"-x86_64/samples/livepatch/livepatch-extern-symbol.s
movq ".klp.sym.rela.vmlinux.vmlinux.saved_command_line,0"(%rip), %rdx
You could alternatively poke at it through the compiler explorer service
and toggle the source and binutils versions:
(error) binutils 2.36.1 : https://godbolt.org/z/cGGs6rfWe
(success) binutils 2.38 : https://godbolt.org/z/ffzza3vYd
--
Joe
Hello Joe,
> > +#define KLP_RELOC_SYMBOL_POS(LP_OBJ_NAME, SYM_OBJ_NAME, SYM_NAME, SYM_POS) \
> > + asm("\".klp.sym.rela." #LP_OBJ_NAME "." #SYM_OBJ_NAME "." #SYM_NAME "," #SYM_POS "\"")
> ^^^
> I think I found a potential bug, or at least compatiblity problem with
> including a comma "," character in this symbol format and older versions
> of the GNU assembler. The good news is that other delimiter characters
> like "." or "#" seem to work out fine.
Thank you for spotting this. I was using binutils 2.38, so I did not
even notice this problem. Unfortunately, I was not able to make it work
with "#" as a delimiter; only "." worked. Additionally, any type of
parenthesis apparently has some special purpose even in labels, so they
are also not an option.
> If you want to reproduce, you'll need a version of `as` like binutils
> 2.36.1 and try building the samples/livepatch/livepatch-extern-symbol.ko
> and you should get an error like:
>
> Assembler messages:
> Warning: missing closing '"'
> Warning: missing closing '"'
> Error: too many memory references for `movq'
>
>
> If you want to retrace my adventure, here are my steps:
>
> 1) Clone klp-convert-tree repo branch containing this patchset +
> Petr's review comments + a few helpful things for klp-convert
> development:
>
> $ git clone \
> --single-branch --branch=klp-convert-minimal-v1-review --depth=9 \
> https://github.com/joe-lawrence/klp-convert-tree.git
> [ ... snip ... ]
> $ cd klp-convert-tree
>
> 2) Override .cross-dev defaults:
>
> $ export BUILD_ARCHES=x86_64
> $ export COMPILER=gcc-11.1.0
> $ export URL=https://cdn.kernel.org/pub/tools/crosstool/files/bin/x86_64/
> $ export OUTDIR_PREFIX=$(pwd)/build
> $ export COMPILER_INSTALL_PATH=$(pwd)/cross-compiler
>
> 3) Setup x86_64 default .config (this will download and install the
> gcc-11.1.0 compiler from cdn.kernel.org):
>
> $ ./cross-dev make defconfig
>
> x86_64 : make defconfig ...
> Compiler will be installed in /root/klp-convert-tree/cross-compiler
> [ ... snip ... ]
>
> 4) Add kernel livepatching configuration options:
>
> $ ./cross-dev klp-config
>
> Configuring x86_64 ...
> [ ... snip ... ]
>
> $ grep LIVEPATCH "$OUTDIR_PREFIX"-x86_64/.config
> CONFIG_HAVE_LIVEPATCH=y
> CONFIG_LIVEPATCH=y
> CONFIG_SAMPLE_LIVEPATCH=m
>
> 5) Run the cross-compiler build until it hits a build error on
> livepatch-extern-symbol.ko:
>
> $ ./cross-dev make -j$(nproc)
> [ ... snip ... ]
> make: Target '__all' not remade because of errors.
> [ x86_64 : make -j48 = FAIL ]
>
> 6) With pre-requisites already built, retry the external symbol sample
> and add -save-temps to the KCFLAGS to keep the generated assembly file:
>
> $ KCFLAGS="-save-temps=obj" ./cross-dev make samples/livepatch/livepatch-extern-symbol.ko
> [ ... snip ... ]
> samples/livepatch/livepatch-extern-symbol.s: Assembler messages:
> samples/livepatch/livepatch-extern-symbol.s:103: Warning: missing closing '"'
> samples/livepatch/livepatch-extern-symbol.s:103: Warning: missing closing '"'
> samples/livepatch/livepatch-extern-symbol.s:103: Error: too many memory references for `movq'
> [ ... snip ... ]
>
> 7) Which line is that?
>
> $ awk 'NR==103' "$OUTDIR_PREFIX"-x86_64/samples/livepatch/livepatch-extern-symbol.s
> movq ".klp.sym.rela.vmlinux.vmlinux.saved_command_line,0"(%rip), %rdx
>
>
> You could alternatively poke at it through the compiler explorer service
> and toggle the source and binutils versions:
>
> (error) binutils 2.36.1 : https://godbolt.org/z/cGGs6rfWe
> (success) binutils 2.38 : https://godbolt.org/z/ffzza3vYd
Thank you for those detailed step-by-step instruction to reproduce it!
It helped me a lot to understand the problem.
Lukas