[PATCH v6 11/30] objtool: Trace instruction state changes during function validation

Alexandre Chartre posted 30 patches 2 months, 2 weeks ago
[PATCH v6 11/30] objtool: Trace instruction state changes during function validation
Posted by Alexandre Chartre 2 months, 2 weeks ago
During function validation, objtool maintains a per-instruction state,
in particular to track call frame information. When tracing validation,
print any instruction state changes.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
 tools/objtool/check.c                 |   8 +-
 tools/objtool/include/objtool/trace.h |  10 ++
 tools/objtool/trace.c                 | 132 ++++++++++++++++++++++++++
 3 files changed, 149 insertions(+), 1 deletion(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 2352b9668b126..e12dba144731f 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3729,6 +3729,8 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 			 struct instruction *prev_insn, struct instruction *next_insn,
 			 bool *dead_end)
 {
+	/* prev_state is not used if there is no disassembly support */
+	struct insn_state prev_state __maybe_unused;
 	struct alternative *alt;
 	u8 visited;
 	int ret;
@@ -3837,7 +3839,11 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 	if (skip_alt_group(insn))
 		return 0;
 
-	if (handle_insn_ops(insn, next_insn, statep))
+	prev_state = *statep;
+	ret = handle_insn_ops(insn, next_insn, statep);
+	TRACE_INSN_STATE(insn, &prev_state, statep);
+
+	if (ret)
 		return 1;
 
 	switch (insn->type) {
diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h
index 3f3c830ed114e..33fe9c6acb4fd 100644
--- a/tools/objtool/include/objtool/trace.h
+++ b/tools/objtool/include/objtool/trace.h
@@ -30,6 +30,12 @@ extern int trace_depth;
 	}							\
 })
 
+#define TRACE_INSN_STATE(insn, sprev, snext)			\
+({								\
+	if (trace)						\
+		trace_insn_state(insn, sprev, snext);		\
+})
+
 static inline void trace_enable(void)
 {
 	trace = true;
@@ -53,10 +59,14 @@ static inline void trace_depth_dec(void)
 		trace_depth--;
 }
 
+void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
+		      struct insn_state *snext);
+
 #else /* DISAS */
 
 #define TRACE(fmt, ...) ({})
 #define TRACE_INSN(insn, fmt, ...) ({})
+#define TRACE_INSN_STATE(insn, sprev, snext) ({})
 
 static inline void trace_enable(void) {}
 static inline void trace_disable(void) {}
diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c
index 134cc33ffe970..12bbad09d9c02 100644
--- a/tools/objtool/trace.c
+++ b/tools/objtool/trace.c
@@ -7,3 +7,135 @@
 
 bool trace;
 int trace_depth;
+
+/*
+ * Macros to trace CFI state attributes changes.
+ */
+
+#define TRACE_CFI_ATTR(attr, prev, next, fmt, ...)		\
+({								\
+	if ((prev)->attr != (next)->attr)			\
+		TRACE("%s=" fmt " ", #attr, __VA_ARGS__);	\
+})
+
+#define TRACE_CFI_ATTR_BOOL(attr, prev, next)			\
+	TRACE_CFI_ATTR(attr, prev, next,			\
+		       "%s", (next)->attr ? "true" : "false")
+
+#define TRACE_CFI_ATTR_NUM(attr, prev, next, fmt)		\
+	TRACE_CFI_ATTR(attr, prev, next, fmt, (next)->attr)
+
+#define CFI_REG_NAME_MAXLEN   16
+
+/*
+ * Return the name of a register. Note that the same static buffer
+ * is returned if the name is dynamically generated.
+ */
+static const char *cfi_reg_name(unsigned int reg)
+{
+	static char rname_buffer[CFI_REG_NAME_MAXLEN];
+
+	switch (reg) {
+	case CFI_UNDEFINED:
+		return "<undefined>";
+	case CFI_CFA:
+		return "cfa";
+	case CFI_SP_INDIRECT:
+		return "(sp)";
+	case CFI_BP_INDIRECT:
+		return "(bp)";
+	}
+
+	if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == -1)
+		return "<error>";
+
+	return (const char *)rname_buffer;
+}
+
+/*
+ * Functions and macros to trace CFI registers changes.
+ */
+
+static void trace_cfi_reg(const char *prefix, int reg, const char *fmt,
+			  int base_prev, int offset_prev,
+			  int base_next, int offset_next)
+{
+	char *rname;
+
+	if (base_prev == base_next && offset_prev == offset_next)
+		return;
+
+	if (prefix)
+		TRACE("%s:", prefix);
+
+	if (base_next == CFI_UNDEFINED) {
+		TRACE("%1$s=<undef> ", cfi_reg_name(reg));
+	} else {
+		rname = strdup(cfi_reg_name(reg));
+		TRACE(fmt, rname, cfi_reg_name(base_next), offset_next);
+		free(rname);
+	}
+}
+
+static void trace_cfi_reg_val(const char *prefix, int reg,
+			      int base_prev, int offset_prev,
+			      int base_next, int offset_next)
+{
+	trace_cfi_reg(prefix, reg, "%1$s=%2$s%3$+d ",
+		      base_prev, offset_prev, base_next, offset_next);
+}
+
+static void trace_cfi_reg_ref(const char *prefix, int reg,
+			      int base_prev, int offset_prev,
+			      int base_next, int offset_next)
+{
+	trace_cfi_reg(prefix, reg, "%1$s=(%2$s%3$+d) ",
+		      base_prev, offset_prev, base_next, offset_next);
+}
+
+#define TRACE_CFI_REG_VAL(reg, prev, next)				\
+	trace_cfi_reg_val(NULL, reg, prev.base, prev.offset,		\
+			  next.base, next.offset)
+
+#define TRACE_CFI_REG_REF(reg, prev, next)				\
+	trace_cfi_reg_ref(NULL, reg, prev.base, prev.offset,		\
+			  next.base, next.offset)
+
+void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
+		      struct insn_state *snext)
+{
+	struct cfi_state *cprev, *cnext;
+	int i;
+
+	if (!memcmp(sprev, snext, sizeof(struct insn_state)))
+		return;
+
+	cprev = &sprev->cfi;
+	cnext = &snext->cfi;
+
+	disas_print_insn(stderr, objtool_disas_ctx, insn,
+			 trace_depth - 1, "state: ");
+
+	/* print registers changes */
+	TRACE_CFI_REG_VAL(CFI_CFA, cprev->cfa, cnext->cfa);
+	for (i = 0; i < CFI_NUM_REGS; i++) {
+		TRACE_CFI_REG_VAL(i, cprev->vals[i], cnext->vals[i]);
+		TRACE_CFI_REG_REF(i, cprev->regs[i], cnext->regs[i]);
+	}
+
+	/* print attributes changes */
+	TRACE_CFI_ATTR_NUM(stack_size, cprev, cnext, "%d");
+	TRACE_CFI_ATTR_BOOL(drap, cprev, cnext);
+	if (cnext->drap) {
+		trace_cfi_reg_val("drap", cnext->drap_reg,
+				  cprev->drap_reg, cprev->drap_offset,
+				  cnext->drap_reg, cnext->drap_offset);
+	}
+	TRACE_CFI_ATTR_BOOL(bp_scratch, cprev, cnext);
+	TRACE_CFI_ATTR_NUM(instr, sprev, snext, "%d");
+	TRACE_CFI_ATTR_NUM(uaccess_stack, sprev, snext, "%u");
+
+	TRACE("\n");
+
+	insn->trace = 1;
+}
-- 
2.43.5
Re: [PATCH v6 11/30] objtool: Trace instruction state changes during function validation
Posted by Nathan Chancellor 2 months, 1 week ago
Hi Alexandre,

On Fri, Nov 21, 2025 at 10:53:21AM +0100, Alexandre Chartre wrote:
> During function validation, objtool maintains a per-instruction state,
> in particular to track call frame information. When tracing validation,
> print any instruction state changes.
> 
> Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>

I am seeing a segfault after this change in -next as commit fcb268b47a2f
("objtool: Trace instruction state changes during function validation")
when building allmodconfig with clang 21.1.6 [1] (I did not check
earlier versions).

  $ clang --version | head -1
  ClangBuiltLinux clang version 21.1.6 (https://github.com/llvm/llvm-project.git a832a5222e489298337fbb5876f8dcaf072c5cca)

  $ make -skj"$(nproc)" ARCH=x86_64 LLVM=1 clean allmodconfig drivers/scsi/qla2xxx/qla2xxx.o
  make[7]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
  ...

  $ ld.lld -m elf_x86_64 --fatal-warnings -z noexecstack -r -o drivers/scsi/qla2xxx/qla2xxx.o @drivers/scsi/qla2xxx/qla2xxx.mod

  $ tools/objtool/objtool --hacks=jump_label --hacks=noinstr --hacks=skylake --ibt --cfi --mcount --mnop --orc --retpoline --rethunk --sls --static-call --uaccess --no-unreachable --link --module drivers/scsi/qla2xxx/qla2xxx.o
  fish: Job 1, 'tools/objtool/objtool --hacks=j…' terminated by signal SIGSEGV (Address boundary error)

If there is any other information I can provide or patches I can test, I
am more than happy to do so.

[1]: https://mirrors.edge.kernel.org/pub/tools/llvm/files/llvm-21.1.6-x86_64.tar.xz

Cheers,
Nathan

# bad: [95cb2fd6ce0ad61af54191fe5ef271d7177f9c3a] Add linux-next specific files for 20251201
# good: [e69c7c175115c51c7f95394fc55425a395b3af59] Merge tag 'timers_urgent_for_v6.18_rc8' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
git bisect start '95cb2fd6ce0ad61af54191fe5ef271d7177f9c3a' 'e69c7c175115c51c7f95394fc55425a395b3af59'
# good: [87d5c4addc7e535618586e7205191a7f402288ba] Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/herbert/cryptodev-2.6.git
git bisect good 87d5c4addc7e535618586e7205191a7f402288ba
# good: [a4ad48eac682ccdc21e2f16b8f27abbf615d8d3d] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git
git bisect good a4ad48eac682ccdc21e2f16b8f27abbf615d8d3d
# bad: [b99f4ac0a6c7ccf37be14f5ef61b160b1c8a74b0] Merge branch 'driver-core-next' of https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git
git bisect bad b99f4ac0a6c7ccf37be14f5ef61b160b1c8a74b0
# bad: [24cefd05bbf969c95fff3733da174e8a352c1cb2] Merge branch 'master' of https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git
git bisect bad 24cefd05bbf969c95fff3733da174e8a352c1cb2
# bad: [4fa1e8d3340bc660d72a21fc8d4c566045e488fc] Merge branch into tip/master: 'timers/clocksource'
git bisect bad 4fa1e8d3340bc660d72a21fc8d4c566045e488fc
# good: [51446852de95594c02d6b3b700e15b7c886534d6] Merge branch into tip/master: 'core/bugs'
git bisect good 51446852de95594c02d6b3b700e15b7c886534d6
# good: [b032713af9475274762fa664ca2705372b414215] Merge branch into tip/master: 'locking/core'
git bisect good b032713af9475274762fa664ca2705372b414215
# good: [9929dffce5ed7e2988e0274f4db98035508b16d9] perf/x86/intel: Fix and clean up intel_pmu_drain_arch_pebs() type use
git bisect good 9929dffce5ed7e2988e0274f4db98035508b16d9
# bad: [59bfa6408214b6533d8691715cf5459e89b45b89] objtool: Build with disassembly can fail when including bdf.h
git bisect bad 59bfa6408214b6533d8691715cf5459e89b45b89
# bad: [350c7ab8577a32c101a097f4c072220d9ce64f3b] objtool: Improve tracing of alternative instructions
git bisect bad 350c7ab8577a32c101a097f4c072220d9ce64f3b
# good: [0bb080ba6469a573bc85122153d931334d10a173] objtool: Disassemble instruction on warning or backtrace
git bisect good 0bb080ba6469a573bc85122153d931334d10a173
# bad: [fcb268b47a2f4a497fdb40ef24bb9e06488b7213] objtool: Trace instruction state changes during function validation
git bisect bad fcb268b47a2f4a497fdb40ef24bb9e06488b7213
# good: [de0248fbbf999d0fd3ca2aa5ba515ab78703d129] objtool: Record symbol name max length
git bisect good de0248fbbf999d0fd3ca2aa5ba515ab78703d129
# good: [70589843b36fee0c6e73632469da4e5fd11f0968] objtool: Add option to trace function validation
git bisect good 70589843b36fee0c6e73632469da4e5fd11f0968
# first bad commit: [fcb268b47a2f4a497fdb40ef24bb9e06488b7213] objtool: Trace instruction state changes during function validation
Re: [PATCH v6 11/30] objtool: Trace instruction state changes during function validation
Posted by Josh Poimboeuf 2 months, 1 week ago
On Mon, Dec 01, 2025 at 01:23:29PM -0700, Nathan Chancellor wrote:
> Hi Alexandre,
> 
> On Fri, Nov 21, 2025 at 10:53:21AM +0100, Alexandre Chartre wrote:
> > During function validation, objtool maintains a per-instruction state,
> > in particular to track call frame information. When tracing validation,
> > print any instruction state changes.
> > 
> > Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
> 
> I am seeing a segfault after this change in -next as commit fcb268b47a2f
> ("objtool: Trace instruction state changes during function validation")
> when building allmodconfig with clang 21.1.6 [1] (I did not check
> earlier versions).
> 
>   $ clang --version | head -1
>   ClangBuiltLinux clang version 21.1.6 (https://github.com/llvm/llvm-project.git a832a5222e489298337fbb5876f8dcaf072c5cca)
> 
>   $ make -skj"$(nproc)" ARCH=x86_64 LLVM=1 clean allmodconfig drivers/scsi/qla2xxx/qla2xxx.o
>   make[7]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
>   ...
> 
>   $ ld.lld -m elf_x86_64 --fatal-warnings -z noexecstack -r -o drivers/scsi/qla2xxx/qla2xxx.o @drivers/scsi/qla2xxx/qla2xxx.mod
> 
>   $ tools/objtool/objtool --hacks=jump_label --hacks=noinstr --hacks=skylake --ibt --cfi --mcount --mnop --orc --retpoline --rethunk --sls --static-call --uaccess --no-unreachable --link --module drivers/scsi/qla2xxx/qla2xxx.o
>   fish: Job 1, 'tools/objtool/objtool --hacks=j…' terminated by signal SIGSEGV (Address boundary error)
> 
> If there is any other information I can provide or patches I can test, I
> am more than happy to do so.
> 
> [1]: https://mirrors.edge.kernel.org/pub/tools/llvm/files/llvm-21.1.6-x86_64.tar.xz

Objtool is overflowing the stack due to the large number of jumps it has
to follow in that code, thanks to kasan.  The above mentioned patch

  fcb268b47a2f ("objtool: Trace instruction state changes during function validation")

added a 328-byte struct to the stack in validate_insn() which
drastically increased the amount of stack size needed.

I suppose we could hack a fix by making it a static local variable, like
below.

Or, objtool could setrlimit(RLIMIT_STACK) to 16MB?

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index a02f8db75827..206b8589d82b 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3678,7 +3678,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 			 bool *dead_end)
 {
 	/* prev_state is not used if there is no disassembly support */
-	struct insn_state prev_state __maybe_unused;
+	static struct insn_state prev_state __maybe_unused;
 	struct alternative *alt;
 	u8 visited;
 	int ret;
Re: [PATCH v6 11/30] objtool: Trace instruction state changes during function validation
Posted by Alexandre Chartre 2 months ago
On 12/2/25 02:30, Josh Poimboeuf wrote:
> On Mon, Dec 01, 2025 at 01:23:29PM -0700, Nathan Chancellor wrote:
>> Hi Alexandre,
>>
>> On Fri, Nov 21, 2025 at 10:53:21AM +0100, Alexandre Chartre wrote:
>>> During function validation, objtool maintains a per-instruction state,
>>> in particular to track call frame information. When tracing validation,
>>> print any instruction state changes.
>>>
>>> Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
>>
>> I am seeing a segfault after this change in -next as commit fcb268b47a2f
>> ("objtool: Trace instruction state changes during function validation")
>> when building allmodconfig with clang 21.1.6 [1] (I did not check
>> earlier versions).
>>
>>    $ clang --version | head -1
>>    ClangBuiltLinux clang version 21.1.6 (https://github.com/llvm/llvm-project.git a832a5222e489298337fbb5876f8dcaf072c5cca)
>>
>>    $ make -skj"$(nproc)" ARCH=x86_64 LLVM=1 clean allmodconfig drivers/scsi/qla2xxx/qla2xxx.o
>>    make[7]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
>>    ...
>>
>>    $ ld.lld -m elf_x86_64 --fatal-warnings -z noexecstack -r -o drivers/scsi/qla2xxx/qla2xxx.o @drivers/scsi/qla2xxx/qla2xxx.mod
>>
>>    $ tools/objtool/objtool --hacks=jump_label --hacks=noinstr --hacks=skylake --ibt --cfi --mcount --mnop --orc --retpoline --rethunk --sls --static-call --uaccess --no-unreachable --link --module drivers/scsi/qla2xxx/qla2xxx.o
>>    fish: Job 1, 'tools/objtool/objtool --hacks=j…' terminated by signal SIGSEGV (Address boundary error)
>>
>> If there is any other information I can provide or patches I can test, I
>> am more than happy to do so.
>>
>> [1]: https://mirrors.edge.kernel.org/pub/tools/llvm/files/llvm-21.1.6-x86_64.tar.xz
> 
> Objtool is overflowing the stack due to the large number of jumps it has
> to follow in that code, thanks to kasan.  The above mentioned patch
> 
>    fcb268b47a2f ("objtool: Trace instruction state changes during function validation")
> 
> added a 328-byte struct to the stack in validate_insn() which
> drastically increased the amount of stack size needed.
> 
> I suppose we could hack a fix by making it a static local variable, like
> below.
> 
> Or, objtool could setrlimit(RLIMIT_STACK) to 16MB?
> 
> diff --git a/tools/objtool/check.c b/tools/objtool/check.c
> index a02f8db75827..206b8589d82b 100644
> --- a/tools/objtool/check.c
> +++ b/tools/objtool/check.c
> @@ -3678,7 +3678,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
>   			 bool *dead_end)
>   {
>   	/* prev_state is not used if there is no disassembly support */
> -	struct insn_state prev_state __maybe_unused;
> +	static struct insn_state prev_state __maybe_unused;
>   	struct alternative *alt;
>   	u8 visited;
>   	int ret;

static looks good enough to me.

Reviewed-by: Alexandre Chartre <alexandre.chartre@oracle.com>

Thanks,

alex.

Re: [PATCH v6 11/30] objtool: Trace instruction state changes during function validation
Posted by Josh Poimboeuf 2 months ago
On Tue, Dec 02, 2025 at 09:34:20AM +0100, Alexandre Chartre wrote:
> 
> On 12/2/25 02:30, Josh Poimboeuf wrote:
> > On Mon, Dec 01, 2025 at 01:23:29PM -0700, Nathan Chancellor wrote:
> > > Hi Alexandre,
> > > 
> > > On Fri, Nov 21, 2025 at 10:53:21AM +0100, Alexandre Chartre wrote:
> > > > During function validation, objtool maintains a per-instruction state,
> > > > in particular to track call frame information. When tracing validation,
> > > > print any instruction state changes.
> > > > 
> > > > Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
> > > 
> > > I am seeing a segfault after this change in -next as commit fcb268b47a2f
> > > ("objtool: Trace instruction state changes during function validation")
> > > when building allmodconfig with clang 21.1.6 [1] (I did not check
> > > earlier versions).
> > > 
> > >    $ clang --version | head -1
> > >    ClangBuiltLinux clang version 21.1.6 (https://github.com/llvm/llvm-project.git a832a5222e489298337fbb5876f8dcaf072c5cca)
> > > 
> > >    $ make -skj"$(nproc)" ARCH=x86_64 LLVM=1 clean allmodconfig drivers/scsi/qla2xxx/qla2xxx.o
> > >    make[7]: *** [scripts/Makefile.build:503: drivers/scsi/qla2xxx/qla2xxx.o] Error 139
> > >    ...
> > > 
> > >    $ ld.lld -m elf_x86_64 --fatal-warnings -z noexecstack -r -o drivers/scsi/qla2xxx/qla2xxx.o @drivers/scsi/qla2xxx/qla2xxx.mod
> > > 
> > >    $ tools/objtool/objtool --hacks=jump_label --hacks=noinstr --hacks=skylake --ibt --cfi --mcount --mnop --orc --retpoline --rethunk --sls --static-call --uaccess --no-unreachable --link --module drivers/scsi/qla2xxx/qla2xxx.o
> > >    fish: Job 1, 'tools/objtool/objtool --hacks=j…' terminated by signal SIGSEGV (Address boundary error)
> > > 
> > > If there is any other information I can provide or patches I can test, I
> > > am more than happy to do so.
> > > 
> > > [1]: https://mirrors.edge.kernel.org/pub/tools/llvm/files/llvm-21.1.6-x86_64.tar.xz
> > 
> > Objtool is overflowing the stack due to the large number of jumps it has
> > to follow in that code, thanks to kasan.  The above mentioned patch
> > 
> >    fcb268b47a2f ("objtool: Trace instruction state changes during function validation")
> > 
> > added a 328-byte struct to the stack in validate_insn() which
> > drastically increased the amount of stack size needed.
> > 
> > I suppose we could hack a fix by making it a static local variable, like
> > below.
> > 
> > Or, objtool could setrlimit(RLIMIT_STACK) to 16MB?
> > 
> > diff --git a/tools/objtool/check.c b/tools/objtool/check.c
> > index a02f8db75827..206b8589d82b 100644
> > --- a/tools/objtool/check.c
> > +++ b/tools/objtool/check.c
> > @@ -3678,7 +3678,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
> >   			 bool *dead_end)
> >   {
> >   	/* prev_state is not used if there is no disassembly support */
> > -	struct insn_state prev_state __maybe_unused;
> > +	static struct insn_state prev_state __maybe_unused;
> >   	struct alternative *alt;
> >   	u8 visited;
> >   	int ret;
> 
> static looks good enough to me.
> 
> Reviewed-by: Alexandre Chartre <alexandre.chartre@oracle.com>
> 
> Thanks,

The static local bothered me, I went with a different approach to move
that variable (and its usage) to handle_insn_ops().  Will post shortly.

-- 
Josh
Re: [PATCH v6 11/30] objtool: Trace instruction state changes during function validation
Posted by David Laight 2 months ago
On Tue, 2 Dec 2025 08:13:21 -0800
Josh Poimboeuf <jpoimboe@kernel.org> wrote:

...
> The static local bothered me, I went with a different approach to move
> that variable (and its usage) to handle_insn_ops().  Will post shortly.
> 

Does look a little wrong inside a recursively called function.

How does the code get on for 32bit builds?
I'd guess they have a lot less stack available?

	David
[tip: objtool/core] objtool: Trace instruction state changes during function validation
Posted by tip-bot2 for Alexandre Chartre 2 months, 2 weeks ago
The following commit has been merged into the objtool/core branch of tip:

Commit-ID:     fcb268b47a2f4a497fdb40ef24bb9e06488b7213
Gitweb:        https://git.kernel.org/tip/fcb268b47a2f4a497fdb40ef24bb9e06488b7213
Author:        Alexandre Chartre <alexandre.chartre@oracle.com>
AuthorDate:    Fri, 21 Nov 2025 10:53:21 +01:00
Committer:     Peter Zijlstra <peterz@infradead.org>
CommitterDate: Fri, 21 Nov 2025 15:30:10 +01:00

objtool: Trace instruction state changes during function validation

During function validation, objtool maintains a per-instruction state,
in particular to track call frame information. When tracing validation,
print any instruction state changes.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
Link: https://patch.msgid.link/20251121095340.464045-12-alexandre.chartre@oracle.com
---
 tools/objtool/check.c                 |   8 +-
 tools/objtool/include/objtool/trace.h |  10 ++-
 tools/objtool/trace.c                 | 132 +++++++++++++++++++++++++-
 3 files changed, 149 insertions(+), 1 deletion(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 409dec9..a02f8db 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3677,6 +3677,8 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 			 struct instruction *prev_insn, struct instruction *next_insn,
 			 bool *dead_end)
 {
+	/* prev_state is not used if there is no disassembly support */
+	struct insn_state prev_state __maybe_unused;
 	struct alternative *alt;
 	u8 visited;
 	int ret;
@@ -3785,7 +3787,11 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 	if (skip_alt_group(insn))
 		return 0;
 
-	if (handle_insn_ops(insn, next_insn, statep))
+	prev_state = *statep;
+	ret = handle_insn_ops(insn, next_insn, statep);
+	TRACE_INSN_STATE(insn, &prev_state, statep);
+
+	if (ret)
 		return 1;
 
 	switch (insn->type) {
diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h
index 3f3c830..33fe9c6 100644
--- a/tools/objtool/include/objtool/trace.h
+++ b/tools/objtool/include/objtool/trace.h
@@ -30,6 +30,12 @@ extern int trace_depth;
 	}							\
 })
 
+#define TRACE_INSN_STATE(insn, sprev, snext)			\
+({								\
+	if (trace)						\
+		trace_insn_state(insn, sprev, snext);		\
+})
+
 static inline void trace_enable(void)
 {
 	trace = true;
@@ -53,10 +59,14 @@ static inline void trace_depth_dec(void)
 		trace_depth--;
 }
 
+void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
+		      struct insn_state *snext);
+
 #else /* DISAS */
 
 #define TRACE(fmt, ...) ({})
 #define TRACE_INSN(insn, fmt, ...) ({})
+#define TRACE_INSN_STATE(insn, sprev, snext) ({})
 
 static inline void trace_enable(void) {}
 static inline void trace_disable(void) {}
diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c
index 134cc33..12bbad0 100644
--- a/tools/objtool/trace.c
+++ b/tools/objtool/trace.c
@@ -7,3 +7,135 @@
 
 bool trace;
 int trace_depth;
+
+/*
+ * Macros to trace CFI state attributes changes.
+ */
+
+#define TRACE_CFI_ATTR(attr, prev, next, fmt, ...)		\
+({								\
+	if ((prev)->attr != (next)->attr)			\
+		TRACE("%s=" fmt " ", #attr, __VA_ARGS__);	\
+})
+
+#define TRACE_CFI_ATTR_BOOL(attr, prev, next)			\
+	TRACE_CFI_ATTR(attr, prev, next,			\
+		       "%s", (next)->attr ? "true" : "false")
+
+#define TRACE_CFI_ATTR_NUM(attr, prev, next, fmt)		\
+	TRACE_CFI_ATTR(attr, prev, next, fmt, (next)->attr)
+
+#define CFI_REG_NAME_MAXLEN   16
+
+/*
+ * Return the name of a register. Note that the same static buffer
+ * is returned if the name is dynamically generated.
+ */
+static const char *cfi_reg_name(unsigned int reg)
+{
+	static char rname_buffer[CFI_REG_NAME_MAXLEN];
+
+	switch (reg) {
+	case CFI_UNDEFINED:
+		return "<undefined>";
+	case CFI_CFA:
+		return "cfa";
+	case CFI_SP_INDIRECT:
+		return "(sp)";
+	case CFI_BP_INDIRECT:
+		return "(bp)";
+	}
+
+	if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == -1)
+		return "<error>";
+
+	return (const char *)rname_buffer;
+}
+
+/*
+ * Functions and macros to trace CFI registers changes.
+ */
+
+static void trace_cfi_reg(const char *prefix, int reg, const char *fmt,
+			  int base_prev, int offset_prev,
+			  int base_next, int offset_next)
+{
+	char *rname;
+
+	if (base_prev == base_next && offset_prev == offset_next)
+		return;
+
+	if (prefix)
+		TRACE("%s:", prefix);
+
+	if (base_next == CFI_UNDEFINED) {
+		TRACE("%1$s=<undef> ", cfi_reg_name(reg));
+	} else {
+		rname = strdup(cfi_reg_name(reg));
+		TRACE(fmt, rname, cfi_reg_name(base_next), offset_next);
+		free(rname);
+	}
+}
+
+static void trace_cfi_reg_val(const char *prefix, int reg,
+			      int base_prev, int offset_prev,
+			      int base_next, int offset_next)
+{
+	trace_cfi_reg(prefix, reg, "%1$s=%2$s%3$+d ",
+		      base_prev, offset_prev, base_next, offset_next);
+}
+
+static void trace_cfi_reg_ref(const char *prefix, int reg,
+			      int base_prev, int offset_prev,
+			      int base_next, int offset_next)
+{
+	trace_cfi_reg(prefix, reg, "%1$s=(%2$s%3$+d) ",
+		      base_prev, offset_prev, base_next, offset_next);
+}
+
+#define TRACE_CFI_REG_VAL(reg, prev, next)				\
+	trace_cfi_reg_val(NULL, reg, prev.base, prev.offset,		\
+			  next.base, next.offset)
+
+#define TRACE_CFI_REG_REF(reg, prev, next)				\
+	trace_cfi_reg_ref(NULL, reg, prev.base, prev.offset,		\
+			  next.base, next.offset)
+
+void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
+		      struct insn_state *snext)
+{
+	struct cfi_state *cprev, *cnext;
+	int i;
+
+	if (!memcmp(sprev, snext, sizeof(struct insn_state)))
+		return;
+
+	cprev = &sprev->cfi;
+	cnext = &snext->cfi;
+
+	disas_print_insn(stderr, objtool_disas_ctx, insn,
+			 trace_depth - 1, "state: ");
+
+	/* print registers changes */
+	TRACE_CFI_REG_VAL(CFI_CFA, cprev->cfa, cnext->cfa);
+	for (i = 0; i < CFI_NUM_REGS; i++) {
+		TRACE_CFI_REG_VAL(i, cprev->vals[i], cnext->vals[i]);
+		TRACE_CFI_REG_REF(i, cprev->regs[i], cnext->regs[i]);
+	}
+
+	/* print attributes changes */
+	TRACE_CFI_ATTR_NUM(stack_size, cprev, cnext, "%d");
+	TRACE_CFI_ATTR_BOOL(drap, cprev, cnext);
+	if (cnext->drap) {
+		trace_cfi_reg_val("drap", cnext->drap_reg,
+				  cprev->drap_reg, cprev->drap_offset,
+				  cnext->drap_reg, cnext->drap_offset);
+	}
+	TRACE_CFI_ATTR_BOOL(bp_scratch, cprev, cnext);
+	TRACE_CFI_ATTR_NUM(instr, sprev, snext, "%d");
+	TRACE_CFI_ATTR_NUM(uaccess_stack, sprev, snext, "%u");
+
+	TRACE("\n");
+
+	insn->trace = 1;
+}