Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
arch/x86/lib/cpu-feature-names.c
arch/x86/lib/inat-tables.c
/objtool
feature
FEATURE-DUMP.objtool
fixdep
libsubcmd/
+37
View File
@@ -0,0 +1,37 @@
objtool-y += arch/$(SRCARCH)/
objtool-y += weak.o
objtool-y += check.o
objtool-y += special.o
objtool-y += builtin-check.o
objtool-y += elf.o
objtool-y += objtool.o
objtool-$(BUILD_DISAS) += disas.o
objtool-$(BUILD_DISAS) += trace.o
objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o
objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o klp-post-link.o
objtool-y += libstring.o
objtool-y += libctype.o
objtool-y += str_error_r.o
objtool-y += librbtree.o
objtool-y += signal.o
$(OUTPUT)libstring.o: ../lib/string.c FORCE
$(call rule_mkdir)
$(call if_changed_dep,cc_o_c)
$(OUTPUT)libctype.o: ../lib/ctype.c FORCE
$(call rule_mkdir)
$(call if_changed_dep,cc_o_c)
$(OUTPUT)str_error_r.o: ../lib/str_error_r.c FORCE
$(call rule_mkdir)
$(call if_changed_dep,cc_o_c)
$(OUTPUT)librbtree.o: ../lib/rbtree.c FORCE
$(call rule_mkdir)
$(call if_changed_dep,cc_o_c)
@@ -0,0 +1,488 @@
Objtool
=======
The kernel CONFIG_OBJTOOL option enables a host tool named 'objtool'
which runs at compile time. It can do various validations and
transformations on .o files.
Objtool has become an integral part of the x86-64 kernel toolchain. The
kernel depends on it for a variety of security and performance features
(and other types of features as well).
Features
--------
Objtool has the following features:
- Stack unwinding metadata validation -- useful for helping to ensure
stack traces are reliable for live patching
- ORC unwinder metadata generation -- a faster and more precise
alternative to frame pointer based unwinding
- Retpoline validation -- ensures that all indirect calls go through
retpoline thunks, for Spectre v2 mitigations
- Retpoline call site annotation -- annotates all retpoline thunk call
sites, enabling the kernel to patch them inline, to prevent "thunk
funneling" for both security and performance reasons
- Return thunk validation -- validates return thunks are used for
certain CPU mitigations including Retbleed and SRSO
- Return thunk annotation -- annotates all return thunk sites so kernel
can patch them inline, depending on enabled mitigations
- Return thunk untraining validation -- validate that all entry paths
untrain a "safe return" before the first return (or call)
- Non-instrumentation validation -- validates non-instrumentable
("noinstr") code rules, preventing instrumentation in low-level C
entry code
- Static call annotation -- annotates static call sites, enabling the
kernel to implement inline static calls, a faster alternative to some
indirect branches
- Uaccess validation -- validates uaccess rules for a proper
implementation of Supervisor Mode Access Protection (SMAP)
- Straight Line Speculation validation -- validates certain SLS
mitigations
- Indirect Branch Tracking validation -- validates Intel CET IBT rules
to ensure that all functions referenced by function pointers have
corresponding ENDBR instructions
- Indirect Branch Tracking annotation -- annotates unused ENDBR
instruction sites, enabling the kernel to "seal" them (replace them
with NOPs) to further harden IBT
- Function entry annotation -- annotates function entries, enabling
kernel function tracing
- Function preamble (prefix) annotation and/or symbol generation -- used
for FineIBT and call depth tracking
- Other toolchain hacks which will go unmentioned at this time...
Each feature can be enabled individually or in combination using the
objtool cmdline.
Objects
-------
Typically, objtool runs on every translation unit (TU, aka ".o file") in
the kernel. If a TU is part of a kernel module, the '--module' option
is added.
However:
- If noinstr validation is enabled, it also runs on vmlinux.o, with all
options removed and '--noinstr' added.
- If IBT or LTO is enabled, it doesn't run on TUs at all. Instead it
runs on vmlinux.o and linked modules, with all options.
In summary:
A) Legacy mode:
TU: objtool [--module] <options>
vmlinux: N/A
module: N/A
B) CONFIG_NOINSTR_VALIDATION=y && !(CONFIG_X86_KERNEL_IBT=y || CONFIG_LTO=y):
TU: objtool [--module] <options> // no --noinstr
vmlinux: objtool --noinstr // other options removed
module: N/A
C) CONFIG_X86_KERNEL_IBT=y || CONFIG_LTO=y:
TU: N/A
vmlinux: objtool --noinstr <options>
module: objtool --module --noinstr <options>
Stack validation
----------------
Objtool's stack validation feature analyzes every .o file and ensures
the validity of its stack metadata. It enforces a set of rules on asm
code and C inline assembly code so that stack traces can be reliable.
For each function, it recursively follows all possible code paths and
validates the correct frame pointer state at each instruction.
It also follows code paths involving special sections, like
.altinstructions, __jump_table, and __ex_table, which can add
alternative execution paths to a given instruction (or set of
instructions). Similarly, it knows how to follow switch statements, for
which gcc sometimes uses jump tables.
Here are some of the benefits of validating stack metadata:
a) More reliable stack traces for frame pointer enabled kernels
Frame pointers are used for debugging purposes. They allow runtime
code and debug tools to be able to walk the stack to determine the
chain of function call sites that led to the currently executing
code.
For some architectures, frame pointers are enabled by
CONFIG_FRAME_POINTER. For some other architectures they may be
required by the ABI (sometimes referred to as "backchain pointers").
For C code, gcc automatically generates instructions for setting up
frame pointers when the -fno-omit-frame-pointer option is used.
But for asm code, the frame setup instructions have to be written by
hand, which most people don't do. So the end result is that
CONFIG_FRAME_POINTER is honored for C code but not for most asm code.
For stack traces based on frame pointers to be reliable, all
functions which call other functions must first create a stack frame
and update the frame pointer. If a first function doesn't properly
create a stack frame before calling a second function, the *caller*
of the first function will be skipped on the stack trace.
For example, consider the following example backtrace with frame
pointers enabled:
[<ffffffff81812584>] dump_stack+0x4b/0x63
[<ffffffff812d6dc2>] cmdline_proc_show+0x12/0x30
[<ffffffff8127f568>] seq_read+0x108/0x3e0
[<ffffffff812cce62>] proc_reg_read+0x42/0x70
[<ffffffff81256197>] __vfs_read+0x37/0x100
[<ffffffff81256b16>] vfs_read+0x86/0x130
[<ffffffff81257898>] SyS_read+0x58/0xd0
[<ffffffff8181c1f2>] entry_SYSCALL_64_fastpath+0x12/0x76
It correctly shows that the caller of cmdline_proc_show() is
seq_read().
If we remove the frame pointer logic from cmdline_proc_show() by
replacing the frame pointer related instructions with nops, here's
what it looks like instead:
[<ffffffff81812584>] dump_stack+0x4b/0x63
[<ffffffff812d6dc2>] cmdline_proc_show+0x12/0x30
[<ffffffff812cce62>] proc_reg_read+0x42/0x70
[<ffffffff81256197>] __vfs_read+0x37/0x100
[<ffffffff81256b16>] vfs_read+0x86/0x130
[<ffffffff81257898>] SyS_read+0x58/0xd0
[<ffffffff8181c1f2>] entry_SYSCALL_64_fastpath+0x12/0x76
Notice that cmdline_proc_show()'s caller, seq_read(), has been
skipped. Instead the stack trace seems to show that
cmdline_proc_show() was called by proc_reg_read().
The benefit of objtool here is that because it ensures that *all*
functions honor CONFIG_FRAME_POINTER, no functions will ever[*] be
skipped on a stack trace.
[*] unless an interrupt or exception has occurred at the very
beginning of a function before the stack frame has been created,
or at the very end of the function after the stack frame has been
destroyed. This is an inherent limitation of frame pointers.
b) ORC (Oops Rewind Capability) unwind table generation
An alternative to frame pointers and DWARF, ORC unwind data can be
used to walk the stack. Unlike frame pointers, ORC data is out of
band. So it doesn't affect runtime performance and it can be
reliable even when interrupts or exceptions are involved.
For more details, see Documentation/arch/x86/orc-unwinder.rst.
c) Higher live patching compatibility rate
Livepatch has an optional "consistency model", which is needed for
more complex patches. In order for the consistency model to work,
stack traces need to be reliable (or an unreliable condition needs to
be detectable). Objtool makes that possible.
For more details, see the livepatch documentation in the Linux kernel
source tree at Documentation/livepatch/livepatch.rst.
To achieve the validation, objtool enforces the following rules:
1. Each callable function must be annotated as such with the ELF
function type. In asm code, this is typically done using the
SYM_FUNC_{START,END} macros. If objtool finds a return instruction
outside of a function, it flags an error since that usually indicates
callable code which should be annotated accordingly.
This rule is needed so that objtool can properly identify each
callable function in order to analyze its stack metadata.
2. Conversely, each section of code which is *not* callable, or is
otherwise doing funny things with the stack or registers, should
*not* be annotated as an ELF function. Rather, SYM_CODE_{START,END}
should be used along with unwind hints.
3. Each callable function which calls another function must have the
correct frame pointer logic, if required by CONFIG_FRAME_POINTER or
the architecture's back chain rules. This can by done in asm code
with the FRAME_BEGIN/FRAME_END macros.
This rule ensures that frame pointer based stack traces will work as
designed. If function A doesn't create a stack frame before calling
function B, the _caller_ of function A will be skipped on the stack
trace.
4. Indirect jumps and jumps to undefined symbols are only allowed if:
a) the jump is part of a switch statement; or
b) the jump matches sibling call semantics and the frame pointer has
the same value it had on function entry.
This rule is needed so that objtool can reliably analyze all of a
function's code paths. If a function jumps to code in another file,
and it's not a sibling call, objtool has no way to follow the jump
because it only analyzes a single file at a time.
5. A callable function may not execute kernel entry/exit instructions.
The only code which needs such instructions is kernel entry code,
which shouldn't be be in callable functions anyway.
This rule is just a sanity check to ensure that callable functions
return normally.
Objtool warnings
----------------
NOTE: When requesting help with an objtool warning, please recreate with
OBJTOOL_VERBOSE=1 (e.g., "make OBJTOOL_VERBOSE=1") and send the full
output, including any disassembly or backtrace below the warning, to the
objtool maintainers.
For asm files, if you're getting an error which doesn't make sense,
first make sure that the affected code follows the above rules.
For C files, the common culprits are inline asm statements and calls to
"noreturn" functions. See below for more details.
Another possible cause for errors in C code is if the Makefile removes
-fno-omit-frame-pointer or adds -fomit-frame-pointer to the gcc options.
Here are some examples of common warnings reported by objtool, what
they mean, and suggestions for how to fix them. When in doubt, ping
the objtool maintainers.
1. file.o: warning: objtool: func()+0x128: call without frame pointer save/setup
The func() function made a function call without first saving and/or
updating the frame pointer, and CONFIG_FRAME_POINTER is enabled.
If the error is for an asm file, and func() is indeed a callable
function, add proper frame pointer logic using the FRAME_BEGIN and
FRAME_END macros. Otherwise, if it's not a callable function, remove
its ELF function annotation by using SYM_CODE_{START,END} and use the
manual unwind hint macros in asm/unwind_hints.h.
If it's a GCC-compiled .c file, the error may be because the function
uses an inline asm() statement which has a "call" instruction. An
asm() statement with a call instruction must declare the use of the
stack pointer in its output operand. On x86_64, this means adding
the ASM_CALL_CONSTRAINT as an output constraint:
asm volatile("call func" : ASM_CALL_CONSTRAINT);
Otherwise the stack frame may not get created before the call.
objtool can help with pinpointing the exact function where it happens:
$ OBJTOOL_ARGS="--verbose" make arch/x86/kvm/
arch/x86/kvm/kvm.o: warning: objtool: .altinstr_replacement+0xc5: call without frame pointer save/setup
arch/x86/kvm/kvm.o: warning: objtool: em_loop.part.0+0x29: (alt)
arch/x86/kvm/kvm.o: warning: objtool: em_loop.part.0+0x0: <=== (sym)
LD [M] arch/x86/kvm/kvm-intel.o
0000 0000000000028220 <em_loop.part.0>:
0000 28220: 0f b6 47 61 movzbl 0x61(%rdi),%eax
0004 28224: 3c e2 cmp $0xe2,%al
0006 28226: 74 2c je 28254 <em_loop.part.0+0x34>
0008 28228: 48 8b 57 10 mov 0x10(%rdi),%rdx
000c 2822c: 83 f0 05 xor $0x5,%eax
000f 2822f: 48 c1 e0 04 shl $0x4,%rax
0013 28233: 25 f0 00 00 00 and $0xf0,%eax
0018 28238: 81 e2 d5 08 00 00 and $0x8d5,%edx
001e 2823e: 80 ce 02 or $0x2,%dh
...
2. file.o: warning: objtool: .text+0x53: unreachable instruction
Objtool couldn't find a code path to reach the instruction.
If the error is for an asm file, and the instruction is inside (or
reachable from) a callable function, the function should be annotated
with the SYM_FUNC_START and SYM_FUNC_END macros.
Otherwise, SYM_CODE_START can be used. In that case the code needs
to be annotated with unwind hint macros.
If you're sure the code won't affect the reliability of runtime stack
traces and want objtool to ignore it, see "Adding exceptions" below.
3. file.o: warning: objtool: foo+0x48c: bar() missing __noreturn in .c/.h or NORETURN() in noreturns.h
The call from foo() to bar() doesn't return, but bar() is incorrectly
annotated. A noreturn function must be marked __noreturn in both its
declaration and its definition, and must have a NORETURN() annotation
in tools/objtool/noreturns.h.
4. file.o: warning: objtool: func(): can't find starting instruction
or
file.o: warning: objtool: func()+0x11dd: can't decode instruction
Does the file have data in a text section? If so, that can confuse
objtool's instruction decoder. Move the data to a more appropriate
section like .data or .rodata.
5. file.o: warning: objtool: func()+0x6: unsupported instruction in callable function
This is a kernel entry/exit instruction like sysenter or iret. Such
instructions aren't allowed in a callable function, and are most
likely part of the kernel entry code. Such code should probably be
placed in a SYM_CODE_{START,END} block with unwind hints.
6. file.o: warning: objtool: func()+0x26: sibling call from callable instruction with modified stack frame
This is a branch to an UNDEF symbol. Objtool assumed it's a
sibling call and detected that the stack wasn't first restored to its
original state.
If it's not really a sibling call, you may need to use unwind hints
and/or move the destination code to the local file.
If the instruction is not actually in a callable function (e.g.
kernel entry code), use SYM_CODE_{START,END} and unwind hints.
7. file: warning: objtool: func()+0x5c: stack state mismatch
The instruction's frame pointer state is inconsistent, depending on
which execution path was taken to reach the instruction.
Make sure that, when CONFIG_FRAME_POINTER is enabled, the function
pushes and sets up the frame pointer (for x86_64, this means rbp) at
the beginning of the function and pops it at the end of the function.
Also make sure that no other code in the function touches the frame
pointer.
Another possibility is that the code has some asm or inline asm which
does some unusual things to the stack or the frame pointer. In such
cases it's probably appropriate to use SYM_CODE_{START,END} with unwind
hints.
8. file.o: warning: objtool: funcA() falls through to next function funcB()
This means that funcA() doesn't end with a return instruction or an
unconditional jump, and that objtool has determined that the function
can fall through into the next function. There could be different
reasons for this:
a) funcA()'s last instruction is a call to a "noreturn" function like
panic(). In this case the noreturn function needs to be added to
objtool's hard-coded global_noreturns array. Feel free to bug the
objtool maintainer, or you can submit a patch.
b) funcA() uses the unreachable() annotation in a section of code
that is actually reachable.
c) Some undefined behavior like divide by zero.
9. file.o: warning: objtool: funcA() call to funcB() with UACCESS enabled
This means that an unexpected call to a non-whitelisted function exists
outside of arch-specific guards.
X86: SMAP (stac/clac): __uaccess_begin()/__uaccess_end()
ARM: PAN: uaccess_enable()/uaccess_disable()
These functions should be called to denote a minimal critical section around
access to __user variables. See also: https://lwn.net/Articles/517475/
The intention of the warning is to prevent calls to funcB() from eventually
calling schedule(), potentially leaking the AC flags state, and not
restoring them correctly.
It also helps verify that there are no unexpected calls to funcB() which may
access user space pages with protections against doing so disabled.
To fix, either:
1) remove explicit calls to funcB() from funcA().
2) add the correct guards before and after calls to low level functions like
__get_user_size()/__put_user_size().
3) add funcB to uaccess_safe_builtin whitelist in tools/objtool/check.c, if
funcB obviously does not call schedule(), and is marked notrace (since
function tracing inserts additional calls, which is not obvious from the
sources).
10. file.o: warning: func()+0x5c: stack layout conflict in alternatives
This means that in the use of the alternative() or ALTERNATIVE()
macro, the code paths have conflicting modifications to the stack.
The problem is that there is only one ORC unwind table, which means
that the ORC unwind entries must be consistent for all possible
instruction boundaries regardless of which code has been patched.
This limitation can be overcome by massaging the alternatives with
NOPs to shift the stack changes around so they no longer conflict.
11. file.o: warning: unannotated intra-function call
This warning means that a direct call is done to a destination which
is not at the beginning of a function. If this is a legit call, you
can remove this warning by putting the ANNOTATE_INTRA_FUNCTION_CALL
directive right before the call.
12. file.o: warning: func(): not an indirect call target
This means that objtool is running with --ibt and a function
expected to be an indirect call target is not. In particular, this
happens for init_module() or cleanup_module() if a module relies on
these special names and does not use module_init() / module_exit()
macros to create them.
If the error doesn't seem to make sense, it could be a bug in objtool.
Feel free to ask objtool maintainers for help.
Adding exceptions
-----------------
If you _really_ need objtool to ignore something, and are 100% sure
that it won't affect kernel stack traces, you can tell objtool to
ignore it:
- To skip validation of a function, use the STACK_FRAME_NON_STANDARD
macro.
- To skip validation of a file, add
OBJECT_FILES_NON_STANDARD_filename.o := y
to the Makefile.
- To skip validation of a directory, add
OBJECT_FILES_NON_STANDARD := y
to the Makefile.
NOTE: OBJECT_FILES_NON_STANDARD doesn't work for link time validation of
vmlinux.o or a linked module. So it should only be used for files which
aren't linked into vmlinux or a module.
+156
View File
@@ -0,0 +1,156 @@
# SPDX-License-Identifier: GPL-2.0
include ../scripts/Makefile.include
include ../scripts/Makefile.arch
ifeq ($(SRCARCH),x86)
BUILD_ORC := y
ARCH_HAS_KLP := y
endif
ifeq ($(SRCARCH),loongarch)
BUILD_ORC := y
endif
ifeq ($(ARCH_HAS_KLP),y)
HAVE_XXHASH = $(shell printf "$(pound)include <xxhash.h>\nXXH3_state_t *state;int main() {}" | \
$(HOSTCC) $(HOSTCFLAGS) -xc - -o /dev/null -lxxhash 2> /dev/null && echo y || echo n)
ifeq ($(HAVE_XXHASH),y)
BUILD_KLP := y
LIBXXHASH_CFLAGS := $(shell $(HOSTPKG_CONFIG) libxxhash --cflags 2>/dev/null) \
-DBUILD_KLP
LIBXXHASH_LIBS := $(shell $(HOSTPKG_CONFIG) libxxhash --libs 2>/dev/null || echo -lxxhash)
endif
endif
export BUILD_ORC BUILD_KLP
ifeq ($(srctree),)
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
endif
RM ?= rm -f
LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/
ifneq ($(OUTPUT),)
LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd
else
LIBSUBCMD_OUTPUT = $(CURDIR)/libsubcmd
endif
LIBSUBCMD = $(LIBSUBCMD_OUTPUT)/libsubcmd.a
OBJTOOL := $(OUTPUT)objtool
OBJTOOL_IN := $(OBJTOOL)-in.o
LIBELF_FLAGS := $(shell $(HOSTPKG_CONFIG) libelf --cflags 2>/dev/null)
LIBELF_LIBS := $(shell $(HOSTPKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
all: $(OBJTOOL)
WARNINGS := -Werror -Wall -Wextra -Wmissing-prototypes \
-Wmissing-declarations -Wwrite-strings \
-Wno-implicit-fallthrough -Wno-sign-compare \
-Wno-unused-parameter
INCLUDES := -I$(srctree)/tools/include \
-I$(srctree)/tools/include/uapi \
-I$(srctree)/tools/arch/$(HOSTARCH)/include/uapi \
-I$(srctree)/tools/arch/$(SRCARCH)/include \
-I$(srctree)/tools/objtool/include \
-I$(srctree)/tools/objtool/arch/$(SRCARCH)/include \
-I$(srctree)/tools/lib
OBJTOOL_CFLAGS := -std=gnu11 -fomit-frame-pointer -O2 -g $(WARNINGS) \
$(INCLUDES) $(LIBELF_FLAGS) $(LIBXXHASH_CFLAGS) $(HOSTCFLAGS)
OBJTOOL_LDFLAGS := $(LIBSUBCMD) $(LIBELF_LIBS) $(LIBXXHASH_LIBS) $(HOSTLDFLAGS)
# Allow old libelf to be used:
elfshdr := $(shell echo '$(pound)include <libelf.h>' | $(HOSTCC) $(OBJTOOL_CFLAGS) -x c -E - 2>/dev/null | grep elf_getshdr)
OBJTOOL_CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED)
# Always want host compilation.
HOST_OVERRIDES := CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)"
#
# To support disassembly, objtool needs libopcodes which is provided
# with libbfd (binutils-dev or binutils-devel package).
#
# We check using HOSTCC directly rather than the shared feature framework
# because objtool is a host tool that links against host libraries.
#
# When using shared libraries, -lopcodes is sufficient as dependencies are
# resolved automatically. With static libraries, we must explicitly link
# against libopcodes' dependencies: libbfd, libiberty, and sometimes libz.
# Try each combination and use the first one that succeeds.
#
LIBOPCODES_LIBS := $(shell \
for libs in "-lopcodes" \
"-lopcodes -lbfd" \
"-lopcodes -lbfd -liberty" \
"-lopcodes -lbfd -liberty -lz"; do \
echo 'extern void disassemble_init_for_target(void *);' \
'int main(void) { disassemble_init_for_target(0); return 0; }' | \
$(HOSTCC) -xc - -o /dev/null $$libs 2>/dev/null && \
echo "$$libs" && break; \
done)
# Styled disassembler support requires binutils >= 2.39
HAVE_DISASM_STYLED := $(shell echo '$(pound)include <dis-asm.h>' | \
$(HOSTCC) -E -xc - 2>/dev/null | grep -q disassembler_style && echo y)
BUILD_DISAS := n
ifneq ($(LIBOPCODES_LIBS),)
BUILD_DISAS := y
OBJTOOL_CFLAGS += -DDISAS -DPACKAGE='"objtool"'
OBJTOOL_LDFLAGS += $(LIBOPCODES_LIBS)
ifeq ($(HAVE_DISASM_STYLED),y)
OBJTOOL_CFLAGS += -DDISASM_INIT_STYLED
endif
endif
export BUILD_DISAS
AWK = awk
MKDIR = mkdir
export srctree OUTPUT CFLAGS SRCARCH AWK
include $(srctree)/tools/build/Makefile.include
$(OBJTOOL_IN): fixdep $(LIBSUBCMD) FORCE
$(Q)$(CONFIG_SHELL) ./sync-check.sh
$(Q)$(MAKE) $(build)=objtool $(HOST_OVERRIDES) CFLAGS="$(OBJTOOL_CFLAGS)" \
LDFLAGS="$(OBJTOOL_LDFLAGS)"
$(OBJTOOL): $(LIBSUBCMD) $(OBJTOOL_IN)
$(QUIET_LINK)$(HOSTCC) $(OBJTOOL_IN) $(OBJTOOL_LDFLAGS) -o $@
$(LIBSUBCMD_OUTPUT):
$(Q)$(MKDIR) -p $@
$(LIBSUBCMD): fixdep $(LIBSUBCMD_OUTPUT) FORCE
$(Q)$(MAKE) -C $(LIBSUBCMD_DIR) O=$(LIBSUBCMD_OUTPUT) \
DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir= \
$(HOST_OVERRIDES) EXTRA_CFLAGS="$(OBJTOOL_CFLAGS)" \
$@
$(LIBSUBCMD)-clean:
$(call QUIET_CLEAN, libsubcmd)
$(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT)
clean: $(LIBSUBCMD)-clean
$(Q)find $(OUTPUT) \( -name '*.o' -o -name '\.*.cmd' -o -name '\.*.d' \) -type f -print | xargs $(RM)
$(Q)$(RM) $(OUTPUT)arch/x86/lib/cpu-feature-names.c $(OUTPUT)fixdep
$(Q)$(RM) $(OUTPUT)arch/x86/lib/inat-tables.c $(OUTPUT)fixdep
$(Q)$(RM) -- $(OUTPUT)FEATURE-DUMP.objtool
$(Q)$(RM) -r -- $(OUTPUT)feature
mrproper: clean
$(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL)
FORCE:
.PHONY: clean mrproper FORCE
@@ -0,0 +1,3 @@
objtool-y += decode.o
objtool-y += special.o
objtool-y += orc.o
@@ -0,0 +1,439 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <objtool/check.h>
#include <objtool/disas.h>
#include <objtool/warn.h>
#include <asm/inst.h>
#include <asm/orc_types.h>
#include <linux/objtool_types.h>
#include <arch/elf.h>
const char *arch_reg_name[CFI_NUM_REGS] = {
"zero", "ra", "tp", "sp",
"a0", "a1", "a2", "a3",
"a4", "a5", "a6", "a7",
"t0", "t1", "t2", "t3",
"t4", "t5", "t6", "t7",
"t8", "u0", "fp", "s0",
"s1", "s2", "s3", "s4",
"s5", "s6", "s7", "s8"
};
int arch_ftrace_match(const char *name)
{
return !strcmp(name, "_mcount");
}
unsigned long arch_jump_destination(struct instruction *insn)
{
return insn->offset + (insn->immediate << 2);
}
s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
{
return reloc_addend(reloc);
}
bool arch_pc_relative_reloc(struct reloc *reloc)
{
return false;
}
bool arch_callee_saved_reg(unsigned char reg)
{
switch (reg) {
case CFI_RA:
case CFI_FP:
case CFI_S0 ... CFI_S8:
return true;
default:
return false;
}
}
int arch_decode_hint_reg(u8 sp_reg, int *base)
{
switch (sp_reg) {
case ORC_REG_UNDEFINED:
*base = CFI_UNDEFINED;
break;
case ORC_REG_SP:
*base = CFI_SP;
break;
case ORC_REG_FP:
*base = CFI_FP;
break;
default:
return -1;
}
return 0;
}
static bool is_loongarch(const struct elf *elf)
{
if (elf->ehdr.e_machine == EM_LOONGARCH)
return true;
ERROR("unexpected ELF machine type %d", elf->ehdr.e_machine);
return false;
}
#define ADD_OP(op) \
if (!(op = calloc(1, sizeof(*op)))) \
return -1; \
else for (*ops_list = op, ops_list = &op->next; op; op = NULL)
static bool decode_insn_reg0i26_fomat(union loongarch_instruction inst,
struct instruction *insn)
{
switch (inst.reg0i26_format.opcode) {
case b_op:
insn->type = INSN_JUMP_UNCONDITIONAL;
insn->immediate = sign_extend64(inst.reg0i26_format.immediate_h << 16 |
inst.reg0i26_format.immediate_l, 25);
break;
case bl_op:
insn->type = INSN_CALL;
insn->immediate = sign_extend64(inst.reg0i26_format.immediate_h << 16 |
inst.reg0i26_format.immediate_l, 25);
break;
default:
return false;
}
return true;
}
static bool decode_insn_reg1i21_fomat(union loongarch_instruction inst,
struct instruction *insn)
{
switch (inst.reg1i21_format.opcode) {
case beqz_op:
case bnez_op:
case bceqz_op:
insn->type = INSN_JUMP_CONDITIONAL;
insn->immediate = sign_extend64(inst.reg1i21_format.immediate_h << 16 |
inst.reg1i21_format.immediate_l, 20);
break;
default:
return false;
}
return true;
}
static bool decode_insn_reg2i12_fomat(union loongarch_instruction inst,
struct instruction *insn,
struct stack_op **ops_list,
struct stack_op *op)
{
switch (inst.reg2i12_format.opcode) {
case addid_op:
if ((inst.reg2i12_format.rd == CFI_SP) || (inst.reg2i12_format.rj == CFI_SP)) {
/* addi.d sp,sp,si12 or addi.d fp,sp,si12 or addi.d sp,fp,si12 */
insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
ADD_OP(op) {
op->src.type = OP_SRC_ADD;
op->src.reg = inst.reg2i12_format.rj;
op->src.offset = insn->immediate;
op->dest.type = OP_DEST_REG;
op->dest.reg = inst.reg2i12_format.rd;
}
}
if ((inst.reg2i12_format.rd == CFI_SP) && (inst.reg2i12_format.rj == CFI_FP)) {
/* addi.d sp,fp,si12 */
struct symbol *func = find_func_containing(insn->sec, insn->offset);
if (!func)
return false;
func->frame_pointer = true;
}
break;
case ldd_op:
if (inst.reg2i12_format.rj == CFI_SP) {
/* ld.d rd,sp,si12 */
insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
ADD_OP(op) {
op->src.type = OP_SRC_REG_INDIRECT;
op->src.reg = CFI_SP;
op->src.offset = insn->immediate;
op->dest.type = OP_DEST_REG;
op->dest.reg = inst.reg2i12_format.rd;
}
}
break;
case std_op:
if (inst.reg2i12_format.rj == CFI_SP) {
/* st.d rd,sp,si12 */
insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = inst.reg2i12_format.rd;
op->dest.type = OP_DEST_REG_INDIRECT;
op->dest.reg = CFI_SP;
op->dest.offset = insn->immediate;
}
}
break;
case andi_op:
if (inst.reg2i12_format.rd == 0 &&
inst.reg2i12_format.rj == 0 &&
inst.reg2i12_format.immediate == 0)
/* andi r0,r0,0 */
insn->type = INSN_NOP;
break;
default:
return false;
}
return true;
}
static bool decode_insn_reg2i14_fomat(union loongarch_instruction inst,
struct instruction *insn,
struct stack_op **ops_list,
struct stack_op *op)
{
switch (inst.reg2i14_format.opcode) {
case ldptrd_op:
if (inst.reg2i14_format.rj == CFI_SP) {
/* ldptr.d rd,sp,si14 */
insn->immediate = sign_extend64(inst.reg2i14_format.immediate, 13);
ADD_OP(op) {
op->src.type = OP_SRC_REG_INDIRECT;
op->src.reg = CFI_SP;
op->src.offset = insn->immediate;
op->dest.type = OP_DEST_REG;
op->dest.reg = inst.reg2i14_format.rd;
}
}
break;
case stptrd_op:
if (inst.reg2i14_format.rj == CFI_SP) {
/* stptr.d ra,sp,0 */
if (inst.reg2i14_format.rd == LOONGARCH_GPR_RA &&
inst.reg2i14_format.immediate == 0)
break;
/* stptr.d rd,sp,si14 */
insn->immediate = sign_extend64(inst.reg2i14_format.immediate, 13);
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = inst.reg2i14_format.rd;
op->dest.type = OP_DEST_REG_INDIRECT;
op->dest.reg = CFI_SP;
op->dest.offset = insn->immediate;
}
}
break;
default:
return false;
}
return true;
}
static bool decode_insn_reg2i16_fomat(union loongarch_instruction inst,
struct instruction *insn)
{
switch (inst.reg2i16_format.opcode) {
case jirl_op:
if (inst.reg2i16_format.rd == 0 &&
inst.reg2i16_format.rj == CFI_RA &&
inst.reg2i16_format.immediate == 0) {
/* jirl r0,ra,0 */
insn->type = INSN_RETURN;
} else if (inst.reg2i16_format.rd == CFI_RA) {
/* jirl ra,rj,offs16 */
insn->type = INSN_CALL_DYNAMIC;
} else if (inst.reg2i16_format.rd == CFI_A0 &&
inst.reg2i16_format.immediate == 0) {
/*
* jirl a0,t0,0
* this is a special case in loongarch_suspend_enter,
* just treat it as a call instruction.
*/
insn->type = INSN_CALL_DYNAMIC;
} else if (inst.reg2i16_format.rd == 0 &&
inst.reg2i16_format.immediate == 0) {
/* jirl r0,rj,0 */
insn->type = INSN_JUMP_DYNAMIC;
} else if (inst.reg2i16_format.rd == 0 &&
inst.reg2i16_format.immediate != 0) {
/*
* jirl r0,t0,12
* this is a rare case in JUMP_VIRT_ADDR,
* just ignore it due to it is harmless for tracing.
*/
break;
} else {
/* jirl rd,rj,offs16 */
insn->type = INSN_JUMP_UNCONDITIONAL;
insn->immediate = sign_extend64(inst.reg2i16_format.immediate, 15);
}
break;
case beq_op:
case bne_op:
case blt_op:
case bge_op:
case bltu_op:
case bgeu_op:
insn->type = INSN_JUMP_CONDITIONAL;
insn->immediate = sign_extend64(inst.reg2i16_format.immediate, 15);
break;
default:
return false;
}
return true;
}
static bool decode_insn_reg3_fomat(union loongarch_instruction inst,
struct instruction *insn)
{
switch (inst.reg3_format.opcode) {
case amswapw_op:
if (inst.reg3_format.rd == LOONGARCH_GPR_ZERO &&
inst.reg3_format.rk == LOONGARCH_GPR_RA &&
inst.reg3_format.rj == LOONGARCH_GPR_ZERO) {
/* amswap.w $zero, $ra, $zero */
insn->type = INSN_BUG;
}
break;
default:
return false;
}
return true;
}
int arch_decode_instruction(struct objtool_file *file, const struct section *sec,
unsigned long offset, unsigned int maxlen,
struct instruction *insn)
{
struct stack_op **ops_list = &insn->stack_ops;
const struct elf *elf = file->elf;
struct stack_op *op = NULL;
union loongarch_instruction inst;
if (!is_loongarch(elf))
return -1;
if (maxlen < LOONGARCH_INSN_SIZE)
return 0;
insn->len = LOONGARCH_INSN_SIZE;
insn->type = INSN_OTHER;
insn->immediate = 0;
inst = *(union loongarch_instruction *)(sec->data->d_buf + offset);
if (decode_insn_reg0i26_fomat(inst, insn))
return 0;
if (decode_insn_reg1i21_fomat(inst, insn))
return 0;
if (decode_insn_reg2i12_fomat(inst, insn, ops_list, op))
return 0;
if (decode_insn_reg2i14_fomat(inst, insn, ops_list, op))
return 0;
if (decode_insn_reg2i16_fomat(inst, insn))
return 0;
if (decode_insn_reg3_fomat(inst, insn))
return 0;
if (inst.word == 0) {
/* andi $zero, $zero, 0x0 */
insn->type = INSN_NOP;
} else if (inst.reg0i15_format.opcode == break_op &&
inst.reg0i15_format.immediate == 0x0) {
/* break 0x0 */
insn->type = INSN_TRAP;
} else if (inst.reg0i15_format.opcode == break_op &&
inst.reg0i15_format.immediate == 0x1) {
/* break 0x1 */
insn->type = INSN_BUG;
} else if (inst.reg2_format.opcode == ertn_op) {
/* ertn */
insn->type = INSN_RETURN;
}
return 0;
}
const char *arch_nop_insn(int len)
{
static u32 nop;
if (len != LOONGARCH_INSN_SIZE) {
ERROR("invalid NOP size: %d\n", len);
return NULL;
}
nop = LOONGARCH_INSN_NOP;
return (const char *)&nop;
}
const char *arch_ret_insn(int len)
{
static u32 ret;
if (len != LOONGARCH_INSN_SIZE) {
ERROR("invalid RET size: %d\n", len);
return NULL;
}
emit_jirl((union loongarch_instruction *)&ret, LOONGARCH_GPR_RA, LOONGARCH_GPR_ZERO, 0);
return (const char *)&ret;
}
void arch_initial_func_cfi_state(struct cfi_init_state *state)
{
int i;
for (i = 0; i < CFI_NUM_REGS; i++) {
state->regs[i].base = CFI_UNDEFINED;
state->regs[i].offset = 0;
}
/* initial CFA (call frame address) */
state->cfa.base = CFI_SP;
state->cfa.offset = 0;
}
unsigned int arch_reloc_size(struct reloc *reloc)
{
switch (reloc_type(reloc)) {
case R_LARCH_32:
case R_LARCH_32_PCREL:
return 4;
default:
return 8;
}
}
unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table)
{
switch (reloc_type(reloc)) {
case R_LARCH_32_PCREL:
case R_LARCH_64_PCREL:
return reloc->sym->offset + reloc_addend(reloc) -
(reloc_offset(reloc) - reloc_offset(table));
default:
return reloc->sym->offset + reloc_addend(reloc);
}
}
#ifdef DISAS
int arch_disas_info_init(struct disassemble_info *dinfo)
{
return disas_info_init(dinfo, bfd_arch_loongarch,
bfd_mach_loongarch32, bfd_mach_loongarch64,
NULL);
}
#endif /* DISAS */
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ARCH_CFI_REGS_H
#define _OBJTOOL_ARCH_CFI_REGS_H
#define CFI_RA 1
#define CFI_SP 3
#define CFI_A0 4
#define CFI_FP 22
#define CFI_S0 23
#define CFI_S1 24
#define CFI_S2 25
#define CFI_S3 26
#define CFI_S4 27
#define CFI_S5 28
#define CFI_S6 29
#define CFI_S7 30
#define CFI_S8 31
#define CFI_NUM_REGS 32
#define CFI_BP CFI_FP
#endif /* _OBJTOOL_ARCH_CFI_REGS_H */
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ARCH_ELF_H
#define _OBJTOOL_ARCH_ELF_H
/*
* See the following link for more info about ELF Relocation types:
* https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html#_relocations
*/
#ifndef R_LARCH_NONE
#define R_LARCH_NONE 0
#endif
#ifndef R_LARCH_32
#define R_LARCH_32 1
#endif
#ifndef R_LARCH_64
#define R_LARCH_64 2
#endif
#ifndef R_LARCH_32_PCREL
#define R_LARCH_32_PCREL 99
#endif
#ifndef R_LARCH_64_PCREL
#define R_LARCH_64_PCREL 109
#endif
#ifndef EM_LOONGARCH
#define EM_LOONGARCH 258
#endif
#define R_NONE R_LARCH_NONE
#define R_ABS32 R_LARCH_32
#define R_ABS64 R_LARCH_64
#define R_DATA32 R_LARCH_32_PCREL
#define R_DATA64 R_LARCH_32_PCREL
#define R_TEXT32 R_LARCH_32_PCREL
#define R_TEXT64 R_LARCH_32_PCREL
#endif /* _OBJTOOL_ARCH_ELF_H */
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ARCH_SPECIAL_H
#define _OBJTOOL_ARCH_SPECIAL_H
/*
* See more info about struct exception_table_entry
* in arch/loongarch/include/asm/extable.h
*/
#define EX_ENTRY_SIZE 12
#define EX_ORIG_OFFSET 0
#define EX_NEW_OFFSET 4
/*
* See more info about struct jump_entry
* in include/linux/jump_label.h
*/
#define JUMP_ENTRY_SIZE 16
#define JUMP_ORIG_OFFSET 0
#define JUMP_NEW_OFFSET 4
#define JUMP_KEY_OFFSET 8
/*
* See more info about struct alt_instr
* in arch/loongarch/include/asm/alternative.h
*/
#define ALT_ENTRY_SIZE 12
#define ALT_ORIG_OFFSET 0
#define ALT_NEW_OFFSET 4
#define ALT_FEATURE_OFFSET 8
#define ALT_ORIG_LEN_OFFSET 10
#define ALT_NEW_LEN_OFFSET 11
#endif /* _OBJTOOL_ARCH_SPECIAL_H */
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/objtool_types.h>
#include <asm/orc_types.h>
#include <objtool/check.h>
#include <objtool/orc.h>
#include <objtool/warn.h>
int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, struct instruction *insn)
{
struct cfi_reg *fp = &cfi->regs[CFI_FP];
struct cfi_reg *ra = &cfi->regs[CFI_RA];
memset(orc, 0, sizeof(*orc));
if (!cfi) {
/*
* This is usually either unreachable nops/traps (which don't
* trigger unreachable instruction warnings), or
* STACK_FRAME_NON_STANDARD functions.
*/
orc->type = ORC_TYPE_UNDEFINED;
return 0;
}
switch (cfi->type) {
case UNWIND_HINT_TYPE_UNDEFINED:
orc->type = ORC_TYPE_UNDEFINED;
return 0;
case UNWIND_HINT_TYPE_END_OF_STACK:
orc->type = ORC_TYPE_END_OF_STACK;
return 0;
case UNWIND_HINT_TYPE_CALL:
orc->type = ORC_TYPE_CALL;
break;
case UNWIND_HINT_TYPE_REGS:
orc->type = ORC_TYPE_REGS;
break;
case UNWIND_HINT_TYPE_REGS_PARTIAL:
orc->type = ORC_TYPE_REGS_PARTIAL;
break;
default:
ERROR_INSN(insn, "unknown unwind hint type %d", cfi->type);
return -1;
}
orc->signal = cfi->signal;
switch (cfi->cfa.base) {
case CFI_SP:
orc->sp_reg = ORC_REG_SP;
break;
case CFI_FP:
orc->sp_reg = ORC_REG_FP;
break;
default:
ERROR_INSN(insn, "unknown CFA base reg %d", cfi->cfa.base);
return -1;
}
switch (fp->base) {
case CFI_UNDEFINED:
orc->fp_reg = ORC_REG_UNDEFINED;
orc->fp_offset = 0;
break;
case CFI_CFA:
orc->fp_reg = ORC_REG_PREV_SP;
orc->fp_offset = fp->offset;
break;
case CFI_FP:
orc->fp_reg = ORC_REG_FP;
break;
default:
ERROR_INSN(insn, "unknown FP base reg %d", fp->base);
return -1;
}
switch (ra->base) {
case CFI_UNDEFINED:
orc->ra_reg = ORC_REG_UNDEFINED;
orc->ra_offset = 0;
break;
case CFI_CFA:
orc->ra_reg = ORC_REG_PREV_SP;
orc->ra_offset = ra->offset;
break;
case CFI_FP:
orc->ra_reg = ORC_REG_FP;
break;
default:
ERROR_INSN(insn, "unknown RA base reg %d", ra->base);
return -1;
}
orc->sp_offset = cfi->cfa.offset;
return 0;
}
int write_orc_entry(struct elf *elf, struct section *orc_sec,
struct section *ip_sec, unsigned int idx,
struct section *insn_sec, unsigned long insn_off,
struct orc_entry *o)
{
struct orc_entry *orc;
/* populate ORC data */
orc = (struct orc_entry *)orc_sec->data->d_buf + idx;
memcpy(orc, o, sizeof(*orc));
/* populate reloc for ip */
if (!elf_init_reloc_text_sym(elf, ip_sec, idx * sizeof(int), idx,
insn_sec, insn_off))
return -1;
return 0;
}
static const char *reg_name(unsigned int reg)
{
switch (reg) {
case ORC_REG_SP:
return "sp";
case ORC_REG_FP:
return "fp";
case ORC_REG_PREV_SP:
return "prevsp";
default:
return "?";
}
}
static const char *orc_type_name(unsigned int type)
{
switch (type) {
case UNWIND_HINT_TYPE_CALL:
return "call";
case UNWIND_HINT_TYPE_REGS:
return "regs";
case UNWIND_HINT_TYPE_REGS_PARTIAL:
return "regs (partial)";
default:
return "?";
}
}
static void print_reg(unsigned int reg, int offset)
{
if (reg == ORC_REG_UNDEFINED)
printf(" (und) ");
else
printf("%s + %3d", reg_name(reg), offset);
}
void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i)
{
printf("type:%s", orc_type_name(orc[i].type));
printf(" sp:");
print_reg(orc[i].sp_reg, orc[i].sp_offset);
printf(" fp:");
print_reg(orc[i].fp_reg, orc[i].fp_offset);
printf(" ra:");
print_reg(orc[i].ra_reg, orc[i].ra_offset);
printf(" signal:%d\n", orc[i].signal);
}
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <objtool/special.h>
#include <objtool/warn.h>
bool arch_support_alt_relocation(struct special_alt *special_alt,
struct instruction *insn,
struct reloc *reloc)
{
return false;
}
struct table_info {
struct list_head jump_info;
unsigned long insn_offset;
unsigned long rodata_offset;
};
static void get_rodata_table_size_by_table_annotate(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size)
{
struct section *rsec;
struct reloc *reloc;
struct list_head table_list;
struct table_info *orig_table;
struct table_info *next_table;
unsigned long tmp_insn_offset;
unsigned long tmp_rodata_offset;
bool is_valid_list = false;
rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
if (!rsec)
return;
INIT_LIST_HEAD(&table_list);
for_each_reloc(rsec, reloc) {
if (reloc->sym->sec->rodata)
continue;
if (strcmp(insn->sec->name, reloc->sym->sec->name))
continue;
orig_table = malloc(sizeof(struct table_info));
if (!orig_table) {
WARN("malloc failed");
return;
}
orig_table->insn_offset = reloc->sym->offset + reloc_addend(reloc);
reloc++;
orig_table->rodata_offset = reloc->sym->offset + reloc_addend(reloc);
list_add_tail(&orig_table->jump_info, &table_list);
if (reloc_idx(reloc) + 1 == sec_num_entries(rsec))
break;
if (strcmp(insn->sec->name, (reloc + 1)->sym->sec->name)) {
list_for_each_entry(orig_table, &table_list, jump_info) {
if (orig_table->insn_offset == insn->offset) {
is_valid_list = true;
break;
}
}
if (!is_valid_list) {
list_del_init(&table_list);
continue;
}
break;
}
}
list_for_each_entry(orig_table, &table_list, jump_info) {
next_table = list_next_entry(orig_table, jump_info);
list_for_each_entry_from(next_table, &table_list, jump_info) {
if (next_table->rodata_offset < orig_table->rodata_offset) {
tmp_insn_offset = next_table->insn_offset;
tmp_rodata_offset = next_table->rodata_offset;
next_table->insn_offset = orig_table->insn_offset;
next_table->rodata_offset = orig_table->rodata_offset;
orig_table->insn_offset = tmp_insn_offset;
orig_table->rodata_offset = tmp_rodata_offset;
}
}
}
list_for_each_entry(orig_table, &table_list, jump_info) {
if (insn->offset == orig_table->insn_offset) {
next_table = list_next_entry(orig_table, jump_info);
if (&next_table->jump_info == &table_list) {
*table_size = 0;
return;
}
while (next_table->rodata_offset == orig_table->rodata_offset) {
next_table = list_next_entry(next_table, jump_info);
if (&next_table->jump_info == &table_list) {
*table_size = 0;
return;
}
}
*table_size = next_table->rodata_offset - orig_table->rodata_offset;
}
}
}
static struct reloc *find_reloc_by_table_annotate(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size)
{
struct section *rsec;
struct reloc *reloc;
unsigned long offset;
rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
if (!rsec)
return NULL;
for_each_reloc(rsec, reloc) {
if (reloc->sym->sec->rodata)
continue;
if (strcmp(insn->sec->name, reloc->sym->sec->name))
continue;
offset = reloc->sym->offset + reloc_addend(reloc);
if (insn->offset == offset) {
get_rodata_table_size_by_table_annotate(file, insn, table_size);
reloc++;
return reloc;
}
}
return NULL;
}
static struct reloc *find_reloc_of_rodata_c_jump_table(struct section *sec,
unsigned long offset,
unsigned long *table_size)
{
struct section *rsec;
struct reloc *reloc;
rsec = sec->rsec;
if (!rsec)
return NULL;
for_each_reloc(rsec, reloc) {
if (reloc_offset(reloc) > offset)
break;
if (!strcmp(reloc->sym->sec->name, C_JUMP_TABLE_SECTION)) {
*table_size = 0;
return reloc;
}
}
return NULL;
}
struct reloc *arch_find_switch_table(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size)
{
struct reloc *annotate_reloc;
struct reloc *rodata_reloc;
struct section *table_sec;
unsigned long table_offset;
annotate_reloc = find_reloc_by_table_annotate(file, insn, table_size);
if (!annotate_reloc) {
annotate_reloc = find_reloc_of_rodata_c_jump_table(
insn->sec, insn->offset, table_size);
if (!annotate_reloc)
return NULL;
}
table_sec = annotate_reloc->sym->sec;
table_offset = annotate_reloc->sym->offset + reloc_addend(annotate_reloc);
/*
* Each table entry has a rela associated with it. The rela
* should reference text in the same function as the original
* instruction.
*/
rodata_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
if (!rodata_reloc)
return NULL;
return rodata_reloc;
}
const char *arch_cpu_feature_name(int feature_number)
{
return NULL;
}
@@ -0,0 +1,2 @@
objtool-y += decode.o
objtool-y += special.o
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdio.h>
#include <stdlib.h>
#include <objtool/check.h>
#include <objtool/disas.h>
#include <objtool/elf.h>
#include <objtool/arch.h>
#include <objtool/warn.h>
#include <objtool/builtin.h>
const char *arch_reg_name[CFI_NUM_REGS] = {
"r0", "sp", "r2", "r3",
"r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15",
"r16", "r17", "r18", "r19",
"r20", "r21", "r22", "r23",
"r24", "r25", "r26", "r27",
"r28", "r29", "r30", "r31",
"ra"
};
int arch_ftrace_match(const char *name)
{
return !strcmp(name, "_mcount");
}
s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
{
return reloc_addend(reloc);
}
bool arch_callee_saved_reg(unsigned char reg)
{
return false;
}
int arch_decode_hint_reg(u8 sp_reg, int *base)
{
exit(-1);
}
const char *arch_nop_insn(int len)
{
exit(-1);
}
const char *arch_ret_insn(int len)
{
exit(-1);
}
int arch_decode_instruction(struct objtool_file *file, const struct section *sec,
unsigned long offset, unsigned int maxlen,
struct instruction *insn)
{
unsigned int opcode;
enum insn_type typ;
unsigned long imm;
u32 ins;
ins = bswap_if_needed(file->elf, *(u32 *)(sec->data->d_buf + offset));
opcode = ins >> 26;
typ = INSN_OTHER;
imm = 0;
switch (opcode) {
case 18: /* b[l][a] */
if (ins == 0x48000005) /* bl .+4 */
typ = INSN_OTHER;
else if (ins & 1) /* bl[a] */
typ = INSN_CALL;
else /* b[a] */
typ = INSN_JUMP_UNCONDITIONAL;
imm = ins & 0x3fffffc;
if (imm & 0x2000000)
imm -= 0x4000000;
imm |= ins & 2; /* AA flag */
break;
}
if (opcode == 1)
insn->len = 8;
else
insn->len = 4;
insn->type = typ;
insn->immediate = imm;
return 0;
}
unsigned long arch_jump_destination(struct instruction *insn)
{
if (insn->immediate & 2)
return insn->immediate & ~2;
return insn->offset + insn->immediate;
}
bool arch_pc_relative_reloc(struct reloc *reloc)
{
/*
* The powerpc build only allows certain relocation types, see
* relocs_check.sh, and none of those accepted are PC relative.
*/
return false;
}
void arch_initial_func_cfi_state(struct cfi_init_state *state)
{
int i;
for (i = 0; i < CFI_NUM_REGS; i++) {
state->regs[i].base = CFI_UNDEFINED;
state->regs[i].offset = 0;
}
/* initial CFA (call frame address) */
state->cfa.base = CFI_SP;
state->cfa.offset = 0;
/* initial LR (return address) */
state->regs[CFI_RA].base = CFI_CFA;
state->regs[CFI_RA].offset = 0;
}
unsigned int arch_reloc_size(struct reloc *reloc)
{
switch (reloc_type(reloc)) {
case R_PPC_REL32:
case R_PPC_ADDR32:
case R_PPC_UADDR32:
case R_PPC_PLT32:
case R_PPC_PLTREL32:
return 4;
default:
return 8;
}
}
#ifdef DISAS
int arch_disas_info_init(struct disassemble_info *dinfo)
{
return disas_info_init(dinfo, bfd_arch_powerpc,
bfd_mach_ppc, bfd_mach_ppc64,
NULL);
}
#endif /* DISAS */
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_CFI_REGS_H
#define _OBJTOOL_CFI_REGS_H
#define CFI_BP 1
#define CFI_SP CFI_BP
#define CFI_RA 32
#define CFI_NUM_REGS 33
#endif
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ARCH_ELF
#define _OBJTOOL_ARCH_ELF
#define R_NONE R_PPC_NONE
#define R_ABS64 R_PPC64_ADDR64
#define R_ABS32 R_PPC_ADDR32
#define R_DATA32 R_PPC_REL32
#define R_DATA64 R_PPC64_REL64
#define R_TEXT32 R_PPC_REL32
#define R_TEXT64 R_PPC64_REL32
#endif /* _OBJTOOL_ARCH_ELF */
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _PPC_ARCH_SPECIAL_H
#define _PPC_ARCH_SPECIAL_H
#define EX_ENTRY_SIZE 8
#define EX_ORIG_OFFSET 0
#define EX_NEW_OFFSET 4
#define JUMP_ENTRY_SIZE 16
#define JUMP_ORIG_OFFSET 0
#define JUMP_NEW_OFFSET 4
#define JUMP_KEY_OFFSET 8
#define ALT_ENTRY_SIZE 12
#define ALT_ORIG_OFFSET 0
#define ALT_NEW_OFFSET 4
#define ALT_FEATURE_OFFSET 8
#define ALT_ORIG_LEN_OFFSET 10
#define ALT_NEW_LEN_OFFSET 11
#endif /* _PPC_ARCH_SPECIAL_H */
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <stdlib.h>
#include <objtool/special.h>
#include <objtool/builtin.h>
bool arch_support_alt_relocation(struct special_alt *special_alt,
struct instruction *insn,
struct reloc *reloc)
{
exit(-1);
}
struct reloc *arch_find_switch_table(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size)
{
exit(-1);
}
const char *arch_cpu_feature_name(int feature_number)
{
return NULL;
}
+25
View File
@@ -0,0 +1,25 @@
objtool-y += decode.o
objtool-y += special.o
objtool-y += orc.o
inat_tables_script = ../arch/x86/tools/gen-insn-attr-x86.awk
inat_tables_maps = ../arch/x86/lib/x86-opcode-map.txt
$(OUTPUT)arch/x86/lib/inat-tables.c: $(inat_tables_script) $(inat_tables_maps)
$(call rule_mkdir)
$(Q)$(call echo-cmd,gen)$(AWK) -f $(inat_tables_script) $(inat_tables_maps) > $@
$(OUTPUT)arch/x86/decode.o: $(OUTPUT)arch/x86/lib/inat-tables.c
CFLAGS_decode.o += -I$(OUTPUT)arch/x86/lib
cpu_features = ../arch/x86/include/asm/cpufeatures.h
cpu_features_script = ../arch/x86/tools/gen-cpu-feature-names-x86.awk
$(OUTPUT)arch/x86/lib/cpu-feature-names.c: $(cpu_features_script) $(cpu_features)
$(call rule_mkdir)
$(Q)$(call echo-cmd,gen)$(AWK) -f $(cpu_features_script) $(cpu_features) > $@
$(OUTPUT)arch/x86/special.o: $(OUTPUT)arch/x86/lib/cpu-feature-names.c
CFLAGS_special.o += -I$(OUTPUT)arch/x86/lib
+978
View File
@@ -0,0 +1,978 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#include <stdio.h>
#include <stdlib.h>
#define unlikely(cond) (cond)
#include <asm/insn.h>
#include "../../../arch/x86/lib/inat.c"
#include "../../../arch/x86/lib/insn.c"
#define CONFIG_64BIT 1
#include <asm/nops.h>
#include <asm/orc_types.h>
#include <objtool/check.h>
#include <objtool/disas.h>
#include <objtool/elf.h>
#include <objtool/arch.h>
#include <objtool/warn.h>
#include <objtool/builtin.h>
#include <arch/elf.h>
const char *arch_reg_name[CFI_NUM_REGS] = {
"rax", "rcx", "rdx", "rbx",
"rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15",
"ra"
};
int arch_ftrace_match(const char *name)
{
return !strcmp(name, "__fentry__");
}
static int is_x86_64(const struct elf *elf)
{
switch (elf->ehdr.e_machine) {
case EM_X86_64:
return 1;
case EM_386:
return 0;
default:
ERROR("unexpected ELF machine type %d", elf->ehdr.e_machine);
return -1;
}
}
bool arch_callee_saved_reg(unsigned char reg)
{
switch (reg) {
case CFI_BP:
case CFI_BX:
case CFI_R12:
case CFI_R13:
case CFI_R14:
case CFI_R15:
return true;
case CFI_AX:
case CFI_CX:
case CFI_DX:
case CFI_SI:
case CFI_DI:
case CFI_SP:
case CFI_R8:
case CFI_R9:
case CFI_R10:
case CFI_R11:
case CFI_RA:
default:
return false;
}
}
/* Undo the effects of __pa_symbol() if necessary */
static unsigned long phys_to_virt(unsigned long pa)
{
s64 va = pa;
if (va > 0)
va &= ~(0x80000000);
return va;
}
s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
{
s64 addend = reloc_addend(reloc);
if (arch_pc_relative_reloc(reloc))
addend += insn->offset + insn->len - reloc_offset(reloc);
return phys_to_virt(addend);
}
static void scan_for_insn(struct section *sec, unsigned long offset,
unsigned long *insn_off, unsigned int *insn_len)
{
unsigned long o = 0;
struct insn insn;
while (1) {
insn_decode(&insn, sec->data->d_buf + o, sec_size(sec) - o,
INSN_MODE_64);
if (o + insn.length > offset) {
*insn_off = o;
*insn_len = insn.length;
return;
}
o += insn.length;
}
}
u64 arch_adjusted_addend(struct reloc *reloc)
{
unsigned int type = reloc_type(reloc);
s64 addend = reloc_addend(reloc);
unsigned long insn_off;
unsigned int insn_len;
if (type == R_X86_64_PLT32)
return addend + 4;
if (type != R_X86_64_PC32 || !is_text_sec(reloc->sec->base))
return addend;
scan_for_insn(reloc->sec->base, reloc_offset(reloc),
&insn_off, &insn_len);
return addend + insn_off + insn_len - reloc_offset(reloc);
}
unsigned long arch_jump_destination(struct instruction *insn)
{
return insn->offset + insn->len + insn->immediate;
}
bool arch_pc_relative_reloc(struct reloc *reloc)
{
/*
* All relocation types where P (the address of the target)
* is included in the computation.
*/
switch (reloc_type(reloc)) {
case R_X86_64_PC8:
case R_X86_64_PC16:
case R_X86_64_PC32:
case R_X86_64_PC64:
case R_X86_64_PLT32:
case R_X86_64_GOTPC32:
case R_X86_64_GOTPCREL:
return true;
default:
break;
}
return false;
}
#define ADD_OP(op) \
if (!(op = calloc(1, sizeof(*op)))) \
return -1; \
else for (*ops_list = op, ops_list = &op->next; op; op = NULL)
/*
* Helpers to decode ModRM/SIB:
*
* r/m| AX CX DX BX | SP | BP | SI DI |
* | R8 R9 R10 R11 | R12 | R13 | R14 R15 |
* Mod+----------------+-----+-----+---------+
* 00 | [r/m] |[SIB]|[IP+]| [r/m] |
* 01 | [r/m + d8] |[S+d]| [r/m + d8] |
* 10 | [r/m + d32] |[S+D]| [r/m + d32] |
* 11 | r/ m |
*/
#define mod_is_mem() (modrm_mod != 3)
#define mod_is_reg() (modrm_mod == 3)
#define is_RIP() ((modrm_rm & 7) == CFI_BP && modrm_mod == 0)
#define have_SIB() ((modrm_rm & 7) == CFI_SP && mod_is_mem())
/*
* Check the ModRM register. If there is a SIB byte then check with
* the SIB base register. But if the SIB base is 5 (i.e. CFI_BP) and
* ModRM mod is 0 then there is no base register.
*/
#define rm_is(reg) (have_SIB() ? \
sib_base == (reg) && sib_index == CFI_SP && \
(sib_base != CFI_BP || modrm_mod != 0) : \
modrm_rm == (reg))
#define rm_is_mem(reg) (mod_is_mem() && !is_RIP() && rm_is(reg))
#define rm_is_reg(reg) (mod_is_reg() && modrm_rm == (reg))
static bool has_notrack_prefix(struct insn *insn)
{
int i;
for (i = 0; i < insn->prefixes.nbytes; i++) {
if (insn->prefixes.bytes[i] == 0x3e)
return true;
}
return false;
}
int arch_decode_instruction(struct objtool_file *file, const struct section *sec,
unsigned long offset, unsigned int maxlen,
struct instruction *insn)
{
struct stack_op **ops_list = &insn->stack_ops;
const struct elf *elf = file->elf;
struct insn ins;
int x86_64, ret;
unsigned char op1, op2, op3, prefix,
rex = 0, rex_b = 0, rex_r = 0, rex_w = 0, rex_x = 0,
modrm = 0, modrm_mod = 0, modrm_rm = 0, modrm_reg = 0,
sib = 0, /* sib_scale = 0, */ sib_index = 0, sib_base = 0;
struct stack_op *op = NULL;
struct symbol *sym;
u64 imm;
x86_64 = is_x86_64(elf);
if (x86_64 == -1)
return -1;
ret = insn_decode(&ins, sec->data->d_buf + offset, maxlen,
x86_64 ? INSN_MODE_64 : INSN_MODE_32);
if (ret < 0) {
ERROR("can't decode instruction at %s:0x%lx", sec->name, offset);
return -1;
}
insn->len = ins.length;
insn->type = INSN_OTHER;
if (ins.vex_prefix.nbytes)
return 0;
prefix = ins.prefixes.bytes[0];
op1 = ins.opcode.bytes[0];
op2 = ins.opcode.bytes[1];
op3 = ins.opcode.bytes[2];
if (ins.rex_prefix.nbytes) {
rex = ins.rex_prefix.bytes[0];
rex_w = X86_REX_W(rex) >> 3;
rex_r = X86_REX_R(rex) >> 2;
rex_x = X86_REX_X(rex) >> 1;
rex_b = X86_REX_B(rex);
}
if (ins.modrm.nbytes) {
modrm = ins.modrm.bytes[0];
modrm_mod = X86_MODRM_MOD(modrm);
modrm_reg = X86_MODRM_REG(modrm) + 8*rex_r;
modrm_rm = X86_MODRM_RM(modrm) + 8*rex_b;
}
if (ins.sib.nbytes) {
sib = ins.sib.bytes[0];
/* sib_scale = X86_SIB_SCALE(sib); */
sib_index = X86_SIB_INDEX(sib) + 8*rex_x;
sib_base = X86_SIB_BASE(sib) + 8*rex_b;
}
switch (op1) {
case 0x1:
case 0x29:
if (rex_w && rm_is_reg(CFI_SP)) {
/* add/sub reg, %rsp */
ADD_OP(op) {
op->src.type = OP_SRC_ADD;
op->src.reg = modrm_reg;
op->dest.type = OP_DEST_REG;
op->dest.reg = CFI_SP;
}
}
break;
case 0x50 ... 0x57:
/* push reg */
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = (op1 & 0x7) + 8*rex_b;
op->dest.type = OP_DEST_PUSH;
}
break;
case 0x58 ... 0x5f:
/* pop reg */
ADD_OP(op) {
op->src.type = OP_SRC_POP;
op->dest.type = OP_DEST_REG;
op->dest.reg = (op1 & 0x7) + 8*rex_b;
}
break;
case 0x68:
case 0x6a:
/* push immediate */
ADD_OP(op) {
op->src.type = OP_SRC_CONST;
op->dest.type = OP_DEST_PUSH;
}
break;
case 0x70 ... 0x7f:
insn->type = INSN_JUMP_CONDITIONAL;
break;
case 0x80 ... 0x83:
/*
* 1000 00sw : mod OP r/m : immediate
*
* s - sign extend immediate
* w - imm8 / imm32
*
* OP: 000 ADD 100 AND
* 001 OR 101 SUB
* 010 ADC 110 XOR
* 011 SBB 111 CMP
*/
/* 64bit only */
if (!rex_w)
break;
/* %rsp target only */
if (!rm_is_reg(CFI_SP))
break;
imm = ins.immediate.value;
if (op1 & 2) { /* sign extend */
if (op1 & 1) { /* imm32 */
imm <<= 32;
imm = (s64)imm >> 32;
} else { /* imm8 */
imm <<= 56;
imm = (s64)imm >> 56;
}
}
switch (modrm_reg & 7) {
case 5:
imm = -imm;
fallthrough;
case 0:
/* add/sub imm, %rsp */
ADD_OP(op) {
op->src.type = OP_SRC_ADD;
op->src.reg = CFI_SP;
op->src.offset = imm;
op->dest.type = OP_DEST_REG;
op->dest.reg = CFI_SP;
}
break;
case 4:
/* and imm, %rsp */
ADD_OP(op) {
op->src.type = OP_SRC_AND;
op->src.reg = CFI_SP;
op->src.offset = ins.immediate.value;
op->dest.type = OP_DEST_REG;
op->dest.reg = CFI_SP;
}
break;
default:
/* ERROR ? */
break;
}
break;
case 0x89:
if (!rex_w)
break;
if (mod_is_reg()) {
/* mov reg, reg */
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = modrm_reg;
op->dest.type = OP_DEST_REG;
op->dest.reg = modrm_rm;
}
break;
}
/* skip RIP relative displacement */
if (is_RIP())
break;
/* skip nontrivial SIB */
if (have_SIB()) {
modrm_rm = sib_base;
if (sib_index != CFI_SP)
break;
}
/* mov %rsp, disp(%reg) */
if (modrm_reg == CFI_SP) {
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = CFI_SP;
op->dest.type = OP_DEST_REG_INDIRECT;
op->dest.reg = modrm_rm;
op->dest.offset = ins.displacement.value;
}
break;
}
fallthrough;
case 0x88:
if (!rex_w)
break;
if (rm_is_mem(CFI_BP)) {
/* mov reg, disp(%rbp) */
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = modrm_reg;
op->dest.type = OP_DEST_REG_INDIRECT;
op->dest.reg = CFI_BP;
op->dest.offset = ins.displacement.value;
}
break;
}
if (rm_is_mem(CFI_SP)) {
/* mov reg, disp(%rsp) */
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = modrm_reg;
op->dest.type = OP_DEST_REG_INDIRECT;
op->dest.reg = CFI_SP;
op->dest.offset = ins.displacement.value;
}
break;
}
break;
case 0x8b:
if (!rex_w)
break;
if (rm_is_mem(CFI_BP)) {
/* mov disp(%rbp), reg */
ADD_OP(op) {
op->src.type = OP_SRC_REG_INDIRECT;
op->src.reg = CFI_BP;
op->src.offset = ins.displacement.value;
op->dest.type = OP_DEST_REG;
op->dest.reg = modrm_reg;
}
break;
}
if (rm_is_mem(CFI_SP)) {
/* mov disp(%rsp), reg */
ADD_OP(op) {
op->src.type = OP_SRC_REG_INDIRECT;
op->src.reg = CFI_SP;
op->src.offset = ins.displacement.value;
op->dest.type = OP_DEST_REG;
op->dest.reg = modrm_reg;
}
break;
}
break;
case 0x8d:
if (mod_is_reg()) {
WARN("invalid LEA encoding at %s:0x%lx", sec->name, offset);
break;
}
/* skip non 64bit ops */
if (!rex_w)
break;
/* skip nontrivial SIB */
if (have_SIB()) {
modrm_rm = sib_base;
if (sib_index != CFI_SP)
break;
}
/* lea disp(%rip), %dst */
if (is_RIP()) {
insn->type = INSN_LEA_RIP;
break;
}
/* lea disp(%src), %dst */
ADD_OP(op) {
op->src.offset = ins.displacement.value;
if (!op->src.offset) {
/* lea (%src), %dst */
op->src.type = OP_SRC_REG;
} else {
/* lea disp(%src), %dst */
op->src.type = OP_SRC_ADD;
}
op->src.reg = modrm_rm;
op->dest.type = OP_DEST_REG;
op->dest.reg = modrm_reg;
}
break;
case 0x8f:
/* pop to mem */
ADD_OP(op) {
op->src.type = OP_SRC_POP;
op->dest.type = OP_DEST_MEM;
}
break;
case 0x90:
if (rex_b) /* XCHG %r8, %rax */
break;
if (prefix == 0xf3) /* REP NOP := PAUSE */
break;
insn->type = INSN_NOP;
break;
case 0x9c:
/* pushf */
ADD_OP(op) {
op->src.type = OP_SRC_CONST;
op->dest.type = OP_DEST_PUSHF;
}
break;
case 0x9d:
/* popf */
ADD_OP(op) {
op->src.type = OP_SRC_POPF;
op->dest.type = OP_DEST_MEM;
}
break;
case 0x0f:
if (op2 == 0x01) {
switch (insn_last_prefix_id(&ins)) {
case INAT_PFX_REPE:
case INAT_PFX_REPNE:
if (modrm == 0xca)
/* eretu/erets */
insn->type = INSN_SYSRET;
break;
default:
if (modrm == 0xca)
insn->type = INSN_CLAC;
else if (modrm == 0xcb)
insn->type = INSN_STAC;
break;
}
} else if (op2 >= 0x80 && op2 <= 0x8f) {
insn->type = INSN_JUMP_CONDITIONAL;
} else if (op2 == 0x05 || op2 == 0x34) {
/* syscall, sysenter */
insn->type = INSN_SYSCALL;
} else if (op2 == 0x07 || op2 == 0x35) {
/* sysret, sysexit */
insn->type = INSN_SYSRET;
} else if (op2 == 0x0b || op2 == 0xb9) {
/* ud2, ud1 */
insn->type = INSN_BUG;
} else if (op2 == 0x1f) {
/* 0f 1f /0 := NOPL */
if (modrm_reg == 0)
insn->type = INSN_NOP;
} else if (op2 == 0x1e) {
if (prefix == 0xf3 && (modrm == 0xfa || modrm == 0xfb))
insn->type = INSN_ENDBR;
} else if (op2 == 0x38 && op3 == 0xf8) {
if (ins.prefixes.nbytes == 1 &&
ins.prefixes.bytes[0] == 0xf2) {
/* ENQCMD cannot be used in the kernel. */
WARN("ENQCMD instruction at %s:%lx", sec->name, offset);
}
} else if (op2 == 0xa0 || op2 == 0xa8) {
/* push fs/gs */
ADD_OP(op) {
op->src.type = OP_SRC_CONST;
op->dest.type = OP_DEST_PUSH;
}
} else if (op2 == 0xa1 || op2 == 0xa9) {
/* pop fs/gs */
ADD_OP(op) {
op->src.type = OP_SRC_POP;
op->dest.type = OP_DEST_MEM;
}
}
break;
case 0xc9:
/*
* leave
*
* equivalent to:
* mov bp, sp
* pop bp
*/
ADD_OP(op) {
op->src.type = OP_SRC_REG;
op->src.reg = CFI_BP;
op->dest.type = OP_DEST_REG;
op->dest.reg = CFI_SP;
}
ADD_OP(op) {
op->src.type = OP_SRC_POP;
op->dest.type = OP_DEST_REG;
op->dest.reg = CFI_BP;
}
break;
case 0xcc:
/* int3 */
insn->type = INSN_TRAP;
break;
case 0xe3:
/* jecxz/jrcxz */
insn->type = INSN_JUMP_CONDITIONAL;
break;
case 0xe9:
case 0xeb:
insn->type = INSN_JUMP_UNCONDITIONAL;
break;
case 0xc2:
case 0xc3:
insn->type = INSN_RETURN;
break;
case 0xc7: /* mov imm, r/m */
if (!opts.noinstr)
break;
if (ins.length == 3+4+4 && !strncmp(sec->name, ".init.text", 10)) {
struct reloc *immr, *disp;
struct symbol *func;
int idx;
immr = find_reloc_by_dest(elf, (void *)sec, offset+3);
disp = find_reloc_by_dest(elf, (void *)sec, offset+7);
if (!immr || strncmp(immr->sym->name, "pv_ops", 6))
break;
idx = pv_ops_idx_off(immr->sym->name);
if (idx < 0)
break;
idx += (reloc_addend(immr) + 8) / sizeof(void *);
func = disp->sym;
if (disp->sym->type == STT_SECTION)
func = find_symbol_by_offset(disp->sym->sec, reloc_addend(disp));
if (!func) {
ERROR("no func for pv_ops[]");
return -1;
}
objtool_pv_add(file, idx, func);
}
break;
case 0xcf: /* iret */
/*
* Handle sync_core(), which has an IRET to self.
* All other IRET are in STT_NONE entry code.
*/
sym = find_symbol_containing(sec, offset);
if (sym && sym->type == STT_FUNC) {
ADD_OP(op) {
/* add $40, %rsp */
op->src.type = OP_SRC_ADD;
op->src.reg = CFI_SP;
op->src.offset = 5*8;
op->dest.type = OP_DEST_REG;
op->dest.reg = CFI_SP;
}
break;
}
fallthrough;
case 0xca: /* retf */
case 0xcb: /* retf */
insn->type = INSN_SYSRET;
break;
case 0xd6: /* udb */
insn->type = INSN_BUG;
break;
case 0xe0: /* loopne */
case 0xe1: /* loope */
case 0xe2: /* loop */
insn->type = INSN_JUMP_CONDITIONAL;
break;
case 0xe8:
insn->type = INSN_CALL;
/*
* For the impact on the stack, a CALL behaves like
* a PUSH of an immediate value (the return address).
*/
ADD_OP(op) {
op->src.type = OP_SRC_CONST;
op->dest.type = OP_DEST_PUSH;
}
break;
case 0xfc:
insn->type = INSN_CLD;
break;
case 0xfd:
insn->type = INSN_STD;
break;
case 0xff:
if (modrm_reg == 2 || modrm_reg == 3) {
insn->type = INSN_CALL_DYNAMIC;
if (has_notrack_prefix(&ins))
WARN("notrack prefix found at %s:0x%lx", sec->name, offset);
} else if (modrm_reg == 4) {
insn->type = INSN_JUMP_DYNAMIC;
if (has_notrack_prefix(&ins))
WARN("notrack prefix found at %s:0x%lx", sec->name, offset);
} else if (modrm_reg == 5) {
/* jmpf */
insn->type = INSN_SYSRET;
} else if (modrm_reg == 6) {
/* push from mem */
ADD_OP(op) {
op->src.type = OP_SRC_CONST;
op->dest.type = OP_DEST_PUSH;
}
}
break;
default:
break;
}
if (ins.immediate.nbytes) {
insn->immediate = ins.immediate.value;
insn->immediate_len = ins.immediate.nbytes;
} else if (ins.displacement.nbytes) {
insn->immediate = ins.displacement.value;
insn->immediate_len = ins.displacement.nbytes;
}
return 0;
}
size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
unsigned char *buf)
{
size_t len;
len = insn->len - insn->immediate_len;
memcpy(buf, insn->sec->data->d_buf + insn->offset, len);
return len;
}
void arch_initial_func_cfi_state(struct cfi_init_state *state)
{
int i;
for (i = 0; i < CFI_NUM_REGS; i++) {
state->regs[i].base = CFI_UNDEFINED;
state->regs[i].offset = 0;
}
/* initial CFA (call frame address) */
state->cfa.base = CFI_SP;
state->cfa.offset = 8;
/* initial RA (return address) */
state->regs[CFI_RA].base = CFI_CFA;
state->regs[CFI_RA].offset = -8;
}
const char *arch_nop_insn(int len)
{
static const char nops[5][5] = {
{ BYTES_NOP1 },
{ BYTES_NOP2 },
{ BYTES_NOP3 },
{ BYTES_NOP4 },
{ BYTES_NOP5 },
};
if (len < 1 || len > 5) {
ERROR("invalid NOP size: %d\n", len);
return NULL;
}
return nops[len-1];
}
#define BYTE_RET 0xC3
const char *arch_ret_insn(int len)
{
static const char ret[5][5] = {
{ BYTE_RET },
{ BYTE_RET, 0xcc },
{ BYTE_RET, 0xcc, BYTES_NOP1 },
{ BYTE_RET, 0xcc, BYTES_NOP2 },
{ BYTE_RET, 0xcc, BYTES_NOP3 },
};
if (len < 1 || len > 5) {
ERROR("invalid RET size: %d\n", len);
return NULL;
}
return ret[len-1];
}
int arch_decode_hint_reg(u8 sp_reg, int *base)
{
switch (sp_reg) {
case ORC_REG_UNDEFINED:
*base = CFI_UNDEFINED;
break;
case ORC_REG_AX:
*base = CFI_AX;
break;
case ORC_REG_DX:
*base = CFI_DX;
break;
case ORC_REG_SP:
*base = CFI_SP;
break;
case ORC_REG_BP:
*base = CFI_BP;
break;
case ORC_REG_DI:
*base = CFI_DI;
break;
case ORC_REG_R10:
*base = CFI_R10;
break;
case ORC_REG_R13:
*base = CFI_R13;
break;
case ORC_REG_SP_INDIRECT:
*base = CFI_SP_INDIRECT;
break;
case ORC_REG_BP_INDIRECT:
*base = CFI_BP_INDIRECT;
break;
default:
return -1;
}
return 0;
}
bool arch_is_retpoline(struct symbol *sym)
{
return !strncmp(sym->name, "__x86_indirect_", 15) ||
!strncmp(sym->name, "__pi___x86_indirect_", 20);
}
bool arch_is_rethunk(struct symbol *sym)
{
return !strcmp(sym->name, "__x86_return_thunk") ||
!strcmp(sym->name, "__pi___x86_return_thunk");
}
bool arch_is_embedded_insn(struct symbol *sym)
{
return !strcmp(sym->name, "retbleed_return_thunk") ||
!strcmp(sym->name, "srso_alias_safe_ret") ||
!strcmp(sym->name, "srso_safe_ret");
}
unsigned int arch_reloc_size(struct reloc *reloc)
{
switch (reloc_type(reloc)) {
case R_X86_64_32:
case R_X86_64_32S:
case R_X86_64_PC32:
case R_X86_64_PLT32:
return 4;
default:
return 8;
}
}
bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc)
{
switch (reloc_type(reloc)) {
case R_X86_64_32:
case R_X86_64_32S:
case R_X86_64_64:
return true;
default:
return false;
}
}
#ifdef DISAS
int arch_disas_info_init(struct disassemble_info *dinfo)
{
return disas_info_init(dinfo, bfd_arch_i386,
bfd_mach_i386_i386, bfd_mach_x86_64,
"att");
}
#endif /* DISAS */
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_CFI_REGS_H
#define _OBJTOOL_CFI_REGS_H
#define CFI_AX 0
#define CFI_CX 1
#define CFI_DX 2
#define CFI_BX 3
#define CFI_SP 4
#define CFI_BP 5
#define CFI_SI 6
#define CFI_DI 7
#define CFI_R8 8
#define CFI_R9 9
#define CFI_R10 10
#define CFI_R11 11
#define CFI_R12 12
#define CFI_R13 13
#define CFI_R14 14
#define CFI_R15 15
#define CFI_RA 16
#define CFI_NUM_REGS 17
#endif /* _OBJTOOL_CFI_REGS_H */
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ARCH_ELF
#define _OBJTOOL_ARCH_ELF
#define R_NONE R_X86_64_NONE
#define R_ABS32 R_X86_64_32
#define R_ABS64 R_X86_64_64
#define R_DATA32 R_X86_64_PC32
#define R_DATA64 R_X86_64_PC32
#define R_TEXT32 R_X86_64_PC32
#define R_TEXT64 R_X86_64_PC32
#endif /* _OBJTOOL_ARCH_ELF */
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _X86_ARCH_SPECIAL_H
#define _X86_ARCH_SPECIAL_H
#define EX_ENTRY_SIZE 12
#define EX_ORIG_OFFSET 0
#define EX_NEW_OFFSET 4
#define JUMP_ENTRY_SIZE 16
#define JUMP_ORIG_OFFSET 0
#define JUMP_NEW_OFFSET 4
#define JUMP_KEY_OFFSET 8
#define ALT_ENTRY_SIZE 14
#define ALT_ORIG_OFFSET 0
#define ALT_NEW_OFFSET 4
#define ALT_FEATURE_OFFSET 8
#define ALT_ORIG_LEN_OFFSET 12
#define ALT_NEW_LEN_OFFSET 13
#endif /* _X86_ARCH_SPECIAL_H */
+192
View File
@@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/objtool_types.h>
#include <asm/orc_types.h>
#include <objtool/check.h>
#include <objtool/orc.h>
#include <objtool/warn.h>
int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, struct instruction *insn)
{
struct cfi_reg *bp = &cfi->regs[CFI_BP];
memset(orc, 0, sizeof(*orc));
if (!cfi) {
/*
* This is usually either unreachable nops/traps (which don't
* trigger unreachable instruction warnings), or
* STACK_FRAME_NON_STANDARD functions.
*/
orc->type = ORC_TYPE_UNDEFINED;
return 0;
}
switch (cfi->type) {
case UNWIND_HINT_TYPE_UNDEFINED:
orc->type = ORC_TYPE_UNDEFINED;
return 0;
case UNWIND_HINT_TYPE_END_OF_STACK:
orc->type = ORC_TYPE_END_OF_STACK;
return 0;
case UNWIND_HINT_TYPE_CALL:
orc->type = ORC_TYPE_CALL;
break;
case UNWIND_HINT_TYPE_REGS:
orc->type = ORC_TYPE_REGS;
break;
case UNWIND_HINT_TYPE_REGS_PARTIAL:
orc->type = ORC_TYPE_REGS_PARTIAL;
break;
default:
ERROR_INSN(insn, "unknown unwind hint type %d", cfi->type);
return -1;
}
orc->signal = cfi->signal;
switch (cfi->cfa.base) {
case CFI_AX:
orc->sp_reg = ORC_REG_AX;
break;
case CFI_DX:
orc->sp_reg = ORC_REG_DX;
break;
case CFI_SP:
orc->sp_reg = ORC_REG_SP;
break;
case CFI_BP:
orc->sp_reg = ORC_REG_BP;
break;
case CFI_DI:
orc->sp_reg = ORC_REG_DI;
break;
case CFI_R10:
orc->sp_reg = ORC_REG_R10;
break;
case CFI_R13:
orc->sp_reg = ORC_REG_R13;
break;
case CFI_SP_INDIRECT:
orc->sp_reg = ORC_REG_SP_INDIRECT;
break;
case CFI_BP_INDIRECT:
orc->sp_reg = ORC_REG_BP_INDIRECT;
break;
default:
ERROR_INSN(insn, "unknown CFA base reg %d", cfi->cfa.base);
return -1;
}
switch (bp->base) {
case CFI_UNDEFINED:
orc->bp_reg = ORC_REG_UNDEFINED;
break;
case CFI_CFA:
orc->bp_reg = ORC_REG_PREV_SP;
break;
case CFI_BP:
orc->bp_reg = ORC_REG_BP;
break;
default:
ERROR_INSN(insn, "unknown BP base reg %d", bp->base);
return -1;
}
orc->sp_offset = cfi->cfa.offset;
orc->bp_offset = bp->offset;
return 0;
}
int write_orc_entry(struct elf *elf, struct section *orc_sec,
struct section *ip_sec, unsigned int idx,
struct section *insn_sec, unsigned long insn_off,
struct orc_entry *o)
{
struct orc_entry *orc;
/* populate ORC data */
orc = (struct orc_entry *)orc_sec->data->d_buf + idx;
memcpy(orc, o, sizeof(*orc));
orc->sp_offset = bswap_if_needed(elf, orc->sp_offset);
orc->bp_offset = bswap_if_needed(elf, orc->bp_offset);
/* populate reloc for ip */
if (!elf_init_reloc_text_sym(elf, ip_sec, idx * sizeof(int), idx,
insn_sec, insn_off))
return -1;
return 0;
}
static const char *reg_name(unsigned int reg)
{
switch (reg) {
case ORC_REG_PREV_SP:
return "prevsp";
case ORC_REG_AX:
return "ax";
case ORC_REG_DX:
return "dx";
case ORC_REG_BP:
return "bp";
case ORC_REG_SP:
return "sp";
case ORC_REG_DI:
return "di";
case ORC_REG_R10:
return "r10";
case ORC_REG_R13:
return "r13";
case ORC_REG_SP_INDIRECT:
return "sp(ind)";
case ORC_REG_BP_INDIRECT:
return "bp(ind)";
default:
return "?";
}
}
static const char *orc_type_name(unsigned int type)
{
switch (type) {
case ORC_TYPE_UNDEFINED:
return "(und)";
case ORC_TYPE_END_OF_STACK:
return "end";
case ORC_TYPE_CALL:
return "call";
case ORC_TYPE_REGS:
return "regs";
case ORC_TYPE_REGS_PARTIAL:
return "regs (partial)";
default:
return "?";
}
}
static void print_reg(unsigned int reg, int offset)
{
if (reg == ORC_REG_BP_INDIRECT)
printf("(bp%+d)", offset);
else if (reg == ORC_REG_SP_INDIRECT)
printf("(sp)%+d", offset);
else if (reg == ORC_REG_UNDEFINED)
printf("(und)");
else
printf("%s%+d", reg_name(reg), offset);
}
void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i)
{
printf("type:%s", orc_type_name(orc[i].type));
printf(" sp:");
print_reg(orc[i].sp_reg, bswap_if_needed(dummy_elf, orc[i].sp_offset));
printf(" bp:");
print_reg(orc[i].bp_reg, bswap_if_needed(dummy_elf, orc[i].bp_offset));
printf(" signal:%d\n", orc[i].signal);
}
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <objtool/special.h>
#include <objtool/builtin.h>
#include <objtool/warn.h>
#include <asm/cpufeatures.h>
/* cpu feature name array generated from cpufeatures.h */
#include "cpu-feature-names.c"
void arch_handle_alternative(struct special_alt *alt)
{
static struct special_alt *group, *prev;
/*
* Recompute orig_len for nested ALTERNATIVE()s.
*/
if (group && group->orig_sec == alt->orig_sec &&
group->orig_off == alt->orig_off) {
struct special_alt *iter = group;
for (;;) {
unsigned int len = max(iter->orig_len, alt->orig_len);
iter->orig_len = alt->orig_len = len;
if (iter == prev)
break;
iter = list_next_entry(iter, list);
}
} else group = alt;
prev = alt;
}
bool arch_support_alt_relocation(struct special_alt *special_alt,
struct instruction *insn,
struct reloc *reloc)
{
return true;
}
/*
* There are 3 basic jump table patterns:
*
* 1. jmpq *[rodata addr](,%reg,8)
*
* This is the most common case by far. It jumps to an address in a simple
* jump table which is stored in .rodata.
*
* 2. jmpq *[rodata addr](%rip)
*
* This is caused by a rare GCC quirk, currently only seen in three driver
* functions in the kernel, only with certain obscure non-distro configs.
*
* As part of an optimization, GCC makes a copy of an existing switch jump
* table, modifies it, and then hard-codes the jump (albeit with an indirect
* jump) to use a single entry in the table. The rest of the jump table and
* some of its jump targets remain as dead code.
*
* In such a case we can just crudely ignore all unreachable instruction
* warnings for the entire object file. Ideally we would just ignore them
* for the function, but that would require redesigning the code quite a
* bit. And honestly that's just not worth doing: unreachable instruction
* warnings are of questionable value anyway, and this is such a rare issue.
*
* 3. mov [rodata addr],%reg1
* ... some instructions ...
* jmpq *(%reg1,%reg2,8)
*
* This is a fairly uncommon pattern which is new for GCC 6. As of this
* writing, there are 11 occurrences of it in the allmodconfig kernel.
*
* As of GCC 7 there are quite a few more of these and the 'in between' code
* is significant. Esp. with KASAN enabled some of the code between the mov
* and jmpq uses .rodata itself, which can confuse things.
*
* TODO: Once we have DWARF CFI and smarter instruction decoding logic,
* ensure the same register is used in the mov and jump instructions.
*
* NOTE: MITIGATION_RETPOLINE made it harder still to decode dynamic jumps.
*/
struct reloc *arch_find_switch_table(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size)
{
struct reloc *text_reloc, *rodata_reloc;
struct section *table_sec;
unsigned long table_offset;
/* look for a relocation which references .rodata */
text_reloc = find_reloc_by_dest_range(file->elf, insn->sec,
insn->offset, insn->len);
if (!text_reloc || !is_sec_sym(text_reloc->sym) ||
!text_reloc->sym->sec->rodata)
return NULL;
table_offset = reloc_addend(text_reloc);
table_sec = text_reloc->sym->sec;
if (reloc_type(text_reloc) == R_X86_64_PC32)
table_offset += 4;
/*
* Make sure the .rodata address isn't associated with a
* symbol. GCC jump tables are anonymous data.
*
* Also support C jump tables which are in the same format as
* switch jump tables. For objtool to recognize them, they
* need to be placed in the C_JUMP_TABLE_SECTION section. They
* have symbols associated with them.
*/
if (find_symbol_containing(table_sec, table_offset) &&
strcmp(table_sec->name, C_JUMP_TABLE_SECTION))
return NULL;
/*
* Each table entry has a rela associated with it. The rela
* should reference text in the same function as the original
* instruction.
*/
rodata_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
if (!rodata_reloc)
return NULL;
/*
* Use of RIP-relative switch jumps is quite rare, and
* indicates a rare GCC quirk/bug which can leave dead
* code behind.
*/
if (!file->ignore_unreachables && reloc_type(text_reloc) == R_X86_64_PC32) {
WARN_INSN(insn, "ignoring unreachables due to jump table quirk");
file->ignore_unreachables = true;
}
*table_size = 0;
return rodata_reloc;
}
const char *arch_cpu_feature_name(int feature_number)
{
return (feature_number < ARRAY_SIZE(cpu_feature_names)) ?
cpu_feature_names[feature_number] : NULL;
}
+348
View File
@@ -0,0 +1,348 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#include <subcmd/parse-options.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <objtool/builtin.h>
#include <objtool/objtool.h>
#include <objtool/warn.h>
#define ORIG_SUFFIX ".orig"
int orig_argc;
static char **orig_argv;
const char *objname;
struct opts opts;
static const char * const check_usage[] = {
"objtool <actions> [<options>] file.o",
NULL,
};
static const char * const env_usage[] = {
"OBJTOOL_ARGS=\"<options>\"",
NULL,
};
static int parse_dump(const struct option *opt, const char *str, int unset)
{
if (!str || !strcmp(str, "orc")) {
opts.dump_orc = true;
return 0;
}
return -1;
}
static int parse_hacks(const struct option *opt, const char *str, int unset)
{
bool found = false;
/*
* Use strstr() as a lazy method of checking for comma-separated
* options.
*
* No string provided == enable all options.
*/
if (!str || strstr(str, "jump_label")) {
opts.hack_jump_label = true;
found = true;
}
if (!str || strstr(str, "noinstr")) {
opts.hack_noinstr = true;
found = true;
}
if (!str || strstr(str, "skylake")) {
opts.hack_skylake = true;
found = true;
}
return found ? 0 : -1;
}
static const struct option check_options[] = {
OPT_GROUP("Actions:"),
OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"),
OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks),
OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"),
OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"),
OPT_BOOLEAN(0, "noabs", &opts.noabs, "reject absolute references in allocatable sections"),
OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"),
OPT_BOOLEAN(0, "orc", &opts.orc, "generate ORC metadata"),
OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"),
OPT_BOOLEAN(0, "rethunk", &opts.rethunk, "validate and annotate rethunk usage"),
OPT_BOOLEAN(0, "unret", &opts.unret, "validate entry unret placement"),
OPT_INTEGER(0, "prefix", &opts.prefix, "generate or grow prefix symbols for N-byte function padding"),
OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"),
OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate frame pointer rules"),
OPT_BOOLEAN('t', "static-call", &opts.static_call, "annotate static calls"),
OPT_BOOLEAN('u', "uaccess", &opts.uaccess, "validate uaccess rules for SMAP"),
OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump),
OPT_GROUP("Options:"),
OPT_BOOLEAN(0, "cfi", &opts.cfi, "grow kCFI preamble symbols (use with --prefix)"),
OPT_BOOLEAN(0, "fineibt", &opts.fineibt, "create .cfi_sites section for FineIBT"),
OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"),
OPT_BOOLEAN(0, "backup", &opts.backup, "create backup (.orig) file on warning/error"),
OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"),
OPT_BOOLEAN(0, "link", &opts.link, "object is a linked object"),
OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"),
OPT_BOOLEAN(0, "mnop", &opts.mnop, "nop out mcount call sites"),
OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"),
OPT_STRING('o', "output", &opts.output, "file", "output file name"),
OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"),
OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"),
OPT_STRING(0, "trace", &opts.trace, "func", "trace function validation"),
OPT_BOOLEAN('v', "verbose", &opts.verbose, "verbose warnings"),
OPT_BOOLEAN(0, "werror", &opts.werror, "return error on warnings"),
OPT_BOOLEAN(0, "wide", &opts.wide, "wide output"),
OPT_END(),
};
int cmd_parse_options(int argc, const char **argv, const char * const usage[])
{
const char *envv[16] = { };
char *env;
int envc;
env = getenv("OBJTOOL_ARGS");
if (env) {
envv[0] = "OBJTOOL_ARGS";
for (envc = 1; envc < ARRAY_SIZE(envv); ) {
envv[envc++] = env;
env = strchr(env, ' ');
if (!env)
break;
*env = '\0';
env++;
}
parse_options(envc, envv, check_options, env_usage, 0);
}
env = getenv("OBJTOOL_VERBOSE");
if (env && !strcmp(env, "1"))
opts.verbose = true;
argc = parse_options(argc, argv, check_options, usage, 0);
if (argc != 1)
usage_with_options(usage, check_options);
return argc;
}
static bool opts_valid(void)
{
if (opts.mnop && !opts.mcount) {
ERROR("--mnop requires --mcount");
return false;
}
if (opts.noinstr && !opts.link) {
ERROR("--noinstr requires --link");
return false;
}
if (opts.ibt && !opts.link) {
ERROR("--ibt requires --link");
return false;
}
if (opts.unret && !opts.link) {
ERROR("--unret requires --link");
return false;
}
if (opts.cfi && !opts.prefix) {
ERROR("--cfi requires --prefix");
return false;
}
if (opts.fineibt && !opts.cfi) {
ERROR("--fineibt requires --cfi");
return false;
}
if (opts.disas ||
opts.hack_jump_label ||
opts.hack_noinstr ||
opts.ibt ||
opts.mcount ||
opts.noabs ||
opts.noinstr ||
opts.orc ||
opts.retpoline ||
opts.rethunk ||
opts.sls ||
opts.stackval ||
opts.static_call ||
opts.uaccess) {
if (opts.dump_orc) {
ERROR("--dump can't be combined with other actions");
return false;
}
return true;
}
if (opts.dump_orc)
return true;
ERROR("At least one action required");
return false;
}
static int copy_file(const char *src, const char *dst)
{
size_t to_copy, copied;
int dst_fd, src_fd;
struct stat stat;
off_t offset = 0;
src_fd = open(src, O_RDONLY);
if (src_fd == -1) {
ERROR("can't open %s for reading: %s", src, strerror(errno));
return 1;
}
dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0400);
if (dst_fd == -1) {
ERROR("can't open %s for writing: %s", dst, strerror(errno));
return 1;
}
if (fstat(src_fd, &stat) == -1) {
ERROR_GLIBC("fstat");
return 1;
}
if (fchmod(dst_fd, stat.st_mode) == -1) {
ERROR_GLIBC("fchmod");
return 1;
}
for (to_copy = stat.st_size; to_copy > 0; to_copy -= copied) {
copied = sendfile(dst_fd, src_fd, &offset, to_copy);
if (copied == -1) {
ERROR_GLIBC("sendfile");
return 1;
}
}
close(dst_fd);
close(src_fd);
return 0;
}
static void save_argv(int argc, const char **argv)
{
orig_argv = calloc(argc, sizeof(char *));
if (!orig_argv) {
ERROR_GLIBC("calloc");
exit(1);
}
for (int i = 0; i < argc; i++) {
orig_argv[i] = strdup(argv[i]);
if (!orig_argv[i]) {
ERROR_GLIBC("strdup(%s)", argv[i]);
exit(1);
}
}
}
int make_backup(void)
{
char *backup;
/*
* Make a backup before kbuild deletes the file so the error
* can be recreated without recompiling or relinking.
*/
backup = malloc(strlen(objname) + strlen(ORIG_SUFFIX) + 1);
if (!backup) {
ERROR_GLIBC("malloc");
return 1;
}
strcpy(backup, objname);
strcat(backup, ORIG_SUFFIX);
if (copy_file(objname, backup))
return 1;
/*
* Print the cmdline args to make it easier to recreate.
*/
fprintf(stderr, "%s", orig_argv[0]);
for (int i = 1; i < orig_argc; i++) {
char *arg = orig_argv[i];
/* Modify the printed args to use the backup */
if (!opts.output && !strcmp(arg, objname))
fprintf(stderr, " %s -o %s", backup, objname);
else
fprintf(stderr, " %s", arg);
}
fprintf(stderr, "\n");
return 0;
}
int objtool_run(int argc, const char **argv)
{
struct objtool_file *file;
int ret = 0;
orig_argc = argc;
save_argv(argc, argv);
cmd_parse_options(argc, argv, check_usage);
if (!opts_valid())
return 1;
objname = argv[0];
if (opts.dump_orc)
return orc_dump(objname);
if (!opts.dryrun && opts.output) {
/* copy original .o file to output file */
if (copy_file(objname, opts.output))
return 1;
/* from here on, work directly on the output file */
objname = opts.output;
}
file = objtool_open_read(objname);
if (!file)
return 1;
if (!opts.link && has_multiple_files(file->elf)) {
ERROR("Linked object requires --link");
return 1;
}
ret = check(file);
if (ret)
return ret;
if (!opts.dryrun && file->elf->changed && elf_write(file->elf))
return 1;
return elf_close(file->elf);
}
+54
View File
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <subcmd/parse-options.h>
#include <string.h>
#include <stdlib.h>
#include <objtool/builtin.h>
#include <objtool/objtool.h>
#include <objtool/klp.h>
struct subcmd {
const char *name;
const char *description;
int (*fn)(int, const char **);
};
static struct subcmd subcmds[] = {
{ "checksum", "Generate per-function checksums", cmd_klp_checksum, },
{ "diff", "Generate binary diff of two object files", cmd_klp_diff, },
{ "post-link", "Finalize klp symbols/relocs after module linking", cmd_klp_post_link, },
};
static void cmd_klp_usage(void)
{
fprintf(stderr, "usage: objtool klp <subcommand> [<options>]\n\n");
fprintf(stderr, "Subcommands:\n");
for (int i = 0; i < ARRAY_SIZE(subcmds); i++) {
struct subcmd *cmd = &subcmds[i];
fprintf(stderr, " %s\t%s\n", cmd->name, cmd->description);
}
exit(1);
}
int cmd_klp(int argc, const char **argv)
{
argc--;
argv++;
if (!argc)
cmd_klp_usage();
if (argc) {
for (int i = 0; i < ARRAY_SIZE(subcmds); i++) {
struct subcmd *cmd = &subcmds[i];
if (!strcmp(cmd->name, argv[0]))
return cmd->fn(argc, argv);
}
}
cmd_klp_usage();
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _ARCH_H
#define _ARCH_H
#include <stdbool.h>
#include <linux/list.h>
#include <objtool/objtool.h>
#include <objtool/cfi.h>
enum insn_type {
INSN_JUMP_CONDITIONAL,
INSN_JUMP_UNCONDITIONAL,
INSN_JUMP_DYNAMIC,
INSN_JUMP_DYNAMIC_CONDITIONAL,
INSN_CALL,
INSN_CALL_DYNAMIC,
INSN_RETURN,
INSN_SYSCALL,
INSN_SYSRET,
INSN_BUG,
INSN_NOP,
INSN_STAC,
INSN_CLAC,
INSN_STD,
INSN_CLD,
INSN_TRAP,
INSN_ENDBR,
INSN_LEA_RIP,
INSN_OTHER,
};
enum op_dest_type {
OP_DEST_REG,
OP_DEST_REG_INDIRECT,
OP_DEST_MEM,
OP_DEST_PUSH,
OP_DEST_PUSHF,
};
struct op_dest {
enum op_dest_type type;
unsigned char reg;
int offset;
};
enum op_src_type {
OP_SRC_REG,
OP_SRC_REG_INDIRECT,
OP_SRC_CONST,
OP_SRC_POP,
OP_SRC_POPF,
OP_SRC_ADD,
OP_SRC_AND,
};
struct op_src {
enum op_src_type type;
unsigned char reg;
int offset;
};
struct stack_op {
struct stack_op *next;
struct op_dest dest;
struct op_src src;
};
struct instruction;
int arch_ftrace_match(const char *name);
void arch_initial_func_cfi_state(struct cfi_init_state *state);
int arch_decode_instruction(struct objtool_file *file, const struct section *sec,
unsigned long offset, unsigned int maxlen,
struct instruction *insn);
size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
unsigned char *buf);
bool arch_callee_saved_reg(unsigned char reg);
unsigned long arch_jump_destination(struct instruction *insn);
s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc);
u64 arch_adjusted_addend(struct reloc *reloc);
const char *arch_nop_insn(int len);
const char *arch_ret_insn(int len);
int arch_decode_hint_reg(u8 sp_reg, int *base);
bool arch_is_retpoline(struct symbol *sym);
bool arch_is_rethunk(struct symbol *sym);
bool arch_is_embedded_insn(struct symbol *sym);
int arch_rewrite_retpolines(struct objtool_file *file);
bool arch_pc_relative_reloc(struct reloc *reloc);
bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc);
unsigned int arch_reloc_size(struct reloc *reloc);
unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table);
extern const char *arch_reg_name[CFI_NUM_REGS];
#ifdef DISAS
#include <bfd.h>
#include <dis-asm.h>
int arch_disas_info_init(struct disassemble_info *dinfo);
#endif /* DISAS */
#endif /* _ARCH_H */
@@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _BUILTIN_H
#define _BUILTIN_H
#include <subcmd/parse-options.h>
struct opts {
/* actions: */
bool checksum;
const char *disas;
bool dump_orc;
bool hack_jump_label;
bool hack_noinstr;
bool hack_skylake;
bool ibt;
bool mcount;
bool noabs;
bool noinstr;
bool orc;
int prefix;
bool retpoline;
bool rethunk;
bool unret;
bool sls;
bool stackval;
bool static_call;
bool uaccess;
/* options: */
bool backtrace;
bool backup;
bool cfi;
const char *debug_checksum;
bool dryrun;
bool fineibt;
bool link;
bool mnop;
bool module;
bool no_unreachable;
const char *output;
bool sec_address;
bool stats;
const char *trace;
bool verbose;
bool werror;
bool wide;
};
extern struct opts opts;
int cmd_parse_options(int argc, const char **argv, const char * const usage[]);
int objtool_run(int argc, const char **argv);
int make_backup(void);
int cmd_klp(int argc, const char **argv);
#endif /* _BUILTIN_H */
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _OBJTOOL_CFI_H
#define _OBJTOOL_CFI_H
#include <arch/cfi_regs.h>
#include <linux/list.h>
#define CFI_UNDEFINED -1
#define CFI_CFA -2
#define CFI_SP_INDIRECT -3
#define CFI_BP_INDIRECT -4
struct cfi_reg {
int base;
int offset;
};
struct cfi_init_state {
struct cfi_reg regs[CFI_NUM_REGS];
struct cfi_reg cfa;
};
struct cfi_state {
struct hlist_node hash; /* must be first, cficmp() */
struct cfi_reg regs[CFI_NUM_REGS];
struct cfi_reg vals[CFI_NUM_REGS];
struct cfi_reg cfa;
int stack_size;
int drap_reg, drap_offset;
unsigned char type;
bool bp_scratch;
bool drap;
bool signal;
bool end;
bool force_undefined;
};
#endif /* _OBJTOOL_CFI_H */
@@ -0,0 +1,192 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _CHECK_H
#define _CHECK_H
#include <stdbool.h>
#include <objtool/cfi.h>
#include <objtool/arch.h>
struct insn_state {
struct cfi_state cfi;
unsigned int uaccess_stack;
bool uaccess;
bool df;
bool noinstr;
s8 instr;
};
struct alt_group {
/*
* Pointer from a replacement group to the original group. NULL if it
* *is* the original group.
*/
struct alt_group *orig_group;
/* First and last instructions in the group */
struct instruction *first_insn, *last_insn, *nop;
/*
* Byte-offset-addressed len-sized array of pointers to CFI structs.
* This is shared with the other alt_groups in the same alternative.
*/
struct cfi_state **cfi;
bool ignore;
unsigned int feature;
};
enum alternative_type {
ALT_TYPE_INSTRUCTIONS,
ALT_TYPE_JUMP_TABLE,
ALT_TYPE_EX_TABLE,
};
struct alternative {
struct alternative *next;
struct instruction *insn;
enum alternative_type type;
};
#define INSN_CHUNK_BITS 8
#define INSN_CHUNK_SIZE (1 << INSN_CHUNK_BITS)
#define INSN_CHUNK_MAX (INSN_CHUNK_SIZE - 1)
struct instruction {
struct hlist_node hash;
struct list_head call_node;
struct section *sec;
unsigned long offset;
unsigned long immediate;
u8 len;
u8 prev_len;
u8 type;
s8 instr;
u32 idx : INSN_CHUNK_BITS,
immediate_len : 4,
dead_end : 1,
ignore_alts : 1,
hint : 1,
save : 1,
restore : 1,
retpoline_safe : 1,
noendbr : 1,
unret : 1,
visited : 4,
no_reloc : 1,
hole : 1,
fake : 1,
trace : 1;
/* 4 bit hole */
struct alt_group *alt_group;
struct instruction *jump_dest;
struct instruction *first_jump_src;
union {
struct symbol *_call_dest;
struct {
struct reloc *_jump_table;
unsigned long _jump_table_size;
};
};
struct alternative *alts;
struct symbol *_sym;
struct stack_op *stack_ops;
struct cfi_state *cfi;
};
/*
* Return the symbol associated with an instruction. For alternative
* replacements, return the symbol of the original code being replaced rather
* than NULL. insn->_sym reflects the actual location in the ELF file.
*/
static inline struct symbol *insn_sym(struct instruction *insn)
{
struct symbol *sym = insn->_sym;
if ((!sym || !is_func_sym(sym)) &&
insn->alt_group && insn->alt_group->orig_group)
sym = insn->alt_group->orig_group->first_insn->_sym;
return sym;
}
static inline struct symbol *insn_func(struct instruction *insn)
{
struct symbol *sym = insn_sym(insn);
if (sym && sym->type != STT_FUNC)
sym = NULL;
return sym;
}
#define VISITED_BRANCH 0x01
#define VISITED_BRANCH_UACCESS 0x02
#define VISITED_BRANCH_MASK 0x03
#define VISITED_UNRET 0x04
static inline bool is_static_jump(struct instruction *insn)
{
return insn->type == INSN_JUMP_CONDITIONAL ||
insn->type == INSN_JUMP_UNCONDITIONAL;
}
static inline bool is_dynamic_jump(struct instruction *insn)
{
return insn->type == INSN_JUMP_DYNAMIC ||
insn->type == INSN_JUMP_DYNAMIC_CONDITIONAL;
}
static inline bool is_jump(struct instruction *insn)
{
return is_static_jump(insn) || is_dynamic_jump(insn);
}
static inline struct symbol *insn_call_dest(struct instruction *insn)
{
if (insn->type == INSN_JUMP_DYNAMIC ||
insn->type == INSN_CALL_DYNAMIC)
return NULL;
return insn->_call_dest;
}
struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset);
struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruction *insn);
struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn);
#define func_for_each_insn(file, func, insn) \
for (insn = find_insn(file, func->sec, func->offset); \
insn; \
insn = next_insn_same_func(file, insn))
#define sec_for_each_insn(file, _sec, insn) \
for (insn = find_insn(file, _sec, 0); \
insn && insn->sec == _sec; \
insn = next_insn_same_sec(file, insn))
#define sym_for_each_insn(file, sym, insn) \
for (insn = find_insn(file, sym->sec, sym->offset); \
insn && insn->offset < sym->offset + sym->len; \
insn = next_insn_same_sec(file, insn))
struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn);
int decode_file(struct objtool_file *file);
void free_insns(struct objtool_file *file);
const char *objtool_disas_insn(struct instruction *insn);
extern size_t sym_name_max_len;
extern struct disas_context *objtool_disas_ctx;
int pv_ops_idx_off(const char *symname);
#endif /* _CHECK_H */
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_CHECKSUM_H
#define _OBJTOOL_CHECKSUM_H
#include <objtool/elf.h>
#ifdef BUILD_KLP
static inline void checksum_init(struct symbol *sym)
{
if (sym && !sym->csum.state) {
sym->csum.state = XXH3_createState();
XXH3_64bits_reset(sym->csum.state);
}
}
static inline void __checksum_update(struct symbol *sym, const void *data,
size_t size)
{
XXH3_64bits_update(sym->csum.state, data, size);
}
static inline void __checksum_update_insn(struct symbol *sym,
struct instruction *insn,
const void *data, size_t size)
{
__checksum_update(sym, data, size);
dbg_checksum_insn(sym, insn, XXH3_64bits_digest(sym->csum.state));
}
static inline void __checksum_update_object(struct symbol *sym,
unsigned long offset,
const char *what, const void *data,
size_t size)
{
__checksum_update(sym, &offset, sizeof(offset));
__checksum_update(sym, data, size);
dbg_checksum_object(sym, offset, what, XXH3_64bits_digest(sym->csum.state));
}
static inline void checksum_finish(struct symbol *sym)
{
if (sym && sym->csum.state) {
sym->csum.checksum = XXH3_64bits_digest(sym->csum.state);
XXH3_freeState(sym->csum.state);
sym->csum.state = NULL;
}
}
int calculate_checksums(struct objtool_file *file);
int create_sym_checksum_section(struct objtool_file *file);
#else /* !BUILD_KLP */
static inline int calculate_checksums(struct objtool_file *file) { return -ENOSYS; }
static inline int create_sym_checksum_section(struct objtool_file *file) { return -EINVAL; }
#endif /* !BUILD_KLP */
#endif /* _OBJTOOL_CHECKSUM_H */
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _OBJTOOL_CHECKSUM_TYPES_H
#define _OBJTOOL_CHECKSUM_TYPES_H
struct sym_checksum {
u64 addr;
u64 checksum;
};
#ifdef BUILD_KLP
#include <xxhash.h>
struct checksum {
XXH3_state_t *state;
XXH64_hash_t checksum;
};
#else /* !BUILD_KLP */
struct checksum {};
#endif /* !BUILD_KLP */
#endif /* _OBJTOOL_CHECKSUM_TYPES_H */
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2025, Oracle and/or its affiliates.
*/
#ifndef _DISAS_H
#define _DISAS_H
struct alternative;
struct disas_context;
struct disassemble_info;
#ifdef DISAS
struct disas_context *disas_context_create(struct objtool_file *file);
void disas_context_destroy(struct disas_context *dctx);
void disas_warned_funcs(struct disas_context *dctx);
void disas_funcs(struct disas_context *dctx);
int disas_info_init(struct disassemble_info *dinfo,
int arch, int mach32, int mach64,
const char *options);
size_t disas_insn(struct disas_context *dctx, struct instruction *insn);
char *disas_result(struct disas_context *dctx);
void disas_print_info(FILE *stream, struct instruction *insn, int depth,
const char *format, ...);
void disas_print_insn(FILE *stream, struct disas_context *dctx,
struct instruction *insn, int depth,
const char *format, ...);
char *disas_alt_name(struct alternative *alt);
const char *disas_alt_type_name(struct instruction *insn);
#else /* DISAS */
#include <objtool/warn.h>
static inline struct disas_context *disas_context_create(struct objtool_file *file)
{
WARN("Rebuild with libopcodes for disassembly support");
return NULL;
}
static inline void disas_context_destroy(struct disas_context *dctx) {}
static inline void disas_warned_funcs(struct disas_context *dctx) {}
static inline void disas_funcs(struct disas_context *dctx) {}
static inline int disas_info_init(struct disassemble_info *dinfo,
int arch, int mach32, int mach64,
const char *options)
{
return -1;
}
static inline size_t disas_insn(struct disas_context *dctx,
struct instruction *insn)
{
return -1;
}
static inline char *disas_result(struct disas_context *dctx)
{
return NULL;
}
static inline void disas_print_info(FILE *stream, struct instruction *insn,
int depth, const char *format, ...) {}
static inline void disas_print_insn(FILE *stream, struct disas_context *dctx,
struct instruction *insn, int depth,
const char *format, ...) {}
static inline char *disas_alt_name(struct alternative *alt)
{
return NULL;
}
static inline const char *disas_alt_type_name(struct instruction *insn)
{
return NULL;
}
#endif /* DISAS */
#endif /* _DISAS_H */
@@ -0,0 +1,582 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _OBJTOOL_ELF_H
#define _OBJTOOL_ELF_H
#include <stdio.h>
#include <gelf.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/hashtable.h>
#include <linux/rbtree.h>
#include <linux/jhash.h>
#include <objtool/endianness.h>
#include <objtool/checksum_types.h>
#include <arch/elf.h>
#define SEC_NAME_LEN 1024
#define SYM_NAME_LEN 512
static inline u32 str_hash(const char *str)
{
return jhash(str, strlen(str), 0);
}
u32 str_hash_demangled(const char *str);
#define bswap_if_needed(elf, val) __bswap_if_needed(&elf->ehdr, val)
#ifdef LIBELF_USE_DEPRECATED
# define elf_getshdrnum elf_getshnum
# define elf_getshdrstrndx elf_getshstrndx
#endif
/*
* Fallback for systems without this "read, mmaping if possible" cmd.
*/
#ifndef ELF_C_READ_MMAP
#define ELF_C_READ_MMAP ELF_C_READ
#endif
struct elf_hash_node {
struct elf_hash_node *next;
};
struct section {
struct list_head list;
struct elf_hash_node hash;
struct elf_hash_node name_hash;
GElf_Shdr sh;
struct rb_root_cached symbol_tree;
struct list_head symbol_list;
struct section *base, *rsec;
struct symbol *sym;
Elf_Data *data;
const char *name;
int idx;
bool _changed, text, rodata, noinstr, init, truncate;
struct reloc *relocs;
unsigned long nr_alloc_relocs;
struct section *twin;
};
struct symbol {
struct list_head list;
struct list_head global_list;
struct rb_node node;
struct elf_hash_node hash;
struct elf_hash_node name_hash;
GElf_Sym sym;
struct section *sec;
const char *name, *demangled_name;
unsigned int idx, len;
unsigned long offset;
unsigned long __subtree_last;
struct symbol *pfunc, *cfunc, *alias, *file;
unsigned char bind, type;
u8 uaccess_safe : 1;
u8 static_call_tramp : 1;
u8 retpoline_thunk : 1;
u8 return_thunk : 1;
u8 fentry : 1;
u8 profiling_func : 1;
u8 warned : 1;
u8 embedded_insn : 1;
u8 local_label : 1;
u8 frame_pointer : 1;
u8 ignore : 1;
u8 nocfi : 1;
u8 cold : 1;
u8 prefix : 1;
u8 debug_checksum : 1;
u8 changed : 1;
u8 included : 1;
u8 klp : 1;
u8 dont_correlate : 1;
struct list_head pv_target;
struct reloc *relocs;
struct section *group_sec;
struct checksum csum;
struct symbol *twin, *clone;
};
struct reloc {
struct elf_hash_node hash;
struct section *sec;
struct symbol *sym;
unsigned long _sym_next_reloc;
};
struct elf {
Elf *elf;
GElf_Ehdr ehdr;
int fd;
bool changed;
const char *name, *tmp_name;
unsigned int num_files;
struct list_head sections;
struct list_head symbols;
unsigned long num_relocs;
int symbol_bits;
int symbol_name_bits;
int section_bits;
int section_name_bits;
int reloc_bits;
struct elf_hash_node **symbol_hash;
struct elf_hash_node **symbol_name_hash;
struct elf_hash_node **section_hash;
struct elf_hash_node **section_name_hash;
struct elf_hash_node **reloc_hash;
struct section *section_data;
struct symbol *symbol_data;
};
#define __elf_table(elf, name) ((elf)->name##_hash)
#define __elf_bits(elf, name) ((elf)->name##_bits)
#define __elf_table_entry(elf, name, key) \
__elf_table(elf, name)[hash_min(key, __elf_bits(elf, name))]
#define elf_list_entry(ptr, type, member) \
({ \
typeof(ptr) __ptr = (ptr); \
__ptr ? container_of(__ptr, type, member) : NULL; \
})
#define elf_hash_for_each_possible(elf, name, obj, member, key) \
for (obj = elf_list_entry(__elf_table_entry(elf, name, key), typeof(*obj), member); \
obj; \
obj = elf_list_entry(obj->member.next, typeof(*(obj)), member))
struct elf *elf_open_read(const char *name, int flags);
struct elf *elf_create_file(GElf_Ehdr *ehdr, const char *name);
struct section *elf_create_section(struct elf *elf, const char *name,
size_t size, size_t entsize,
unsigned int type, unsigned int align,
unsigned int flags);
struct section *elf_create_section_pair(struct elf *elf, const char *name,
size_t entsize, unsigned int nr,
unsigned int reloc_nr);
struct section *elf_create_rela_section(struct elf *elf, struct section *sec,
unsigned int reloc_nr);
struct symbol *elf_create_symbol(struct elf *elf, const char *name,
struct section *sec, unsigned int bind,
unsigned int type, unsigned long offset,
size_t size);
struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec);
void *elf_add_data(struct elf *elf, struct section *sec, const void *data,
size_t size);
unsigned int elf_add_string(struct elf *elf, struct section *strtab, const char *str);
struct reloc *elf_create_reloc(struct elf *elf, struct section *sec,
unsigned long offset, struct symbol *sym,
s64 addend, unsigned int type);
struct reloc *elf_init_reloc(struct elf *elf, struct section *rsec,
unsigned int reloc_idx, unsigned long offset,
struct symbol *sym, s64 addend, unsigned int type);
struct reloc *elf_init_reloc_text_sym(struct elf *elf, struct section *sec,
unsigned long offset,
unsigned int reloc_idx,
struct section *insn_sec,
unsigned long insn_off);
struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec,
unsigned long offset,
unsigned int reloc_idx,
struct symbol *sym,
s64 addend);
int elf_write_symbol(struct elf *elf, struct symbol *sym);
int elf_write_insn(struct elf *elf, struct section *sec, unsigned long offset,
unsigned int len, const char *insn);
int elf_write(struct elf *elf);
int elf_close(struct elf *elf);
struct section *find_section_by_name(const struct elf *elf, const char *name);
struct symbol *find_func_by_offset(struct section *sec, unsigned long offset);
struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset);
struct symbol *find_symbol_by_name(const struct elf *elf, const char *name);
struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *name);
struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset);
struct symbol *find_symbol_containing_inclusive(const struct section *sec, unsigned long offset);
int find_symbol_hole_containing(const struct section *sec, unsigned long offset);
struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset);
struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec,
unsigned long offset, unsigned int len);
struct symbol *find_func_containing(struct section *sec, unsigned long offset);
/*
* Try to see if it's a whole archive (vmlinux.o or module).
*
* Note this will miss the case where a module only has one source file.
*/
static inline bool has_multiple_files(struct elf *elf)
{
return elf->num_files > 1;
}
static inline size_t elf_addr_size(struct elf *elf)
{
return elf->ehdr.e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8;
}
static inline size_t elf_rela_size(struct elf *elf)
{
return elf_addr_size(elf) == 4 ? sizeof(Elf32_Rela) : sizeof(Elf64_Rela);
}
static inline unsigned int elf_data_rela_type(struct elf *elf)
{
return elf_addr_size(elf) == 4 ? R_DATA32 : R_DATA64;
}
static inline unsigned int elf_text_rela_type(struct elf *elf)
{
return elf_addr_size(elf) == 4 ? R_TEXT32 : R_TEXT64;
}
static inline bool is_undef_sym(struct symbol *sym)
{
return !sym->sec->idx;
}
static inline bool is_null_sym(struct symbol *sym)
{
return !sym->idx;
}
static inline bool is_sec_sym(struct symbol *sym)
{
return sym->type == STT_SECTION;
}
static inline bool is_object_sym(struct symbol *sym)
{
return sym->type == STT_OBJECT;
}
static inline bool is_func_sym(struct symbol *sym)
{
return sym->type == STT_FUNC;
}
static inline bool is_file_sym(struct symbol *sym)
{
return sym->type == STT_FILE;
}
static inline bool is_notype_sym(struct symbol *sym)
{
return sym->type == STT_NOTYPE;
}
static inline bool is_global_sym(struct symbol *sym)
{
return sym->bind == STB_GLOBAL;
}
static inline bool is_weak_sym(struct symbol *sym)
{
return sym->bind == STB_WEAK;
}
static inline bool is_local_sym(struct symbol *sym)
{
return sym->bind == STB_LOCAL;
}
static inline bool is_alias_sym(struct symbol *sym)
{
return sym->alias != sym;
}
static inline bool is_prefix_func(struct symbol *sym)
{
return sym->prefix;
}
static inline bool is_cold_func(struct symbol *sym)
{
return sym->cold;
}
static inline bool is_reloc_sec(struct section *sec)
{
return sec->sh.sh_type == SHT_RELA || sec->sh.sh_type == SHT_REL;
}
static inline bool is_string_sec(struct section *sec)
{
return sec->sh.sh_flags & SHF_STRINGS;
}
static inline bool is_text_sec(struct section *sec)
{
return sec->sh.sh_flags & SHF_EXECINSTR;
}
static inline bool is_rodata_sec(struct section *sec)
{
return sec->rodata;
}
static inline bool sec_changed(struct section *sec)
{
return sec->_changed;
}
static inline void mark_sec_changed(struct elf *elf, struct section *sec,
bool changed)
{
sec->_changed = changed;
elf->changed |= changed;
}
static inline unsigned int sec_num_entries(struct section *sec)
{
return sec->sh.sh_size / sec->sh.sh_entsize;
}
static inline unsigned int reloc_idx(struct reloc *reloc)
{
return reloc - reloc->sec->relocs;
}
static inline void *reloc_rel(struct reloc *reloc)
{
struct section *rsec = reloc->sec;
return rsec->data->d_buf + (reloc_idx(reloc) * rsec->sh.sh_entsize);
}
static inline bool is_32bit_reloc(struct reloc *reloc)
{
/*
* Elf32_Rel: 8 bytes
* Elf32_Rela: 12 bytes
* Elf64_Rel: 16 bytes
* Elf64_Rela: 24 bytes
*/
return reloc->sec->sh.sh_entsize < 16;
}
static inline unsigned long sec_size(struct section *sec)
{
return sec->sh.sh_size;
}
#define __get_reloc_field(reloc, field) \
({ \
is_32bit_reloc(reloc) ? \
((Elf32_Rela *)reloc_rel(reloc))->field : \
((Elf64_Rela *)reloc_rel(reloc))->field; \
})
#define __set_reloc_field(reloc, field, val) \
({ \
if (is_32bit_reloc(reloc)) \
((Elf32_Rela *)reloc_rel(reloc))->field = val; \
else \
((Elf64_Rela *)reloc_rel(reloc))->field = val; \
})
static inline u64 reloc_offset(struct reloc *reloc)
{
return __get_reloc_field(reloc, r_offset);
}
static inline void set_reloc_offset(struct elf *elf, struct reloc *reloc, u64 offset)
{
__set_reloc_field(reloc, r_offset, offset);
mark_sec_changed(elf, reloc->sec, true);
}
static inline s64 reloc_addend(struct reloc *reloc)
{
return __get_reloc_field(reloc, r_addend);
}
static inline void set_reloc_addend(struct elf *elf, struct reloc *reloc, s64 addend)
{
__set_reloc_field(reloc, r_addend, addend);
mark_sec_changed(elf, reloc->sec, true);
}
static inline unsigned int reloc_sym(struct reloc *reloc)
{
u64 info = __get_reloc_field(reloc, r_info);
return is_32bit_reloc(reloc) ?
ELF32_R_SYM(info) :
ELF64_R_SYM(info);
}
static inline unsigned int reloc_type(struct reloc *reloc)
{
u64 info = __get_reloc_field(reloc, r_info);
return is_32bit_reloc(reloc) ?
ELF32_R_TYPE(info) :
ELF64_R_TYPE(info);
}
static inline void set_reloc_sym(struct elf *elf, struct reloc *reloc, unsigned int sym)
{
u64 info = is_32bit_reloc(reloc) ?
ELF32_R_INFO(sym, reloc_type(reloc)) :
ELF64_R_INFO(sym, reloc_type(reloc));
__set_reloc_field(reloc, r_info, info);
mark_sec_changed(elf, reloc->sec, true);
}
static inline void set_reloc_type(struct elf *elf, struct reloc *reloc, unsigned int type)
{
u64 info = is_32bit_reloc(reloc) ?
ELF32_R_INFO(reloc_sym(reloc), type) :
ELF64_R_INFO(reloc_sym(reloc), type);
__set_reloc_field(reloc, r_info, info);
mark_sec_changed(elf, reloc->sec, true);
}
static inline unsigned int annotype(struct elf *elf, struct section *sec,
struct reloc *reloc)
{
unsigned int type;
type = *(u32 *)(sec->data->d_buf + (reloc_idx(reloc) * 8) + 4);
return bswap_if_needed(elf, type);
}
#define RELOC_JUMP_TABLE_BIT 1UL
/* Does reloc mark the beginning of a jump table? */
static inline bool is_jump_table(struct reloc *reloc)
{
return reloc->_sym_next_reloc & RELOC_JUMP_TABLE_BIT;
}
static inline void set_jump_table(struct reloc *reloc)
{
reloc->_sym_next_reloc |= RELOC_JUMP_TABLE_BIT;
}
static inline struct reloc *sym_next_reloc(struct reloc *reloc)
{
return (struct reloc *)(reloc->_sym_next_reloc & ~RELOC_JUMP_TABLE_BIT);
}
static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next)
{
unsigned long bit = reloc->_sym_next_reloc & RELOC_JUMP_TABLE_BIT;
reloc->_sym_next_reloc = (unsigned long)next | bit;
}
#define for_each_sec(elf, sec) \
list_for_each_entry(sec, &elf->sections, list)
#define sec_for_each_sym(sec, sym) \
list_for_each_entry(sym, &sec->symbol_list, list)
#define sec_prev_sym(sym) \
sym->sec && sym->list.prev != &sym->sec->symbol_list ? \
list_prev_entry(sym, list) : NULL
#define for_each_sym(elf, sym) \
list_for_each_entry(sym, &elf->symbols, global_list)
#define for_each_sym_continue(elf, sym) \
list_for_each_entry_continue(sym, &elf->symbols, global_list)
#define for_each_sym_by_name(elf, _name, sym) \
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \
str_hash_demangled(_name)) \
if (strcmp(sym->name, _name)) {} else
#define for_each_sym_by_demangled_name(elf, name, sym) \
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \
str_hash(name)) \
if (strcmp(sym->demangled_name, name)) {} else
#define rsec_next_reloc(rsec, reloc) \
reloc_idx(reloc) < sec_num_entries(rsec) - 1 ? reloc + 1 : NULL
#define for_each_reloc(rsec, reloc) \
for (reloc = rsec->relocs; reloc; reloc = rsec_next_reloc(rsec, reloc))
#define for_each_reloc_from(rsec, reloc) \
for (; reloc; reloc = rsec_next_reloc(rsec, reloc))
#define for_each_reloc_continue(rsec, reloc) \
for (reloc = rsec_next_reloc(rsec, reloc); reloc; \
reloc = rsec_next_reloc(rsec, reloc))
#define sym_for_each_reloc(elf, sym, reloc) \
for (reloc = find_reloc_by_dest_range(elf, sym->sec, \
sym->offset, sym->len); \
reloc && reloc_offset(reloc) < sym->offset + sym->len; \
reloc = rsec_next_reloc(sym->sec->rsec, reloc))
static inline struct symbol *get_func_prefix(struct symbol *func)
{
struct symbol *prev;
if (!is_func_sym(func) || !func->offset)
return NULL;
prev = find_func_containing(func->sec, func->offset - 1);
if (prev && is_prefix_func(prev))
return prev;
return NULL;
}
#define OFFSET_STRIDE_BITS 4
#define OFFSET_STRIDE (1UL << OFFSET_STRIDE_BITS)
#define OFFSET_STRIDE_MASK (~(OFFSET_STRIDE - 1))
#define for_offset_range(_offset, _start, _end) \
for (_offset = ((_start) & OFFSET_STRIDE_MASK); \
_offset >= ((_start) & OFFSET_STRIDE_MASK) && \
_offset <= ((_end) & OFFSET_STRIDE_MASK); \
_offset += OFFSET_STRIDE)
static inline u32 sec_offset_hash(struct section *sec, unsigned long offset)
{
u32 ol, oh, idx = sec->idx;
offset &= OFFSET_STRIDE_MASK;
ol = offset;
oh = (offset >> 16) >> 16;
__jhash_mix(ol, oh, idx);
return ol;
}
static inline u32 reloc_hash(struct reloc *reloc)
{
return sec_offset_hash(reloc->sec, reloc_offset(reloc));
}
#endif /* _OBJTOOL_ELF_H */
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ENDIANNESS_H
#define _OBJTOOL_ENDIANNESS_H
#include <linux/kernel.h>
#include <endian.h>
/*
* Does a byte swap if target file endianness doesn't match the host, i.e. cross
* compilation for little endian on big endian and vice versa.
* To be used for multi-byte values conversion, which are read from / about
* to be written to a target native endianness ELF file.
*/
static inline bool need_bswap(GElf_Ehdr *ehdr)
{
return (__BYTE_ORDER == __LITTLE_ENDIAN) ^
(ehdr->e_ident[EI_DATA] == ELFDATA2LSB);
}
#define __bswap_if_needed(ehdr, val) \
({ \
__typeof__(val) __ret; \
bool __need_bswap = need_bswap(ehdr); \
switch (sizeof(val)) { \
case 8: \
__ret = __need_bswap ? bswap_64(val) : (val); break; \
case 4: \
__ret = __need_bswap ? bswap_32(val) : (val); break; \
case 2: \
__ret = __need_bswap ? bswap_16(val) : (val); break; \
default: \
BUILD_BUG(); break; \
} \
__ret; \
})
#endif /* _OBJTOOL_ENDIANNESS_H */
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_KLP_H
#define _OBJTOOL_KLP_H
#define SHF_RELA_LIVEPATCH 0x00100000
#define SHN_LIVEPATCH 0xff20
/*
* .init.klp_objects and .init.klp_funcs are created by klp diff and used by the
* patch module init code to build the klp_patch, klp_object and klp_func
* structs needed by the livepatch API.
*/
#define KLP_OBJECTS_SEC ".init.klp_objects"
#define KLP_FUNCS_SEC ".init.klp_funcs"
/*
* __klp_relocs is an intermediate section which are created by klp diff and
* converted into KLP symbols/relas by "objtool klp post-link". This is needed
* to work around the linker, which doesn't preserve SHN_LIVEPATCH or
* SHF_RELA_LIVEPATCH, nor does it support having two RELA sections for a
* single PROGBITS section.
*/
#define KLP_RELOCS_SEC "__klp_relocs"
#define KLP_STRINGS_SEC ".rodata.klp.str1.1"
struct klp_reloc {
void *offset;
void *sym;
u32 type;
};
int cmd_klp_checksum(int argc, const char **argv);
int cmd_klp_diff(int argc, const char **argv);
int cmd_klp_post_link(int argc, const char **argv);
#endif /* _OBJTOOL_KLP_H */
@@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020 Matt Helsley <mhelsley@vmware.com>
*/
#ifndef _OBJTOOL_H
#define _OBJTOOL_H
#include <stdbool.h>
#include <linux/list.h>
#include <linux/hashtable.h>
#include <objtool/elf.h>
#define __weak __attribute__((weak))
struct pv_state {
bool clean;
struct list_head targets;
};
struct objtool_file {
struct elf *elf;
DECLARE_HASHTABLE(insn_hash, 20);
struct list_head retpoline_call_list;
struct list_head return_thunk_list;
struct list_head static_call_list;
struct list_head mcount_loc_list;
struct list_head endbr_list;
struct list_head call_list;
bool ignore_unreachables, hints, rodata, klp;
unsigned int nr_endbr;
unsigned int nr_endbr_int;
unsigned long jl_short, jl_long;
unsigned long jl_nop_short, jl_nop_long;
struct pv_state *pv_ops;
};
char *top_level_dir(const char *file);
int init_signal_handler(void);
struct objtool_file *objtool_open_read(const char *_objname);
int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
int check(struct objtool_file *file);
int orc_dump(const char *objname);
int orc_create(struct objtool_file *file);
#endif /* _OBJTOOL_H */
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _OBJTOOL_ORC_H
#define _OBJTOOL_ORC_H
#include <objtool/check.h>
int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, struct instruction *insn);
void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i);
int write_orc_entry(struct elf *elf, struct section *orc_sec,
struct section *ip_sec, unsigned int idx,
struct section *insn_sec, unsigned long insn_off,
struct orc_entry *o);
#endif /* _OBJTOOL_ORC_H */
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _SPECIAL_H
#define _SPECIAL_H
#include <stdbool.h>
#include <objtool/check.h>
#include <objtool/elf.h>
#define C_JUMP_TABLE_SECTION ".data.rel.ro.c_jump_table"
struct special_alt {
struct list_head list;
bool group;
bool jump_or_nop;
u8 key_addend;
struct section *orig_sec;
unsigned long orig_off;
struct section *new_sec;
unsigned long new_off;
unsigned int orig_len, new_len, feature; /* group only */
};
int special_get_alts(struct elf *elf, struct list_head *alts);
void arch_handle_alternative(struct special_alt *alt);
bool arch_support_alt_relocation(struct special_alt *special_alt,
struct instruction *insn,
struct reloc *reloc);
struct reloc *arch_find_switch_table(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size);
const char *arch_cpu_feature_name(int feature_number);
#endif /* _SPECIAL_H */
@@ -0,0 +1,141 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2025, Oracle and/or its affiliates.
*/
#ifndef _TRACE_H
#define _TRACE_H
#include <objtool/check.h>
#include <objtool/disas.h>
#ifdef DISAS
extern bool trace;
extern int trace_depth;
#define TRACE(fmt, ...) \
({ if (trace) \
fprintf(stderr, fmt, ##__VA_ARGS__); \
})
/*
* Print the instruction address and a message. The instruction
* itself is not printed.
*/
#define TRACE_ADDR(insn, fmt, ...) \
({ \
if (trace) { \
disas_print_info(stderr, insn, trace_depth - 1, \
fmt "\n", ##__VA_ARGS__); \
} \
})
/*
* Print the instruction address, the instruction and a message.
*/
#define TRACE_INSN(insn, fmt, ...) \
({ \
if (trace) { \
disas_print_insn(stderr, objtool_disas_ctx, \
insn, trace_depth - 1, \
fmt, ##__VA_ARGS__); \
fprintf(stderr, "\n"); \
insn->trace = 1; \
} \
})
#define TRACE_INSN_STATE(insn, sprev, snext) \
({ \
if (trace) \
trace_insn_state(insn, sprev, snext); \
})
#define TRACE_ALT_FMT(pfx, fmt) pfx "<%s.%lx> " fmt
#define TRACE_ALT_ARG(insn) disas_alt_type_name(insn), (insn)->offset
#define TRACE_ALT(insn, fmt, ...) \
TRACE_INSN(insn, TRACE_ALT_FMT("", fmt), \
TRACE_ALT_ARG(insn), ##__VA_ARGS__)
#define TRACE_ALT_INFO(insn, pfx, fmt, ...) \
TRACE_ADDR(insn, TRACE_ALT_FMT(pfx, fmt), \
TRACE_ALT_ARG(insn), ##__VA_ARGS__)
#define TRACE_ALT_INFO_NOADDR(insn, pfx, fmt, ...) \
TRACE_ADDR(NULL, TRACE_ALT_FMT(pfx, fmt), \
TRACE_ALT_ARG(insn), ##__VA_ARGS__)
#define TRACE_ALT_BEGIN(insn, alt, alt_name) \
({ \
if (trace) { \
alt_name = disas_alt_name(alt); \
trace_alt_begin(insn, alt, alt_name); \
} \
})
#define TRACE_ALT_END(insn, alt, alt_name) \
({ \
if (trace) { \
trace_alt_end(insn, alt, alt_name); \
free(alt_name); \
} \
})
static inline void trace_enable(void)
{
trace = true;
trace_depth = 0;
}
static inline void trace_disable(void)
{
trace = false;
}
static inline void trace_depth_inc(void)
{
if (trace)
trace_depth++;
}
static inline void trace_depth_dec(void)
{
if (trace)
trace_depth--;
}
void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
struct insn_state *snext);
void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt,
char *alt_name);
void trace_alt_end(struct instruction *orig_insn, struct alternative *alt,
char *alt_name);
#else /* DISAS */
#define TRACE(fmt, ...) ({})
#define TRACE_ADDR(insn, fmt, ...) ({})
#define TRACE_INSN(insn, fmt, ...) ({})
#define TRACE_INSN_STATE(insn, sprev, snext) ({})
#define TRACE_ALT(insn, fmt, ...) ({})
#define TRACE_ALT_INFO(insn, fmt, ...) ({})
#define TRACE_ALT_INFO_NOADDR(insn, fmt, ...) ({})
#define TRACE_ALT_BEGIN(insn, alt, alt_name) ({})
#define TRACE_ALT_END(insn, alt, alt_name) ({})
static inline void trace_enable(void) {}
static inline void trace_disable(void) {}
static inline void trace_depth_inc(void) {}
static inline void trace_depth_dec(void) {}
static inline void trace_alt_begin(struct instruction *orig_insn,
struct alternative *alt,
char *alt_name) {};
static inline void trace_alt_end(struct instruction *orig_insn,
struct alternative *alt,
char *alt_name) {};
#endif
#endif /* _TRACE_H */
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _UTIL_H
#define _UTIL_H
#include <objtool/warn.h>
#define snprintf_check(str, size, format, args...) \
({ \
int __ret = snprintf(str, size, format, args); \
if (__ret < 0) \
ERROR_GLIBC("snprintf"); \
else if (__ret >= size) \
ERROR("snprintf() failed for '" format "'", args); \
else \
__ret = 0; \
__ret; \
})
#endif /* _UTIL_H */
@@ -0,0 +1,168 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#ifndef _WARN_H
#define _WARN_H
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <objtool/builtin.h>
#include <objtool/elf.h>
extern const char *objname;
static inline char *offstr(struct section *sec, unsigned long offset)
{
bool is_text = (sec->sh.sh_flags & SHF_EXECINSTR);
struct symbol *sym = NULL;
char *str;
int len;
if (is_text)
sym = find_func_containing(sec, offset);
if (!sym)
sym = find_symbol_containing(sec, offset);
if (sym) {
str = malloc(strlen(sym->name) + strlen(sec->name) + 40);
len = sprintf(str, "%s+0x%lx", sym->name, offset - sym->offset);
if (opts.sec_address)
sprintf(str+len, " (%s+0x%lx)", sec->name, offset);
} else {
str = malloc(strlen(sec->name) + 20);
sprintf(str, "%s+0x%lx", sec->name, offset);
}
return str;
}
#define ___WARN(severity, extra, format, ...) \
fprintf(stderr, \
"%s%s%s: objtool" extra ": " format "\n", \
objname ?: "", \
objname ? ": " : "", \
severity, \
##__VA_ARGS__)
#define __WARN(severity, format, ...) \
___WARN(severity, "", format, ##__VA_ARGS__)
#define __WARN_LINE(severity, format, ...) \
___WARN(severity, " [%s:%d]", format, __FILE__, __LINE__, ##__VA_ARGS__)
#define __WARN_ELF(severity, format, ...) \
__WARN_LINE(severity, "%s: " format " failed: %s", __func__, ##__VA_ARGS__, elf_errmsg(-1))
#define __WARN_GLIBC(severity, format, ...) \
__WARN_LINE(severity, "%s: " format " failed: %s", __func__, ##__VA_ARGS__, strerror(errno))
#define __WARN_FUNC(severity, sec, offset, format, ...) \
({ \
char *_str = offstr(sec, offset); \
__WARN(severity, "%s: " format, _str, ##__VA_ARGS__); \
free(_str); \
})
#define WARN_STR (opts.werror ? "error" : "warning")
#define WARN(format, ...) __WARN(WARN_STR, format, ##__VA_ARGS__)
#define WARN_FUNC(sec, offset, format, ...) __WARN_FUNC(WARN_STR, sec, offset, format, ##__VA_ARGS__)
#define WARN_INSN(insn, format, ...) \
({ \
struct instruction *_insn = (insn); \
if (!insn_sym(_insn) || !insn_sym(_insn)->warned) { \
WARN_FUNC(_insn->sec, _insn->offset, format, \
##__VA_ARGS__); \
BT_INSN(_insn, ""); \
} \
if (insn_sym(_insn)) \
insn_sym(_insn)->warned = 1; \
})
#define BT_INSN(insn, format, ...) \
({ \
if (opts.verbose || opts.backtrace) { \
struct instruction *__insn = (insn); \
char *_str = offstr(__insn->sec, __insn->offset); \
const char *_istr = objtool_disas_insn(__insn); \
int _len; \
_len = snprintf(NULL, 0, " %s: " format, _str, ##__VA_ARGS__); \
_len = (_len < 50) ? 50 - _len : 0; \
WARN(" %s: " format " %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \
free(_str); \
__insn->trace = 1; \
} \
})
#define ERROR_STR "error"
#define ERROR(format, ...) __WARN(ERROR_STR, format, ##__VA_ARGS__)
#define ERROR_ELF(format, ...) __WARN_ELF(ERROR_STR, format, ##__VA_ARGS__)
#define ERROR_GLIBC(format, ...) __WARN_GLIBC(ERROR_STR, format, ##__VA_ARGS__)
#define ERROR_FUNC(sec, offset, format, ...) __WARN_FUNC(ERROR_STR, sec, offset, format, ##__VA_ARGS__)
#define ERROR_INSN(insn, format, ...) ERROR_FUNC(insn->sec, insn->offset, format, ##__VA_ARGS__)
extern bool debug, debug_correlate, debug_clone;
extern int indent;
static inline void unindent(int *unused) { indent--; }
/*
* Clang prior to 17 is being silly and considers many __cleanup() variables
* as unused (because they are, their sole purpose is to go out of scope).
*
* https://github.com/llvm/llvm-project/commit/877210faa447f4cc7db87812f8ed80e398fedd61
*/
#undef __cleanup
#define __cleanup(func) __maybe_unused __attribute__((__cleanup__(func)))
#define __dbg(format, ...) \
fprintf(stderr, \
"DEBUG: %s%s" format "\n", \
objname ?: "", \
objname ? ": " : "", \
##__VA_ARGS__)
#define dbg_checksum_insn(func, insn, checksum) \
({ \
if (unlikely(func->debug_checksum)) { \
char *insn_off = offstr(insn->sec, insn->offset); \
__dbg("checksum: %s(): %s %016llx", \
func->name, insn_off, (unsigned long long)checksum);\
free(insn_off); \
} \
})
#define dbg_checksum_object(sym, offset, what, checksum) \
({ \
if (unlikely(sym->debug_checksum)) \
__dbg("checksum: %s+0x%lx: %s %016llx", \
sym->name, offset, what, \
(unsigned long long)checksum); \
})
#define dbg_correlate(args...) \
({ \
if (unlikely(debug_correlate)) \
__dbg(args); \
})
#define __dbg_clone(format, ...) \
({ \
if (unlikely(debug_clone)) \
__dbg("%*s" format, indent * 8, "", ##__VA_ARGS__); \
})
#define dbg_clone(args...) \
int __cleanup(unindent) __dummy_##__COUNTER__; \
__dbg_clone(args); \
indent++
#endif /* _WARN_H */
+347
View File
@@ -0,0 +1,347 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <subcmd/parse-options.h>
#include <objtool/arch.h>
#include <objtool/builtin.h>
#include <objtool/check.h>
#include <objtool/elf.h>
#include <objtool/klp.h>
#include <objtool/objtool.h>
#include <objtool/warn.h>
#include <objtool/checksum.h>
static int checksum_debug_init(struct objtool_file *file)
{
char *dup, *s;
if (!opts.debug_checksum)
return 0;
dup = strdup(opts.debug_checksum);
if (!dup) {
ERROR_GLIBC("strdup");
return -1;
}
s = dup;
while (*s) {
bool found = false;
struct symbol *sym;
char *comma;
comma = strchr(s, ',');
if (comma)
*comma = '\0';
for_each_sym_by_name(file->elf, s, sym) {
if (!is_func_sym(sym) && !is_object_sym(sym))
continue;
sym->debug_checksum = 1;
found = true;
}
if (!found)
WARN("--debug-checksum: can't find '%s'", s);
if (!comma)
break;
s = comma + 1;
}
free(dup);
return 0;
}
static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
struct instruction *insn)
{
struct reloc *reloc = insn_reloc(file, insn);
struct alternative *alt;
unsigned long offset;
struct symbol *sym;
static bool in_alt;
if (insn->fake)
return;
if (!reloc) {
struct symbol *call_dest = insn_call_dest(insn);
struct instruction *jump_dest = insn->jump_dest;
/*
* For a jump/call non-relocated dest offset embedded in the
* instruction, the offset may vary due to changes in
* surrounding code. Just hash the opcode and a
* position-independent representation of the destination.
*/
if (call_dest || jump_dest) {
unsigned char buf[16];
size_t len;
len = arch_jump_opcode_bytes(file, insn, buf);
__checksum_update_insn(func, insn, buf, len);
if (call_dest) {
__checksum_update_insn(func, insn, call_dest->demangled_name,
strlen(call_dest->demangled_name));
} else if (jump_dest) {
struct symbol *dest_sym;
unsigned long offset;
/*
* use insn->_sym instead of insn_sym() here.
* For alternative replacements, the latter
* would give the function of the code being
* replaced.
*/
dest_sym = jump_dest->_sym;
if (!dest_sym)
goto alts;
__checksum_update_insn(func, insn, dest_sym->demangled_name,
strlen(dest_sym->demangled_name));
offset = jump_dest->offset - dest_sym->offset;
__checksum_update_insn(func, insn, &offset, sizeof(offset));
}
goto alts;
}
}
__checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
if (!reloc)
goto alts;
sym = reloc->sym;
offset = arch_insn_adjusted_addend(insn, reloc);
if (is_string_sec(sym->sec)) {
char *str;
str = sym->sec->data->d_buf + sym->offset + offset;
__checksum_update_insn(func, insn, str, strlen(str));
goto alts;
}
if (is_sec_sym(sym)) {
sym = find_symbol_containing(reloc->sym->sec, offset);
if (!sym)
goto alts;
offset -= sym->offset;
}
__checksum_update_insn(func, insn, sym->demangled_name,
strlen(sym->demangled_name));
__checksum_update_insn(func, insn, &offset, sizeof(offset));
alts:
for (alt = insn->alts; alt; alt = alt->next) {
struct alt_group *alt_group = alt->insn->alt_group;
/* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */
if (in_alt)
break;
in_alt = true;
__checksum_update_insn(func, insn, &alt->type,
sizeof(alt->type));
if (alt_group && alt_group->orig_group) {
struct instruction *alt_insn;
__checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature));
for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) {
checksum_update_insn(file, func, alt_insn);
if (!alt_group->last_insn || alt_insn == alt_group->last_insn)
break;
}
} else {
checksum_update_insn(file, func, alt->insn);
}
in_alt = false;
}
}
static void checksum_update_object(struct objtool_file *file, struct symbol *sym)
{
struct reloc *reloc;
__checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len));
if (sym->sec->data->d_buf)
__checksum_update_object(sym, 0, "data",
sym->sec->data->d_buf + sym->offset,
sym->len);
sym_for_each_reloc(file->elf, sym, reloc) {
unsigned long sym_offset = reloc_offset(reloc) - sym->offset;
struct symbol *target = reloc->sym;
s64 offset;
offset = reloc_addend(reloc);
if (is_string_sec(target->sec)) {
char *str;
str = target->sec->data->d_buf + target->offset + offset;
__checksum_update_object(sym, sym_offset,
"reloc string", str, strlen(str));
continue;
}
if (is_sec_sym(target)) {
target = find_symbol_containing(reloc->sym->sec, offset);
if (!target)
continue;
offset -= target->offset;
}
__checksum_update_object(sym, sym_offset, "reloc name",
target->demangled_name,
strlen(target->demangled_name));
__checksum_update_object(sym, sym_offset, "reloc addend",
&offset, sizeof(offset));
}
}
int calculate_checksums(struct objtool_file *file)
{
struct instruction *insn;
struct symbol *sym;
if (checksum_debug_init(file))
return -1;
for_each_sym(file->elf, sym) {
/*
* Skip cold subfunctions and aliases: they share the
* parent's checksum via func_for_each_insn() which
* follows func->cfunc into the cold subfunction.
*/
if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len ||
!sym->sec || !sym->sec->data)
continue;
if (is_func_sym(sym)) {
checksum_init(sym);
func_for_each_insn(file, sym, insn)
checksum_update_insn(file, sym, insn);
checksum_finish(sym);
} else if (is_object_sym(sym)) {
checksum_init(sym);
checksum_update_object(file, sym);
checksum_finish(sym);
}
}
return 0;
}
int create_sym_checksum_section(struct objtool_file *file)
{
struct section *sec;
struct symbol *sym;
unsigned int idx = 0;
struct sym_checksum *checksum;
size_t entsize = sizeof(struct sym_checksum);
sec = find_section_by_name(file->elf, ".discard.sym_checksum");
if (sec) {
if (!opts.dryrun)
WARN("file already has .discard.sym_checksum section, skipping");
return 0;
}
for_each_sym(file->elf, sym)
if (sym->csum.checksum)
idx++;
sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize,
idx, idx);
if (!sec)
return -1;
idx = 0;
for_each_sym(file->elf, sym) {
if (!sym->csum.checksum)
continue;
if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize,
sym, 0, R_TEXT64))
return -1;
checksum = (struct sym_checksum *)sec->data->d_buf + idx;
checksum->addr = 0; /* reloc */
checksum->checksum = sym->csum.checksum;
mark_sec_changed(file->elf, sec, true);
idx++;
}
return 0;
}
static const char * const klp_checksum_usage[] = {
"objtool klp checksum [<options>] file.o",
NULL,
};
int cmd_klp_checksum(int argc, const char **argv)
{
struct objtool_file *file;
int ret;
const struct option options[] = {
OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "syms", "enable checksum debug output"),
OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"),
OPT_END(),
};
argc = parse_options(argc, argv, options, klp_checksum_usage, 0);
if (argc != 1)
usage_with_options(klp_checksum_usage, options);
opts.checksum = true;
objname = argv[0];
file = objtool_open_read(objname);
if (!file)
return 1;
ret = decode_file(file);
if (ret)
goto out;
ret = calculate_checksums(file);
if (ret)
goto out;
ret = create_sym_checksum_section(file);
out:
free_insns(file);
if (ret)
return ret;
if (!opts.dryrun && file->elf->changed && elf_write(file->elf))
return 1;
return elf_close(file->elf);
}
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Read the intermediate KLP reloc/symbol representations created by klp diff
* and convert them to the proper format required by livepatch. This needs to
* run last to avoid linker wreckage. Linkers don't tend to handle the "two
* rela sections for a single base section" case very well, nor do they like
* SHN_LIVEPATCH.
*
* This is the final tool in the livepatch module generation pipeline:
*
* kernel builds -> objtool klp diff -> module link -> objtool klp post-link
*/
#include <fcntl.h>
#include <gelf.h>
#include <objtool/objtool.h>
#include <objtool/warn.h>
#include <objtool/klp.h>
#include <objtool/util.h>
#include <linux/livepatch_external.h>
static int fix_klp_relocs(struct elf *elf)
{
struct section *symtab, *klp_relocs;
klp_relocs = find_section_by_name(elf, KLP_RELOCS_SEC);
if (!klp_relocs)
return 0;
symtab = find_section_by_name(elf, ".symtab");
if (!symtab) {
ERROR("missing .symtab");
return -1;
}
for (int i = 0; i < sec_size(klp_relocs) / sizeof(struct klp_reloc); i++) {
struct klp_reloc *klp_reloc;
unsigned long klp_reloc_off;
struct section *sec, *tmp, *klp_rsec;
unsigned long offset;
struct reloc *reloc;
char sym_modname[64];
char rsec_name[SEC_NAME_LEN];
u64 addend;
struct symbol *sym, *klp_sym;
klp_reloc_off = i * sizeof(*klp_reloc);
klp_reloc = klp_relocs->data->d_buf + klp_reloc_off;
/*
* Read __klp_relocs[i]:
*/
/* klp_reloc.sec_offset */
reloc = find_reloc_by_dest(elf, klp_relocs,
klp_reloc_off + offsetof(struct klp_reloc, offset));
if (!reloc) {
ERROR("malformed " KLP_RELOCS_SEC " section");
return -1;
}
sec = reloc->sym->sec;
offset = reloc_addend(reloc);
/* klp_reloc.sym */
reloc = find_reloc_by_dest(elf, klp_relocs,
klp_reloc_off + offsetof(struct klp_reloc, sym));
if (!reloc) {
ERROR("malformed " KLP_RELOCS_SEC " section");
return -1;
}
klp_sym = reloc->sym;
addend = reloc_addend(reloc);
/* symbol format: .klp.sym.modname.sym_name,sympos */
if (sscanf(klp_sym->name + strlen(KLP_SYM_PREFIX), "%55[^.]", sym_modname) != 1)
ERROR("can't find modname in klp symbol '%s'", klp_sym->name);
/*
* Create the KLP rela:
*/
/* section format: .klp.rela.sec_objname.section_name */
if (snprintf_check(rsec_name, SEC_NAME_LEN,
KLP_RELOC_SEC_PREFIX "%s.%s",
sym_modname, sec->name))
return -1;
klp_rsec = find_section_by_name(elf, rsec_name);
if (!klp_rsec) {
klp_rsec = elf_create_section(elf, rsec_name, 0,
elf_rela_size(elf),
SHT_RELA, elf_addr_size(elf),
SHF_ALLOC | SHF_INFO_LINK | SHF_RELA_LIVEPATCH);
if (!klp_rsec)
return -1;
klp_rsec->sh.sh_link = symtab->idx;
klp_rsec->sh.sh_info = sec->idx;
klp_rsec->base = sec;
}
tmp = sec->rsec;
sec->rsec = klp_rsec;
if (!elf_create_reloc(elf, sec, offset, klp_sym, addend, klp_reloc->type))
return -1;
sec->rsec = tmp;
/*
* Fix up the corresponding KLP symbol:
*/
klp_sym->sym.st_shndx = SHN_LIVEPATCH;
if (!gelf_update_sym(symtab->data, klp_sym->idx, &klp_sym->sym)) {
ERROR_ELF("gelf_update_sym");
return -1;
}
/*
* Disable the original non-KLP reloc by converting it to R_*_NONE:
*/
reloc = find_reloc_by_dest(elf, sec, offset);
sym = reloc->sym;
sym->sym.st_shndx = SHN_LIVEPATCH;
set_reloc_type(elf, reloc, 0);
if (!gelf_update_sym(symtab->data, sym->idx, &sym->sym)) {
ERROR_ELF("gelf_update_sym");
return -1;
}
}
return 0;
}
/*
* This runs on the livepatch module after all other linking has been done. It
* converts the intermediate __klp_relocs section into proper KLP relocs to be
* processed by livepatch. This needs to run last to avoid linker wreckage.
* Linkers don't tend to handle the "two rela sections for a single base
* section" case very well, nor do they appreciate SHN_LIVEPATCH.
*/
int cmd_klp_post_link(int argc, const char **argv)
{
struct elf *elf;
argc--;
argv++;
if (argc != 1) {
fprintf(stderr, "%d\n", argc);
fprintf(stderr, "usage: objtool link <file.ko>\n");
return -1;
}
elf = elf_open_read(argv[0], O_RDWR);
if (!elf)
return -1;
if (fix_klp_relocs(elf))
return -1;
if (elf_write(elf))
return -1;
return elf_close(elf);
}
+55
View File
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This is a (sorted!) list of all known __noreturn functions in the kernel.
* It's needed for objtool to properly reverse-engineer the control flow graph.
*
* Yes, this is unfortunate. A better solution is in the works.
*/
NORETURN(__fortify_panic)
NORETURN(__ia32_sys_exit)
NORETURN(__ia32_sys_exit_group)
NORETURN(__kunit_abort)
NORETURN(__module_put_and_kthread_exit)
NORETURN(__stack_chk_fail)
NORETURN(__tdx_hypercall_failed)
NORETURN(__ubsan_handle_builtin_unreachable)
NORETURN(__x64_sys_exit)
NORETURN(__x64_sys_exit_group)
NORETURN(acpi_processor_ffh_play_dead)
NORETURN(arch_cpu_idle_dead)
NORETURN(bch2_trans_in_restart_error)
NORETURN(bch2_trans_restart_error)
NORETURN(bch2_trans_unlocked_or_in_restart_error)
NORETURN(cpu_bringup_and_idle)
NORETURN(cpu_startup_entry)
NORETURN(do_exit)
NORETURN(do_group_exit)
NORETURN(do_task_dead)
NORETURN(ex_handler_msr_mce)
NORETURN(hlt_play_dead)
NORETURN(hv_ghcb_terminate)
NORETURN(kthread_complete_and_exit)
NORETURN(kthread_exit)
NORETURN(kunit_try_catch_throw)
NORETURN(machine_real_restart)
NORETURN(make_task_dead)
NORETURN(mpt_halt_firmware)
NORETURN(mwait_play_dead)
NORETURN(native_play_dead)
NORETURN(nmi_panic_self_stop)
NORETURN(panic)
NORETURN(vpanic)
NORETURN(panic_smp_self_stop)
NORETURN(rest_init)
NORETURN(rewind_stack_and_make_dead)
NORETURN(rust_begin_unwind)
NORETURN(rust_helper_BUG)
NORETURN(sev_es_terminate)
NORETURN(start_kernel)
NORETURN(stop_this_cpu)
NORETURN(usercopy_abort)
NORETURN(x86_64_start_kernel)
NORETURN(x86_64_start_reservations)
NORETURN(xen_cpu_bringup_again)
NORETURN(xen_start_kernel)
+122
View File
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <subcmd/exec-cmd.h>
#include <subcmd/pager.h>
#include <linux/kernel.h>
#include <objtool/builtin.h>
#include <objtool/objtool.h>
#include <objtool/warn.h>
static struct objtool_file file;
struct objtool_file *objtool_open_read(const char *filename)
{
if (file.elf) {
ERROR("won't handle more than one file at a time");
return NULL;
}
file.elf = elf_open_read(filename, O_RDWR);
if (!file.elf)
return NULL;
hash_init(file.insn_hash);
INIT_LIST_HEAD(&file.retpoline_call_list);
INIT_LIST_HEAD(&file.return_thunk_list);
INIT_LIST_HEAD(&file.static_call_list);
INIT_LIST_HEAD(&file.mcount_loc_list);
INIT_LIST_HEAD(&file.endbr_list);
INIT_LIST_HEAD(&file.call_list);
file.ignore_unreachables = opts.no_unreachable;
file.hints = false;
return &file;
}
int objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func)
{
if (!opts.noinstr)
return 0;
if (!f->pv_ops) {
ERROR("paravirt confusion");
return -1;
}
/*
* These functions will be patched into native code,
* see paravirt_patch().
*/
if (!strcmp(func->name, "_paravirt_nop") ||
!strcmp(func->name, "_paravirt_ident_64"))
return 0;
/* already added this function */
if (!list_empty(&func->pv_target))
return 0;
list_add(&func->pv_target, &f->pv_ops[idx].targets);
f->pv_ops[idx].clean = false;
return 0;
}
char *top_level_dir(const char *file)
{
ssize_t len, self_len, file_len;
char self[PATH_MAX], *str;
int i;
len = readlink("/proc/self/exe", self, sizeof(self) - 1);
if (len <= 0)
return NULL;
self[len] = '\0';
for (i = 0; i < 3; i++) {
char *s = strrchr(self, '/');
if (!s)
return NULL;
*s = '\0';
}
self_len = strlen(self);
file_len = strlen(file);
str = malloc(self_len + file_len + 2);
if (!str)
return NULL;
memcpy(str, self, self_len);
str[self_len] = '/';
strcpy(str + self_len + 1, file);
return str;
}
int main(int argc, const char **argv)
{
static const char *UNUSED = "OBJTOOL_NOT_IMPLEMENTED";
if (init_signal_handler())
return -1;
/* libsubcmd init */
exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED);
pager_init(UNUSED);
if (argc > 1 && !strcmp(argv[1], "klp")) {
argc--;
argv++;
return cmd_klp(argc, argv);
}
return objtool_run(argc, argv);
}
+156
View File
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#include <unistd.h>
#include <asm/orc_types.h>
#include <objtool/objtool.h>
#include <objtool/orc.h>
#include <objtool/warn.h>
int orc_dump(const char *filename)
{
int fd, nr_entries, i, *orc_ip = NULL, orc_size = 0;
struct orc_entry *orc = NULL;
char *name;
size_t nr_sections;
Elf64_Addr orc_ip_addr = 0;
size_t shstrtab_idx, strtab_idx = 0;
Elf *elf;
Elf_Scn *scn;
GElf_Shdr sh;
GElf_Rela rela;
GElf_Sym sym;
Elf_Data *data, *symtab = NULL, *rela_orc_ip = NULL;
struct elf dummy_elf = {};
elf_version(EV_CURRENT);
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("open");
return -1;
}
elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
if (!elf) {
ERROR_ELF("elf_begin");
return -1;
}
if (!elf64_getehdr(elf)) {
ERROR_ELF("elf64_getehdr");
return -1;
}
memcpy(&dummy_elf.ehdr, elf64_getehdr(elf), sizeof(dummy_elf.ehdr));
if (elf_getshdrnum(elf, &nr_sections)) {
ERROR_ELF("elf_getshdrnum");
return -1;
}
if (elf_getshdrstrndx(elf, &shstrtab_idx)) {
ERROR_ELF("elf_getshdrstrndx");
return -1;
}
for (i = 0; i < nr_sections; i++) {
scn = elf_getscn(elf, i);
if (!scn) {
ERROR_ELF("elf_getscn");
return -1;
}
if (!gelf_getshdr(scn, &sh)) {
ERROR_ELF("gelf_getshdr");
return -1;
}
name = elf_strptr(elf, shstrtab_idx, sh.sh_name);
if (!name) {
ERROR_ELF("elf_strptr");
return -1;
}
data = elf_getdata(scn, NULL);
if (!data) {
ERROR_ELF("elf_getdata");
return -1;
}
if (!strcmp(name, ".symtab")) {
symtab = data;
} else if (!strcmp(name, ".strtab")) {
strtab_idx = i;
} else if (!strcmp(name, ".orc_unwind")) {
orc = data->d_buf;
orc_size = sh.sh_size;
} else if (!strcmp(name, ".orc_unwind_ip")) {
orc_ip = data->d_buf;
orc_ip_addr = sh.sh_addr;
} else if (!strcmp(name, ".rela.orc_unwind_ip")) {
rela_orc_ip = data;
}
}
if (!symtab || !strtab_idx || !orc || !orc_ip)
return 0;
if (orc_size % sizeof(*orc) != 0) {
ERROR("bad .orc_unwind section size");
return -1;
}
nr_entries = orc_size / sizeof(*orc);
for (i = 0; i < nr_entries; i++) {
if (rela_orc_ip) {
if (!gelf_getrela(rela_orc_ip, i, &rela)) {
ERROR_ELF("gelf_getrela");
return -1;
}
if (!gelf_getsym(symtab, GELF_R_SYM(rela.r_info), &sym)) {
ERROR_ELF("gelf_getsym");
return -1;
}
if (GELF_ST_TYPE(sym.st_info) == STT_SECTION) {
scn = elf_getscn(elf, sym.st_shndx);
if (!scn) {
ERROR_ELF("elf_getscn");
return -1;
}
if (!gelf_getshdr(scn, &sh)) {
ERROR_ELF("gelf_getshdr");
return -1;
}
name = elf_strptr(elf, shstrtab_idx, sh.sh_name);
if (!name) {
ERROR_ELF("elf_strptr");
return -1;
}
} else {
name = elf_strptr(elf, strtab_idx, sym.st_name);
if (!name) {
ERROR_ELF("elf_strptr");
return -1;
}
}
printf("%s+%llx:", name, (unsigned long long)rela.r_addend);
} else {
printf("%llx:", (unsigned long long)(orc_ip_addr + (i * sizeof(int)) + orc_ip[i]));
}
orc_print_dump(&dummy_elf, orc, i);
}
elf_end(elf);
close(fd);
return 0;
}
+150
View File
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
#include <stdlib.h>
#include <string.h>
#include <linux/objtool_types.h>
#include <asm/orc_types.h>
#include <objtool/check.h>
#include <objtool/orc.h>
#include <objtool/warn.h>
struct orc_list_entry {
struct list_head list;
struct orc_entry orc;
struct section *insn_sec;
unsigned long insn_off;
};
static int orc_list_add(struct list_head *orc_list, struct orc_entry *orc,
struct section *sec, unsigned long offset)
{
struct orc_list_entry *entry = malloc(sizeof(*entry));
if (!entry) {
WARN("malloc failed");
return -1;
}
entry->orc = *orc;
entry->insn_sec = sec;
entry->insn_off = offset;
list_add_tail(&entry->list, orc_list);
return 0;
}
static unsigned long alt_group_len(struct alt_group *alt_group)
{
return alt_group->last_insn->offset +
alt_group->last_insn->len -
alt_group->first_insn->offset;
}
int orc_create(struct objtool_file *file)
{
struct section *sec, *orc_sec;
unsigned int nr = 0, idx = 0;
struct orc_list_entry *entry;
struct list_head orc_list;
struct orc_entry null = { .type = ORC_TYPE_UNDEFINED };
/* Build a deduplicated list of ORC entries: */
INIT_LIST_HEAD(&orc_list);
for_each_sec(file->elf, sec) {
struct orc_entry orc, prev_orc = {0};
struct instruction *insn;
bool empty = true;
if (!sec->text)
continue;
sec_for_each_insn(file, sec, insn) {
struct alt_group *alt_group = insn->alt_group;
int i;
if (!alt_group) {
if (init_orc_entry(&orc, insn->cfi, insn))
return -1;
if (!memcmp(&prev_orc, &orc, sizeof(orc)))
continue;
if (orc_list_add(&orc_list, &orc, sec,
insn->offset))
return -1;
nr++;
prev_orc = orc;
empty = false;
continue;
}
/*
* Alternatives can have different stack layout
* possibilities (but they shouldn't conflict).
* Instead of traversing the instructions, use the
* alt_group's flattened byte-offset-addressed CFI
* array.
*/
for (i = 0; i < alt_group_len(alt_group); i++) {
struct cfi_state *cfi = alt_group->cfi[i];
if (!cfi)
continue;
/* errors are reported on the original insn */
if (init_orc_entry(&orc, cfi, insn))
return -1;
if (!memcmp(&prev_orc, &orc, sizeof(orc)))
continue;
if (orc_list_add(&orc_list, &orc, insn->sec,
insn->offset + i))
return -1;
nr++;
prev_orc = orc;
empty = false;
}
/* Skip to the end of the alt_group */
insn = alt_group->last_insn;
}
/* Add a section terminator */
if (!empty) {
orc_list_add(&orc_list, &null, sec, sec->sh.sh_size);
nr++;
}
}
if (!nr)
return 0;
/* Create .orc_unwind, .orc_unwind_ip and .rela.orc_unwind_ip sections: */
sec = find_section_by_name(file->elf, ".orc_unwind");
if (sec) {
WARN("file already has .orc_unwind section, skipping");
return -1;
}
orc_sec = elf_create_section(file->elf, ".orc_unwind",
nr * sizeof(struct orc_entry),
sizeof(struct orc_entry),
SHT_PROGBITS,
1,
SHF_ALLOC);
if (!orc_sec)
return -1;
sec = elf_create_section_pair(file->elf, ".orc_unwind_ip", sizeof(int), nr, nr);
if (!sec)
return -1;
/* Write ORC entries to sections: */
list_for_each_entry(entry, &orc_list, list) {
if (write_orc_entry(file->elf, orc_sec, sec, idx++,
entry->insn_sec, entry->insn_off,
&entry->orc))
return -1;
}
return 0;
}
+135
View File
@@ -0,0 +1,135 @@
/*
* signal.c: Register a sigaltstack for objtool, to be able to
* run a signal handler on a separate stack even if
* the main process stack has overflown. Print out
* stack overflow errors when this happens.
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/resource.h>
#include <string.h>
#include <objtool/objtool.h>
#include <objtool/warn.h>
static unsigned long stack_limit;
static bool is_stack_overflow(void *fault_addr)
{
unsigned long fault = (unsigned long)fault_addr;
/* Check if fault is in the guard page just below the limit. */
return fault < stack_limit && fault >= stack_limit - 4096;
}
static void signal_handler(int sig_num, siginfo_t *info, void *context)
{
struct sigaction sa_dfl = {0};
const char *sig_name;
char msg[256];
int msg_len;
switch (sig_num) {
case SIGSEGV: sig_name = "SIGSEGV"; break;
case SIGBUS: sig_name = "SIGBUS"; break;
case SIGILL: sig_name = "SIGILL"; break;
case SIGABRT: sig_name = "SIGABRT"; break;
default: sig_name = "Unknown signal"; break;
}
if (is_stack_overflow(info->si_addr)) {
msg_len = snprintf(msg, sizeof(msg),
"%s: error: %s: objtool stack overflow!\n",
objname, sig_name);
} else {
msg_len = snprintf(msg, sizeof(msg),
"%s: error: %s: objtool crash!\n",
objname, sig_name);
}
msg_len = write(STDERR_FILENO, msg, msg_len);
/* Re-raise the signal to trigger the core dump */
sa_dfl.sa_handler = SIG_DFL;
sigaction(sig_num, &sa_dfl, NULL);
raise(sig_num);
}
static int read_stack_limit(void)
{
unsigned long stack_start, stack_end;
struct rlimit rlim;
char line[256];
int ret = 0;
FILE *fp;
if (getrlimit(RLIMIT_STACK, &rlim)) {
ERROR_GLIBC("getrlimit");
return -1;
}
fp = fopen("/proc/self/maps", "r");
if (!fp) {
ERROR_GLIBC("fopen");
return -1;
}
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "[stack]")) {
if (sscanf(line, "%lx-%lx", &stack_start, &stack_end) != 2) {
ERROR_GLIBC("sscanf");
ret = -1;
goto done;
}
stack_limit = stack_end - rlim.rlim_cur;
goto done;
}
}
ret = -1;
ERROR("/proc/self/maps: can't find [stack]");
done:
fclose(fp);
return ret;
}
int init_signal_handler(void)
{
int signals[] = {SIGSEGV, SIGBUS, SIGILL, SIGABRT};
struct sigaction sa;
stack_t ss;
if (read_stack_limit())
return -1;
ss.ss_sp = malloc(SIGSTKSZ);
if (!ss.ss_sp) {
ERROR_GLIBC("malloc");
return -1;
}
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) == -1) {
ERROR_GLIBC("sigaltstack");
return -1;
}
sa.sa_sigaction = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
for (int i = 0; i < ARRAY_SIZE(signals); i++) {
if (sigaction(signals[i], &sa, NULL) == -1) {
ERROR_GLIBC("sigaction");
return -1;
}
}
return 0;
}
+169
View File
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*/
/*
* This file reads all the special sections which have alternate instructions
* which can be patched in or redirected to at runtime.
*/
#include <stdlib.h>
#include <string.h>
#include <arch/special.h>
#include <objtool/builtin.h>
#include <objtool/special.h>
#include <objtool/warn.h>
struct special_entry {
const char *sec;
bool group, jump_or_nop;
unsigned char size, orig, new;
unsigned char orig_len, new_len; /* group only */
unsigned char feature; /* ALTERNATIVE macro CPU feature */
unsigned char key; /* jump_label key */
};
static const struct special_entry entries[] = {
{
.sec = ".altinstructions",
.group = true,
.size = ALT_ENTRY_SIZE,
.orig = ALT_ORIG_OFFSET,
.orig_len = ALT_ORIG_LEN_OFFSET,
.new = ALT_NEW_OFFSET,
.new_len = ALT_NEW_LEN_OFFSET,
.feature = ALT_FEATURE_OFFSET,
},
{
.sec = "__jump_table",
.jump_or_nop = true,
.size = JUMP_ENTRY_SIZE,
.orig = JUMP_ORIG_OFFSET,
.new = JUMP_NEW_OFFSET,
.key = JUMP_KEY_OFFSET,
},
{
.sec = "__ex_table",
.size = EX_ENTRY_SIZE,
.orig = EX_ORIG_OFFSET,
.new = EX_NEW_OFFSET,
},
{},
};
void __weak arch_handle_alternative(struct special_alt *alt)
{
}
static void reloc_to_sec_off(struct reloc *reloc, struct section **sec,
unsigned long *off)
{
*sec = reloc->sym->sec;
*off = reloc->sym->offset + reloc_addend(reloc);
}
static int get_alt_entry(struct elf *elf, const struct special_entry *entry,
struct section *sec, int idx,
struct special_alt *alt)
{
struct reloc *orig_reloc, *new_reloc;
unsigned long offset;
offset = idx * entry->size;
alt->group = entry->group;
alt->jump_or_nop = entry->jump_or_nop;
if (alt->group) {
alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset +
entry->orig_len);
alt->new_len = *(unsigned char *)(sec->data->d_buf + offset +
entry->new_len);
alt->feature = *(unsigned int *)(sec->data->d_buf + offset +
entry->feature);
}
orig_reloc = find_reloc_by_dest(elf, sec, offset + entry->orig);
if (!orig_reloc) {
ERROR_FUNC(sec, offset + entry->orig, "can't find orig reloc");
return -1;
}
reloc_to_sec_off(orig_reloc, &alt->orig_sec, &alt->orig_off);
arch_handle_alternative(alt);
if (!entry->group || alt->new_len) {
new_reloc = find_reloc_by_dest(elf, sec, offset + entry->new);
if (!new_reloc) {
ERROR_FUNC(sec, offset + entry->new, "can't find new reloc");
return -1;
}
reloc_to_sec_off(new_reloc, &alt->new_sec, &alt->new_off);
/* _ASM_EXTABLE_EX hack */
if (alt->new_off >= 0x7ffffff0)
alt->new_off -= 0x7ffffff0;
}
if (entry->key) {
struct reloc *key_reloc;
key_reloc = find_reloc_by_dest(elf, sec, offset + entry->key);
if (!key_reloc) {
ERROR_FUNC(sec, offset + entry->key, "can't find key reloc");
return -1;
}
alt->key_addend = reloc_addend(key_reloc);
}
return 0;
}
/*
* Read all the special sections and create a list of special_alt structs which
* describe all the alternate instructions which can be patched in or
* redirected to at runtime.
*/
int special_get_alts(struct elf *elf, struct list_head *alts)
{
const struct special_entry *entry;
struct section *sec;
unsigned int nr_entries;
struct special_alt *alt;
int idx;
INIT_LIST_HEAD(alts);
for (entry = entries; entry->sec; entry++) {
sec = find_section_by_name(elf, entry->sec);
if (!sec)
continue;
if (sec_size(sec) % entry->size != 0) {
ERROR("%s size not a multiple of %d", sec->name, entry->size);
return -1;
}
nr_entries = sec_size(sec) / entry->size;
for (idx = 0; idx < nr_entries; idx++) {
alt = malloc(sizeof(*alt));
if (!alt) {
ERROR_GLIBC("malloc failed");
return -1;
}
memset(alt, 0, sizeof(*alt));
if (get_alt_entry(elf, entry, sec, idx, alt))
return -1;
list_add_tail(&alt->list, alts);
}
}
return 0;
}
+77
View File
@@ -0,0 +1,77 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
if [ -z "$SRCARCH" ]; then
echo 'sync-check.sh: error: missing $SRCARCH environment variable' >&2
exit 1
fi
FILES="include/linux/objtool_types.h"
if [ "$SRCARCH" = "x86" ]; then
FILES="$FILES
arch/x86/include/asm/nops.h
arch/x86/include/asm/inat_types.h
arch/x86/include/asm/orc_types.h
arch/x86/include/asm/emulate_prefix.h
arch/x86/lib/x86-opcode-map.txt
arch/x86/tools/gen-insn-attr-x86.awk
include/linux/interval_tree_generic.h
include/linux/livepatch_external.h
include/linux/static_call_types.h
"
SYNC_CHECK_FILES='
arch/x86/include/asm/inat.h
arch/x86/include/asm/insn.h
arch/x86/lib/inat.c
arch/x86/lib/insn.c
'
fi
check_2 () {
file1=$1
file2=$2
shift
shift
cmd="diff $* $file1 $file2 > /dev/null"
test -f $file2 && {
eval $cmd || {
echo "Warning: Kernel ABI header at '$file1' differs from latest version at '$file2'" >&2
echo diff -u $file1 $file2
}
}
}
check () {
file=$1
shift
check_2 tools/$file $file $*
}
if [ ! -d ../../kernel ] || [ ! -d ../../tools ] || [ ! -d ../objtool ]; then
exit 0
fi
cd ../..
while read -r file_entry; do
if [ -z "$file_entry" ]; then
continue
fi
check $file_entry
done <<EOF
$FILES
EOF
if [ "$SRCARCH" = "x86" ]; then
for i in $SYNC_CHECK_FILES; do
check $i '-I "^.*\/\*.*__ignore_sync_check__.*\*\/.*$"'
done
fi
+203
View File
@@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2025, Oracle and/or its affiliates.
*/
#include <objtool/trace.h>
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];
const char *rname;
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 (reg < CFI_NUM_REGS) {
rname = arch_reg_name[reg];
if (rname)
return rname;
}
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;
}
void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt,
char *alt_name)
{
struct instruction *alt_insn;
char suffix[2];
alt_insn = alt->insn;
if (alt->type == ALT_TYPE_EX_TABLE) {
/*
* When there is an exception table then the instruction
* at the original location is executed but it can cause
* an exception. In that case, the execution will be
* redirected to the alternative instruction.
*
* The instruction at the original location can have
* instruction alternatives, so we just print the location
* of the instruction that can cause the exception and
* not the instruction itself.
*/
TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s for instruction at 0x%lx <%s+0x%lx>",
alt_name,
orig_insn->offset, insn_sym(orig_insn)->name,
orig_insn->offset - insn_sym(orig_insn)->offset);
} else {
TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s", alt_name);
}
if (alt->type == ALT_TYPE_JUMP_TABLE) {
/*
* For a jump alternative, if the default instruction is
* a NOP then it is replaced with the jmp instruction,
* otherwise it is replaced with a NOP instruction.
*/
trace_depth++;
if (orig_insn->type == INSN_NOP) {
suffix[0] = (orig_insn->len == 5) ? 'q' : '\0';
TRACE_ADDR(orig_insn, "jmp%-3s %lx <%s+0x%lx>", suffix,
alt_insn->offset, insn_sym(alt_insn)->name,
alt_insn->offset - insn_sym(alt_insn)->offset);
} else {
TRACE_ADDR(orig_insn, "nop%d", orig_insn->len);
trace_depth--;
}
}
}
void trace_alt_end(struct instruction *orig_insn, struct alternative *alt,
char *alt_name)
{
if (alt->type == ALT_TYPE_JUMP_TABLE && orig_insn->type == INSN_NOP)
trace_depth--;
TRACE_ALT_INFO_NOADDR(orig_insn, "\\ ", "%s", alt_name);
}
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2020 Matt Helsley <mhelsley@vmware.com>
* Weak definitions necessary to compile objtool without
* some subcommands (e.g. check, orc).
*/
#include <stdbool.h>
#include <errno.h>
#include <objtool/objtool.h>
#include <objtool/arch.h>
#include <objtool/builtin.h>
#define UNSUPPORTED(name) \
({ \
fprintf(stderr, "error: objtool: " name " not implemented\n"); \
return ENOSYS; \
})
int __weak orc_dump(const char *_objname)
{
UNSUPPORTED("ORC");
}
int __weak orc_create(struct objtool_file *file)
{
UNSUPPORTED("ORC");
}
int __weak cmd_klp(int argc, const char **argv)
{
UNSUPPORTED("klp");
}