[PATCH v2 0/1] objtool/klp-diff: fix klp-build data sympos bug

Joe Lawrence <[email protected]> Fri, 24 Jul 2026 18:17:29 -0400
Newsgroups org.kernel.vger.live-patching
Message-ID <[email protected]>
Hi Josh,

Here's v2 addressing your feedback, rather than extending the
section-ordering HACK, this computes sympos during the checksum phase as
passes it through with that.

This adds --linked-obj to `objtool klp checksum`.  (Not sure if there is
any other way other than to pass that around.)  It opens the linked
binary (vmlinux or .ko), finds all duplicate local symbols matching the
.o being checksummed, sorts them to match the kallsyms order, and writes
the result into a .discard.sym_order section.  klp-diff then reads this
section instead of trying to determine the order from vmlinux.o's symbol
table.

Note that the sort order matches what the livepatch core sees when
loading the patch:

- For vmlinux, sort by address: this matches
  kallsyms_on_each_match_symbol() which loops through the symbols in
  address order, counting dupes until it hits the target sympos.

- For modules, sort by symtab index: this matches
  module_kallsyms_on_each_symbol() which iterates the .ko's symtab
  as-is.

With this, the section-ordering HACK is completely removed.

On the klp-build side, the script now stashes the original vmlinux/.ko
into 1-orig/ during copy_orig_objects before the patched rebuild
overwrites them, then passes --linked-obj pointing there during the
original checksum pass.  As mentioned earlier, I'm not sure if there is
a better way to do this.

Finally, this doesn't address the age-old, same source-file::symbol-name
problem.  That was astutely flagged by one of our internal AI reviews :)
But it's Friday and I don't think I need to solve that one today, too.


v1: https://lore.kernel.org/live-patching/ini7aoz6vvowmu5dvxoxpi6l4ednsov6omjcoffmuod3d2iaof@vflcrwqktqsl/T/#t
- Complete rewrite and change of approach to stashing sympos with the
  checksum [Josh]

Joe Lawrence (1):
  objtool/klp: compute sympos during checksum phase

 scripts/livepatch/klp-build                   |  36 +++-
 .../objtool/include/objtool/checksum_types.h  |   6 +
 tools/objtool/klp-checksum.c                  | 195 +++++++++++++++++-
 tools/objtool/klp-diff.c                      |  88 ++++----
 4 files changed, 267 insertions(+), 58 deletions(-)


Reproducer and analysis
=======================

Config
------
Basic config plus a DM module for testing:

  $ make defconfig
  $ ./scripts/config --file .config \
             --set-val CONFIG_FTRACE y \
             --set-val CONFIG_KALLSYMS_ALL y \
             --set-val CONFIG_FUNCTION_TRACER y \
             --set-val CONFIG_DYNAMIC_FTRACE y \
             --set-val CONFIG_DYNAMIC_DEBUG y \
             --set-val CONFIG_LIVEPATCH y \
             --set-val CONFIG_DM_THIN_PROVISIONING m
  $ make olddefconfig

Helper debug functions
----------------------
These help shorten some of the analysis below:

  # Symbol table order
  $ dump_sym() { objdump -t "$1" | awk -vSYM="$2" '/\.c$/{file=$NF}; $NF==SYM {print file": "$0}' | column -t | sort -k2; }

  # Kallsyms address-sorted order
  $ dump_sym_addr_sort() { dump_sym "$1" "$2" | sort -k2; }

  # Magic spell to coax pahole into pretty-printing the klp_func_ext struct
  $ dump_klp_func_ext() {
    echo '#include "include/linux/livepatch_external.h"
    struct klp_func_ext dummy;' | gcc -g -c -x c -include linux/types.h - -o /tmp/type.o
    objcopy -O binary --only-section=.init.klp_funcs "$1" /tmp/funcs.bin
    pahole -F dwarf -C klp_func_ext /tmp/type.o --prettify /tmp/funcs.bin 2>/dev/null
  }


Find initial sympos
-------------------
Find the symbol positions for pwq_cache symbols and unregister_vclock
functions:

  $ dump_sym_addr_sort vmlinux pwq_cache
  eventpoll.c:  ffffffff82b8ab40  l  O  .rodata  0000000000000008  pwq_cache        << sympos=1
  workqueue.c:  ffffffff83a99b00  l  O  .bss     0000000000000008  pwq_cache        << sympos=2

  $ dump_sym_addr_sort vmlinux unregister_vclock
  ptp_sysfs.c:  ffffffff81265a00  l  F  .text  0000000000000056  unregister_vclock  << sympos=1
  ptp_clock.c:  ffffffff81f01680  l  F  .text  0000000000000024  unregister_vclock  << sympos=2

Test 1: vmlinux sympos=1
------------------------

  $ cat test-sympos-vmlinux-1.patch
  diff --git a/drivers/ptp/ptp_sysfs.c b/drivers/ptp/ptp_sysfs.c
  index dc398c6b7528..b3267430f4c2 100644
  --- a/drivers/ptp/ptp_sysfs.c
  +++ b/drivers/ptp/ptp_sysfs.c
  @@ -177,6 +177,7 @@ static int unregister_vclock(struct device *dev, void *data)
   	ptp_vclock_unregister(vclock);
   	(*num)--;

  +	pr_info("klp-build test");
   	/* For break. Not error. */
   	if (*num == 0)
   		return -EINVAL;
  diff --git a/fs/eventpoll.c b/fs/eventpoll.c
  index 0e65c7431dfc..b99bfe757c50 100644
  --- a/fs/eventpoll.c
  +++ b/fs/eventpoll.c
  @@ -905,6 +905,7 @@ static void ep_unregister_pollwait(struct eventpoll *ep, struct epitem *epi)
   	while ((pwq = *p) != NULL) {
   		*p = pwq->next;
   		ep_remove_wait_queue(pwq);
  +		WARN_ON_ONCE(!pwq_cache);
   		kmem_cache_free(pwq_cache, pwq);
   	}
   }

  $ ./scripts/livepatch/klp-build -T test-sympos-vmlinux-1.patch

Check the pwq_cache symbol klp-relocation sympos:

  $ readelf --wide --relocs livepatch-test-sympos-vmlinux-1.ko | grep -w '.klp.sym.vmlinux.pwq_cache'
  0000000000000008  0000006100000001 R_X86_64_64            0000000000000000 .klp.sym.vmlinux.pwq_cache,1 - 4
  0000000000000069  0000006100000002 R_X86_64_PC32          0000000000000000 .klp.sym.vmlinux.pwq_cache,1 - 4

Check the unregister_vclock function sympos, first dump out the klp_funcs
relocations to figure out the entries in the array:

  $ readelf --wide --relocs livepatch-test-sympos-vmlinux-1.ko | awk '/.rela.init.klp_funcs/' RS="\n\n" ORS="\n\n"
  Relocation section '.rela.init.klp_funcs' at offset 0x1b0 contains 8 entries:
      Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
  0000000000000000  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + 9c
  0000000000000008  0000002b00000001 R_X86_64_64            00000000000000a0 ep_remove + 0
  0000000000000018  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + a6
  0000000000000020  0000002d00000001 R_X86_64_64            0000000000000140 ep_clear_and_put + 0
  0000000000000030  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + b7
  0000000000000038  0000002f00000001 R_X86_64_64            0000000000000210 unregister_vclock + 0
  0000000000000048  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + c9
  0000000000000050  0000006800000001 R_X86_64_64            0000000000000280 eventpoll_release_file + 0

and then pretty-print the klp_func_ext struct:

  $ dump_klp_func_ext livepatch-test-sympos-vmlinux-1.ko
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00, 
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << ep_remove
          .sympos = 0,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << ep_clear_and_put
          .sympos = 0,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << unregister_vclock
          .sympos = 1,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << eventpoll_release_file
          .sympos = 0,
  },


Test2: vmlinux sympos=2
-----------------------

  $ cat test-sympos-vmlinux-2.patch
  diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
  index d6f54ccaf93b..69c4d1f75e15 100644
  --- a/drivers/ptp/ptp_clock.c
  +++ b/drivers/ptp/ptp_clock.c
  @@ -490,6 +490,7 @@ static int unregister_vclock(struct device *dev, void *data)
   {
   	struct ptp_clock *ptp = dev_get_drvdata(dev);

  +	pr_info("klp-build test");
   	ptp_vclock_unregister(info_to_vclock(ptp->info));
   	return 0;
   }
  diff --git a/kernel/workqueue.c b/kernel/workqueue.c
  index 78068ae8f28a..2068c7aa8d6d 100644
  --- a/kernel/workqueue.c
  +++ b/kernel/workqueue.c
  @@ -5310,6 +5310,7 @@ static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
   	if (!pool)
   		return NULL;

  +	WARN_ON(!pwq_cache);
   	pwq = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
   	if (!pwq) {
   		put_unbound_pool(pool);

  $ make -j$(nproc) && ./scripts/livepatch/klp-build -T test-sympos-vmlinux-2.patch

Check the pwq_cache symbol klp-relocation sympos:

  $ readelf --wide --relocs livepatch-test-sympos-vmlinux-2.ko | grep -w '.klp.sym.vmlinux.pwq_cache'
  0000000000000038  0000005a00000001 R_X86_64_64            0000000000000000 .klp.sym.vmlinux.pwq_cache,2 - 4
  0000000000000167  0000005a00000002 R_X86_64_PC32          0000000000000000 .klp.sym.vmlinux.pwq_cache,2 - 4

Check the unregister_vclock function sympos, first dump out the klp_funcs
relocations to figure out the entries in the array:

  $ readelf --wide --relocs livepatch-test-sympos-vmlinux-2.ko | awk '/.rela.init.klp_funcs/' RS="\n\n" ORS="\n\n"
  Relocation section '.rela.init.klp_funcs' at offset 0x128 contains 4 entries:
      Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
  0000000000000000  0000000d00000001 R_X86_64_64            0000000000000000 .rodata + 74
  0000000000000008  0000002700000001 R_X86_64_64            0000000000000040 alloc_unbound_pwq + 0
  0000000000000018  0000000d00000001 R_X86_64_64            0000000000000000 .rodata + 86
  0000000000000020  0000002900000001 R_X86_64_64            0000000000000370 unregister_vclock + 0

and then pretty-print the klp_func_ext struct:

  $ dump_klp_func_ext livepatch-test-sympos-vmlinux-2.ko
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .sympos = 0,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .sympos = 2,
  },

Test3: Test1 + Test2 + module sympos=1
--------------------------------------

This last test combines the vmlinux test cases and then adds a module
case on top.

Dump the klp-relocations, we should see the vmlinux.pwq_cache,1 and ,2
as well as dm_bio_prison._cell_cache,1 for the module:

  $ readelf --wide --relocs livepatch-test-sympos-vmlinux-module.ko | \
    grep -w -e '.klp.sym.vmlinux.pwq_cache' -e '.klp.sym.dm_bio_prison._cell_cache'
  0000000000000008  0000007100000001 R_X86_64_64            0000000000000000 .klp.sym.dm_bio_prison._cell_cache,1 - 5
  0000000000000020  0000007100000001 R_X86_64_64            0000000000000000 .klp.sym.dm_bio_prison._cell_cache,1 - 4
  0000000000000068  0000009000000001 R_X86_64_64            0000000000000000 .klp.sym.vmlinux.pwq_cache,2 - 4
  00000000000001b8  0000007a00000001 R_X86_64_64            0000000000000000 .klp.sym.vmlinux.pwq_cache,1 - 4
  000000000000004c  0000007100000002 R_X86_64_PC32          0000000000000000 .klp.sym.dm_bio_prison._cell_cache,1 - 5
  00000000000000f1  0000007100000002 R_X86_64_PC32          0000000000000000 .klp.sym.dm_bio_prison._cell_cache,1 - 4
  0000000000000287  0000009000000002 R_X86_64_PC32          0000000000000000 .klp.sym.vmlinux.pwq_cache,2 - 4
  00000000000004c9  0000007a00000002 R_X86_64_PC32          0000000000000000 .klp.sym.vmlinux.pwq_cache,1 - 4

Finally dump the klp_func_ext struct to see that it's got the correct
sympos for both module unregister_vclock cases:

  $ readelf --wide --relocs livepatch-test-sympos-vmlinux-module.ko | awk '/.rela.init.klp_funcs/' RS="\n\n" ORS="\n\n"
  Relocation section '.rela.init.klp_funcs' at offset 0x250 contains 14 entries:
      Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
  0000000000000000  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + 62
  0000000000000008  0000005800000001 R_X86_64_64            0000000000000040 dm_bio_prison_create + 0
  0000000000000018  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + 78
  0000000000000020  0000002b00000001 R_X86_64_64            0000000000000160 alloc_unbound_pwq + 0
  0000000000000030  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + 8a
  0000000000000038  0000002f00000001 R_X86_64_64            0000000000000500 ep_remove + 0
  0000000000000048  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + 94
  0000000000000050  0000003100000001 R_X86_64_64            00000000000005a0 ep_clear_and_put + 0
  0000000000000060  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + a5
  0000000000000068  0000003300000001 R_X86_64_64            0000000000000670 unregister_vclock + 0
  0000000000000078  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + b7
  0000000000000080  0000003500000001 R_X86_64_64            00000000000006c0 unregister_vclock + 0
  0000000000000090  0000000e00000001 R_X86_64_64            0000000000000000 .rodata + c9
  0000000000000098  0000008900000001 R_X86_64_64            0000000000000730 eventpoll_release_file + 0
  
  $ dump_klp_func_ext livepatch-test-sympos-vmlinux-module.ko 
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << dm_bio_prison_create
          .sympos = 0,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << alloc_unbound_pwq
          .sympos = 0,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << ep_remove
          .sympos = 0,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << ep_clear_and_put
          .sympos = 0,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << unregister_vclock
          .sympos = 2,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << unregister_vclock
          .sympos = 1,
  },
  {
          .old_name = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,
          .new_func = 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00,    << eventpoll_release_file
          .sympos = 0,
  },


base-commit: 393988901632138e76d55518d5a0c6cc07cd3ad3
-- 
2.54.0