Re: [PATCH v2 17/21] objtool: decode instructions and resolve branch targets in parallel

From: Kees Cook

Date: Mon Sep 14 2026 - 14:33:55 EST


On Mon, Sep 14, 2026 at 10:22:16AM +0100, Lorenzo Stoakes (ARM) wrote:
> Threads are only created for objects with 8 MiB or more of text, meaning
> that runs involving smaller objects remain unaffected.
> [...]
> objtool on vmlinux.o is on the serial tail of every build that links
> vmlinux, no-op builds are unchanged.

This isn't limited to vmlinux.o, though? With CONFIG_X86_KERNEL_IBT
(default y on x86_64), delay-objtool is set in scripts/Makefile.lib, and
objtool then runs on every multi-object module, in the middle of the
parallel part of the build. In an x86_64 allmodconfig build here,
amdgpu.o has 46.2 MiB of executable text and i915.o has 10.2 MiB, so
each of those objtool runs gets 16 threads on top of the -jN jobs that
are already running.

Any parallelism added need to be handled by the make jobserver, not
hard-coded anyway.

> [...]
> +/* Only an object this large, e.g. vmlinux.o, is decoded on several threads. */
> +#define DECODE_THREADED_MIN_TEXT SZ_8M
> +/* Only decoding and the branch passes are threaded, so more gains nothing. */
> +#define DECODE_MAX_THREADS 16
> +#define DECODE_RANGES_PER_THREAD 4

I just don't think a size-based approach is going to work. And any max
parallelism needs a rationale.

> +static unsigned int decode_threads(unsigned long text_size)
> +{
> + const long nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
> +
> + if (text_size < DECODE_THREADED_MIN_TEXT || nr_cpus < 2)
> + return 1;
> +
> + return min_t(unsigned int, nr_cpus, DECODE_MAX_THREADS);
> +}

No, anything using _SC_NPROCESSORS_ONLN internally is wrong, whether its
pigz or objtool.

> [...]
> +static int add_jump_destinations(struct objtool_file *file)
> +{
> + return run_insn_ranges(file, add_jump_destinations_range);
> +}

I haven't examined this myself yet, but my LLM doesn't like this,
saying:

For an internal sibling call, add_jump_destination() calls
add_call_dest(file, insn, dest_sym, true), and add_call_dest() calls
annotate_call_site(), which for --hack-noinstr profiling calls, and for
__fentry__ calls with --mcount --mnop, does set_reloc_type() and
elf_write_insn() on file->elf. The per-range shadow gives each thread
its own lists, but the struct elf is shared, and both helpers write to
the section and mark it changed without any locking.
add_call_destinations() is kept serial because "annotating a call site
rewrites instructions the dead end walks read", so it looks like this
path needs the same care: a lock, or deferring those writes until the
threads have been joined. This is from reading the code; I haven't hit
a failure. Was the thread sanitizer run mentioned in the cover letter
done with those objtool options enabled?

So, take it with a grain of salt. ;)

-Kees

--
Kees Cook