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
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
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;
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.
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
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
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;
+}
© 2016 - 2026 Red Hat, Inc.