diff options
Diffstat (limited to 'tools/objtool')
50 files changed, 7429 insertions, 2494 deletions
diff --git a/tools/objtool/.gitignore b/tools/objtool/.gitignore index 45cefda24c7b..4faa4dd72f35 100644 --- a/tools/objtool/.gitignore +++ b/tools/objtool/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only arch/x86/lib/inat-tables.c -objtool +/objtool fixdep +libsubcmd/ diff --git a/tools/objtool/Build b/tools/objtool/Build index b7222d5cc7bc..a3cdf8af6635 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -2,24 +2,20 @@ objtool-y += arch/$(SRCARCH)/ objtool-y += weak.o -objtool-$(SUBCMD_CHECK) += check.o -objtool-$(SUBCMD_CHECK) += special.o -objtool-$(SUBCMD_ORC) += check.o -objtool-$(SUBCMD_ORC) += orc_gen.o -objtool-$(SUBCMD_ORC) += orc_dump.o - +objtool-y += check.o +objtool-y += special.o objtool-y += builtin-check.o -objtool-y += builtin-orc.o objtool-y += elf.o objtool-y += objtool.o +objtool-$(BUILD_ORC) += orc_gen.o +objtool-$(BUILD_ORC) += orc_dump.o + objtool-y += libstring.o objtool-y += libctype.o objtool-y += str_error_r.o objtool-y += librbtree.o -CFLAGS += -I$(srctree)/tools/lib - $(OUTPUT)libstring.o: ../lib/string.c FORCE $(call rule_mkdir) $(call if_changed_dep,cc_o_c) diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/objtool.txt index 0542e46c7552..9e97fc25b2d8 100644 --- a/tools/objtool/Documentation/stack-validation.txt +++ b/tools/objtool/Documentation/objtool.txt @@ -1,15 +1,115 @@ -Compile-time stack metadata validation -====================================== +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. -Overview +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 -------- -The kernel CONFIG_STACK_VALIDATION option enables a host tool named -objtool which runs at compile time. It has a "check" subcommand which -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. +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. @@ -20,14 +120,6 @@ 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. -(Objtool also has an 'orc generate' subcommand which generates debuginfo -for the ORC unwinder. See Documentation/x86/orc-unwinder.rst in the -kernel tree for more details.) - - -Why do we need stack metadata validation? ------------------------------------------ - Here are some of the benefits of validating stack metadata: a) More reliable stack traces for frame pointer enabled kernels @@ -101,7 +193,7 @@ b) ORC (Oops Rewind Capability) unwind table generation 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/x86/orc-unwinder.rst. + For more details, see Documentation/arch/x86/orc-unwinder.rst. c) Higher live patching compatibility rate @@ -113,26 +205,21 @@ c) Higher live patching compatibility rate For more details, see the livepatch documentation in the Linux kernel source tree at Documentation/livepatch/livepatch.rst. -Rules ------ - 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 - ENTRY/ENDPROC macros. If objtool finds a return instruction + 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 should *not* - be annotated as an ELF function. The ENDPROC macro shouldn't be used - in this case. - - This rule is needed so that objtool can ignore non-callable code. - Such code doesn't have to follow any of the other rules. +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 @@ -144,7 +231,7 @@ To achieve the validation, objtool enforces the following rules: function B, the _caller_ of function A will be skipped on the stack trace. -4. Dynamic jumps and jumps to undefined symbols are only allowed if: +4. Indirect jumps and jumps to undefined symbols are only allowed if: a) the jump is part of a switch statement; or @@ -167,6 +254,11 @@ To achieve the validation, objtool enforces the following rules: 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. @@ -177,7 +269,8 @@ 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. +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 @@ -188,8 +281,8 @@ they mean, and suggestions for how to fix them. 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 changing ENDPROC to END, and instead - use the manual unwind hint macros in asm/unwind_hints.h. + 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 @@ -201,6 +294,26 @@ they mean, and suggestions for how to fix them. 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 @@ -208,17 +321,21 @@ they mean, and suggestions for how to fix them. 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 ENTRY/ENDPROC macros (ENDPROC is the important one). - Otherwise, the code should probably be annotated with the unwind hint - macros in asm/unwind_hints.h so objtool and the unwinder can know the - stack state associated with the code. + 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. + - If you're 100% sure the code won't affect stack traces, or if you're - a just a bad person, you can tell objtool to ignore it. See the - "Adding exceptions" section below. +3. file.o: warning: objtool: foo+0x48c: bar() missing __noreturn in .c/.h or NORETURN() in noreturns.h - If it's not actually in a callable function (e.g. kernel entry code), - change ENDPROC to END. + 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 @@ -234,23 +351,21 @@ they mean, and suggestions for how to fix them. 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. They should usually not have - the callable function annotation (ENDPROC) and should always be - annotated with the unwind hint macros in asm/unwind_hints.h. + 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 dynamic jump or a jump to an undefined symbol. Objtool - assumed it's a sibling call and detected that the frame pointer - wasn't first restored to its original state. + 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 move the - destination code to the local file. + 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), change ENDPROC to END and annotate manually with - the unwind hint macros in asm/unwind_hints.h. + kernel entry code), use SYM_CODE_{START,END} and unwind hints. 7. file: warning: objtool: func()+0x5c: stack state mismatch @@ -266,8 +381,8 @@ they mean, and suggestions for how to fix them. 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 the unwind hint macros in - asm/unwind_hints.h. + 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() @@ -277,17 +392,16 @@ they mean, and suggestions for how to fix them. can fall through into the next function. There could be different reasons for this: - 1) funcA()'s last instruction is a call to a "noreturn" function like + 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. - 2) funcA() uses the unreachable() annotation in a section of code + b) funcA() uses the unreachable() annotation in a section of code that is actually reachable. - 3) If funcA() calls an inline function, the object code for funcA() - might be corrupt due to a gcc bug. For more details, see: - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646 + c) Some undefined behavior like divide by zero. + 9. file.o: warning: objtool: funcA() call to funcB() with UACCESS enabled @@ -315,24 +429,36 @@ they mean, and suggestions for how to fix them. function tracing inserts additional calls, which is not obvious from the sources). -10. file.o: warning: func()+0x5c: alternative modifies stack +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. - This means that an alternative includes instructions that modify the - stack. The problem is that there is only one ORC unwind table, this means - that the ORC unwind entries must be valid for each of the alternatives. - The easiest way to enforce this is to ensure alternatives do not contain - any ORC entries, which in turn implies the above constraint. 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. + 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 the objtool maintainer for help. +Feel free to ask objtool maintainers for help. Adding exceptions @@ -356,3 +482,7 @@ ignore it: 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. diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 7770edcda3a0..8c20361dd100 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -2,78 +2,91 @@ include ../scripts/Makefile.include include ../scripts/Makefile.arch -# always use the host compiler -ifneq ($(LLVM),) -HOSTAR ?= llvm-ar -HOSTCC ?= clang -HOSTLD ?= ld.lld -else -HOSTAR ?= ar -HOSTCC ?= gcc -HOSTLD ?= ld -endif -AR = $(HOSTAR) -CC = $(HOSTCC) -LD = $(HOSTLD) - ifeq ($(srctree),) srctree := $(patsubst %/,%,$(dir $(CURDIR))) srctree := $(patsubst %/,%,$(dir $(srctree))) endif -SUBCMD_SRCDIR = $(srctree)/tools/lib/subcmd/ -LIBSUBCMD_OUTPUT = $(if $(OUTPUT),$(OUTPUT),$(CURDIR)/) -LIBSUBCMD = $(LIBSUBCMD_OUTPUT)libsubcmd.a +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 pkg-config libelf --cflags 2>/dev/null) -LIBELF_LIBS := $(shell pkg-config libelf --libs 2>/dev/null || echo -lelf) +LIBELF_FLAGS := $(shell $(HOSTPKG_CONFIG) libelf --cflags 2>/dev/null) +LIBELF_LIBS := $(shell $(HOSTPKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf) all: $(OBJTOOL) 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/arch/$(SRCARCH)/include -WARNINGS := $(EXTRA_WARNINGS) -Wno-switch-default -Wno-switch-enum -Wno-packed -CFLAGS := -Werror $(WARNINGS) $(KBUILD_HOSTCFLAGS) -g $(INCLUDES) $(LIBELF_FLAGS) -LDFLAGS += $(LIBELF_LIBS) $(LIBSUBCMD) $(KBUILD_HOSTLDFLAGS) + -I$(srctree)/tools/objtool/include \ + -I$(srctree)/tools/objtool/arch/$(SRCARCH)/include \ + -I$(LIBSUBCMD_OUTPUT)/include +# Note, EXTRA_WARNINGS here was determined for CC and not HOSTCC, it +# is passed here to match a legacy behavior. +WARNINGS := $(EXTRA_WARNINGS) -Wno-switch-default -Wno-switch-enum -Wno-packed -Wno-nested-externs +OBJTOOL_CFLAGS := -Werror $(WARNINGS) $(KBUILD_HOSTCFLAGS) -g $(INCLUDES) $(LIBELF_FLAGS) +OBJTOOL_LDFLAGS := $(LIBELF_LIBS) $(LIBSUBCMD) $(KBUILD_HOSTLDFLAGS) # Allow old libelf to be used: -elfshdr := $(shell echo '$(pound)include <libelf.h>' | $(CC) $(CFLAGS) -x c -E - | grep elf_getshdr) -CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED) +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)" AWK = awk +MKDIR = mkdir -SUBCMD_CHECK := n -SUBCMD_ORC := n +BUILD_ORC := n ifeq ($(SRCARCH),x86) - SUBCMD_CHECK := y - SUBCMD_ORC := y + BUILD_ORC := y endif -export SUBCMD_CHECK SUBCMD_ORC +ifeq ($(SRCARCH),loongarch) + BUILD_ORC := y +endif + +export BUILD_ORC export srctree OUTPUT CFLAGS SRCARCH AWK include $(srctree)/tools/build/Makefile.include -$(OBJTOOL_IN): fixdep FORCE - @$(CONFIG_SHELL) ./sync-check.sh - @$(MAKE) $(build)=objtool +$(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)$(CC) $(OBJTOOL_IN) $(LDFLAGS) -o $@ + $(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)" \ + $@ install_headers -$(LIBSUBCMD): fixdep FORCE - $(Q)$(MAKE) -C $(SUBCMD_SRCDIR) OUTPUT=$(LIBSUBCMD_OUTPUT) +$(LIBSUBCMD)-clean: + $(call QUIET_CLEAN, libsubcmd) + $(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT) -clean: +clean: $(LIBSUBCMD)-clean $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) $(Q)find $(OUTPUT) -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete - $(Q)$(RM) $(OUTPUT)arch/x86/inat-tables.c $(OUTPUT)fixdep + $(Q)$(RM) $(OUTPUT)arch/x86/lib/inat-tables.c $(OUTPUT)fixdep FORCE: diff --git a/tools/objtool/arch/loongarch/Build b/tools/objtool/arch/loongarch/Build new file mode 100644 index 000000000000..1d4b784b6887 --- /dev/null +++ b/tools/objtool/arch/loongarch/Build @@ -0,0 +1,3 @@ +objtool-y += decode.o +objtool-y += special.o +objtool-y += orc.o diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c new file mode 100644 index 000000000000..b6fdc68053cc --- /dev/null +++ b/tools/objtool/arch/loongarch/decode.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <string.h> +#include <objtool/check.h> +#include <objtool/warn.h> +#include <asm/inst.h> +#include <asm/orc_types.h> +#include <linux/objtool_types.h> +#include <arch/elf.h> + +int arch_ftrace_match(char *name) +{ + return !strcmp(name, "_mcount"); +} + +unsigned long arch_jump_destination(struct instruction *insn) +{ + return insn->offset + (insn->immediate << 2); +} + +unsigned long arch_dest_reloc_offset(int addend) +{ + return addend; +} + +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; +} + +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 (inst.word == 0) + insn->type = INSN_NOP; + else if (inst.reg0i15_format.opcode == break_op) { + /* break */ + 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); + } +} diff --git a/tools/objtool/arch/loongarch/include/arch/cfi_regs.h b/tools/objtool/arch/loongarch/include/arch/cfi_regs.h new file mode 100644 index 000000000000..d183cc8f43bf --- /dev/null +++ b/tools/objtool/arch/loongarch/include/arch/cfi_regs.h @@ -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 */ diff --git a/tools/objtool/arch/loongarch/include/arch/elf.h b/tools/objtool/arch/loongarch/include/arch/elf.h new file mode 100644 index 000000000000..ec79062c9554 --- /dev/null +++ b/tools/objtool/arch/loongarch/include/arch/elf.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 */ diff --git a/tools/objtool/arch/loongarch/include/arch/special.h b/tools/objtool/arch/loongarch/include/arch/special.h new file mode 100644 index 000000000000..35fc979b550a --- /dev/null +++ b/tools/objtool/arch/loongarch/include/arch/special.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 */ diff --git a/tools/objtool/arch/loongarch/orc.c b/tools/objtool/arch/loongarch/orc.c new file mode 100644 index 000000000000..b58c5ff443c9 --- /dev/null +++ b/tools/objtool/arch/loongarch/orc.c @@ -0,0 +1,171 @@ +// 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> +#include <objtool/endianness.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); +} diff --git a/tools/objtool/arch/loongarch/special.c b/tools/objtool/arch/loongarch/special.c new file mode 100644 index 000000000000..e39f86d97002 --- /dev/null +++ b/tools/objtool/arch/loongarch/special.c @@ -0,0 +1,173 @@ +// 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; + + rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate"); + if (!rsec) + return; + + INIT_LIST_HEAD(&table_list); + + for_each_reloc(rsec, reloc) { + 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; + } + + 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; +} diff --git a/tools/objtool/arch/powerpc/Build b/tools/objtool/arch/powerpc/Build new file mode 100644 index 000000000000..d24d5636a5b8 --- /dev/null +++ b/tools/objtool/arch/powerpc/Build @@ -0,0 +1,2 @@ +objtool-y += decode.o +objtool-y += special.o diff --git a/tools/objtool/arch/powerpc/decode.c b/tools/objtool/arch/powerpc/decode.c new file mode 100644 index 000000000000..c851c51d4bd3 --- /dev/null +++ b/tools/objtool/arch/powerpc/decode.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <stdio.h> +#include <stdlib.h> +#include <objtool/check.h> +#include <objtool/elf.h> +#include <objtool/arch.h> +#include <objtool/warn.h> +#include <objtool/builtin.h> +#include <objtool/endianness.h> + +int arch_ftrace_match(char *name) +{ + return !strcmp(name, "_mcount"); +} + +unsigned long arch_dest_reloc_offset(int addend) +{ + return addend; +} + +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; + } +} diff --git a/tools/objtool/arch/powerpc/include/arch/cfi_regs.h b/tools/objtool/arch/powerpc/include/arch/cfi_regs.h new file mode 100644 index 000000000000..59638ebeafc8 --- /dev/null +++ b/tools/objtool/arch/powerpc/include/arch/cfi_regs.h @@ -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 diff --git a/tools/objtool/arch/powerpc/include/arch/elf.h b/tools/objtool/arch/powerpc/include/arch/elf.h new file mode 100644 index 000000000000..66814fa28024 --- /dev/null +++ b/tools/objtool/arch/powerpc/include/arch/elf.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_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 */ diff --git a/tools/objtool/arch/powerpc/include/arch/special.h b/tools/objtool/arch/powerpc/include/arch/special.h new file mode 100644 index 000000000000..ffef9ada7133 --- /dev/null +++ b/tools/objtool/arch/powerpc/include/arch/special.h @@ -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 */ diff --git a/tools/objtool/arch/powerpc/special.c b/tools/objtool/arch/powerpc/special.c new file mode 100644 index 000000000000..51610689abf7 --- /dev/null +++ b/tools/objtool/arch/powerpc/special.c @@ -0,0 +1,20 @@ +// 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); +} diff --git a/tools/objtool/arch/x86/Build b/tools/objtool/arch/x86/Build index 7c5004008e97..3dedb2fd8f3a 100644 --- a/tools/objtool/arch/x86/Build +++ b/tools/objtool/arch/x86/Build @@ -1,4 +1,6 @@ +objtool-y += special.o objtool-y += decode.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 diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 4b504fc90bbb..98c4713c1b09 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -11,21 +11,22 @@ #include "../../../arch/x86/lib/inat.c" #include "../../../arch/x86/lib/insn.c" -#include "../../check.h" -#include "../../elf.h" -#include "../../arch.h" -#include "../../warn.h" - -static unsigned char op_to_cfi_reg[][2] = { - {CFI_AX, CFI_R8}, - {CFI_CX, CFI_R9}, - {CFI_DX, CFI_R10}, - {CFI_BX, CFI_R11}, - {CFI_SP, CFI_R12}, - {CFI_BP, CFI_R13}, - {CFI_SI, CFI_R14}, - {CFI_DI, CFI_R15}, -}; +#define CONFIG_64BIT 1 +#include <asm/nops.h> + +#include <asm/orc_types.h> +#include <objtool/check.h> +#include <objtool/elf.h> +#include <objtool/arch.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> +#include <objtool/builtin.h> +#include <arch/elf.h> + +int arch_ftrace_match(char *name) +{ + return !strcmp(name, "__fentry__"); +} static int is_x86_64(const struct elf *elf) { @@ -35,7 +36,7 @@ static int is_x86_64(const struct elf *elf) case EM_386: return 0; default: - WARN("unexpected ELF machine type %d", elf->ehdr.e_machine); + ERROR("unexpected ELF machine type %d", elf->ehdr.e_machine); return -1; } } @@ -67,7 +68,7 @@ bool arch_callee_saved_reg(unsigned char reg) } } -unsigned long arch_dest_rela_offset(int addend) +unsigned long arch_dest_reloc_offset(int addend) { return addend + 4; } @@ -77,74 +78,158 @@ 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 (list_add_tail(&op->list, ops_list); op; op = NULL) + 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; -int arch_decode_instruction(const struct elf *elf, const struct section *sec, + 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, - unsigned int *len, enum insn_type *type, - unsigned long *immediate, - struct list_head *ops_list) + struct instruction *insn) { - struct insn insn; - int x86_64, sign; - unsigned char op1, op2, 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; + 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; - insn_init(&insn, sec->data->d_buf + offset, maxlen, x86_64); - insn_get_length(&insn); - - if (!insn_complete(&insn)) { - WARN("can't decode instruction at %s:0x%lx", sec->name, offset); + 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; } - *len = insn.length; - *type = INSN_OTHER; + insn->len = ins.length; + insn->type = INSN_OTHER; - if (insn.vex_prefix.nbytes) + if (ins.vex_prefix.nbytes) return 0; - op1 = insn.opcode.bytes[0]; - op2 = insn.opcode.bytes[1]; + prefix = ins.prefixes.bytes[0]; + + op1 = ins.opcode.bytes[0]; + op2 = ins.opcode.bytes[1]; + op3 = ins.opcode.bytes[2]; + + /* + * XXX hack, decoder is buggered and thinks 0xea is 7 bytes long. + */ + if (op1 == 0xea) { + insn->len = 1; + insn->type = INSN_BUG; + return 0; + } - if (insn.rex_prefix.nbytes) { - rex = insn.rex_prefix.bytes[0]; + 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 (insn.modrm.nbytes) { - modrm = insn.modrm.bytes[0]; + if (ins.modrm.nbytes) { + modrm = ins.modrm.bytes[0]; modrm_mod = X86_MODRM_MOD(modrm); - modrm_reg = X86_MODRM_REG(modrm); - modrm_rm = X86_MODRM_RM(modrm); + modrm_reg = X86_MODRM_REG(modrm) + 8*rex_r; + modrm_rm = X86_MODRM_RM(modrm) + 8*rex_b; } - if (insn.sib.nbytes) - sib = insn.sib.bytes[0]; + 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 && !rex_b && modrm_mod == 3 && modrm_rm == 4) { + if (rex_w && rm_is_reg(CFI_SP)) { /* add/sub reg, %rsp */ ADD_OP(op) { op->src.type = OP_SRC_ADD; - op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->src.reg = modrm_reg; op->dest.type = OP_DEST_REG; op->dest.reg = CFI_SP; } @@ -156,7 +241,7 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, /* push reg */ ADD_OP(op) { op->src.type = OP_SRC_REG; - op->src.reg = op_to_cfi_reg[op1 & 0x7][rex_b]; + op->src.reg = (op1 & 0x7) + 8*rex_b; op->dest.type = OP_DEST_PUSH; } @@ -168,7 +253,7 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, ADD_OP(op) { op->src.type = OP_SRC_POP; op->dest.type = OP_DEST_REG; - op->dest.reg = op_to_cfi_reg[op1 & 0x7][rex_b]; + op->dest.reg = (op1 & 0x7) + 8*rex_b; } break; @@ -183,186 +268,230 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, break; case 0x70 ... 0x7f: - *type = INSN_JUMP_CONDITIONAL; + insn->type = INSN_JUMP_CONDITIONAL; break; - case 0x81: - case 0x83: - if (rex != 0x48) + 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; - if (modrm == 0xe4) { + case 4: /* and imm, %rsp */ ADD_OP(op) { op->src.type = OP_SRC_AND; op->src.reg = CFI_SP; - op->src.offset = insn.immediate.value; + op->src.offset = ins.immediate.value; op->dest.type = OP_DEST_REG; op->dest.reg = CFI_SP; } break; - } - if (modrm == 0xc4) - sign = 1; - else if (modrm == 0xec) - sign = -1; - else + default: + /* ERROR ? */ break; - - /* add/sub imm, %rsp */ - ADD_OP(op) { - op->src.type = OP_SRC_ADD; - op->src.reg = CFI_SP; - op->src.offset = insn.immediate.value * sign; - op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_SP; } + break; case 0x89: - if (rex_w && !rex_r && modrm_mod == 3 && modrm_reg == 4) { + if (!rex_w) + break; - /* mov %rsp, reg */ - ADD_OP(op) { - op->src.type = OP_SRC_REG; - op->src.reg = CFI_SP; - op->dest.type = OP_DEST_REG; - op->dest.reg = op_to_cfi_reg[modrm_rm][rex_b]; + if (modrm_reg == CFI_SP) { + + if (mod_is_reg()) { + /* mov %rsp, reg */ + ADD_OP(op) { + op->src.type = OP_SRC_REG; + op->src.reg = CFI_SP; + op->dest.type = OP_DEST_REG; + op->dest.reg = modrm_rm; + } + break; + + } else { + /* 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) */ + 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; } + break; } - if (rex_w && !rex_b && modrm_mod == 3 && modrm_rm == 4) { + if (rm_is_reg(CFI_SP)) { /* mov reg, %rsp */ ADD_OP(op) { op->src.type = OP_SRC_REG; - op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->src.reg = modrm_reg; op->dest.type = OP_DEST_REG; op->dest.reg = CFI_SP; } break; } - /* fallthrough */ + fallthrough; case 0x88: - if (!rex_b && - (modrm_mod == 1 || modrm_mod == 2) && modrm_rm == 5) { + 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 = op_to_cfi_reg[modrm_reg][rex_r]; + op->src.reg = modrm_reg; op->dest.type = OP_DEST_REG_INDIRECT; op->dest.reg = CFI_BP; - op->dest.offset = insn.displacement.value; + op->dest.offset = ins.displacement.value; } + break; + } - } else if (rex_w && !rex_b && modrm_rm == 4 && sib == 0x24) { + if (rm_is_mem(CFI_SP)) { /* mov reg, disp(%rsp) */ ADD_OP(op) { op->src.type = OP_SRC_REG; - op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->src.reg = modrm_reg; op->dest.type = OP_DEST_REG_INDIRECT; op->dest.reg = CFI_SP; - op->dest.offset = insn.displacement.value; + op->dest.offset = ins.displacement.value; } + break; } break; case 0x8b: - if (rex_w && !rex_b && modrm_mod == 1 && modrm_rm == 5) { + 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 = insn.displacement.value; + op->src.offset = ins.displacement.value; op->dest.type = OP_DEST_REG; - op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.reg = modrm_reg; } + break; + } - } else if (rex_w && !rex_b && sib == 0x24 && - modrm_mod != 3 && modrm_rm == 4) { + 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 = insn.displacement.value; + op->src.offset = ins.displacement.value; op->dest.type = OP_DEST_REG; - op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.reg = modrm_reg; } + break; } break; case 0x8d: - if (sib == 0x24 && rex_w && !rex_b && !rex_x) { - - ADD_OP(op) { - if (!insn.displacement.value) { - /* lea (%rsp), reg */ - op->src.type = OP_SRC_REG; - } else { - /* lea disp(%rsp), reg */ - op->src.type = OP_SRC_ADD; - op->src.offset = insn.displacement.value; - } - op->src.reg = CFI_SP; - op->dest.type = OP_DEST_REG; - op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; - } - - } else if (rex == 0x48 && modrm == 0x65) { - - /* lea disp(%rbp), %rsp */ - ADD_OP(op) { - op->src.type = OP_SRC_ADD; - op->src.reg = CFI_BP; - op->src.offset = insn.displacement.value; - op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_SP; - } + if (mod_is_reg()) { + WARN("invalid LEA encoding at %s:0x%lx", sec->name, offset); + break; + } - } else if (rex == 0x49 && modrm == 0x62 && - insn.displacement.value == -8) { + /* skip non 64bit ops */ + if (!rex_w) + break; - /* - * lea -0x8(%r10), %rsp - * - * Restoring rsp back to its original value after a - * stack realignment. - */ - ADD_OP(op) { - op->src.type = OP_SRC_ADD; - op->src.reg = CFI_R10; - op->src.offset = -8; - op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_SP; - } + /* skip nontrivial SIB */ + if (have_SIB()) { + modrm_rm = sib_base; + if (sib_index != CFI_SP) + break; + } - } else if (rex == 0x49 && modrm == 0x65 && - insn.displacement.value == -16) { + /* lea disp(%rip), %dst */ + if (is_RIP()) { + insn->type = INSN_LEA_RIP; + break; + } - /* - * lea -0x10(%r13), %rsp - * - * Restoring rsp back to its original value after a - * stack realignment. - */ - ADD_OP(op) { + /* 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 = CFI_R13; - op->src.offset = -16; - op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_SP; } + op->src.reg = modrm_rm; + op->dest.type = OP_DEST_REG; + op->dest.reg = modrm_reg; } - break; case 0x8f: @@ -374,7 +503,7 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, break; case 0x90: - *type = INSN_NOP; + insn->type = INSN_NOP; break; case 0x9c: @@ -397,30 +526,56 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, if (op2 == 0x01) { - if (modrm == 0xca) - *type = INSN_CLAC; - else if (modrm == 0xcb) - *type = INSN_STAC; - + 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) { - *type = INSN_JUMP_CONDITIONAL; + insn->type = INSN_JUMP_CONDITIONAL; - } else if (op2 == 0x05 || op2 == 0x07 || op2 == 0x34 || - op2 == 0x35) { + } else if (op2 == 0x05 || op2 == 0x34) { - /* sysenter, sysret */ - *type = INSN_CONTEXT_SWITCH; + /* 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 */ - *type = INSN_BUG; + insn->type = INSN_BUG; } else if (op2 == 0x0d || op2 == 0x1f) { /* nopl/nopw */ - *type = INSN_NOP; + 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) { @@ -449,24 +604,67 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, * mov bp, sp * pop bp */ - ADD_OP(op) - op->dest.type = OP_DEST_LEAVE; + 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 */ - *type = INSN_JUMP_CONDITIONAL; + insn->type = INSN_JUMP_CONDITIONAL; break; case 0xe9: case 0xeb: - *type = INSN_JUMP_UNCONDITIONAL; + insn->type = INSN_JUMP_UNCONDITIONAL; break; case 0xc2: case 0xc3: - *type = INSN_RETURN; + 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 || strcmp(immr->sym->name, "pv_ops")) + 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 */ @@ -487,15 +685,21 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, break; } - /* fallthrough */ + fallthrough; case 0xca: /* retf */ case 0xcb: /* retf */ - *type = INSN_CONTEXT_SWITCH; + insn->type = INSN_SYSRET; + break; + + case 0xe0: /* loopne */ + case 0xe1: /* loope */ + case 0xe2: /* loop */ + insn->type = INSN_JUMP_CONDITIONAL; break; case 0xe8: - *type = INSN_CALL; + insn->type = INSN_CALL; /* * For the impact on the stack, a CALL behaves like * a PUSH of an immediate value (the return address). @@ -507,28 +711,32 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, break; case 0xfc: - *type = INSN_CLD; + insn->type = INSN_CLD; break; case 0xfd: - *type = INSN_STD; + insn->type = INSN_STD; break; case 0xff: - if (modrm_reg == 2 || modrm_reg == 3) + if (modrm_reg == 2 || modrm_reg == 3) { - *type = INSN_CALL_DYNAMIC; + 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) + } else if (modrm_reg == 4) { - *type = INSN_JUMP_DYNAMIC; + 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) + } else if (modrm_reg == 5) { /* jmpf */ - *type = INSN_CONTEXT_SWITCH; + insn->type = INSN_SYSRET; - else if (modrm_reg == 6) { + } else if (modrm_reg == 6) { /* push from mem */ ADD_OP(op) { @@ -543,7 +751,10 @@ int arch_decode_instruction(const struct elf *elf, const struct section *sec, break; } - *immediate = insn.immediate.nbytes ? insn.immediate.value : 0; + if (ins.immediate.nbytes) + insn->immediate = ins.immediate.value; + else if (ins.displacement.nbytes) + insn->immediate = ins.displacement.value; return 0; } @@ -562,6 +773,110 @@ void arch_initial_func_cfi_state(struct cfi_init_state *state) state->cfa.offset = 8; /* initial RA (return address) */ - state->regs[16].base = CFI_CFA; - state->regs[16].offset = -8; + 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_SP: + *base = CFI_SP; + break; + case ORC_REG_BP: + *base = CFI_BP; + break; + case ORC_REG_SP_INDIRECT: + *base = CFI_SP_INDIRECT; + break; + case ORC_REG_R10: + *base = CFI_R10; + break; + case ORC_REG_R13: + *base = CFI_R13; + break; + case ORC_REG_DI: + *base = CFI_DI; + break; + case ORC_REG_DX: + *base = CFI_DX; + 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; + } } diff --git a/tools/objtool/arch/x86/include/cfi_regs.h b/tools/objtool/arch/x86/include/arch/cfi_regs.h index 79bc517efba8..0579d22c433c 100644 --- a/tools/objtool/arch/x86/include/cfi_regs.h +++ b/tools/objtool/arch/x86/include/arch/cfi_regs.h @@ -4,13 +4,13 @@ #define _OBJTOOL_CFI_REGS_H #define CFI_AX 0 -#define CFI_DX 1 -#define CFI_CX 2 +#define CFI_CX 1 +#define CFI_DX 2 #define CFI_BX 3 -#define CFI_SI 4 -#define CFI_DI 5 -#define CFI_BP 6 -#define CFI_SP 7 +#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 diff --git a/tools/objtool/arch/x86/include/arch/elf.h b/tools/objtool/arch/x86/include/arch/elf.h new file mode 100644 index 000000000000..7131f7f51a4e --- /dev/null +++ b/tools/objtool/arch/x86/include/arch/elf.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 */ diff --git a/tools/objtool/arch/x86/include/arch/special.h b/tools/objtool/arch/x86/include/arch/special.h new file mode 100644 index 000000000000..ca8131352994 --- /dev/null +++ b/tools/objtool/arch/x86/include/arch/special.h @@ -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 */ diff --git a/tools/objtool/arch/x86/orc.c b/tools/objtool/arch/x86/orc.c new file mode 100644 index 000000000000..7176b9ec5b05 --- /dev/null +++ b/tools/objtool/arch/x86/orc.c @@ -0,0 +1,188 @@ +// 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> +#include <objtool/endianness.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_SP: + orc->sp_reg = ORC_REG_SP; + break; + case CFI_SP_INDIRECT: + orc->sp_reg = ORC_REG_SP_INDIRECT; + break; + case CFI_BP: + orc->sp_reg = ORC_REG_BP; + break; + case CFI_BP_INDIRECT: + orc->sp_reg = ORC_REG_BP_INDIRECT; + break; + case CFI_R10: + orc->sp_reg = ORC_REG_R10; + break; + case CFI_R13: + orc->sp_reg = ORC_REG_R13; + break; + case CFI_DI: + orc->sp_reg = ORC_REG_DI; + break; + case CFI_DX: + orc->sp_reg = ORC_REG_DX; + 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_DX: + return "dx"; + case ORC_REG_DI: + return "di"; + case ORC_REG_BP: + return "bp"; + case ORC_REG_SP: + return "sp"; + case ORC_REG_R10: + return "r10"; + case ORC_REG_R13: + return "r13"; + case ORC_REG_BP_INDIRECT: + return "bp(ind)"; + case ORC_REG_SP_INDIRECT: + return "sp(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); +} diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c new file mode 100644 index 000000000000..06ca4a2659a4 --- /dev/null +++ b/tools/objtool/arch/x86/special.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <string.h> + +#include <objtool/special.h> +#include <objtool/builtin.h> +#include <objtool/warn.h> + +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 || text_reloc->sym->type != STT_SECTION || + !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; +} diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 7a44174967b5..80239843e9f0 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -3,55 +3,332 @@ * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> */ -/* - * objtool check: - * - * This command analyzes every .o file and ensures the validity of its stack - * trace metadata. It enforces a set of rules on asm code and C inline - * assembly code so that stack traces can be reliable. - * - * For more information, see tools/objtool/Documentation/stack-validation.txt. - */ - #include <subcmd/parse-options.h> #include <string.h> -#include "builtin.h" -#include "objtool.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> -bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, validate_dup, vmlinux; +#define ORIG_SUFFIX ".orig" + +int orig_argc; +static char **orig_argv; +const char *objname; +struct opts opts; static const char * const check_usage[] = { - "objtool check [<options>] file.o", + "objtool <actions> [<options>] file.o", NULL, }; -const struct option check_options[] = { - OPT_BOOLEAN('f', "no-fp", &no_fp, "Skip frame pointer validation"), - OPT_BOOLEAN('u', "no-unreachable", &no_unreachable, "Skip 'unreachable instruction' warnings"), - OPT_BOOLEAN('r', "retpoline", &retpoline, "Validate retpoline assumptions"), - OPT_BOOLEAN('m', "module", &module, "Indicates the object will be part of a kernel module"), - OPT_BOOLEAN('b', "backtrace", &backtrace, "unwind on error"), - OPT_BOOLEAN('a', "uaccess", &uaccess, "enable uaccess checking"), - OPT_BOOLEAN('s', "stats", &stats, "print statistics"), - OPT_BOOLEAN('d', "duplicate", &validate_dup, "duplicate validation for vmlinux.o"), - OPT_BOOLEAN('l', "vmlinux", &vmlinux, "vmlinux.o validation"), +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_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('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 prefix symbols"), + 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_BOOLEAN(0 , "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"), + OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump), + + OPT_GROUP("Options:"), + OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on 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_BOOLEAN('v', "verbose", &opts.verbose, "verbose warnings"), + OPT_BOOLEAN(0, "Werror", &opts.werror, "return error on warnings"), + OPT_END(), }; -int cmd_check(int argc, const char **argv) +int cmd_parse_options(int argc, const char **argv, const char * const usage[]) { - const char *objname, *s; + 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++; + } - argc = parse_options(argc, argv, check_options, check_usage, 0); + 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(check_usage, check_options); + 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.hack_jump_label || + opts.hack_noinstr || + opts.ibt || + opts.mcount || + 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); + } + }; +} + +void print_args(void) +{ + char *backup = NULL; + + if (opts.output || opts.dryrun) + goto print; + + /* + * 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"); + goto print; + } + + strcpy(backup, objname); + strcat(backup, ORIG_SUFFIX); + if (copy_file(objname, backup)) { + backup = NULL; + goto print; + } + +print: + /* + * Print the cmdline args to make it easier to recreate. If '--output' + * wasn't used, add it to the printed args with the backup as input. + */ + fprintf(stderr, "%s", orig_argv[0]); + + for (int i = 1; i < orig_argc; i++) { + char *arg = orig_argv[i]; + + if (backup && !strcmp(arg, objname)) + fprintf(stderr, " %s -o %s", backup, objname); + else + fprintf(stderr, " %s", arg); + } + + fprintf(stderr, "\n"); +} + +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]; - s = strstr(objname, "vmlinux.o"); - if (s && !s[9]) - vmlinux = true; + 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 check(objname, false); + return 0; } diff --git a/tools/objtool/builtin-orc.c b/tools/objtool/builtin-orc.c deleted file mode 100644 index b1dfe2007962..000000000000 --- a/tools/objtool/builtin-orc.c +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> - */ - -/* - * objtool orc: - * - * This command analyzes a .o file and adds .orc_unwind and .orc_unwind_ip - * sections to it, which is used by the in-kernel ORC unwinder. - * - * This command is a superset of "objtool check". - */ - -#include <string.h> -#include "builtin.h" -#include "objtool.h" - -static const char *orc_usage[] = { - "objtool orc generate [<options>] file.o", - "objtool orc dump file.o", - NULL, -}; - -int cmd_orc(int argc, const char **argv) -{ - const char *objname; - - argc--; argv++; - if (argc <= 0) - usage_with_options(orc_usage, check_options); - - if (!strncmp(argv[0], "gen", 3)) { - argc = parse_options(argc, argv, check_options, orc_usage, 0); - if (argc != 1) - usage_with_options(orc_usage, check_options); - - objname = argv[0]; - - return check(objname, true); - } - - if (!strcmp(argv[0], "dump")) { - if (argc != 2) - usage_with_options(orc_usage, check_options); - - objname = argv[1]; - - return orc_dump(objname); - } - - usage_with_options(orc_usage, check_options); - - return 0; -} diff --git a/tools/objtool/builtin.h b/tools/objtool/builtin.h deleted file mode 100644 index 85c979caa367..000000000000 --- a/tools/objtool/builtin.h +++ /dev/null @@ -1,16 +0,0 @@ -/* 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> - -extern const struct option check_options[]; -extern bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats, validate_dup, vmlinux; - -extern int cmd_check(int argc, const char **argv); -extern int cmd_orc(int argc, const char **argv); - -#endif /* _BUILTIN_H */ diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 5fbb90a80d23..f23bdda737aa 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -5,29 +5,34 @@ #include <string.h> #include <stdlib.h> - -#include "builtin.h" -#include "cfi.h" -#include "arch.h" -#include "check.h" -#include "special.h" -#include "warn.h" - +#include <inttypes.h> +#include <sys/mman.h> + +#include <objtool/builtin.h> +#include <objtool/cfi.h> +#include <objtool/arch.h> +#include <objtool/check.h> +#include <objtool/special.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> + +#include <linux/objtool_types.h> #include <linux/hashtable.h> #include <linux/kernel.h> - -#define FAKE_JUMP_OFFSET -1 - -#define C_JUMP_TABLE_SECTION ".rodata..c_jump_table" +#include <linux/static_call_types.h> +#include <linux/string.h> struct alternative { - struct list_head list; + struct alternative *next; struct instruction *insn; - bool skip_orig; }; -const char *objname; -struct cfi_init_state initial_func_cfi; +static unsigned long nr_cfi, nr_cfi_reused, nr_cfi_cache; + +static struct cfi_init_state initial_func_cfi; +static struct cfi_state init_cfi; +static struct cfi_state func_cfi; +static struct cfi_state force_undefined_cfi; struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset) @@ -42,27 +47,29 @@ struct instruction *find_insn(struct objtool_file *file, return NULL; } -static struct instruction *next_insn_same_sec(struct objtool_file *file, - struct instruction *insn) +struct instruction *next_insn_same_sec(struct objtool_file *file, + struct instruction *insn) { - struct instruction *next = list_next_entry(insn, list); + if (insn->idx == INSN_CHUNK_MAX) + return find_insn(file, insn->sec, insn->offset + insn->len); - if (!next || &next->list == &file->insn_list || next->sec != insn->sec) + insn++; + if (!insn->len) return NULL; - return next; + return insn; } static struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn) { - struct instruction *next = list_next_entry(insn, list); - struct symbol *func = insn->func; + struct instruction *next = next_insn_same_sec(file, insn); + struct symbol *func = insn_func(insn); if (!func) return NULL; - if (&next->list != &file->insn_list && next->func == func) + if (next && insn_func(next) == func) return next; /* Check if we're already in the subfunction: */ @@ -73,17 +80,35 @@ static struct instruction *next_insn_same_func(struct objtool_file *file, return find_insn(file, func->cfunc->sec, func->cfunc->offset); } +static struct instruction *prev_insn_same_sec(struct objtool_file *file, + struct instruction *insn) +{ + if (insn->idx == 0) { + if (insn->prev_len) + return find_insn(file, insn->sec, insn->offset - insn->prev_len); + return NULL; + } + + return insn - 1; +} + static struct instruction *prev_insn_same_sym(struct objtool_file *file, - struct instruction *insn) + struct instruction *insn) { - struct instruction *prev = list_prev_entry(insn, list); + struct instruction *prev = prev_insn_same_sec(file, insn); - if (&prev->list != &file->insn_list && prev->func == insn->func) + if (prev && insn_func(prev) == insn_func(insn)) return prev; return NULL; } +#define for_each_insn(file, insn) \ + for (struct section *__sec, *__fake = (struct section *)1; \ + __fake; __fake = NULL) \ + for_each_sec(file, __sec) \ + sec_for_each_insn(file, __sec, insn) + #define func_for_each_insn(file, func, insn) \ for (insn = find_insn(file, func->sec, func->offset); \ insn; \ @@ -91,16 +116,13 @@ static struct instruction *prev_insn_same_sym(struct objtool_file *file, #define sym_for_each_insn(file, sym, insn) \ for (insn = find_insn(file, sym->sec, sym->offset); \ - insn && &insn->list != &file->insn_list && \ - insn->sec == sym->sec && \ - insn->offset < sym->offset + sym->len; \ - insn = list_next_entry(insn, list)) + insn && insn->offset < sym->offset + sym->len; \ + insn = next_insn_same_sec(file, insn)) #define sym_for_each_insn_continue_reverse(file, sym, insn) \ - for (insn = list_prev_entry(insn, list); \ - &insn->list != &file->insn_list && \ - insn->sec == sym->sec && insn->offset >= sym->offset; \ - insn = list_prev_entry(insn, list)) + for (insn = prev_insn_same_sec(file, insn); \ + insn && insn->offset >= sym->offset; \ + insn = prev_insn_same_sec(file, insn)) #define sec_for_each_insn_from(file, insn) \ for (; insn; insn = next_insn_same_sec(file, insn)) @@ -109,23 +131,108 @@ static struct instruction *prev_insn_same_sym(struct objtool_file *file, for (insn = next_insn_same_sec(file, insn); insn; \ insn = next_insn_same_sec(file, insn)) -static bool is_static_jump(struct instruction *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; +} + +static inline struct reloc *insn_jump_table(struct instruction *insn) +{ + if (insn->type == INSN_JUMP_DYNAMIC || + insn->type == INSN_CALL_DYNAMIC) + return insn->_jump_table; + + return NULL; +} + +static inline unsigned long insn_jump_table_size(struct instruction *insn) +{ + if (insn->type == INSN_JUMP_DYNAMIC || + insn->type == INSN_CALL_DYNAMIC) + return insn->_jump_table_size; + + return 0; +} + +static bool is_jump_table_jump(struct instruction *insn) { - return insn->type == INSN_JUMP_CONDITIONAL || - insn->type == INSN_JUMP_UNCONDITIONAL; + struct alt_group *alt_group = insn->alt_group; + + if (insn_jump_table(insn)) + return true; + + /* Retpoline alternative for a jump table? */ + return alt_group && alt_group->orig_group && + insn_jump_table(alt_group->orig_group->first_insn); } static bool is_sibling_call(struct instruction *insn) { - /* An indirect jump is either a sibling call or a jump to a table. */ - if (insn->type == INSN_JUMP_DYNAMIC) - return list_empty(&insn->alts); + /* + * Assume only STT_FUNC calls have jump-tables. + */ + if (insn_func(insn)) { + /* An indirect jump is either a sibling call or a jump to a table. */ + if (insn->type == INSN_JUMP_DYNAMIC) + return !is_jump_table_jump(insn); + } + + /* add_jump_destinations() sets insn_call_dest(insn) for sibling calls. */ + return (is_static_jump(insn) && insn_call_dest(insn)); +} + +/* + * Checks if a string ends with another. + */ +static bool str_ends_with(const char *s, const char *sub) +{ + const int slen = strlen(s); + const int sublen = strlen(sub); + + if (sublen > slen) + return 0; - if (!is_static_jump(insn)) + return !memcmp(s + slen - sublen, sub, sublen); +} + +/* + * Checks if a function is a Rust "noreturn" one. + */ +static bool is_rust_noreturn(const struct symbol *func) +{ + /* + * If it does not start with "_R", then it is not a Rust symbol. + */ + if (strncmp(func->name, "_R", 2)) return false; - /* add_jump_destinations() sets insn->call_dest for sibling calls. */ - return !!insn->call_dest; + /* + * These are just heuristics -- we do not control the precise symbol + * name, due to the crate disambiguators (which depend on the compiler) + * as well as changes to the source code itself between versions (since + * these come from the Rust standard library). + */ + return str_ends_with(func->name, "_4core5sliceSp15copy_from_slice17len_mismatch_fail") || + str_ends_with(func->name, "_4core6option13unwrap_failed") || + str_ends_with(func->name, "_4core6result13unwrap_failed") || + str_ends_with(func->name, "_4core9panicking5panic") || + str_ends_with(func->name, "_4core9panicking9panic_fmt") || + str_ends_with(func->name, "_4core9panicking14panic_explicit") || + str_ends_with(func->name, "_4core9panicking14panic_nounwind") || + str_ends_with(func->name, "_4core9panicking18panic_bounds_check") || + str_ends_with(func->name, "_4core9panicking19assert_failed_inner") || + str_ends_with(func->name, "_4core9panicking30panic_null_pointer_dereference") || + str_ends_with(func->name, "_4core9panicking36panic_misaligned_pointer_dereference") || + str_ends_with(func->name, "_7___rustc17rust_begin_unwind") || + strstr(func->name, "_4core9panicking13assert_failed") || + strstr(func->name, "_4core9panicking11panic_const24panic_const_") || + (strstr(func->name, "_4core5slice5index") && + strstr(func->name, "slice_") && + str_ends_with(func->name, "_fail")); } /* @@ -144,42 +251,32 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, struct instruction *insn; bool empty = true; - /* - * Unfortunately these have to be hard coded because the noreturn - * attribute isn't provided in ELF data. - */ +#define NORETURN(func) __stringify(func), static const char * const global_noreturns[] = { - "__stack_chk_fail", - "panic", - "do_exit", - "do_task_dead", - "__module_put_and_exit", - "complete_and_exit", - "__reiserfs_panic", - "lbug_with_loc", - "fortify_panic", - "usercopy_abort", - "machine_real_restart", - "rewind_stack_do_exit", - "kunit_try_catch_throw", +#include "noreturns.h" }; +#undef NORETURN if (!func) return false; - if (func->bind == STB_WEAK) - return false; + if (func->bind == STB_GLOBAL || func->bind == STB_WEAK) { + if (is_rust_noreturn(func)) + return true; - if (func->bind == STB_GLOBAL) for (i = 0; i < ARRAY_SIZE(global_noreturns); i++) if (!strcmp(func->name, global_noreturns[i])) return true; + } + + if (func->bind == STB_WEAK) + return false; if (!func->len) return false; insn = find_insn(file, func->sec, func->offset); - if (!insn->func) + if (!insn || !insn_func(insn)) return false; func_for_each_insn(file, func, insn) { @@ -215,7 +312,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, return false; } - return __dead_end_function(file, dest->func, recursion+1); + return __dead_end_function(file, insn_func(dest), recursion+1); } } @@ -240,20 +337,88 @@ static void init_cfi_state(struct cfi_state *cfi) cfi->drap_offset = -1; } -static void init_insn_state(struct insn_state *state, struct section *sec) +static void init_insn_state(struct objtool_file *file, struct insn_state *state, + struct section *sec) { memset(state, 0, sizeof(*state)); init_cfi_state(&state->cfi); - /* - * We need the full vmlinux for noinstr validation, otherwise we can - * not correctly determine insn->call_dest->sec (external symbols do - * not have a section). - */ - if (vmlinux && sec) + if (opts.noinstr && sec) state->noinstr = sec->noinstr; } +static struct cfi_state *cfi_alloc(void) +{ + struct cfi_state *cfi = calloc(1, sizeof(struct cfi_state)); + if (!cfi) { + ERROR_GLIBC("calloc"); + exit(1); + } + nr_cfi++; + return cfi; +} + +static int cfi_bits; +static struct hlist_head *cfi_hash; + +static inline bool cficmp(struct cfi_state *cfi1, struct cfi_state *cfi2) +{ + return memcmp((void *)cfi1 + sizeof(cfi1->hash), + (void *)cfi2 + sizeof(cfi2->hash), + sizeof(struct cfi_state) - sizeof(struct hlist_node)); +} + +static inline u32 cfi_key(struct cfi_state *cfi) +{ + return jhash((void *)cfi + sizeof(cfi->hash), + sizeof(*cfi) - sizeof(cfi->hash), 0); +} + +static struct cfi_state *cfi_hash_find_or_add(struct cfi_state *cfi) +{ + struct hlist_head *head = &cfi_hash[hash_min(cfi_key(cfi), cfi_bits)]; + struct cfi_state *obj; + + hlist_for_each_entry(obj, head, hash) { + if (!cficmp(cfi, obj)) { + nr_cfi_cache++; + return obj; + } + } + + obj = cfi_alloc(); + *obj = *cfi; + hlist_add_head(&obj->hash, head); + + return obj; +} + +static void cfi_hash_add(struct cfi_state *cfi) +{ + struct hlist_head *head = &cfi_hash[hash_min(cfi_key(cfi), cfi_bits)]; + + hlist_add_head(&cfi->hash, head); +} + +static void *cfi_hash_alloc(unsigned long size) +{ + cfi_bits = max(10, ilog2(size)); + cfi_hash = mmap(NULL, sizeof(struct hlist_head) << cfi_bits, + PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANON, -1, 0); + if (cfi_hash == (void *)-1L) { + ERROR_GLIBC("mmap fail cfi_hash"); + cfi_hash = NULL; + } else if (opts.stats) { + printf("cfi_bits: %d\n", cfi_bits); + } + + return cfi_hash; +} + +static unsigned long nr_insns; +static unsigned long nr_insns_visited; + /* * Call the arch-specific instruction decoder for all the instructions and add * them to the global instruction list. @@ -264,10 +429,12 @@ static int decode_instructions(struct objtool_file *file) struct symbol *func; unsigned long offset; struct instruction *insn; - unsigned long nr_insns = 0; int ret; for_each_sec(file, sec) { + struct instruction *insns = NULL; + u8 prev_len = 0; + u8 idx = 0; if (!(sec->sh.sh_flags & SHF_EXECINSTR)) continue; @@ -278,155 +445,536 @@ static int decode_instructions(struct objtool_file *file) sec->text = true; if (!strcmp(sec->name, ".noinstr.text") || - !strcmp(sec->name, ".entry.text")) + !strcmp(sec->name, ".entry.text") || + !strcmp(sec->name, ".cpuidle.text") || + !strncmp(sec->name, ".text..__x86.", 13)) sec->noinstr = true; - for (offset = 0; offset < sec->len; offset += insn->len) { - insn = malloc(sizeof(*insn)); - if (!insn) { - WARN("malloc failed"); - return -1; + /* + * .init.text code is ran before userspace and thus doesn't + * strictly need retpolines, except for modules which are + * loaded late, they very much do need retpoline in their + * .init.text + */ + if (!strcmp(sec->name, ".init.text") && !opts.module) + sec->init = true; + + for (offset = 0; offset < sec->sh.sh_size; offset += insn->len) { + if (!insns || idx == INSN_CHUNK_MAX) { + insns = calloc(sizeof(*insn), INSN_CHUNK_SIZE); + if (!insns) { + ERROR_GLIBC("calloc"); + return -1; + } + idx = 0; + } else { + idx++; } - memset(insn, 0, sizeof(*insn)); - INIT_LIST_HEAD(&insn->alts); - INIT_LIST_HEAD(&insn->stack_ops); - init_cfi_state(&insn->cfi); + insn = &insns[idx]; + insn->idx = idx; + INIT_LIST_HEAD(&insn->call_node); insn->sec = sec; insn->offset = offset; + insn->prev_len = prev_len; - ret = arch_decode_instruction(file->elf, sec, offset, - sec->len - offset, - &insn->len, &insn->type, - &insn->immediate, - &insn->stack_ops); + ret = arch_decode_instruction(file, sec, offset, + sec->sh.sh_size - offset, + insn); if (ret) - goto err; + return ret; + + prev_len = insn->len; + + /* + * By default, "ud2" is a dead end unless otherwise + * annotated, because GCC 7 inserts it for certain + * divide-by-zero cases. + */ + if (insn->type == INSN_BUG) + insn->dead_end = true; hash_add(file->insn_hash, &insn->hash, sec_offset_hash(sec, insn->offset)); - list_add_tail(&insn->list, &file->insn_list); nr_insns++; } - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC || func->alias != func) + sec_for_each_sym(sec, func) { + if (func->type != STT_NOTYPE && func->type != STT_FUNC) + continue; + + if (func->offset == sec->sh.sh_size) { + /* Heuristic: likely an "end" symbol */ + if (func->type == STT_NOTYPE) + continue; + ERROR("%s(): STT_FUNC at end of section", func->name); + return -1; + } + + if (func->embedded_insn || func->alias != func) continue; if (!find_insn(file, sec, func->offset)) { - WARN("%s(): can't find starting instruction", - func->name); + ERROR("%s(): can't find starting instruction", func->name); return -1; } - sym_for_each_insn(file, func, insn) - insn->func = func; + sym_for_each_insn(file, func, insn) { + insn->sym = func; + if (func->type == STT_FUNC && + insn->type == INSN_ENDBR && + list_empty(&insn->call_node)) { + if (insn->offset == func->offset) { + list_add_tail(&insn->call_node, &file->endbr_list); + file->nr_endbr++; + } else { + file->nr_endbr_int++; + } + } + } } } - if (stats) + if (opts.stats) printf("nr_insns: %lu\n", nr_insns); return 0; - -err: - free(insn); - return ret; } -static struct instruction *find_last_insn(struct objtool_file *file, - struct section *sec) +/* + * Read the pv_ops[] .data table to find the static initialized values. + */ +static int add_pv_ops(struct objtool_file *file, const char *symname) { - struct instruction *insn = NULL; - unsigned int offset; - unsigned int end = (sec->len > 10) ? sec->len - 10 : 0; + struct symbol *sym, *func; + unsigned long off, end; + struct reloc *reloc; + int idx; + + sym = find_symbol_by_name(file->elf, symname); + if (!sym) + return 0; - for (offset = sec->len - 1; offset >= end && !insn; offset--) - insn = find_insn(file, sec, offset); + off = sym->offset; + end = off + sym->len; + for (;;) { + reloc = find_reloc_by_dest_range(file->elf, sym->sec, off, end - off); + if (!reloc) + break; - return insn; + idx = (reloc_offset(reloc) - sym->offset) / sizeof(unsigned long); + + func = reloc->sym; + if (func->type == STT_SECTION) + func = find_symbol_by_offset(reloc->sym->sec, + reloc_addend(reloc)); + if (!func) { + ERROR_FUNC(reloc->sym->sec, reloc_addend(reloc), + "can't find func at %s[%d]", symname, idx); + return -1; + } + + if (objtool_pv_add(file, idx, func)) + return -1; + + off = reloc_offset(reloc) + 1; + if (off > end) + break; + } + + return 0; } /* - * Mark "ud2" instructions and manually annotated dead ends. + * Allocate and initialize file->pv_ops[]. */ -static int add_dead_ends(struct objtool_file *file) +static int init_pv_ops(struct objtool_file *file) +{ + static const char *pv_ops_tables[] = { + "pv_ops", + "xen_cpu_ops", + "xen_irq_ops", + "xen_mmu_ops", + NULL, + }; + const char *pv_ops; + struct symbol *sym; + int idx, nr, ret; + + if (!opts.noinstr) + return 0; + + file->pv_ops = NULL; + + sym = find_symbol_by_name(file->elf, "pv_ops"); + if (!sym) + return 0; + + nr = sym->len / sizeof(unsigned long); + file->pv_ops = calloc(sizeof(struct pv_state), nr); + if (!file->pv_ops) { + ERROR_GLIBC("calloc"); + return -1; + } + + for (idx = 0; idx < nr; idx++) + INIT_LIST_HEAD(&file->pv_ops[idx].targets); + + for (idx = 0; (pv_ops = pv_ops_tables[idx]); idx++) { + ret = add_pv_ops(file, pv_ops); + if (ret) + return ret; + } + + return 0; +} + +static int create_static_call_sections(struct objtool_file *file) { + struct static_call_site *site; struct section *sec; - struct rela *rela; struct instruction *insn; + struct symbol *key_sym; + char *key_name, *tmp; + int idx; - /* - * By default, "ud2" is a dead end unless otherwise annotated, because - * GCC 7 inserts it for certain divide-by-zero cases. - */ - for_each_insn(file, insn) - if (insn->type == INSN_BUG) - insn->dead_end = true; + sec = find_section_by_name(file->elf, ".static_call_sites"); + if (sec) { + INIT_LIST_HEAD(&file->static_call_list); + WARN("file already has .static_call_sites section, skipping"); + return 0; + } - /* - * Check for manually annotated dead ends. - */ - sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); + if (list_empty(&file->static_call_list)) + return 0; + + idx = 0; + list_for_each_entry(insn, &file->static_call_list, call_node) + idx++; + + sec = elf_create_section_pair(file->elf, ".static_call_sites", + sizeof(*site), idx, idx * 2); if (!sec) - goto reachable; + return -1; - list_for_each_entry(rela, &sec->rela_list, list) { - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + /* Allow modules to modify the low bits of static_call_site::key */ + sec->sh.sh_flags |= SHF_WRITE; + + idx = 0; + list_for_each_entry(insn, &file->static_call_list, call_node) { + + /* populate reloc for 'addr' */ + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(*site), idx * 2, + insn->sec, insn->offset)) + return -1; + + /* find key symbol */ + key_name = strdup(insn_call_dest(insn)->name); + if (!key_name) { + ERROR_GLIBC("strdup"); + return -1; + } + if (strncmp(key_name, STATIC_CALL_TRAMP_PREFIX_STR, + STATIC_CALL_TRAMP_PREFIX_LEN)) { + ERROR("static_call: trampoline name malformed: %s", key_name); return -1; } - insn = find_insn(file, rela->sym->sec, rela->addend); - if (insn) - insn = list_prev_entry(insn, list); - else if (rela->addend == rela->sym->sec->len) { - insn = find_last_insn(file, rela->sym->sec); - if (!insn) { - WARN("can't find unreachable insn at %s+0x%x", - rela->sym->sec->name, rela->addend); + tmp = key_name + STATIC_CALL_TRAMP_PREFIX_LEN - STATIC_CALL_KEY_PREFIX_LEN; + memcpy(tmp, STATIC_CALL_KEY_PREFIX_STR, STATIC_CALL_KEY_PREFIX_LEN); + + key_sym = find_symbol_by_name(file->elf, tmp); + if (!key_sym) { + if (!opts.module) { + ERROR("static_call: can't find static_call_key symbol: %s", tmp); return -1; } - } else { - WARN("can't find unreachable insn at %s+0x%x", - rela->sym->sec->name, rela->addend); - return -1; + + /* + * For modules(), the key might not be exported, which + * means the module can make static calls but isn't + * allowed to change them. + * + * In that case we temporarily set the key to be the + * trampoline address. This is fixed up in + * static_call_add_module(). + */ + key_sym = insn_call_dest(insn); } - insn->dead_end = true; + /* populate reloc for 'key' */ + if (!elf_init_reloc_data_sym(file->elf, sec, + idx * sizeof(*site) + 4, + (idx * 2) + 1, key_sym, + is_sibling_call(insn) * STATIC_CALL_SITE_TAIL)) + return -1; + + idx++; } -reachable: - /* - * These manually annotated reachable checks are needed for GCC 4.4, - * where the Linux unreachable() macro isn't supported. In that case - * GCC doesn't know the "ud2" is fatal, so it generates code as if it's - * not a dead end. - */ - sec = find_section_by_name(file->elf, ".rela.discard.reachable"); + return 0; +} + +static int create_retpoline_sites_sections(struct objtool_file *file) +{ + struct instruction *insn; + struct section *sec; + int idx; + + sec = find_section_by_name(file->elf, ".retpoline_sites"); + if (sec) { + WARN("file already has .retpoline_sites, skipping"); + return 0; + } + + idx = 0; + list_for_each_entry(insn, &file->retpoline_call_list, call_node) + idx++; + + if (!idx) + return 0; + + sec = elf_create_section_pair(file->elf, ".retpoline_sites", + sizeof(int), idx, idx); + if (!sec) + return -1; + + idx = 0; + list_for_each_entry(insn, &file->retpoline_call_list, call_node) { + + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(int), idx, + insn->sec, insn->offset)) + return -1; + + idx++; + } + + return 0; +} + +static int create_return_sites_sections(struct objtool_file *file) +{ + struct instruction *insn; + struct section *sec; + int idx; + + sec = find_section_by_name(file->elf, ".return_sites"); + if (sec) { + WARN("file already has .return_sites, skipping"); + return 0; + } + + idx = 0; + list_for_each_entry(insn, &file->return_thunk_list, call_node) + idx++; + + if (!idx) + return 0; + + sec = elf_create_section_pair(file->elf, ".return_sites", + sizeof(int), idx, idx); if (!sec) + return -1; + + idx = 0; + list_for_each_entry(insn, &file->return_thunk_list, call_node) { + + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(int), idx, + insn->sec, insn->offset)) + return -1; + + idx++; + } + + return 0; +} + +static int create_ibt_endbr_seal_sections(struct objtool_file *file) +{ + struct instruction *insn; + struct section *sec; + int idx; + + sec = find_section_by_name(file->elf, ".ibt_endbr_seal"); + if (sec) { + WARN("file already has .ibt_endbr_seal, skipping"); return 0; + } + + idx = 0; + list_for_each_entry(insn, &file->endbr_list, call_node) + idx++; + + if (opts.stats) { + printf("ibt: ENDBR at function start: %d\n", file->nr_endbr); + printf("ibt: ENDBR inside functions: %d\n", file->nr_endbr_int); + printf("ibt: superfluous ENDBR: %d\n", idx); + } + + if (!idx) + return 0; + + sec = elf_create_section_pair(file->elf, ".ibt_endbr_seal", + sizeof(int), idx, idx); + if (!sec) + return -1; + + idx = 0; + list_for_each_entry(insn, &file->endbr_list, call_node) { + + int *site = (int *)sec->data->d_buf + idx; + struct symbol *sym = insn->sym; + *site = 0; - list_for_each_entry(rela, &sec->rela_list, list) { - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + if (opts.module && sym && sym->type == STT_FUNC && + insn->offset == sym->offset && + (!strcmp(sym->name, "init_module") || + !strcmp(sym->name, "cleanup_module"))) { + ERROR("%s(): Magic init_module() function name is deprecated, use module_init(fn) instead", + sym->name); return -1; } - insn = find_insn(file, rela->sym->sec, rela->addend); - if (insn) - insn = list_prev_entry(insn, list); - else if (rela->addend == rela->sym->sec->len) { - insn = find_last_insn(file, rela->sym->sec); - if (!insn) { - WARN("can't find reachable insn at %s+0x%x", - rela->sym->sec->name, rela->addend); - return -1; - } - } else { - WARN("can't find reachable insn at %s+0x%x", - rela->sym->sec->name, rela->addend); + + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(int), idx, + insn->sec, insn->offset)) return -1; - } - insn->dead_end = false; + idx++; + } + + return 0; +} + +static int create_cfi_sections(struct objtool_file *file) +{ + struct section *sec; + struct symbol *sym; + int idx; + + sec = find_section_by_name(file->elf, ".cfi_sites"); + if (sec) { + INIT_LIST_HEAD(&file->call_list); + WARN("file already has .cfi_sites section, skipping"); + return 0; + } + + idx = 0; + for_each_sym(file, sym) { + if (sym->type != STT_FUNC) + continue; + + if (strncmp(sym->name, "__cfi_", 6)) + continue; + + idx++; + } + + sec = elf_create_section_pair(file->elf, ".cfi_sites", + sizeof(unsigned int), idx, idx); + if (!sec) + return -1; + + idx = 0; + for_each_sym(file, sym) { + if (sym->type != STT_FUNC) + continue; + + if (strncmp(sym->name, "__cfi_", 6)) + continue; + + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(unsigned int), idx, + sym->sec, sym->offset)) + return -1; + + idx++; + } + + return 0; +} + +static int create_mcount_loc_sections(struct objtool_file *file) +{ + size_t addr_size = elf_addr_size(file->elf); + struct instruction *insn; + struct section *sec; + int idx; + + sec = find_section_by_name(file->elf, "__mcount_loc"); + if (sec) { + INIT_LIST_HEAD(&file->mcount_loc_list); + WARN("file already has __mcount_loc section, skipping"); + return 0; + } + + if (list_empty(&file->mcount_loc_list)) + return 0; + + idx = 0; + list_for_each_entry(insn, &file->mcount_loc_list, call_node) + idx++; + + sec = elf_create_section_pair(file->elf, "__mcount_loc", addr_size, + idx, idx); + if (!sec) + return -1; + + sec->sh.sh_addralign = addr_size; + + idx = 0; + list_for_each_entry(insn, &file->mcount_loc_list, call_node) { + + struct reloc *reloc; + + reloc = elf_init_reloc_text_sym(file->elf, sec, idx * addr_size, idx, + insn->sec, insn->offset); + if (!reloc) + return -1; + + set_reloc_type(file->elf, reloc, addr_size == 8 ? R_ABS64 : R_ABS32); + + idx++; + } + + return 0; +} + +static int create_direct_call_sections(struct objtool_file *file) +{ + struct instruction *insn; + struct section *sec; + int idx; + + sec = find_section_by_name(file->elf, ".call_sites"); + if (sec) { + INIT_LIST_HEAD(&file->call_list); + WARN("file already has .call_sites section, skipping"); + return 0; + } + + if (list_empty(&file->call_list)) + return 0; + + idx = 0; + list_for_each_entry(insn, &file->call_list, call_node) + idx++; + + sec = elf_create_section_pair(file->elf, ".call_sites", + sizeof(unsigned int), idx, idx); + if (!sec) + return -1; + + idx = 0; + list_for_each_entry(insn, &file->call_list, call_node) { + + if (!elf_init_reloc_text_sym(file->elf, sec, + idx * sizeof(unsigned int), idx, + insn->sec, insn->offset)) + return -1; + + idx++; } return 0; @@ -435,37 +983,40 @@ reachable: /* * Warnings shouldn't be reported for ignored functions. */ -static void add_ignores(struct objtool_file *file) +static int add_ignores(struct objtool_file *file) { - struct instruction *insn; - struct section *sec; + struct section *rsec; struct symbol *func; - struct rela *rela; + struct reloc *reloc; - sec = find_section_by_name(file->elf, ".rela.discard.func_stack_frame_non_standard"); - if (!sec) - return; + rsec = find_section_by_name(file->elf, ".rela.discard.func_stack_frame_non_standard"); + if (!rsec) + return 0; - list_for_each_entry(rela, &sec->rela_list, list) { - switch (rela->sym->type) { + for_each_reloc(rsec, reloc) { + switch (reloc->sym->type) { case STT_FUNC: - func = rela->sym; + func = reloc->sym; break; case STT_SECTION: - func = find_func_by_offset(rela->sym->sec, rela->addend); + func = find_func_by_offset(reloc->sym->sec, reloc_addend(reloc)); if (!func) continue; break; default: - WARN("unexpected relocation symbol type in %s: %d", sec->name, rela->sym->type); - continue; + ERROR("unexpected relocation symbol type in %s: %d", + rsec->name, reloc->sym->type); + return -1; } - func_for_each_insn(file, func, insn) - insn->ignore = true; + func->ignore = true; + if (func->cfunc) + func->cfunc->ignore = true; } + + return 0; } /* @@ -478,7 +1029,7 @@ static void add_ignores(struct objtool_file *file) static const char *uaccess_safe_builtin[] = { /* KASAN */ "kasan_report", - "check_memory_region", + "kasan_check_range", /* KASAN out-of-line */ "__asan_loadN_noabort", "__asan_load1_noabort", @@ -492,6 +1043,8 @@ static const char *uaccess_safe_builtin[] = { "__asan_store4_noabort", "__asan_store8_noabort", "__asan_store16_noabort", + "__kasan_check_read", + "__kasan_check_write", /* KASAN in-line */ "__asan_report_load_n_noabort", "__asan_report_load1_noabort", @@ -507,6 +1060,10 @@ static const char *uaccess_safe_builtin[] = { "__asan_report_store16_noabort", /* KCSAN */ "__kcsan_check_access", + "__kcsan_mb", + "__kcsan_wmb", + "__kcsan_rmb", + "__kcsan_release", "kcsan_found_watchpoint", "kcsan_setup_watchpoint", "kcsan_check_scoped_accesses", @@ -527,6 +1084,73 @@ static const char *uaccess_safe_builtin[] = { "__tsan_write4", "__tsan_write8", "__tsan_write16", + "__tsan_read_write1", + "__tsan_read_write2", + "__tsan_read_write4", + "__tsan_read_write8", + "__tsan_read_write16", + "__tsan_volatile_read1", + "__tsan_volatile_read2", + "__tsan_volatile_read4", + "__tsan_volatile_read8", + "__tsan_volatile_read16", + "__tsan_volatile_write1", + "__tsan_volatile_write2", + "__tsan_volatile_write4", + "__tsan_volatile_write8", + "__tsan_volatile_write16", + "__tsan_atomic8_load", + "__tsan_atomic16_load", + "__tsan_atomic32_load", + "__tsan_atomic64_load", + "__tsan_atomic8_store", + "__tsan_atomic16_store", + "__tsan_atomic32_store", + "__tsan_atomic64_store", + "__tsan_atomic8_exchange", + "__tsan_atomic16_exchange", + "__tsan_atomic32_exchange", + "__tsan_atomic64_exchange", + "__tsan_atomic8_fetch_add", + "__tsan_atomic16_fetch_add", + "__tsan_atomic32_fetch_add", + "__tsan_atomic64_fetch_add", + "__tsan_atomic8_fetch_sub", + "__tsan_atomic16_fetch_sub", + "__tsan_atomic32_fetch_sub", + "__tsan_atomic64_fetch_sub", + "__tsan_atomic8_fetch_and", + "__tsan_atomic16_fetch_and", + "__tsan_atomic32_fetch_and", + "__tsan_atomic64_fetch_and", + "__tsan_atomic8_fetch_or", + "__tsan_atomic16_fetch_or", + "__tsan_atomic32_fetch_or", + "__tsan_atomic64_fetch_or", + "__tsan_atomic8_fetch_xor", + "__tsan_atomic16_fetch_xor", + "__tsan_atomic32_fetch_xor", + "__tsan_atomic64_fetch_xor", + "__tsan_atomic8_fetch_nand", + "__tsan_atomic16_fetch_nand", + "__tsan_atomic32_fetch_nand", + "__tsan_atomic64_fetch_nand", + "__tsan_atomic8_compare_exchange_strong", + "__tsan_atomic16_compare_exchange_strong", + "__tsan_atomic32_compare_exchange_strong", + "__tsan_atomic64_compare_exchange_strong", + "__tsan_atomic8_compare_exchange_weak", + "__tsan_atomic16_compare_exchange_weak", + "__tsan_atomic32_compare_exchange_weak", + "__tsan_atomic64_compare_exchange_weak", + "__tsan_atomic8_compare_exchange_val", + "__tsan_atomic16_compare_exchange_val", + "__tsan_atomic32_compare_exchange_val", + "__tsan_atomic64_compare_exchange_val", + "__tsan_atomic_thread_fence", + "__tsan_atomic_signal_fence", + "__tsan_unaligned_read16", + "__tsan_unaligned_write16", /* KCOV */ "write_comp_data", "check_kcov_mode", @@ -540,16 +1164,48 @@ static const char *uaccess_safe_builtin[] = { "__sanitizer_cov_trace_cmp4", "__sanitizer_cov_trace_cmp8", "__sanitizer_cov_trace_switch", + /* KMSAN */ + "kmsan_copy_to_user", + "kmsan_disable_current", + "kmsan_enable_current", + "kmsan_report", + "kmsan_unpoison_entry_regs", + "kmsan_unpoison_memory", + "__msan_chain_origin", + "__msan_get_context_state", + "__msan_instrument_asm_store", + "__msan_metadata_ptr_for_load_1", + "__msan_metadata_ptr_for_load_2", + "__msan_metadata_ptr_for_load_4", + "__msan_metadata_ptr_for_load_8", + "__msan_metadata_ptr_for_load_n", + "__msan_metadata_ptr_for_store_1", + "__msan_metadata_ptr_for_store_2", + "__msan_metadata_ptr_for_store_4", + "__msan_metadata_ptr_for_store_8", + "__msan_metadata_ptr_for_store_n", + "__msan_poison_alloca", + "__msan_warning", /* UBSAN */ "ubsan_type_mismatch_common", "__ubsan_handle_type_mismatch", "__ubsan_handle_type_mismatch_v1", "__ubsan_handle_shift_out_of_bounds", + "__ubsan_handle_load_invalid_value", + /* STACKLEAK */ + "stackleak_track_stack", + /* TRACE_BRANCH_PROFILING */ + "ftrace_likely_update", + /* STACKPROTECTOR */ + "__stack_chk_fail", /* misc */ "csum_partial_copy_generic", - "__memcpy_mcsafe", - "mcsafe_handle_tail", - "ftrace_likely_update", /* CONFIG_TRACE_BRANCH_PROFILING */ + "copy_mc_fragile", + "copy_mc_fragile_handle_tail", + "copy_mc_enhanced_fast_string", + "rep_stos_alternative", + "rep_movs_alternative", + "__copy_user_nocache", NULL }; @@ -558,7 +1214,7 @@ static void add_uaccess_safe(struct objtool_file *file) struct symbol *func; const char **name; - if (!uaccess) + if (!opts.uaccess) return; for (name = uaccess_safe_builtin; *name; name++) { @@ -571,108 +1227,363 @@ static void add_uaccess_safe(struct objtool_file *file) } /* - * FIXME: For now, just ignore any alternatives which add retpolines. This is - * a temporary hack, as it doesn't allow ORC to unwind from inside a retpoline. - * But it at least allows objtool to understand the control flow *around* the - * retpoline. + * Symbols that replace INSN_CALL_DYNAMIC, every (tail) call to such a symbol + * will be added to the .retpoline_sites section. */ -static int add_ignore_alternatives(struct objtool_file *file) +__weak bool arch_is_retpoline(struct symbol *sym) { - struct section *sec; - struct rela *rela; - struct instruction *insn; + return false; +} - sec = find_section_by_name(file->elf, ".rela.discard.ignore_alts"); - if (!sec) +/* + * Symbols that replace INSN_RETURN, every (tail) call to such a symbol + * will be added to the .return_sites section. + */ +__weak bool arch_is_rethunk(struct symbol *sym) +{ + return false; +} + +/* + * Symbols that are embedded inside other instructions, because sometimes crazy + * code exists. These are mostly ignored for validation purposes. + */ +__weak bool arch_is_embedded_insn(struct symbol *sym) +{ + return false; +} + +static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn) +{ + struct reloc *reloc; + + if (insn->no_reloc) + return NULL; + + if (!file) + return NULL; + + reloc = find_reloc_by_dest_range(file->elf, insn->sec, + insn->offset, insn->len); + if (!reloc) { + insn->no_reloc = 1; + return NULL; + } + + return reloc; +} + +static void remove_insn_ops(struct instruction *insn) +{ + struct stack_op *op, *next; + + for (op = insn->stack_ops; op; op = next) { + next = op->next; + free(op); + } + insn->stack_ops = NULL; +} + +static int annotate_call_site(struct objtool_file *file, + struct instruction *insn, bool sibling) +{ + struct reloc *reloc = insn_reloc(file, insn); + struct symbol *sym = insn_call_dest(insn); + + if (!sym) + sym = reloc->sym; + + if (sym->static_call_tramp) { + list_add_tail(&insn->call_node, &file->static_call_list); + return 0; + } + + if (sym->retpoline_thunk) { + list_add_tail(&insn->call_node, &file->retpoline_call_list); return 0; + } - list_for_each_entry(rela, &sec->rela_list, list) { - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); + /* + * Many compilers cannot disable KCOV or sanitizer calls with a function + * attribute so they need a little help, NOP out any such calls from + * noinstr text. + */ + if (opts.hack_noinstr && insn->sec->noinstr && sym->profiling_func) { + if (reloc) + set_reloc_type(file->elf, reloc, R_NONE); + + if (elf_write_insn(file->elf, insn->sec, + insn->offset, insn->len, + sibling ? arch_ret_insn(insn->len) + : arch_nop_insn(insn->len))) { return -1; } - insn = find_insn(file, rela->sym->sec, rela->addend); - if (!insn) { - WARN("bad .discard.ignore_alts entry"); - return -1; + insn->type = sibling ? INSN_RETURN : INSN_NOP; + + if (sibling) { + /* + * We've replaced the tail-call JMP insn by two new + * insn: RET; INT3, except we only have a single struct + * insn here. Mark it retpoline_safe to avoid the SLS + * warning, instead of adding another insn. + */ + insn->retpoline_safe = true; } - insn->ignore_alts = true; + return 0; } + if (opts.mcount && sym->fentry) { + if (sibling) + WARN_INSN(insn, "tail call to __fentry__ !?!?"); + if (opts.mnop) { + if (reloc) + set_reloc_type(file->elf, reloc, R_NONE); + + if (elf_write_insn(file->elf, insn->sec, + insn->offset, insn->len, + arch_nop_insn(insn->len))) { + return -1; + } + + insn->type = INSN_NOP; + } + + list_add_tail(&insn->call_node, &file->mcount_loc_list); + return 0; + } + + if (insn->type == INSN_CALL && !insn->sec->init && + !insn->_call_dest->embedded_insn) + list_add_tail(&insn->call_node, &file->call_list); + + if (!sibling && dead_end_function(file, sym)) + insn->dead_end = true; + return 0; } +static int add_call_dest(struct objtool_file *file, struct instruction *insn, + struct symbol *dest, bool sibling) +{ + insn->_call_dest = dest; + if (!dest) + return 0; + + /* + * Whatever stack impact regular CALLs have, should be undone + * by the RETURN of the called function. + * + * Annotated intra-function calls retain the stack_ops but + * are converted to JUMP, see read_intra_function_calls(). + */ + remove_insn_ops(insn); + + return annotate_call_site(file, insn, sibling); +} + +static int add_retpoline_call(struct objtool_file *file, struct instruction *insn) +{ + /* + * Retpoline calls/jumps are really dynamic calls/jumps in disguise, + * so convert them accordingly. + */ + switch (insn->type) { + case INSN_CALL: + insn->type = INSN_CALL_DYNAMIC; + break; + case INSN_JUMP_UNCONDITIONAL: + insn->type = INSN_JUMP_DYNAMIC; + break; + case INSN_JUMP_CONDITIONAL: + insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL; + break; + default: + return 0; + } + + insn->retpoline_safe = true; + + /* + * Whatever stack impact regular CALLs have, should be undone + * by the RETURN of the called function. + * + * Annotated intra-function calls retain the stack_ops but + * are converted to JUMP, see read_intra_function_calls(). + */ + remove_insn_ops(insn); + + return annotate_call_site(file, insn, false); +} + +static void add_return_call(struct objtool_file *file, struct instruction *insn, bool add) +{ + /* + * Return thunk tail calls are really just returns in disguise, + * so convert them accordingly. + */ + insn->type = INSN_RETURN; + insn->retpoline_safe = true; + + if (add) + list_add_tail(&insn->call_node, &file->return_thunk_list); +} + +static bool is_first_func_insn(struct objtool_file *file, + struct instruction *insn, struct symbol *sym) +{ + if (insn->offset == sym->offset) + return true; + + /* Allow direct CALL/JMP past ENDBR */ + if (opts.ibt) { + struct instruction *prev = prev_insn_same_sym(file, insn); + + if (prev && prev->type == INSN_ENDBR && + insn->offset == sym->offset + prev->len) + return true; + } + + return false; +} + +/* + * A sibling call is a tail-call to another symbol -- to differentiate from a + * recursive tail-call which is to the same symbol. + */ +static bool jump_is_sibling_call(struct objtool_file *file, + struct instruction *from, struct instruction *to) +{ + struct symbol *fs = from->sym; + struct symbol *ts = to->sym; + + /* Not a sibling call if from/to a symbol hole */ + if (!fs || !ts) + return false; + + /* Not a sibling call if not targeting the start of a symbol. */ + if (!is_first_func_insn(file, to, ts)) + return false; + + /* Disallow sibling calls into STT_NOTYPE */ + if (ts->type == STT_NOTYPE) + return false; + + /* Must not be self to be a sibling */ + return fs->pfunc != ts->pfunc; +} + /* * Find the destination instructions for all jumps. */ static int add_jump_destinations(struct objtool_file *file) { - struct instruction *insn; - struct rela *rela; + struct instruction *insn, *jump_dest; + struct reloc *reloc; struct section *dest_sec; unsigned long dest_off; + int ret; for_each_insn(file, insn) { - if (!is_static_jump(insn)) - continue; + struct symbol *func = insn_func(insn); - if (insn->ignore || insn->offset == FAKE_JUMP_OFFSET) + if (insn->jump_dest) { + /* + * handle_group_alt() may have previously set + * 'jump_dest' for some alternatives. + */ + continue; + } + if (!is_static_jump(insn)) continue; - rela = find_rela_by_dest_range(file->elf, insn->sec, - insn->offset, insn->len); - if (!rela) { + reloc = insn_reloc(file, insn); + if (!reloc) { dest_sec = insn->sec; dest_off = arch_jump_destination(insn); - } else if (rela->sym->type == STT_SECTION) { - dest_sec = rela->sym->sec; - dest_off = arch_dest_rela_offset(rela->addend); - } else if (rela->sym->sec->idx) { - dest_sec = rela->sym->sec; - dest_off = rela->sym->sym.st_value + - arch_dest_rela_offset(rela->addend); - } else if (strstr(rela->sym->name, "_indirect_thunk_")) { + } else if (reloc->sym->type == STT_SECTION) { + dest_sec = reloc->sym->sec; + dest_off = arch_dest_reloc_offset(reloc_addend(reloc)); + } else if (reloc->sym->retpoline_thunk) { + ret = add_retpoline_call(file, insn); + if (ret) + return ret; + continue; + } else if (reloc->sym->return_thunk) { + add_return_call(file, insn, true); + continue; + } else if (func) { /* - * Retpoline jumps are really dynamic jumps in - * disguise, so convert them accordingly. + * External sibling call or internal sibling call with + * STT_FUNC reloc. */ - if (insn->type == INSN_JUMP_UNCONDITIONAL) - insn->type = INSN_JUMP_DYNAMIC; - else - insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL; - - insn->retpoline_safe = true; + ret = add_call_dest(file, insn, reloc->sym, true); + if (ret) + return ret; continue; + } else if (reloc->sym->sec->idx) { + dest_sec = reloc->sym->sec; + dest_off = reloc->sym->sym.st_value + + arch_dest_reloc_offset(reloc_addend(reloc)); } else { - /* external sibling call */ - insn->call_dest = rela->sym; + /* non-func asm code jumping to another file */ continue; } - insn->jump_dest = find_insn(file, dest_sec, dest_off); - if (!insn->jump_dest) { + jump_dest = find_insn(file, dest_sec, dest_off); + if (!jump_dest) { + struct symbol *sym = find_symbol_by_offset(dest_sec, dest_off); + + /* + * This is a special case for retbleed_untrain_ret(). + * It jumps to __x86_return_thunk(), but objtool + * can't find the thunk's starting RET + * instruction, because the RET is also in the + * middle of another instruction. Objtool only + * knows about the outer instruction. + */ + if (sym && sym->embedded_insn) { + add_return_call(file, insn, false); + continue; + } /* - * This is a special case where an alt instruction - * jumps past the end of the section. These are - * handled later in handle_group_alt(). + * GCOV/KCOV dead code can jump to the end of the + * function/section. */ - if (!strcmp(insn->sec->name, ".altinstr_replacement")) + if (file->ignore_unreachables && func && + dest_sec == insn->sec && + dest_off == func->offset + func->len) continue; - WARN_FUNC("can't find jump dest instruction at %s+0x%lx", - insn->sec, insn->offset, dest_sec->name, - dest_off); + ERROR_INSN(insn, "can't find jump dest instruction at %s+0x%lx", + dest_sec->name, dest_off); return -1; } /* + * An intra-TU jump in retpoline.o might not have a relocation + * for its jump dest, in which case the above + * add_{retpoline,return}_call() didn't happen. + */ + if (jump_dest->sym && jump_dest->offset == jump_dest->sym->offset) { + if (jump_dest->sym->retpoline_thunk) { + ret = add_retpoline_call(file, insn); + if (ret) + return ret; + continue; + } + if (jump_dest->sym->return_thunk) { + add_return_call(file, insn, true); + continue; + } + } + + /* * Cross-function jump. */ - if (insn->func && insn->jump_dest->func && - insn->func != insn->jump_dest->func) { + if (func && insn_func(jump_dest) && func != insn_func(jump_dest)) { /* * For GCC 8+, create parent/child links for any cold @@ -689,31 +1600,39 @@ static int add_jump_destinations(struct objtool_file *file) * case where the parent function's only reference to a * subfunction is through a jump table. */ - if (!strstr(insn->func->name, ".cold.") && - strstr(insn->jump_dest->func->name, ".cold.")) { - insn->func->cfunc = insn->jump_dest->func; - insn->jump_dest->func->pfunc = insn->func; - - } else if (insn->jump_dest->func->pfunc != insn->func->pfunc && - insn->jump_dest->offset == insn->jump_dest->func->offset) { - - /* internal sibling call */ - insn->call_dest = insn->jump_dest->func; + if (!strstr(func->name, ".cold") && + strstr(insn_func(jump_dest)->name, ".cold")) { + func->cfunc = insn_func(jump_dest); + insn_func(jump_dest)->pfunc = func; } } + + if (jump_is_sibling_call(file, insn, jump_dest)) { + /* + * Internal sibling call without reloc or with + * STT_SECTION reloc. + */ + ret = add_call_dest(file, insn, insn_func(jump_dest), true); + if (ret) + return ret; + continue; + } + + insn->jump_dest = jump_dest; } return 0; } -static void remove_insn_ops(struct instruction *insn) +static struct symbol *find_call_destination(struct section *sec, unsigned long offset) { - struct stack_op *op, *tmp; + struct symbol *call_dest; - list_for_each_entry_safe(op, tmp, &insn->stack_ops, list) { - list_del(&op->list); - free(op); - } + call_dest = find_func_by_offset(sec, offset); + if (!call_dest) + call_dest = find_symbol_by_offset(sec, offset); + + return call_dest; } /* @@ -723,139 +1642,164 @@ static int add_call_destinations(struct objtool_file *file) { struct instruction *insn; unsigned long dest_off; - struct rela *rela; + struct symbol *dest; + struct reloc *reloc; + int ret; for_each_insn(file, insn) { + struct symbol *func = insn_func(insn); if (insn->type != INSN_CALL) continue; - rela = find_rela_by_dest_range(file->elf, insn->sec, - insn->offset, insn->len); - if (!rela) { + reloc = insn_reloc(file, insn); + if (!reloc) { dest_off = arch_jump_destination(insn); - insn->call_dest = find_func_by_offset(insn->sec, dest_off); - if (!insn->call_dest) - insn->call_dest = find_symbol_by_offset(insn->sec, dest_off); + dest = find_call_destination(insn->sec, dest_off); - if (insn->ignore) + ret = add_call_dest(file, insn, dest, false); + if (ret) + return ret; + + if (func && func->ignore) continue; - if (!insn->call_dest) { - WARN_FUNC("unannotated intra-function call", insn->sec, insn->offset); + if (!insn_call_dest(insn)) { + ERROR_INSN(insn, "unannotated intra-function call"); return -1; } - if (insn->func && insn->call_dest->type != STT_FUNC) { - WARN_FUNC("unsupported call to non-function", - insn->sec, insn->offset); + if (func && insn_call_dest(insn)->type != STT_FUNC) { + ERROR_INSN(insn, "unsupported call to non-function"); return -1; } - } else if (rela->sym->type == STT_SECTION) { - dest_off = arch_dest_rela_offset(rela->addend); - insn->call_dest = find_func_by_offset(rela->sym->sec, - dest_off); - if (!insn->call_dest) { - WARN_FUNC("can't find call dest symbol at %s+0x%lx", - insn->sec, insn->offset, - rela->sym->sec->name, - dest_off); + } else if (reloc->sym->type == STT_SECTION) { + dest_off = arch_dest_reloc_offset(reloc_addend(reloc)); + dest = find_call_destination(reloc->sym->sec, dest_off); + if (!dest) { + ERROR_INSN(insn, "can't find call dest symbol at %s+0x%lx", + reloc->sym->sec->name, dest_off); return -1; } - } else - insn->call_dest = rela->sym; - /* - * Whatever stack impact regular CALLs have, should be undone - * by the RETURN of the called function. - * - * Annotated intra-function calls retain the stack_ops but - * are converted to JUMP, see read_intra_function_calls(). - */ - remove_insn_ops(insn); + ret = add_call_dest(file, insn, dest, false); + if (ret) + return ret; + + } else if (reloc->sym->retpoline_thunk) { + ret = add_retpoline_call(file, insn); + if (ret) + return ret; + + } else { + ret = add_call_dest(file, insn, reloc->sym, false); + if (ret) + return ret; + } } return 0; } /* - * The .alternatives section requires some extra special care, over and above - * what other special sections require: - * - * 1. Because alternatives are patched in-place, we need to insert a fake jump - * instruction at the end so that validate_branch() skips all the original - * replaced instructions when validating the new instruction path. - * - * 2. An added wrinkle is that the new instruction length might be zero. In - * that case the old instructions are replaced with noops. We simulate that - * by creating a fake jump as the only new instruction. - * - * 3. In some cases, the alternative section includes an instruction which - * conditionally jumps to the _end_ of the entry. We have to modify these - * jumps' destinations to point back to .text rather than the end of the - * entry in .altinstr_replacement. + * The .alternatives section requires some extra special care over and above + * other special sections because alternatives are patched in place. */ static int handle_group_alt(struct objtool_file *file, struct special_alt *special_alt, struct instruction *orig_insn, struct instruction **new_insn) { - static unsigned int alt_group_next_index = 1; - struct instruction *last_orig_insn, *last_new_insn, *insn, *fake_jump = NULL; - unsigned int alt_group = alt_group_next_index++; + struct instruction *last_new_insn = NULL, *insn, *nop = NULL; + struct alt_group *orig_alt_group, *new_alt_group; unsigned long dest_off; - last_orig_insn = NULL; - insn = orig_insn; - sec_for_each_insn_from(file, insn) { - if (insn->offset >= special_alt->orig_off + special_alt->orig_len) - break; + orig_alt_group = orig_insn->alt_group; + if (!orig_alt_group) { + struct instruction *last_orig_insn = NULL; - insn->alt_group = alt_group; - last_orig_insn = insn; - } + orig_alt_group = calloc(1, sizeof(*orig_alt_group)); + if (!orig_alt_group) { + ERROR_GLIBC("calloc"); + return -1; + } + orig_alt_group->cfi = calloc(special_alt->orig_len, + sizeof(struct cfi_state *)); + if (!orig_alt_group->cfi) { + ERROR_GLIBC("calloc"); + return -1; + } + + insn = orig_insn; + sec_for_each_insn_from(file, insn) { + if (insn->offset >= special_alt->orig_off + special_alt->orig_len) + break; - if (next_insn_same_sec(file, last_orig_insn)) { - fake_jump = malloc(sizeof(*fake_jump)); - if (!fake_jump) { - WARN("malloc failed"); + insn->alt_group = orig_alt_group; + last_orig_insn = insn; + } + orig_alt_group->orig_group = NULL; + orig_alt_group->first_insn = orig_insn; + orig_alt_group->last_insn = last_orig_insn; + orig_alt_group->nop = NULL; + orig_alt_group->ignore = orig_insn->ignore_alts; + } else { + if (orig_alt_group->last_insn->offset + orig_alt_group->last_insn->len - + orig_alt_group->first_insn->offset != special_alt->orig_len) { + ERROR_INSN(orig_insn, "weirdly overlapping alternative! %ld != %d", + orig_alt_group->last_insn->offset + + orig_alt_group->last_insn->len - + orig_alt_group->first_insn->offset, + special_alt->orig_len); return -1; } - memset(fake_jump, 0, sizeof(*fake_jump)); - INIT_LIST_HEAD(&fake_jump->alts); - INIT_LIST_HEAD(&fake_jump->stack_ops); - init_cfi_state(&fake_jump->cfi); + } - fake_jump->sec = special_alt->new_sec; - fake_jump->offset = FAKE_JUMP_OFFSET; - fake_jump->type = INSN_JUMP_UNCONDITIONAL; - fake_jump->jump_dest = list_next_entry(last_orig_insn, list); - fake_jump->func = orig_insn->func; + new_alt_group = calloc(1, sizeof(*new_alt_group)); + if (!new_alt_group) { + ERROR_GLIBC("calloc"); + return -1; } - if (!special_alt->new_len) { - if (!fake_jump) { - WARN("%s: empty alternative at end of section", - special_alt->orig_sec->name); + if (special_alt->new_len < special_alt->orig_len) { + /* + * Insert a fake nop at the end to make the replacement + * alt_group the same size as the original. This is needed to + * allow propagate_alt_cfi() to do its magic. When the last + * instruction affects the stack, the instruction after it (the + * nop) will propagate the new state to the shared CFI array. + */ + nop = calloc(1, sizeof(*nop)); + if (!nop) { + ERROR_GLIBC("calloc"); return -1; } + memset(nop, 0, sizeof(*nop)); + + nop->sec = special_alt->new_sec; + nop->offset = special_alt->new_off + special_alt->new_len; + nop->len = special_alt->orig_len - special_alt->new_len; + nop->type = INSN_NOP; + nop->sym = orig_insn->sym; + nop->alt_group = new_alt_group; + } - *new_insn = fake_jump; - return 0; + if (!special_alt->new_len) { + *new_insn = nop; + goto end; } - last_new_insn = NULL; - alt_group = alt_group_next_index++; insn = *new_insn; sec_for_each_insn_from(file, insn) { + struct reloc *alt_reloc; + if (insn->offset >= special_alt->new_off + special_alt->new_len) break; last_new_insn = insn; - insn->ignore = orig_insn->ignore_alts; - insn->func = orig_insn->func; - insn->alt_group = alt_group; + insn->sym = orig_insn->sym; + insn->alt_group = new_alt_group; /* * Since alternative replacement code is copy/pasted by the @@ -864,17 +1808,12 @@ static int handle_group_alt(struct objtool_file *file, * .altinstr_replacement section, unless the arch's * alternatives code can adjust the relative offsets * accordingly. - * - * The x86 alternatives code adjusts the offsets only when it - * encounters a branch instruction at the very beginning of the - * replacement group. */ - if ((insn->offset != special_alt->new_off || - (insn->type != INSN_CALL && !is_static_jump(insn))) && - find_rela_by_dest_range(file->elf, insn->sec, insn->offset, insn->len)) { + alt_reloc = insn_reloc(file, insn); + if (alt_reloc && arch_pc_relative_reloc(alt_reloc) && + !arch_support_alt_relocation(special_alt, insn, alt_reloc)) { - WARN_FUNC("unsupported relocation in alternatives section", - insn->sec, insn->offset); + ERROR_INSN(insn, "unsupported relocation in alternatives section"); return -1; } @@ -886,30 +1825,27 @@ static int handle_group_alt(struct objtool_file *file, dest_off = arch_jump_destination(insn); if (dest_off == special_alt->new_off + special_alt->new_len) { - if (!fake_jump) { - WARN("%s: alternative jump to end of section", - special_alt->orig_sec->name); + insn->jump_dest = next_insn_same_sec(file, orig_alt_group->last_insn); + if (!insn->jump_dest) { + ERROR_INSN(insn, "can't find alternative jump destination"); return -1; } - insn->jump_dest = fake_jump; - } - - if (!insn->jump_dest) { - WARN_FUNC("can't find alternative jump destination", - insn->sec, insn->offset); - return -1; } } if (!last_new_insn) { - WARN_FUNC("can't find last new alternative instruction", - special_alt->new_sec, special_alt->new_off); + ERROR_FUNC(special_alt->new_sec, special_alt->new_off, + "can't find last new alternative instruction"); return -1; } - if (fake_jump) - list_add(&fake_jump->list, &last_new_insn->list); - +end: + new_alt_group->orig_group = orig_alt_group; + new_alt_group->first_insn = *new_insn; + new_alt_group->last_insn = last_new_insn; + new_alt_group->nop = nop; + new_alt_group->ignore = (*new_insn)->ignore_alts; + new_alt_group->cfi = orig_alt_group->cfi; return 0; } @@ -923,16 +1859,43 @@ static int handle_jump_alt(struct objtool_file *file, struct instruction *orig_insn, struct instruction **new_insn) { - if (orig_insn->type == INSN_NOP) - return 0; + if (orig_insn->type != INSN_JUMP_UNCONDITIONAL && + orig_insn->type != INSN_NOP) { - if (orig_insn->type != INSN_JUMP_UNCONDITIONAL) { - WARN_FUNC("unsupported instruction at jump label", - orig_insn->sec, orig_insn->offset); + ERROR_INSN(orig_insn, "unsupported instruction at jump label"); return -1; } - *new_insn = list_next_entry(orig_insn, list); + if (opts.hack_jump_label && special_alt->key_addend & 2) { + struct reloc *reloc = insn_reloc(file, orig_insn); + + if (reloc) + set_reloc_type(file->elf, reloc, R_NONE); + + if (elf_write_insn(file->elf, orig_insn->sec, + orig_insn->offset, orig_insn->len, + arch_nop_insn(orig_insn->len))) { + return -1; + } + + orig_insn->type = INSN_NOP; + } + + if (orig_insn->type == INSN_NOP) { + if (orig_insn->len == 2) + file->jl_nop_short++; + else + file->jl_nop_long++; + + return 0; + } + + if (orig_insn->len == 2) + file->jl_short++; + else + file->jl_long++; + + *new_insn = next_insn_same_sec(file, orig_insn); return 0; } @@ -950,19 +1913,17 @@ static int add_special_section_alts(struct objtool_file *file) struct alternative *alt; int ret; - ret = special_get_alts(file->elf, &special_alts); - if (ret) - return ret; + if (special_get_alts(file->elf, &special_alts)) + return -1; list_for_each_entry_safe(special_alt, tmp, &special_alts, list) { orig_insn = find_insn(file, special_alt->orig_sec, special_alt->orig_off); if (!orig_insn) { - WARN_FUNC("special: can't find orig instruction", - special_alt->orig_sec, special_alt->orig_off); - ret = -1; - goto out; + ERROR_FUNC(special_alt->orig_sec, special_alt->orig_off, + "special: can't find orig instruction"); + return -1; } new_insn = NULL; @@ -970,102 +1931,122 @@ static int add_special_section_alts(struct objtool_file *file) new_insn = find_insn(file, special_alt->new_sec, special_alt->new_off); if (!new_insn) { - WARN_FUNC("special: can't find new instruction", - special_alt->new_sec, - special_alt->new_off); - ret = -1; - goto out; + ERROR_FUNC(special_alt->new_sec, special_alt->new_off, + "special: can't find new instruction"); + return -1; } } if (special_alt->group) { if (!special_alt->orig_len) { - WARN_FUNC("empty alternative entry", - orig_insn->sec, orig_insn->offset); + ERROR_INSN(orig_insn, "empty alternative entry"); continue; } ret = handle_group_alt(file, special_alt, orig_insn, &new_insn); if (ret) - goto out; + return ret; + } else if (special_alt->jump_or_nop) { ret = handle_jump_alt(file, special_alt, orig_insn, &new_insn); if (ret) - goto out; + return ret; } - alt = malloc(sizeof(*alt)); + alt = calloc(1, sizeof(*alt)); if (!alt) { - WARN("malloc failed"); - ret = -1; - goto out; + ERROR_GLIBC("calloc"); + return -1; } alt->insn = new_insn; - alt->skip_orig = special_alt->skip_orig; - orig_insn->ignore_alts |= special_alt->skip_alt; - list_add_tail(&alt->list, &orig_insn->alts); + alt->next = orig_insn->alts; + orig_insn->alts = alt; list_del(&special_alt->list); free(special_alt); } -out: - return ret; + if (opts.stats) { + printf("jl\\\tNOP\tJMP\n"); + printf("short:\t%ld\t%ld\n", file->jl_nop_short, file->jl_short); + printf("long:\t%ld\t%ld\n", file->jl_nop_long, file->jl_long); + } + + return 0; } -static int add_jump_table(struct objtool_file *file, struct instruction *insn, - struct rela *table) +__weak unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table) { - struct rela *rela = table; + return reloc->sym->offset + reloc_addend(reloc); +} + +static int add_jump_table(struct objtool_file *file, struct instruction *insn) +{ + unsigned long table_size = insn_jump_table_size(insn); + struct symbol *pfunc = insn_func(insn)->pfunc; + struct reloc *table = insn_jump_table(insn); struct instruction *dest_insn; - struct alternative *alt; - struct symbol *pfunc = insn->func->pfunc; unsigned int prev_offset = 0; + struct reloc *reloc = table; + struct alternative *alt; + unsigned long sym_offset; /* - * Each @rela is a switch table relocation which points to the target + * Each @reloc is a switch table relocation which points to the target * instruction. */ - list_for_each_entry_from(rela, &table->sec->rela_list, list) { + for_each_reloc_from(table->sec, reloc) { /* Check for the end of the table: */ - if (rela != table && rela->jump_table_start) + if (table_size && reloc_offset(reloc) - reloc_offset(table) >= table_size) + break; + if (reloc != table && is_jump_table(reloc)) break; /* Make sure the table entries are consecutive: */ - if (prev_offset && rela->offset != prev_offset + 8) + if (prev_offset && reloc_offset(reloc) != prev_offset + arch_reloc_size(reloc)) break; + sym_offset = arch_jump_table_sym_offset(reloc, table); + /* Detect function pointers from contiguous objects: */ - if (rela->sym->sec == pfunc->sec && - rela->addend == pfunc->offset) + if (reloc->sym->sec == pfunc->sec && sym_offset == pfunc->offset) break; - dest_insn = find_insn(file, rela->sym->sec, rela->addend); + /* + * Clang sometimes leaves dangling unused jump table entries + * which point to the end of the function. Ignore them. + */ + if (reloc->sym->sec == pfunc->sec && + sym_offset == pfunc->offset + pfunc->len) + goto next; + + dest_insn = find_insn(file, reloc->sym->sec, sym_offset); if (!dest_insn) break; /* Make sure the destination is in the same function: */ - if (!dest_insn->func || dest_insn->func->pfunc != pfunc) + if (!insn_func(dest_insn) || insn_func(dest_insn)->pfunc != pfunc) break; - alt = malloc(sizeof(*alt)); + alt = calloc(1, sizeof(*alt)); if (!alt) { - WARN("malloc failed"); + ERROR_GLIBC("calloc"); return -1; } alt->insn = dest_insn; - list_add_tail(&alt->list, &insn->alts); - prev_offset = rela->offset; + alt->next = insn->alts; + insn->alts = alt; +next: + prev_offset = reloc_offset(reloc); } if (!prev_offset) { - WARN_FUNC("can't find switch jump table", - insn->sec, insn->offset); + ERROR_INSN(insn, "can't find switch jump table"); return -1; } @@ -1073,56 +2054,16 @@ static int add_jump_table(struct objtool_file *file, struct instruction *insn, } /* - * find_jump_table() - Given a dynamic jump, find the switch jump table in - * .rodata associated with it. - * - * There are 3 basic 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: RETPOLINE made it harder still to decode dynamic jumps. + * find_jump_table() - Given a dynamic jump, find the switch jump table + * associated with it. */ -static struct rela *find_jump_table(struct objtool_file *file, - struct symbol *func, - struct instruction *insn) +static void find_jump_table(struct objtool_file *file, struct symbol *func, + struct instruction *insn) { - struct rela *text_rela, *table_rela; + struct reloc *table_reloc; struct instruction *dest_insn, *orig_insn = insn; - struct section *table_sec; - unsigned long table_offset; + unsigned long table_size; + unsigned long sym_offset; /* * Backward search using the @first_jump_src links, these help avoid @@ -1130,7 +2071,7 @@ static struct rela *find_jump_table(struct objtool_file *file, * it. */ for (; - insn && insn->func && insn->func->pfunc == func; + insn && insn_func(insn) && insn_func(insn)->pfunc == func; insn = insn->first_jump_src ?: prev_insn_same_sym(file, insn)) { if (insn != orig_insn && insn->type == INSN_JUMP_DYNAMIC) @@ -1141,58 +2082,24 @@ static struct rela *find_jump_table(struct objtool_file *file, insn->jump_dest && (insn->jump_dest->offset <= insn->offset || insn->jump_dest->offset > orig_insn->offset)) - break; + break; - /* look for a relocation which references .rodata */ - text_rela = find_rela_by_dest_range(file->elf, insn->sec, - insn->offset, insn->len); - if (!text_rela || text_rela->sym->type != STT_SECTION || - !text_rela->sym->sec->rodata) + table_reloc = arch_find_switch_table(file, insn, &table_size); + if (!table_reloc) continue; - table_offset = text_rela->addend; - table_sec = text_rela->sym->sec; - - if (text_rela->type == 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)) - continue; + sym_offset = table_reloc->sym->offset + reloc_addend(table_reloc); - /* - * Each table entry has a rela associated with it. The rela - * should reference text in the same function as the original - * instruction. - */ - table_rela = find_rela_by_dest(file->elf, table_sec, table_offset); - if (!table_rela) - continue; - dest_insn = find_insn(file, table_rela->sym->sec, table_rela->addend); - if (!dest_insn || !dest_insn->func || dest_insn->func->pfunc != func) + dest_insn = find_insn(file, table_reloc->sym->sec, sym_offset); + if (!dest_insn || !insn_func(dest_insn) || insn_func(dest_insn)->pfunc != func) continue; - /* - * Use of RIP-relative switch jumps is quite rare, and - * indicates a rare GCC quirk/bug which can leave dead code - * behind. - */ - if (text_rela->type == R_X86_64_PC32) - file->ignore_unreachables = true; + set_jump_table(table_reloc); + orig_insn->_jump_table = table_reloc; + orig_insn->_jump_table_size = table_size; - return table_rela; + break; } - - return NULL; } /* @@ -1203,7 +2110,6 @@ static void mark_func_jump_tables(struct objtool_file *file, struct symbol *func) { struct instruction *insn, *last = NULL; - struct rela *rela; func_for_each_insn(file, func, insn) { if (!last) @@ -1226,11 +2132,7 @@ static void mark_func_jump_tables(struct objtool_file *file, if (insn->type != INSN_JUMP_DYNAMIC) continue; - rela = find_jump_table(file, func, insn); - if (rela) { - rela->jump_table_start = true; - insn->jump_table = rela; - } + find_jump_table(file, func, insn); } } @@ -1241,10 +2143,10 @@ static int add_func_jump_tables(struct objtool_file *file, int ret; func_for_each_insn(file, func, insn) { - if (!insn->jump_table) + if (!insn_jump_table(insn)) continue; - ret = add_jump_table(file, insn, insn->jump_table); + ret = add_jump_table(file, insn); if (ret) return ret; } @@ -1259,245 +2161,338 @@ static int add_func_jump_tables(struct objtool_file *file, */ static int add_jump_table_alts(struct objtool_file *file) { - struct section *sec; struct symbol *func; int ret; if (!file->rodata) return 0; - for_each_sec(file, sec) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; + for_each_sym(file, func) { + if (func->type != STT_FUNC) + continue; - mark_func_jump_tables(file, func); - ret = add_func_jump_tables(file, func); - if (ret) - return ret; - } + mark_func_jump_tables(file, func); + ret = add_func_jump_tables(file, func); + if (ret) + return ret; } return 0; } +static void set_func_state(struct cfi_state *state) +{ + state->cfa = initial_func_cfi.cfa; + memcpy(&state->regs, &initial_func_cfi.regs, + CFI_NUM_REGS * sizeof(struct cfi_reg)); + state->stack_size = initial_func_cfi.cfa.offset; + state->type = UNWIND_HINT_TYPE_CALL; +} + static int read_unwind_hints(struct objtool_file *file) { - struct section *sec, *relasec; - struct rela *rela; + struct cfi_state cfi = init_cfi; + struct section *sec; struct unwind_hint *hint; struct instruction *insn; - struct cfi_reg *cfa; + struct reloc *reloc; + unsigned long offset; int i; sec = find_section_by_name(file->elf, ".discard.unwind_hints"); if (!sec) return 0; - relasec = sec->rela; - if (!relasec) { - WARN("missing .rela.discard.unwind_hints section"); + if (!sec->rsec) { + ERROR("missing .rela.discard.unwind_hints section"); return -1; } - if (sec->len % sizeof(struct unwind_hint)) { - WARN("struct unwind_hint size mismatch"); + if (sec->sh.sh_size % sizeof(struct unwind_hint)) { + ERROR("struct unwind_hint size mismatch"); return -1; } file->hints = true; - for (i = 0; i < sec->len / sizeof(struct unwind_hint); i++) { + for (i = 0; i < sec->sh.sh_size / sizeof(struct unwind_hint); i++) { hint = (struct unwind_hint *)sec->data->d_buf + i; - rela = find_rela_by_dest(file->elf, sec, i * sizeof(*hint)); - if (!rela) { - WARN("can't find rela for unwind_hints[%d]", i); + reloc = find_reloc_by_dest(file->elf, sec, i * sizeof(*hint)); + if (!reloc) { + ERROR("can't find reloc for unwind_hints[%d]", i); return -1; } - insn = find_insn(file, rela->sym->sec, rela->addend); + if (reloc->sym->type == STT_SECTION) { + offset = reloc_addend(reloc); + } else if (reloc->sym->local_label) { + offset = reloc->sym->offset; + } else { + ERROR("unexpected relocation symbol type in %s", sec->rsec->name); + return -1; + } + + insn = find_insn(file, reloc->sym->sec, offset); if (!insn) { - WARN("can't find insn for unwind_hints[%d]", i); + ERROR("can't find insn for unwind_hints[%d]", i); return -1; } - cfa = &insn->cfi.cfa; + insn->hint = true; - if (hint->type == UNWIND_HINT_TYPE_RET_OFFSET) { - insn->ret_offset = hint->sp_offset; + if (hint->type == UNWIND_HINT_TYPE_UNDEFINED) { + insn->cfi = &force_undefined_cfi; continue; } - insn->hint = true; + if (hint->type == UNWIND_HINT_TYPE_SAVE) { + insn->hint = false; + insn->save = true; + continue; + } - switch (hint->sp_reg) { - case ORC_REG_UNDEFINED: - cfa->base = CFI_UNDEFINED; - break; - case ORC_REG_SP: - cfa->base = CFI_SP; - break; - case ORC_REG_BP: - cfa->base = CFI_BP; - break; - case ORC_REG_SP_INDIRECT: - cfa->base = CFI_SP_INDIRECT; - break; - case ORC_REG_R10: - cfa->base = CFI_R10; - break; - case ORC_REG_R13: - cfa->base = CFI_R13; - break; - case ORC_REG_DI: - cfa->base = CFI_DI; - break; - case ORC_REG_DX: - cfa->base = CFI_DX; - break; - default: - WARN_FUNC("unsupported unwind_hint sp base reg %d", - insn->sec, insn->offset, hint->sp_reg); + if (hint->type == UNWIND_HINT_TYPE_RESTORE) { + insn->restore = true; + continue; + } + + if (hint->type == UNWIND_HINT_TYPE_REGS_PARTIAL) { + struct symbol *sym = find_symbol_by_offset(insn->sec, insn->offset); + + if (sym && sym->bind == STB_GLOBAL) { + if (opts.ibt && insn->type != INSN_ENDBR && !insn->noendbr) { + ERROR_INSN(insn, "UNWIND_HINT_IRET_REGS without ENDBR"); + return -1; + } + } + } + + if (hint->type == UNWIND_HINT_TYPE_FUNC) { + insn->cfi = &func_cfi; + continue; + } + + if (insn->cfi) + cfi = *(insn->cfi); + + if (arch_decode_hint_reg(hint->sp_reg, &cfi.cfa.base)) { + ERROR_INSN(insn, "unsupported unwind_hint sp base reg %d", hint->sp_reg); return -1; } - cfa->offset = hint->sp_offset; - insn->cfi.type = hint->type; - insn->cfi.end = hint->end; + cfi.cfa.offset = bswap_if_needed(file->elf, hint->sp_offset); + cfi.type = hint->type; + cfi.signal = hint->signal; + + insn->cfi = cfi_hash_find_or_add(&cfi); } return 0; } -static int read_retpoline_hints(struct objtool_file *file) +static int read_annotate(struct objtool_file *file, + int (*func)(struct objtool_file *file, int type, struct instruction *insn)) { struct section *sec; struct instruction *insn; - struct rela *rela; + struct reloc *reloc; + uint64_t offset; + int type, ret; - sec = find_section_by_name(file->elf, ".rela.discard.retpoline_safe"); + sec = find_section_by_name(file->elf, ".discard.annotate_insn"); if (!sec) return 0; - list_for_each_entry(rela, &sec->rela_list, list) { - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); - return -1; + if (!sec->rsec) + return 0; + + if (sec->sh.sh_entsize != 8) { + static bool warned = false; + if (!warned && opts.verbose) { + WARN("%s: dodgy linker, sh_entsize != 8", sec->name); + warned = true; } + sec->sh.sh_entsize = 8; + } + + for_each_reloc(sec->rsec, reloc) { + type = *(u32 *)(sec->data->d_buf + (reloc_idx(reloc) * sec->sh.sh_entsize) + 4); + + offset = reloc->sym->offset + reloc_addend(reloc); + insn = find_insn(file, reloc->sym->sec, offset); - insn = find_insn(file, rela->sym->sec, rela->addend); if (!insn) { - WARN("bad .discard.retpoline_safe entry"); + ERROR("bad .discard.annotate_insn entry: %d of type %d", reloc_idx(reloc), type); return -1; } - if (insn->type != INSN_JUMP_DYNAMIC && - insn->type != INSN_CALL_DYNAMIC) { - WARN_FUNC("retpoline_safe hint not an indirect jump/call", - insn->sec, insn->offset); - return -1; - } + ret = func(file, type, insn); + if (ret < 0) + return ret; + } - insn->retpoline_safe = true; + return 0; +} + +static int __annotate_early(struct objtool_file *file, int type, struct instruction *insn) +{ + switch (type) { + + /* Must be before add_special_section_alts() */ + case ANNOTYPE_IGNORE_ALTS: + insn->ignore_alts = true; + break; + + /* + * Must be before read_unwind_hints() since that needs insn->noendbr. + */ + case ANNOTYPE_NOENDBR: + insn->noendbr = 1; + break; + + default: + break; } return 0; } -static int read_instr_hints(struct objtool_file *file) +static int __annotate_ifc(struct objtool_file *file, int type, struct instruction *insn) { - struct section *sec; - struct instruction *insn; - struct rela *rela; + unsigned long dest_off; - sec = find_section_by_name(file->elf, ".rela.discard.instr_end"); - if (!sec) + if (type != ANNOTYPE_INTRA_FUNCTION_CALL) return 0; - list_for_each_entry(rela, &sec->rela_list, list) { - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); - return -1; - } + if (insn->type != INSN_CALL) { + ERROR_INSN(insn, "intra_function_call not a direct call"); + return -1; + } - insn = find_insn(file, rela->sym->sec, rela->addend); - if (!insn) { - WARN("bad .discard.instr_end entry"); - return -1; - } + /* + * Treat intra-function CALLs as JMPs, but with a stack_op. + * See add_call_destinations(), which strips stack_ops from + * normal CALLs. + */ + insn->type = INSN_JUMP_UNCONDITIONAL; - insn->instr--; + dest_off = arch_jump_destination(insn); + insn->jump_dest = find_insn(file, insn->sec, dest_off); + if (!insn->jump_dest) { + ERROR_INSN(insn, "can't find call dest at %s+0x%lx", + insn->sec->name, dest_off); + return -1; } - sec = find_section_by_name(file->elf, ".rela.discard.instr_begin"); - if (!sec) - return 0; + return 0; +} - list_for_each_entry(rela, &sec->rela_list, list) { - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); - return -1; - } +static int __annotate_late(struct objtool_file *file, int type, struct instruction *insn) +{ + switch (type) { + case ANNOTYPE_NOENDBR: + /* early */ + break; - insn = find_insn(file, rela->sym->sec, rela->addend); - if (!insn) { - WARN("bad .discard.instr_begin entry"); + case ANNOTYPE_RETPOLINE_SAFE: + if (insn->type != INSN_JUMP_DYNAMIC && + insn->type != INSN_CALL_DYNAMIC && + insn->type != INSN_RETURN && + insn->type != INSN_NOP) { + ERROR_INSN(insn, "retpoline_safe hint not an indirect jump/call/ret/nop"); return -1; } + insn->retpoline_safe = true; + break; + + case ANNOTYPE_INSTR_BEGIN: insn->instr++; + break; + + case ANNOTYPE_INSTR_END: + insn->instr--; + break; + + case ANNOTYPE_UNRET_BEGIN: + insn->unret = 1; + break; + + case ANNOTYPE_IGNORE_ALTS: + /* early */ + break; + + case ANNOTYPE_INTRA_FUNCTION_CALL: + /* ifc */ + break; + + case ANNOTYPE_REACHABLE: + insn->dead_end = false; + break; + + default: + ERROR_INSN(insn, "Unknown annotation type: %d", type); + return -1; } return 0; } -static int read_intra_function_calls(struct objtool_file *file) +/* + * Return true if name matches an instrumentation function, where calls to that + * function from noinstr code can safely be removed, but compilers won't do so. + */ +static bool is_profiling_func(const char *name) { - struct instruction *insn; - struct section *sec; - struct rela *rela; + /* + * Many compilers cannot disable KCOV with a function attribute. + */ + if (!strncmp(name, "__sanitizer_cov_", 16)) + return true; - sec = find_section_by_name(file->elf, ".rela.discard.intra_function_calls"); - if (!sec) - return 0; + /* + * Some compilers currently do not remove __tsan_func_entry/exit nor + * __tsan_atomic_signal_fence (used for barrier instrumentation) with + * the __no_sanitize_thread attribute, remove them. Once the kernel's + * minimum Clang version is 14.0, this can be removed. + */ + if (!strncmp(name, "__tsan_func_", 12) || + !strcmp(name, "__tsan_atomic_signal_fence")) + return true; - list_for_each_entry(rela, &sec->rela_list, list) { - unsigned long dest_off; + return false; +} - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", - sec->name); - return -1; - } +static int classify_symbols(struct objtool_file *file) +{ + struct symbol *func; - insn = find_insn(file, rela->sym->sec, rela->addend); - if (!insn) { - WARN("bad .discard.intra_function_call entry"); - return -1; - } + for_each_sym(file, func) { + if (func->type == STT_NOTYPE && strstarts(func->name, ".L")) + func->local_label = true; - if (insn->type != INSN_CALL) { - WARN_FUNC("intra_function_call not a direct call", - insn->sec, insn->offset); - return -1; - } + if (func->bind != STB_GLOBAL) + continue; - /* - * Treat intra-function CALLs as JMPs, but with a stack_op. - * See add_call_destinations(), which strips stack_ops from - * normal CALLs. - */ - insn->type = INSN_JUMP_UNCONDITIONAL; - - dest_off = insn->offset + insn->len + insn->immediate; - insn->jump_dest = find_insn(file, insn->sec, dest_off); - if (!insn->jump_dest) { - WARN_FUNC("can't find call dest at %s+0x%lx", - insn->sec, insn->offset, - insn->sec->name, dest_off); - return -1; - } + if (!strncmp(func->name, STATIC_CALL_TRAMP_PREFIX_STR, + strlen(STATIC_CALL_TRAMP_PREFIX_STR))) + func->static_call_tramp = true; + + if (arch_is_retpoline(func)) + func->retpoline_thunk = true; + + if (arch_is_rethunk(func)) + func->return_thunk = true; + + if (arch_is_embedded_insn(func)) + func->embedded_insn = true; + + if (arch_ftrace_match(func->name)) + func->fentry = true; + + if (is_profiling_func(func->name)) + func->profiling_func = true; } return 0; @@ -1514,13 +2509,14 @@ static void mark_rodata(struct objtool_file *file) * * - .rodata: can contain GCC switch tables * - .rodata.<func>: same, if -fdata-sections is being used - * - .rodata..c_jump_table: contains C annotated jump tables + * - .data.rel.ro.c_jump_table: contains C annotated jump tables * * .rodata.str1.* sections are ignored; they don't contain jump tables. */ for_each_sec(file, sec) { - if (!strncmp(sec->name, ".rodata", 7) && - !strstr(sec->name, ".str1.")) { + if ((!strncmp(sec->name, ".rodata", 7) && + !strstr(sec->name, ".str1.")) || + !strncmp(sec->name, ".data.rel.ro", 12)) { sec->rodata = true; found = true; } @@ -1535,30 +2531,50 @@ static int decode_sections(struct objtool_file *file) mark_rodata(file); - ret = decode_instructions(file); + ret = init_pv_ops(file); if (ret) return ret; - ret = add_dead_ends(file); + /* + * Must be before add_{jump_call}_destination. + */ + ret = classify_symbols(file); if (ret) return ret; - add_ignores(file); - add_uaccess_safe(file); + ret = decode_instructions(file); + if (ret) + return ret; - ret = add_ignore_alternatives(file); + ret = add_ignores(file); if (ret) return ret; - ret = add_jump_destinations(file); + add_uaccess_safe(file); + + ret = read_annotate(file, __annotate_early); if (ret) return ret; - ret = add_special_section_alts(file); + /* + * Must be before add_jump_destinations(), which depends on 'func' + * being set for alternatives, to enable proper sibling call detection. + */ + if (opts.stackval || opts.orc || opts.uaccess || opts.noinstr) { + ret = add_special_section_alts(file); + if (ret) + return ret; + } + + ret = add_jump_destinations(file); if (ret) return ret; - ret = read_intra_function_calls(file); + /* + * Must be before add_call_destination(); it changes INSN_CALL to + * INSN_JUMP. + */ + ret = read_annotate(file, __annotate_ifc); if (ret) return ret; @@ -1574,50 +2590,46 @@ static int decode_sections(struct objtool_file *file) if (ret) return ret; - ret = read_retpoline_hints(file); - if (ret) - return ret; - - ret = read_instr_hints(file); + /* + * Must be after add_call_destinations() such that it can override + * dead_end_function() marks. + */ + ret = read_annotate(file, __annotate_late); if (ret) return ret; return 0; } -static bool is_fentry_call(struct instruction *insn) +static bool is_special_call(struct instruction *insn) { - if (insn->type == INSN_CALL && insn->call_dest && - insn->call_dest->type == STT_NOTYPE && - !strcmp(insn->call_dest->name, "__fentry__")) - return true; + if (insn->type == INSN_CALL) { + struct symbol *dest = insn_call_dest(insn); + + if (!dest) + return false; + + if (dest->fentry || dest->embedded_insn) + return true; + } return false; } static bool has_modified_stack_frame(struct instruction *insn, struct insn_state *state) { - u8 ret_offset = insn->ret_offset; struct cfi_state *cfi = &state->cfi; int i; if (cfi->cfa.base != initial_func_cfi.cfa.base || cfi->drap) return true; - if (cfi->cfa.offset != initial_func_cfi.cfa.offset + ret_offset) + if (cfi->cfa.offset != initial_func_cfi.cfa.offset) return true; - if (cfi->stack_size != initial_func_cfi.cfa.offset + ret_offset) + if (cfi->stack_size != initial_func_cfi.cfa.offset) return true; - /* - * If there is a ret offset hint then don't check registers - * because a callee-saved register might have been pushed on - * the stack. - */ - if (ret_offset) - return false; - for (i = 0; i < CFI_NUM_REGS; i++) { if (cfi->regs[i].base != initial_func_cfi.regs[i].base || cfi->regs[i].offset != initial_func_cfi.regs[i].offset) @@ -1627,12 +2639,20 @@ static bool has_modified_stack_frame(struct instruction *insn, struct insn_state return false; } +static bool check_reg_frame_pos(const struct cfi_reg *reg, + int expected_offset) +{ + return reg->base == CFI_CFA && + reg->offset == expected_offset; +} + static bool has_valid_stack_frame(struct insn_state *state) { struct cfi_state *cfi = &state->cfi; - if (cfi->cfa.base == CFI_BP && cfi->regs[CFI_BP].base == CFI_CFA && - cfi->regs[CFI_BP].offset == -16) + if (cfi->cfa.base == CFI_BP && + check_reg_frame_pos(&cfi->regs[CFI_BP], -cfi->cfa.offset) && + check_reg_frame_pos(&cfi->regs[CFI_RA], -cfi->cfa.offset + 8)) return true; if (cfi->drap && cfi->regs[CFI_BP].base == CFI_BP) @@ -1734,22 +2754,28 @@ static void restore_reg(struct cfi_state *cfi, unsigned char reg) * 41 5d pop %r13 * c3 retq */ -static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, - struct stack_op *op) +static int update_cfi_state(struct instruction *insn, + struct instruction *next_insn, + struct cfi_state *cfi, struct stack_op *op) { struct cfi_reg *cfa = &cfi->cfa; struct cfi_reg *regs = cfi->regs; + /* ignore UNWIND_HINT_UNDEFINED regions */ + if (cfi->force_undefined) + return 0; + /* stack operations don't make sense with an undefined CFA */ if (cfa->base == CFI_UNDEFINED) { - if (insn->func) { - WARN_FUNC("undefined stack state", insn->sec, insn->offset); - return -1; + if (insn_func(insn)) { + WARN_INSN(insn, "undefined stack state"); + return 1; } return 0; } - if (cfi->type == ORC_TYPE_REGS || cfi->type == ORC_TYPE_REGS_IRET) + if (cfi->type == UNWIND_HINT_TYPE_REGS || + cfi->type == UNWIND_HINT_TYPE_REGS_PARTIAL) return update_cfi_state_regs(insn, cfi, op); switch (op->dest.type) { @@ -1760,8 +2786,7 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, case OP_SRC_REG: if (op->src.reg == CFI_SP && op->dest.reg == CFI_BP && cfa->base == CFI_SP && - regs[CFI_BP].base == CFI_CFA && - regs[CFI_BP].offset == -cfa->offset) { + check_reg_frame_pos(®s[CFI_BP], -cfa->offset)) { /* mov %rsp, %rbp */ cfa->base = op->dest.reg; @@ -1794,7 +2819,7 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, } else if (op->src.reg == CFI_BP && op->dest.reg == CFI_SP && - cfa->base == CFI_BP) { + (cfa->base == CFI_BP || cfa->base == cfi->drap_reg)) { /* * mov %rbp, %rsp @@ -1821,12 +2846,58 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, cfa->offset = -cfi->vals[op->src.reg].offset; cfi->stack_size = cfa->offset; + } else if (cfa->base == CFI_SP && + cfi->vals[op->src.reg].base == CFI_SP_INDIRECT && + cfi->vals[op->src.reg].offset == cfa->offset) { + + /* + * Stack swizzle: + * + * 1: mov %rsp, (%[tos]) + * 2: mov %[tos], %rsp + * ... + * 3: pop %rsp + * + * Where: + * + * 1 - places a pointer to the previous + * stack at the Top-of-Stack of the + * new stack. + * + * 2 - switches to the new stack. + * + * 3 - pops the Top-of-Stack to restore + * the original stack. + * + * Note: we set base to SP_INDIRECT + * here and preserve offset. Therefore + * when the unwinder reaches ToS it + * will dereference SP and then add the + * offset to find the next frame, IOW: + * (%rsp) + offset. + */ + cfa->base = CFI_SP_INDIRECT; + } else { cfa->base = CFI_UNDEFINED; cfa->offset = 0; } } + else if (op->dest.reg == CFI_SP && + cfi->vals[op->src.reg].base == CFI_SP_INDIRECT && + cfi->vals[op->src.reg].offset == cfa->offset) { + + /* + * The same stack swizzle case 2) as above. But + * because we can't change cfa->base, case 3) + * will become a regular POP. Pretend we're a + * PUSH so things don't go unbalanced. + */ + cfi->stack_size += 8; + } + + break; case OP_SRC_ADD: @@ -1839,10 +2910,27 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, break; } - if (op->dest.reg == CFI_SP && op->src.reg == CFI_BP) { + if (op->dest.reg == CFI_BP && op->src.reg == CFI_SP && + insn->sym->frame_pointer) { + /* addi.d fp,sp,imm on LoongArch */ + if (cfa->base == CFI_SP && cfa->offset == op->src.offset) { + cfa->base = CFI_BP; + cfa->offset = 0; + } + break; + } - /* lea disp(%rbp), %rsp */ - cfi->stack_size = -(op->src.offset + regs[CFI_BP].offset); + if (op->dest.reg == CFI_SP && op->src.reg == CFI_BP) { + /* addi.d sp,fp,imm on LoongArch */ + if (cfa->base == CFI_BP && cfa->offset == 0) { + if (insn->sym->frame_pointer) { + cfa->base = CFI_SP; + cfa->offset = -op->src.offset; + } + } else { + /* lea disp(%rbp), %rsp */ + cfi->stack_size = -(op->src.offset + regs[CFI_BP].offset); + } break; } @@ -1879,9 +2967,8 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, break; } - if (op->dest.reg == cfi->cfa.base) { - WARN_FUNC("unsupported stack register modification", - insn->sec, insn->offset); + if (op->dest.reg == cfi->cfa.base && !(next_insn && next_insn->hint)) { + WARN_INSN(insn, "unsupported stack register modification"); return -1; } @@ -1891,8 +2978,7 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, if (op->dest.reg != CFI_SP || (cfi->drap_reg != CFI_UNDEFINED && cfa->base != CFI_SP) || (cfi->drap_reg == CFI_UNDEFINED && cfa->base != CFI_BP)) { - WARN_FUNC("unsupported stack pointer realignment", - insn->sec, insn->offset); + WARN_INSN(insn, "unsupported stack pointer realignment"); return -1; } @@ -1912,6 +2998,13 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, case OP_SRC_POP: case OP_SRC_POPF: + if (op->dest.reg == CFI_SP && cfa->base == CFI_SP_INDIRECT) { + + /* pop %rsp; # restore from a stack swizzle */ + cfa->base = CFI_SP; + break; + } + if (!cfi->drap && op->dest.reg == cfa->base) { /* pop %rbp */ @@ -1927,7 +3020,7 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, cfa->offset = 0; cfi->drap_offset = -1; - } else if (regs[op->dest.reg].offset == -cfi->stack_size) { + } else if (cfi->stack_size == -regs[op->dest.reg].offset) { /* pop %reg */ restore_reg(cfi, op->dest.reg); @@ -1940,6 +3033,14 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, break; case OP_SRC_REG_INDIRECT: + if (!cfi->drap && op->dest.reg == cfa->base && + op->dest.reg == CFI_BP) { + + /* mov disp(%rsp), %rbp */ + cfa->base = CFI_SP; + cfa->offset = cfi->stack_size; + } + if (cfi->drap && op->src.reg == CFI_BP && op->src.offset == cfi->drap_offset) { @@ -1961,13 +3062,18 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, /* mov disp(%rbp), %reg */ /* mov disp(%rsp), %reg */ restore_reg(cfi, op->dest.reg); + + } else if (op->src.reg == CFI_SP && + op->src.offset == regs[op->dest.reg].offset + cfi->stack_size) { + + /* mov disp(%rsp), %reg */ + restore_reg(cfi, op->dest.reg); } break; default: - WARN_FUNC("unknown stack-related instruction", - insn->sec, insn->offset); + WARN_INSN(insn, "unknown stack-related instruction"); return -1; } @@ -1997,7 +3103,7 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, /* drap: push %rbp */ cfi->stack_size = 0; - } else if (regs[op->src.reg].base == CFI_UNDEFINED) { + } else { /* drap: push %reg */ save_reg(cfi, op->src.reg, CFI_BP, -cfi->stack_size); @@ -2010,7 +3116,7 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, } /* detect when asm code uses rbp as a scratch register */ - if (!no_fp && insn->func && op->src.reg == CFI_BP && + if (opts.stackval && insn_func(insn) && op->src.reg == CFI_BP && cfa->base != CFI_BP) cfi->bp_scratch = true; break; @@ -2026,9 +3132,7 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, /* save drap offset so we know when to restore it */ cfi->drap_offset = op->dest.offset; - } - - else if (regs[op->src.reg].base == CFI_UNDEFINED) { + } else { /* drap: mov reg, disp(%rbp) */ save_reg(cfi, op->src.reg, CFI_BP, op->dest.offset); @@ -2040,34 +3144,25 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, /* mov reg, disp(%rsp) */ save_reg(cfi, op->src.reg, CFI_CFA, op->dest.offset - cfi->cfa.offset); - } - - break; - case OP_DEST_LEAVE: - if ((!cfi->drap && cfa->base != CFI_BP) || - (cfi->drap && cfa->base != cfi->drap_reg)) { - WARN_FUNC("leave instruction with modified stack frame", - insn->sec, insn->offset); - return -1; - } + } else if (op->dest.reg == CFI_SP) { - /* leave (mov %rbp, %rsp; pop %rbp) */ + /* mov reg, disp(%rsp) */ + save_reg(cfi, op->src.reg, CFI_CFA, + op->dest.offset - cfi->stack_size); - cfi->stack_size = -cfi->regs[CFI_BP].offset - 8; - restore_reg(cfi, CFI_BP); + } else if (op->src.reg == CFI_SP && op->dest.offset == 0) { - if (!cfi->drap) { - cfa->base = CFI_SP; - cfa->offset -= 8; + /* mov %rsp, (%reg); # setup a stack swizzle. */ + cfi->vals[op->dest.reg].base = CFI_SP_INDIRECT; + cfi->vals[op->dest.reg].offset = cfa->offset; } break; case OP_DEST_MEM: if (op->src.type != OP_SRC_POP && op->src.type != OP_SRC_POPF) { - WARN_FUNC("unknown stack-related memory operation", - insn->sec, insn->offset); + WARN_INSN(insn, "unknown stack-related memory operation"); return -1; } @@ -2079,37 +3174,74 @@ static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi, break; default: - WARN_FUNC("unknown stack-related instruction", - insn->sec, insn->offset); + WARN_INSN(insn, "unknown stack-related instruction"); return -1; } return 0; } -static int handle_insn_ops(struct instruction *insn, struct insn_state *state) +/* + * The stack layouts of alternatives instructions can sometimes diverge when + * they have stack modifications. That's fine as long as the potential stack + * layouts don't conflict at any given potential instruction boundary. + * + * Flatten the CFIs of the different alternative code streams (both original + * and replacement) into a single shared CFI array which can be used to detect + * conflicts and nicely feed a linear array of ORC entries to the unwinder. + */ +static int propagate_alt_cfi(struct objtool_file *file, struct instruction *insn) { - struct stack_op *op; + struct cfi_state **alt_cfi; + int group_off; + + if (!insn->alt_group) + return 0; - list_for_each_entry(op, &insn->stack_ops, list) { - struct cfi_state old_cfi = state->cfi; - int res; + if (!insn->cfi) { + WARN("CFI missing"); + return -1; + } - res = update_cfi_state(insn, &state->cfi, op); - if (res) - return res; + alt_cfi = insn->alt_group->cfi; + group_off = insn->offset - insn->alt_group->first_insn->offset; - if (insn->alt_group && memcmp(&state->cfi, &old_cfi, sizeof(struct cfi_state))) { - WARN_FUNC("alternative modifies stack", insn->sec, insn->offset); + if (!alt_cfi[group_off]) { + alt_cfi[group_off] = insn->cfi; + } else { + if (cficmp(alt_cfi[group_off], insn->cfi)) { + struct alt_group *orig_group = insn->alt_group->orig_group ?: insn->alt_group; + struct instruction *orig = orig_group->first_insn; + WARN_INSN(orig, "stack layout conflict in alternatives: %s", + offstr(insn->sec, insn->offset)); return -1; } + } + + return 0; +} + +static int handle_insn_ops(struct instruction *insn, + struct instruction *next_insn, + struct insn_state *state) +{ + struct stack_op *op; + int ret; + + for (op = insn->stack_ops; op; op = op->next) { + + ret = update_cfi_state(insn, next_insn, &state->cfi, op); + if (ret) + return ret; + + if (!opts.uaccess || !insn->alt_group) + continue; if (op->dest.type == OP_DEST_PUSHF) { if (!state->uaccess_stack) { state->uaccess_stack = 1; } else if (state->uaccess_stack >> 31) { - WARN_FUNC("PUSHF stack exhausted", - insn->sec, insn->offset); + WARN_INSN(insn, "PUSHF stack exhausted"); return 1; } state->uaccess_stack <<= 1; @@ -2131,47 +3263,54 @@ static int handle_insn_ops(struct instruction *insn, struct insn_state *state) static bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) { - struct cfi_state *cfi1 = &insn->cfi; + struct cfi_state *cfi1 = insn->cfi; int i; + if (!cfi1) { + WARN("CFI missing"); + return false; + } + if (memcmp(&cfi1->cfa, &cfi2->cfa, sizeof(cfi1->cfa))) { - WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", - insn->sec, insn->offset, + WARN_INSN(insn, "stack state mismatch: cfa1=%d%+d cfa2=%d%+d", cfi1->cfa.base, cfi1->cfa.offset, cfi2->cfa.base, cfi2->cfa.offset); + return false; + + } - } else if (memcmp(&cfi1->regs, &cfi2->regs, sizeof(cfi1->regs))) { + if (memcmp(&cfi1->regs, &cfi2->regs, sizeof(cfi1->regs))) { for (i = 0; i < CFI_NUM_REGS; i++) { - if (!memcmp(&cfi1->regs[i], &cfi2->regs[i], - sizeof(struct cfi_reg))) + + if (!memcmp(&cfi1->regs[i], &cfi2->regs[i], sizeof(struct cfi_reg))) continue; - WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", - insn->sec, insn->offset, + WARN_INSN(insn, "stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", i, cfi1->regs[i].base, cfi1->regs[i].offset, i, cfi2->regs[i].base, cfi2->regs[i].offset); - break; } + return false; + } - } else if (cfi1->type != cfi2->type) { + if (cfi1->type != cfi2->type) { - WARN_FUNC("stack state mismatch: type1=%d type2=%d", - insn->sec, insn->offset, cfi1->type, cfi2->type); + WARN_INSN(insn, "stack state mismatch: type1=%d type2=%d", + cfi1->type, cfi2->type); + return false; + } - } else if (cfi1->drap != cfi2->drap || + if (cfi1->drap != cfi2->drap || (cfi1->drap && cfi1->drap_reg != cfi2->drap_reg) || (cfi1->drap && cfi1->drap_offset != cfi2->drap_offset)) { - WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", - insn->sec, insn->offset, + WARN_INSN(insn, "stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", cfi1->drap, cfi1->drap_reg, cfi1->drap_offset, cfi2->drap, cfi2->drap_reg, cfi2->drap_offset); + return false; + } - } else - return true; - - return false; + return true; } static inline bool func_uaccess_safe(struct symbol *func) @@ -2184,186 +3323,343 @@ static inline bool func_uaccess_safe(struct symbol *func) static inline const char *call_dest_name(struct instruction *insn) { - if (insn->call_dest) - return insn->call_dest->name; + static char pvname[19]; + struct reloc *reloc; + int idx; + + if (insn_call_dest(insn)) + return insn_call_dest(insn)->name; + + reloc = insn_reloc(NULL, insn); + if (reloc && !strcmp(reloc->sym->name, "pv_ops")) { + idx = (reloc_addend(reloc) / sizeof(void *)); + snprintf(pvname, sizeof(pvname), "pv_ops[%d]", idx); + return pvname; + } return "{dynamic}"; } -static int validate_call(struct instruction *insn, struct insn_state *state) +static bool pv_call_dest(struct objtool_file *file, struct instruction *insn) +{ + struct symbol *target; + struct reloc *reloc; + int idx; + + reloc = insn_reloc(file, insn); + if (!reloc || strcmp(reloc->sym->name, "pv_ops")) + return false; + + idx = (arch_dest_reloc_offset(reloc_addend(reloc)) / sizeof(void *)); + + if (file->pv_ops[idx].clean) + return true; + + file->pv_ops[idx].clean = true; + + list_for_each_entry(target, &file->pv_ops[idx].targets, pv_target) { + if (!target->sec->noinstr) { + WARN("pv_ops[%d]: %s", idx, target->name); + file->pv_ops[idx].clean = false; + } + } + + return file->pv_ops[idx].clean; +} + +static inline bool noinstr_call_dest(struct objtool_file *file, + struct instruction *insn, + struct symbol *func) +{ + /* + * We can't deal with indirect function calls at present; + * assume they're instrumented. + */ + if (!func) { + if (file->pv_ops) + return pv_call_dest(file, insn); + + return false; + } + + /* + * If the symbol is from a noinstr section; we good. + */ + if (func->sec->noinstr) + return true; + + /* + * If the symbol is a static_call trampoline, we can't tell. + */ + if (func->static_call_tramp) + return true; + + /* + * The __ubsan_handle_*() calls are like WARN(), they only happen when + * something 'BAD' happened. At the risk of taking the machine down, + * let them proceed to get the message out. + */ + if (!strncmp(func->name, "__ubsan_handle_", 15)) + return true; + + return false; +} + +static int validate_call(struct objtool_file *file, + struct instruction *insn, + struct insn_state *state) { if (state->noinstr && state->instr <= 0 && - (!insn->call_dest || !insn->call_dest->sec->noinstr)) { - WARN_FUNC("call to %s() leaves .noinstr.text section", - insn->sec, insn->offset, call_dest_name(insn)); + !noinstr_call_dest(file, insn, insn_call_dest(insn))) { + WARN_INSN(insn, "call to %s() leaves .noinstr.text section", call_dest_name(insn)); return 1; } - if (state->uaccess && !func_uaccess_safe(insn->call_dest)) { - WARN_FUNC("call to %s() with UACCESS enabled", - insn->sec, insn->offset, call_dest_name(insn)); + if (state->uaccess && !func_uaccess_safe(insn_call_dest(insn))) { + WARN_INSN(insn, "call to %s() with UACCESS enabled", call_dest_name(insn)); return 1; } if (state->df) { - WARN_FUNC("call to %s() with DF set", - insn->sec, insn->offset, call_dest_name(insn)); + WARN_INSN(insn, "call to %s() with DF set", call_dest_name(insn)); return 1; } return 0; } -static int validate_sibling_call(struct instruction *insn, struct insn_state *state) +static int validate_sibling_call(struct objtool_file *file, + struct instruction *insn, + struct insn_state *state) { - if (has_modified_stack_frame(insn, state)) { - WARN_FUNC("sibling call from callable instruction with modified stack frame", - insn->sec, insn->offset); + if (insn_func(insn) && has_modified_stack_frame(insn, state)) { + WARN_INSN(insn, "sibling call from callable instruction with modified stack frame"); return 1; } - return validate_call(insn, state); + return validate_call(file, insn, state); } static int validate_return(struct symbol *func, struct instruction *insn, struct insn_state *state) { if (state->noinstr && state->instr > 0) { - WARN_FUNC("return with instrumentation enabled", - insn->sec, insn->offset); + WARN_INSN(insn, "return with instrumentation enabled"); return 1; } if (state->uaccess && !func_uaccess_safe(func)) { - WARN_FUNC("return with UACCESS enabled", - insn->sec, insn->offset); + WARN_INSN(insn, "return with UACCESS enabled"); return 1; } if (!state->uaccess && func_uaccess_safe(func)) { - WARN_FUNC("return with UACCESS disabled from a UACCESS-safe function", - insn->sec, insn->offset); + WARN_INSN(insn, "return with UACCESS disabled from a UACCESS-safe function"); return 1; } if (state->df) { - WARN_FUNC("return with DF set", - insn->sec, insn->offset); + WARN_INSN(insn, "return with DF set"); return 1; } if (func && has_modified_stack_frame(insn, state)) { - WARN_FUNC("return with modified stack frame", - insn->sec, insn->offset); + WARN_INSN(insn, "return with modified stack frame"); return 1; } if (state->cfi.bp_scratch) { - WARN_FUNC("BP used as a scratch register", - insn->sec, insn->offset); + WARN_INSN(insn, "BP used as a scratch register"); return 1; } return 0; } -/* - * Alternatives should not contain any ORC entries, this in turn means they - * should not contain any CFI ops, which implies all instructions should have - * the same same CFI state. - * - * It is possible to constuct alternatives that have unreachable holes that go - * unreported (because they're NOPs), such holes would result in CFI_UNDEFINED - * states which then results in ORC entries, which we just said we didn't want. - * - * Avoid them by copying the CFI entry of the first instruction into the whole - * alternative. - */ -static void fill_alternative_cfi(struct objtool_file *file, struct instruction *insn) +static struct instruction *next_insn_to_validate(struct objtool_file *file, + struct instruction *insn) { - struct instruction *first_insn = insn; - int alt_group = insn->alt_group; + struct alt_group *alt_group = insn->alt_group; - sec_for_each_insn_continue(file, insn) { - if (insn->alt_group != alt_group) - break; - insn->cfi = first_insn->cfi; + /* + * Simulate the fact that alternatives are patched in-place. When the + * end of a replacement alt_group is reached, redirect objtool flow to + * the end of the original alt_group. + * + * insn->alts->insn -> alt_group->first_insn + * ... + * alt_group->last_insn + * [alt_group->nop] -> next(orig_group->last_insn) + */ + if (alt_group) { + if (alt_group->nop) { + /* ->nop implies ->orig_group */ + if (insn == alt_group->last_insn) + return alt_group->nop; + if (insn == alt_group->nop) + goto next_orig; + } + if (insn == alt_group->last_insn && alt_group->orig_group) + goto next_orig; } + + return next_insn_same_sec(file, insn); + +next_orig: + return next_insn_same_sec(file, alt_group->orig_group->last_insn); +} + +static bool skip_alt_group(struct instruction *insn) +{ + struct instruction *alt_insn = insn->alts ? insn->alts->insn : NULL; + + /* ANNOTATE_IGNORE_ALTERNATIVE */ + if (insn->alt_group && insn->alt_group->ignore) + return true; + + /* + * For NOP patched with CLAC/STAC, only follow the latter to avoid + * impossible code paths combining patched CLAC with unpatched STAC + * or vice versa. + * + * ANNOTATE_IGNORE_ALTERNATIVE could have been used here, but Linus + * requested not to do that to avoid hurting .s file readability + * around CLAC/STAC alternative sites. + */ + + if (!alt_insn) + return false; + + /* Don't override ASM_{CLAC,STAC}_UNSAFE */ + if (alt_insn->alt_group && alt_insn->alt_group->ignore) + return false; + + return alt_insn->type == INSN_CLAC || alt_insn->type == INSN_STAC; } /* * Follow the branch starting at the given instruction, and recursively follow * any other branches (jumps). Meanwhile, track the frame pointer state at * each instruction and validate all the rules described in - * tools/objtool/Documentation/stack-validation.txt. + * tools/objtool/Documentation/objtool.txt. */ static int validate_branch(struct objtool_file *file, struct symbol *func, struct instruction *insn, struct insn_state state) { struct alternative *alt; - struct instruction *next_insn; + struct instruction *next_insn, *prev_insn = NULL; struct section *sec; u8 visited; int ret; + if (func && func->ignore) + return 0; + sec = insn->sec; while (1) { - next_insn = next_insn_same_sec(file, insn); + next_insn = next_insn_to_validate(file, insn); + + if (func && insn_func(insn) && func != insn_func(insn)->pfunc) { + /* Ignore KCFI type preambles, which always fall through */ + if (!strncmp(func->name, "__cfi_", 6) || + !strncmp(func->name, "__pfx_", 6)) + return 0; + + if (file->ignore_unreachables) + return 0; - if (file->c_file && func && insn->func && func != insn->func->pfunc) { WARN("%s() falls through to next function %s()", - func->name, insn->func->name); - return 1; - } + func->name, insn_func(insn)->name); + func->warned = 1; - if (func && insn->ignore) { - WARN_FUNC("BUG: why am I validating an ignored function?", - sec, insn->offset); return 1; } - visited = 1 << state.uaccess; - if (insn->visited) { + visited = VISITED_BRANCH << state.uaccess; + if (insn->visited & VISITED_BRANCH_MASK) { if (!insn->hint && !insn_cfi_match(insn, &state.cfi)) return 1; if (insn->visited & visited) return 0; + } else { + nr_insns_visited++; } if (state.noinstr) state.instr += insn->instr; - if (insn->hint) - state.cfi = insn->cfi; - else - insn->cfi = state.cfi; + if (insn->hint) { + if (insn->restore) { + struct instruction *save_insn, *i; - insn->visited |= visited; + i = insn; + save_insn = NULL; + + sym_for_each_insn_continue_reverse(file, func, i) { + if (i->save) { + save_insn = i; + break; + } + } + + if (!save_insn) { + WARN_INSN(insn, "no corresponding CFI save for CFI restore"); + return 1; + } + + if (!save_insn->visited) { + /* + * If the restore hint insn is at the + * beginning of a basic block and was + * branched to from elsewhere, and the + * save insn hasn't been visited yet, + * defer following this branch for now. + * It will be seen later via the + * straight-line path. + */ + if (!prev_insn) + return 0; + + WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo"); + return 1; + } + + insn->cfi = save_insn->cfi; + nr_cfi_reused++; + } + + state.cfi = *insn->cfi; + } else { + /* XXX track if we actually changed state.cfi */ - if (!insn->ignore_alts && !list_empty(&insn->alts)) { - bool skip_orig = false; + if (prev_insn && !cficmp(prev_insn->cfi, &state.cfi)) { + insn->cfi = prev_insn->cfi; + nr_cfi_reused++; + } else { + insn->cfi = cfi_hash_find_or_add(&state.cfi); + } + } - list_for_each_entry(alt, &insn->alts, list) { - if (alt->skip_orig) - skip_orig = true; + insn->visited |= visited; + if (propagate_alt_cfi(file, insn)) + return 1; + + if (insn->alts) { + for (alt = insn->alts; alt; alt = alt->next) { ret = validate_branch(file, func, alt->insn, state); if (ret) { - if (backtrace) - BT_FUNC("(alt)", insn); + BT_INSN(insn, "(alt)"); return ret; } } - - if (insn->alt_group) - fill_alternative_cfi(file, insn); - - if (skip_orig) - return 0; } - if (handle_insn_ops(insn, &state)) + if (skip_alt_group(insn)) + return 0; + + if (handle_insn_ops(insn, next_insn, &state)) return 1; switch (insn->type) { @@ -2373,26 +3669,22 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, case INSN_CALL: case INSN_CALL_DYNAMIC: - ret = validate_call(insn, &state); + ret = validate_call(file, insn, &state); if (ret) return ret; - if (!no_fp && func && !is_fentry_call(insn) && + if (opts.stackval && func && !is_special_call(insn) && !has_valid_stack_frame(&state)) { - WARN_FUNC("call without frame pointer save/setup", - sec, insn->offset); + WARN_INSN(insn, "call without frame pointer save/setup"); return 1; } - if (dead_end_function(file, insn->call_dest)) - return 0; - break; case INSN_JUMP_CONDITIONAL: case INSN_JUMP_UNCONDITIONAL: - if (func && is_sibling_call(insn)) { - ret = validate_sibling_call(insn, &state); + if (is_sibling_call(insn)) { + ret = validate_sibling_call(file, insn, &state); if (ret) return ret; @@ -2400,8 +3692,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, ret = validate_branch(file, func, insn->jump_dest, state); if (ret) { - if (backtrace) - BT_FUNC("(branch)", insn); + BT_INSN(insn, "(branch)"); return ret; } } @@ -2413,8 +3704,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, case INSN_JUMP_DYNAMIC: case INSN_JUMP_DYNAMIC_CONDITIONAL: - if (func && is_sibling_call(insn)) { - ret = validate_sibling_call(insn, &state); + if (is_sibling_call(insn)) { + ret = validate_sibling_call(file, insn, &state); if (ret) return ret; } @@ -2424,17 +3715,28 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; - case INSN_CONTEXT_SWITCH: + case INSN_SYSCALL: if (func && (!next_insn || !next_insn->hint)) { - WARN_FUNC("unsupported instruction in callable function", - sec, insn->offset); + WARN_INSN(insn, "unsupported instruction in callable function"); return 1; } + + break; + + case INSN_SYSRET: + if (func && (!next_insn || !next_insn->hint)) { + WARN_INSN(insn, "unsupported instruction in callable function"); + return 1; + } + return 0; case INSN_STAC: + if (!opts.uaccess) + break; + if (state.uaccess) { - WARN_FUNC("recursive UACCESS enable", sec, insn->offset); + WARN_INSN(insn, "recursive UACCESS enable"); return 1; } @@ -2442,13 +3744,16 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; case INSN_CLAC: + if (!opts.uaccess) + break; + if (!state.uaccess && func) { - WARN_FUNC("redundant UACCESS disable", sec, insn->offset); + WARN_INSN(insn, "redundant UACCESS disable"); return 1; } if (func_uaccess_safe(func) && !state.uaccess_stack) { - WARN_FUNC("UACCESS-safe disables UACCESS", sec, insn->offset); + WARN_INSN(insn, "UACCESS-safe disables UACCESS"); return 1; } @@ -2456,15 +3761,19 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; case INSN_STD: - if (state.df) - WARN_FUNC("recursive STD", sec, insn->offset); + if (state.df) { + WARN_INSN(insn, "recursive STD"); + return 1; + } state.df = true; break; case INSN_CLD: - if (!state.df && func) - WARN_FUNC("redundant CLD", sec, insn->offset); + if (!state.df && func) { + WARN_INSN(insn, "redundant CLD"); + return 1; + } state.df = false; break; @@ -2479,44 +3788,183 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, if (!next_insn) { if (state.cfi.cfa.base == CFI_UNDEFINED) return 0; - WARN("%s: unexpected end of section", sec->name); + if (file->ignore_unreachables) + return 0; + + WARN("%s%sunexpected end of section %s", + func ? func->name : "", func ? "(): " : "", + sec->name); return 1; } + prev_insn = insn; insn = next_insn; } return 0; } +static int validate_unwind_hint(struct objtool_file *file, + struct instruction *insn, + struct insn_state *state) +{ + if (insn->hint && !insn->visited) { + int ret = validate_branch(file, insn_func(insn), insn, *state); + if (ret) + BT_INSN(insn, "<=== (hint)"); + return ret; + } + + return 0; +} + static int validate_unwind_hints(struct objtool_file *file, struct section *sec) { struct instruction *insn; struct insn_state state; - int ret, warnings = 0; + int warnings = 0; if (!file->hints) return 0; - init_insn_state(&state, sec); + init_insn_state(file, &state, sec); if (sec) { - insn = find_insn(file, sec, 0); - if (!insn) - return 0; + sec_for_each_insn(file, sec, insn) + warnings += validate_unwind_hint(file, insn, &state); } else { - insn = list_first_entry(&file->insn_list, typeof(*insn), list); + for_each_insn(file, insn) + warnings += validate_unwind_hint(file, insn, &state); } - while (&insn->list != &file->insn_list && (!sec || insn->sec == sec)) { - if (insn->hint && !insn->visited) { - ret = validate_branch(file, insn->func, insn, state); - if (ret && backtrace) - BT_FUNC("<=== (hint)", insn); - warnings += ret; + return warnings; +} + +/* + * Validate rethunk entry constraint: must untrain RET before the first RET. + * + * Follow every branch (intra-function) and ensure VALIDATE_UNRET_END comes + * before an actual RET instruction. + */ +static int validate_unret(struct objtool_file *file, struct instruction *insn) +{ + struct instruction *next, *dest; + int ret; + + for (;;) { + next = next_insn_to_validate(file, insn); + + if (insn->visited & VISITED_UNRET) + return 0; + + insn->visited |= VISITED_UNRET; + + if (insn->alts) { + struct alternative *alt; + for (alt = insn->alts; alt; alt = alt->next) { + ret = validate_unret(file, alt->insn); + if (ret) { + BT_INSN(insn, "(alt)"); + return ret; + } + } + } + + switch (insn->type) { + + case INSN_CALL_DYNAMIC: + case INSN_JUMP_DYNAMIC: + case INSN_JUMP_DYNAMIC_CONDITIONAL: + WARN_INSN(insn, "early indirect call"); + return 1; + + case INSN_JUMP_UNCONDITIONAL: + case INSN_JUMP_CONDITIONAL: + if (!is_sibling_call(insn)) { + if (!insn->jump_dest) { + WARN_INSN(insn, "unresolved jump target after linking?!?"); + return 1; + } + ret = validate_unret(file, insn->jump_dest); + if (ret) { + BT_INSN(insn, "(branch%s)", + insn->type == INSN_JUMP_CONDITIONAL ? "-cond" : ""); + return ret; + } + + if (insn->type == INSN_JUMP_UNCONDITIONAL) + return 0; + + break; + } + + /* fallthrough */ + case INSN_CALL: + dest = find_insn(file, insn_call_dest(insn)->sec, + insn_call_dest(insn)->offset); + if (!dest) { + WARN("Unresolved function after linking!?: %s", + insn_call_dest(insn)->name); + return 1; + } + + ret = validate_unret(file, dest); + if (ret) { + BT_INSN(insn, "(call)"); + return ret; + } + /* + * If a call returns without error, it must have seen UNTRAIN_RET. + * Therefore any non-error return is a success. + */ + return 0; + + case INSN_RETURN: + WARN_INSN(insn, "RET before UNTRAIN"); + return 1; + + case INSN_SYSCALL: + break; + + case INSN_SYSRET: + return 0; + + case INSN_NOP: + if (insn->retpoline_safe) + return 0; + break; + + default: + break; } - insn = list_next_entry(insn, list); + if (insn->dead_end) + return 0; + + if (!next) { + WARN_INSN(insn, "teh end!"); + return 1; + } + insn = next; + } + + return 0; +} + +/* + * Validate that all branches starting at VALIDATE_UNRET_BEGIN encounter + * VALIDATE_UNRET_END before RET. + */ +static int validate_unrets(struct objtool_file *file) +{ + struct instruction *insn; + int warnings = 0; + + for_each_insn(file, insn) { + if (!insn->unret) + continue; + + warnings += validate_unret(file, insn); } return warnings; @@ -2529,25 +3977,26 @@ static int validate_retpoline(struct objtool_file *file) for_each_insn(file, insn) { if (insn->type != INSN_JUMP_DYNAMIC && - insn->type != INSN_CALL_DYNAMIC) + insn->type != INSN_CALL_DYNAMIC && + insn->type != INSN_RETURN) continue; if (insn->retpoline_safe) continue; - /* - * .init.text code is ran before userspace and thus doesn't - * strictly need retpolines, except for modules which are - * loaded late, they very much do need retpoline in their - * .init.text - */ - if (!strcmp(insn->sec->name, ".init.text") && !module) + if (insn->sec->init) continue; - WARN_FUNC("indirect %s found in RETPOLINE build", - insn->sec, insn->offset, - insn->type == INSN_JUMP_DYNAMIC ? "jump" : "call"); + if (insn->type == INSN_RETURN) { + if (opts.rethunk) { + WARN_INSN(insn, "'naked' return found in MITIGATION_RETHUNK build"); + warnings++; + } + continue; + } + WARN_INSN(insn, "indirect %s found in MITIGATION_RETPOLINE build", + insn->type == INSN_JUMP_DYNAMIC ? "jump" : "call"); warnings++; } @@ -2557,45 +4006,90 @@ static int validate_retpoline(struct objtool_file *file) static bool is_kasan_insn(struct instruction *insn) { return (insn->type == INSN_CALL && - !strcmp(insn->call_dest->name, "__asan_handle_no_return")); + !strcmp(insn_call_dest(insn)->name, "__asan_handle_no_return")); } static bool is_ubsan_insn(struct instruction *insn) { return (insn->type == INSN_CALL && - !strcmp(insn->call_dest->name, + !strcmp(insn_call_dest(insn)->name, "__ubsan_handle_builtin_unreachable")); } -static bool ignore_unreachable_insn(struct instruction *insn) +static bool ignore_unreachable_insn(struct objtool_file *file, struct instruction *insn) { + struct symbol *func = insn_func(insn); + struct instruction *prev_insn; int i; - if (insn->ignore || insn->type == INSN_NOP) + if (insn->type == INSN_NOP || insn->type == INSN_TRAP || (func && func->ignore)) return true; /* - * Ignore any unused exceptions. This can happen when a whitelisted - * function has an exception table entry. - * - * Also ignore alternative replacement instructions. This can happen + * Ignore alternative replacement instructions. This can happen * when a whitelisted function uses one of the ALTERNATIVE macros. */ - if (!strcmp(insn->sec->name, ".fixup") || - !strcmp(insn->sec->name, ".altinstr_replacement") || + if (!strcmp(insn->sec->name, ".altinstr_replacement") || !strcmp(insn->sec->name, ".altinstr_aux")) return true; - if (!insn->func) + /* + * Whole archive runs might encounter dead code from weak symbols. + * This is where the linker will have dropped the weak symbol in + * favour of a regular symbol, but leaves the code in place. + * + * In this case we'll find a piece of code (whole function) that is not + * covered by a !section symbol. Ignore them. + */ + if (opts.link && !func) { + int size = find_symbol_hole_containing(insn->sec, insn->offset); + unsigned long end = insn->offset + size; + + if (!size) /* not a hole */ + return false; + + if (size < 0) /* hole until the end */ + return true; + + sec_for_each_insn_continue(file, insn) { + /* + * If we reach a visited instruction at or before the + * end of the hole, ignore the unreachable. + */ + if (insn->visited) + return true; + + if (insn->offset >= end) + break; + + /* + * If this hole jumps to a .cold function, mark it ignore too. + */ + if (insn->jump_dest && insn_func(insn->jump_dest) && + strstr(insn_func(insn->jump_dest)->name, ".cold")) { + insn_func(insn->jump_dest)->ignore = true; + } + } + + return false; + } + + if (!func) return false; + if (func->static_call_tramp) + return true; + /* * CONFIG_UBSAN_TRAP inserts a UD2 when it sees * __builtin_unreachable(). The BUG() macro has an unreachable() after * the UD2, which causes GCC's undefined trap logic to emit another UD2 * (or occasionally a JMP to UD2). + * + * It may also insert a UD2 after calling a __noreturn function. */ - if (list_prev_entry(insn, list)->dead_end && + prev_insn = prev_insn_same_sec(file, insn); + if (prev_insn && prev_insn->dead_end && (insn->type == INSN_BUG || (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest && insn->jump_dest->type == INSN_BUG))) @@ -2614,7 +4108,7 @@ static bool ignore_unreachable_insn(struct instruction *insn) if (insn->type == INSN_JUMP_UNCONDITIONAL) { if (insn->jump_dest && - insn->jump_dest->func == insn->func) { + insn_func(insn->jump_dest) == func) { insn = insn->jump_dest; continue; } @@ -2622,15 +4116,83 @@ static bool ignore_unreachable_insn(struct instruction *insn) break; } - if (insn->offset + insn->len >= insn->func->offset + insn->func->len) + if (insn->offset + insn->len >= func->offset + func->len) break; - insn = list_next_entry(insn, list); + insn = next_insn_same_sec(file, insn); } return false; } +static int add_prefix_symbol(struct objtool_file *file, struct symbol *func) +{ + struct instruction *insn, *prev; + struct cfi_state *cfi; + + insn = find_insn(file, func->sec, func->offset); + if (!insn) + return -1; + + for (prev = prev_insn_same_sec(file, insn); + prev; + prev = prev_insn_same_sec(file, prev)) { + u64 offset; + + if (prev->type != INSN_NOP) + return -1; + + offset = func->offset - prev->offset; + + if (offset > opts.prefix) + return -1; + + if (offset < opts.prefix) + continue; + + elf_create_prefix_symbol(file->elf, func, opts.prefix); + break; + } + + if (!prev) + return -1; + + if (!insn->cfi) { + /* + * This can happen if stack validation isn't enabled or the + * function is annotated with STACK_FRAME_NON_STANDARD. + */ + return 0; + } + + /* Propagate insn->cfi to the prefix code */ + cfi = cfi_hash_find_or_add(insn->cfi); + for (; prev != insn; prev = next_insn_same_sec(file, prev)) + prev->cfi = cfi; + + return 0; +} + +static int add_prefix_symbols(struct objtool_file *file) +{ + struct section *sec; + struct symbol *func; + + for_each_sec(file, sec) { + if (!(sec->sh.sh_flags & SHF_EXECINSTR)) + continue; + + sec_for_each_sym(sec, func) { + if (func->type != STT_FUNC) + continue; + + add_prefix_symbol(file, func); + } + } + + return 0; +} + static int validate_symbol(struct objtool_file *file, struct section *sec, struct symbol *sym, struct insn_state *state) { @@ -2646,14 +4208,15 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, return 0; insn = find_insn(file, sec, sym->offset); - if (!insn || insn->ignore || insn->visited) + if (!insn || insn->visited) return 0; - state->uaccess = sym->uaccess_safe; + if (opts.uaccess) + state->uaccess = sym->uaccess_safe; - ret = validate_branch(file, insn->func, insn, *state); - if (ret && backtrace) - BT_FUNC("<=== (sym)", insn); + ret = validate_branch(file, insn_func(insn), insn, *state); + if (ret) + BT_INSN(insn, "<=== (sym)"); return ret; } @@ -2663,15 +4226,12 @@ static int validate_section(struct objtool_file *file, struct section *sec) struct symbol *func; int warnings = 0; - list_for_each_entry(func, &sec->symbol_list, list) { + sec_for_each_sym(sec, func) { if (func->type != STT_FUNC) continue; - init_insn_state(&state, sec); - state.cfi.cfa = initial_func_cfi.cfa; - memcpy(&state.cfi.regs, &initial_func_cfi.regs, - CFI_NUM_REGS * sizeof(struct cfi_reg)); - state.cfi.stack_size = initial_func_cfi.cfa.offset; + init_insn_state(file, &state, sec); + set_func_state(&state.cfi); warnings += validate_symbol(file, sec, func, &state); } @@ -2679,7 +4239,7 @@ static int validate_section(struct objtool_file *file, struct section *sec) return warnings; } -static int validate_vmlinux_functions(struct objtool_file *file) +static int validate_noinstr_sections(struct objtool_file *file) { struct section *sec; int warnings = 0; @@ -2696,6 +4256,12 @@ static int validate_vmlinux_functions(struct objtool_file *file) warnings += validate_unwind_hints(file, sec); } + sec = find_section_by_name(file->elf, ".cpuidle.text"); + if (sec) { + warnings += validate_section(file, sec); + warnings += validate_unwind_hints(file, sec); + } + return warnings; } @@ -2714,108 +4280,529 @@ static int validate_functions(struct objtool_file *file) return warnings; } -static int validate_reachable_instructions(struct objtool_file *file) +static void mark_endbr_used(struct instruction *insn) +{ + if (!list_empty(&insn->call_node)) + list_del_init(&insn->call_node); +} + +static bool noendbr_range(struct objtool_file *file, struct instruction *insn) +{ + struct symbol *sym = find_symbol_containing(insn->sec, insn->offset-1); + struct instruction *first; + + if (!sym) + return false; + + first = find_insn(file, sym->sec, sym->offset); + if (!first) + return false; + + if (first->type != INSN_ENDBR && !first->noendbr) + return false; + + return insn->offset == sym->offset + sym->len; +} + +static int __validate_ibt_insn(struct objtool_file *file, struct instruction *insn, + struct instruction *dest) +{ + if (dest->type == INSN_ENDBR) { + mark_endbr_used(dest); + return 0; + } + + if (insn_func(dest) && insn_func(insn) && + insn_func(dest)->pfunc == insn_func(insn)->pfunc) { + /* + * Anything from->to self is either _THIS_IP_ or + * IRET-to-self. + * + * There is no sane way to annotate _THIS_IP_ since the + * compiler treats the relocation as a constant and is + * happy to fold in offsets, skewing any annotation we + * do, leading to vast amounts of false-positives. + * + * There's also compiler generated _THIS_IP_ through + * KCOV and such which we have no hope of annotating. + * + * As such, blanket accept self-references without + * issue. + */ + return 0; + } + + /* + * Accept anything ANNOTATE_NOENDBR. + */ + if (dest->noendbr) + return 0; + + /* + * Accept if this is the instruction after a symbol + * that is (no)endbr -- typical code-range usage. + */ + if (noendbr_range(file, dest)) + return 0; + + WARN_INSN(insn, "relocation to !ENDBR: %s", offstr(dest->sec, dest->offset)); + return 1; +} + +static int validate_ibt_insn(struct objtool_file *file, struct instruction *insn) { + struct instruction *dest; + struct reloc *reloc; + unsigned long off; + int warnings = 0; + + /* + * Looking for function pointer load relocations. Ignore + * direct/indirect branches: + */ + switch (insn->type) { + + case INSN_CALL: + case INSN_CALL_DYNAMIC: + case INSN_JUMP_CONDITIONAL: + case INSN_JUMP_UNCONDITIONAL: + case INSN_JUMP_DYNAMIC: + case INSN_JUMP_DYNAMIC_CONDITIONAL: + case INSN_RETURN: + case INSN_NOP: + return 0; + + case INSN_LEA_RIP: + if (!insn_reloc(file, insn)) { + /* local function pointer reference without reloc */ + + off = arch_jump_destination(insn); + + dest = find_insn(file, insn->sec, off); + if (!dest) { + WARN_INSN(insn, "corrupt function pointer reference"); + return 1; + } + + return __validate_ibt_insn(file, insn, dest); + } + break; + + default: + break; + } + + for (reloc = insn_reloc(file, insn); + reloc; + reloc = find_reloc_by_dest_range(file->elf, insn->sec, + reloc_offset(reloc) + 1, + (insn->offset + insn->len) - (reloc_offset(reloc) + 1))) { + + off = reloc->sym->offset; + if (reloc_type(reloc) == R_X86_64_PC32 || + reloc_type(reloc) == R_X86_64_PLT32) + off += arch_dest_reloc_offset(reloc_addend(reloc)); + else + off += reloc_addend(reloc); + + dest = find_insn(file, reloc->sym->sec, off); + if (!dest) + continue; + + warnings += __validate_ibt_insn(file, insn, dest); + } + + return warnings; +} + +static int validate_ibt_data_reloc(struct objtool_file *file, + struct reloc *reloc) +{ + struct instruction *dest; + + dest = find_insn(file, reloc->sym->sec, + reloc->sym->offset + reloc_addend(reloc)); + if (!dest) + return 0; + + if (dest->type == INSN_ENDBR) { + mark_endbr_used(dest); + return 0; + } + + if (dest->noendbr) + return 0; + + WARN_FUNC(reloc->sec->base, reloc_offset(reloc), + "data relocation to !ENDBR: %s", offstr(dest->sec, dest->offset)); + + return 1; +} + +/* + * Validate IBT rules and remove used ENDBR instructions from the seal list. + * Unused ENDBR instructions will be annotated for sealing (i.e., replaced with + * NOPs) later, in create_ibt_endbr_seal_sections(). + */ +static int validate_ibt(struct objtool_file *file) +{ + struct section *sec; + struct reloc *reloc; struct instruction *insn; + int warnings = 0; + + for_each_insn(file, insn) + warnings += validate_ibt_insn(file, insn); + + for_each_sec(file, sec) { + + /* Already done by validate_ibt_insn() */ + if (sec->sh.sh_flags & SHF_EXECINSTR) + continue; + + if (!sec->rsec) + continue; + + /* + * These sections can reference text addresses, but not with + * the intent to indirect branch to them. + */ + if ((!strncmp(sec->name, ".discard", 8) && + strcmp(sec->name, ".discard.ibt_endbr_noseal")) || + !strncmp(sec->name, ".debug", 6) || + !strcmp(sec->name, ".altinstructions") || + !strcmp(sec->name, ".ibt_endbr_seal") || + !strcmp(sec->name, ".orc_unwind_ip") || + !strcmp(sec->name, ".parainstructions") || + !strcmp(sec->name, ".retpoline_sites") || + !strcmp(sec->name, ".smp_locks") || + !strcmp(sec->name, ".static_call_sites") || + !strcmp(sec->name, "_error_injection_whitelist") || + !strcmp(sec->name, "_kprobe_blacklist") || + !strcmp(sec->name, "__bug_table") || + !strcmp(sec->name, "__ex_table") || + !strcmp(sec->name, "__jump_table") || + !strcmp(sec->name, "__mcount_loc") || + !strcmp(sec->name, ".kcfi_traps") || + !strcmp(sec->name, ".llvm.call-graph-profile") || + !strcmp(sec->name, ".llvm_bb_addr_map") || + !strcmp(sec->name, "__tracepoints") || + strstr(sec->name, "__patchable_function_entries")) + continue; + + for_each_reloc(sec->rsec, reloc) + warnings += validate_ibt_data_reloc(file, reloc); + } + + return warnings; +} + +static int validate_sls(struct objtool_file *file) +{ + struct instruction *insn, *next_insn; + int warnings = 0; + + for_each_insn(file, insn) { + next_insn = next_insn_same_sec(file, insn); + + if (insn->retpoline_safe) + continue; + + switch (insn->type) { + case INSN_RETURN: + if (!next_insn || next_insn->type != INSN_TRAP) { + WARN_INSN(insn, "missing int3 after ret"); + warnings++; + } + + break; + case INSN_JUMP_DYNAMIC: + if (!next_insn || next_insn->type != INSN_TRAP) { + WARN_INSN(insn, "missing int3 after indirect jump"); + warnings++; + } + break; + default: + break; + } + } + + return warnings; +} + +static int validate_reachable_instructions(struct objtool_file *file) +{ + struct instruction *insn, *prev_insn; + struct symbol *call_dest; + int warnings = 0; if (file->ignore_unreachables) return 0; for_each_insn(file, insn) { - if (insn->visited || ignore_unreachable_insn(insn)) + if (insn->visited || ignore_unreachable_insn(file, insn)) continue; - WARN_FUNC("unreachable instruction", insn->sec, insn->offset); - return 1; + prev_insn = prev_insn_same_sec(file, insn); + if (prev_insn && prev_insn->dead_end) { + call_dest = insn_call_dest(prev_insn); + if (call_dest) { + WARN_INSN(insn, "%s() missing __noreturn in .c/.h or NORETURN() in noreturns.h", + call_dest->name); + warnings++; + continue; + } + } + + WARN_INSN(insn, "unreachable instruction"); + warnings++; } - return 0; + return warnings; } -static struct objtool_file file; +/* 'funcs' is a space-separated list of function names */ +static void disas_funcs(const char *funcs) +{ + const char *objdump_str, *cross_compile; + int size, ret; + char *cmd; + + cross_compile = getenv("CROSS_COMPILE"); + if (!cross_compile) + cross_compile = ""; + + objdump_str = "%sobjdump -wdr %s | gawk -M -v _funcs='%s' '" + "BEGIN { split(_funcs, funcs); }" + "/^$/ { func_match = 0; }" + "/<.*>:/ { " + "f = gensub(/.*<(.*)>:/, \"\\\\1\", 1);" + "for (i in funcs) {" + "if (funcs[i] == f) {" + "func_match = 1;" + "base = strtonum(\"0x\" $1);" + "break;" + "}" + "}" + "}" + "{" + "if (func_match) {" + "addr = strtonum(\"0x\" $1);" + "printf(\"%%04x \", addr - base);" + "print;" + "}" + "}' 1>&2"; + + /* fake snprintf() to calculate the size */ + size = snprintf(NULL, 0, objdump_str, cross_compile, objname, funcs) + 1; + if (size <= 0) { + WARN("objdump string size calculation failed"); + return; + } + + cmd = malloc(size); + + /* real snprintf() */ + snprintf(cmd, size, objdump_str, cross_compile, objname, funcs); + ret = system(cmd); + if (ret) { + WARN("disassembly failed: %d", ret); + return; + } +} -int check(const char *_objname, bool orc) +static void disas_warned_funcs(struct objtool_file *file) { - int ret, warnings = 0; + struct symbol *sym; + char *funcs = NULL, *tmp; + + for_each_sym(file, sym) { + if (sym->warned) { + if (!funcs) { + funcs = malloc(strlen(sym->name) + 1); + if (!funcs) { + ERROR_GLIBC("malloc"); + return; + } + strcpy(funcs, sym->name); + } else { + tmp = malloc(strlen(funcs) + strlen(sym->name) + 2); + if (!tmp) { + ERROR_GLIBC("malloc"); + return; + } + sprintf(tmp, "%s %s", funcs, sym->name); + free(funcs); + funcs = tmp; + } + } + } - objname = _objname; + if (funcs) + disas_funcs(funcs); +} - file.elf = elf_open_read(objname, orc ? O_RDWR : O_RDONLY); - if (!file.elf) - return 1; +struct insn_chunk { + void *addr; + struct insn_chunk *next; +}; + +/* + * Reduce peak RSS usage by freeing insns memory before writing the ELF file, + * which can trigger more allocations for .debug_* sections whose data hasn't + * been read yet. + */ +static void free_insns(struct objtool_file *file) +{ + struct instruction *insn; + struct insn_chunk *chunks = NULL, *chunk; - INIT_LIST_HEAD(&file.insn_list); - hash_init(file.insn_hash); - file.c_file = find_section_by_name(file.elf, ".comment"); - file.ignore_unreachables = no_unreachable; - file.hints = false; + for_each_insn(file, insn) { + if (!insn->idx) { + chunk = malloc(sizeof(*chunk)); + chunk->addr = insn; + chunk->next = chunks; + chunks = chunk; + } + } + + for (chunk = chunks; chunk; chunk = chunk->next) + free(chunk->addr); +} + +int check(struct objtool_file *file) +{ + int ret = 0, warnings = 0; arch_initial_func_cfi_state(&initial_func_cfi); + init_cfi_state(&init_cfi); + init_cfi_state(&func_cfi); + set_func_state(&func_cfi); + init_cfi_state(&force_undefined_cfi); + force_undefined_cfi.force_undefined = true; + + if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3))) { + ret = -1; + goto out; + } + + cfi_hash_add(&init_cfi); + cfi_hash_add(&func_cfi); - ret = decode_sections(&file); - if (ret < 0) + ret = decode_sections(file); + if (ret) goto out; - warnings += ret; - if (list_empty(&file.insn_list)) + if (!nr_insns) goto out; - if (vmlinux && !validate_dup) { - ret = validate_vmlinux_functions(&file); - if (ret < 0) + if (opts.retpoline) + warnings += validate_retpoline(file); + + if (opts.stackval || opts.orc || opts.uaccess) { + int w = 0; + + w += validate_functions(file); + w += validate_unwind_hints(file, NULL); + if (!w) + w += validate_reachable_instructions(file); + + warnings += w; + + } else if (opts.noinstr) { + warnings += validate_noinstr_sections(file); + } + + if (opts.unret) { + /* + * Must be after validate_branch() and friends, it plays + * further games with insn->visited. + */ + warnings += validate_unrets(file); + } + + if (opts.ibt) + warnings += validate_ibt(file); + + if (opts.sls) + warnings += validate_sls(file); + + if (opts.static_call) { + ret = create_static_call_sections(file); + if (ret) goto out; + } - warnings += ret; - goto out; + if (opts.retpoline) { + ret = create_retpoline_sites_sections(file); + if (ret) + goto out; } - if (retpoline) { - ret = validate_retpoline(&file); - if (ret < 0) - return ret; - warnings += ret; + if (opts.cfi) { + ret = create_cfi_sections(file); + if (ret) + goto out; } - ret = validate_functions(&file); - if (ret < 0) - goto out; - warnings += ret; + if (opts.rethunk) { + ret = create_return_sites_sections(file); + if (ret) + goto out; - ret = validate_unwind_hints(&file, NULL); - if (ret < 0) - goto out; - warnings += ret; + if (opts.hack_skylake) { + ret = create_direct_call_sections(file); + if (ret) + goto out; + } + } - if (!warnings) { - ret = validate_reachable_instructions(&file); - if (ret < 0) + if (opts.mcount) { + ret = create_mcount_loc_sections(file); + if (ret) goto out; - warnings += ret; } - if (orc) { - ret = create_orc(&file); - if (ret < 0) + if (opts.prefix) { + ret = add_prefix_symbols(file); + if (ret) goto out; + } - ret = create_orc_sections(&file); - if (ret < 0) + if (opts.ibt) { + ret = create_ibt_endbr_seal_sections(file); + if (ret) goto out; + } - ret = elf_write(file.elf); - if (ret < 0) + if (opts.orc && nr_insns) { + ret = orc_create(file); + if (ret) goto out; } + free_insns(file); + + if (opts.stats) { + printf("nr_insns_visited: %ld\n", nr_insns_visited); + printf("nr_cfi: %ld\n", nr_cfi); + printf("nr_cfi_reused: %ld\n", nr_cfi_reused); + printf("nr_cfi_cache: %ld\n", nr_cfi_cache); + } + out: - if (ret < 0) { - /* - * Fatal error. The binary is corrupt or otherwise broken in - * some way, or objtool itself is broken. Fail the kernel - * build. - */ - return ret; + if (!ret && !warnings) + return 0; + + if (opts.werror && warnings) + ret = 1; + + if (opts.verbose) { + if (opts.werror && warnings) + WARN("%d warning(s) upgraded to errors", warnings); + print_args(); + disas_warned_funcs(file); } - return 0; + return ret; } diff --git a/tools/objtool/check.h b/tools/objtool/check.h deleted file mode 100644 index 906b5210f7ca..000000000000 --- a/tools/objtool/check.h +++ /dev/null @@ -1,61 +0,0 @@ -/* 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 "cfi.h" -#include "arch.h" - -struct insn_state { - struct cfi_state cfi; - unsigned int uaccess_stack; - bool uaccess; - bool df; - bool noinstr; - s8 instr; -}; - -struct instruction { - struct list_head list; - struct hlist_node hash; - struct section *sec; - unsigned long offset; - unsigned int len; - enum insn_type type; - unsigned long immediate; - bool dead_end, ignore, ignore_alts; - bool hint; - bool retpoline_safe; - s8 instr; - u8 visited; - u8 ret_offset; - int alt_group; - struct symbol *call_dest; - struct instruction *jump_dest; - struct instruction *first_jump_src; - struct rela *jump_table; - struct list_head alts; - struct symbol *func; - struct list_head stack_ops; - struct cfi_state cfi; - struct orc_entry orc; -}; - -struct instruction *find_insn(struct objtool_file *file, - struct section *sec, unsigned long offset); - -#define for_each_insn(file, insn) \ - list_for_each_entry(insn, &file->insn_list, list) - -#define sec_for_each_insn(file, sec, insn) \ - for (insn = find_insn(file, sec, 0); \ - insn && &insn->list != &file->insn_list && \ - insn->sec == sec; \ - insn = list_next_entry(insn, list)) - - -#endif /* _CHECK_H */ diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 84225679f96d..ca5d77db692a 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -9,120 +9,120 @@ #include <sys/types.h> #include <sys/stat.h> +#include <sys/mman.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> -#include "builtin.h" +#include <linux/interval_tree_generic.h> +#include <objtool/builtin.h> -#include "elf.h" -#include "warn.h" - -#define MAX_NAME_LEN 128 +#include <objtool/elf.h> +#include <objtool/warn.h> static inline u32 str_hash(const char *str) { return jhash(str, strlen(str), 0); } -static inline int elf_hash_bits(void) -{ - return vmlinux ? ELF_HASH_BITS : 16; -} - -#define elf_hash_add(hashtable, node, key) \ - hlist_add_head(node, &hashtable[hash_min(key, elf_hash_bits())]) +#define __elf_table(name) (elf->name##_hash) +#define __elf_bits(name) (elf->name##_bits) -static void elf_hash_init(struct hlist_head *table) -{ - __hash_init(table, 1U << elf_hash_bits()); -} +#define __elf_table_entry(name, key) \ + __elf_table(name)[hash_min(key, __elf_bits(name))] -#define elf_hash_for_each_possible(name, obj, member, key) \ - hlist_for_each_entry(obj, &name[hash_min(key, elf_hash_bits())], member) +#define elf_hash_add(name, node, key) \ +({ \ + struct elf_hash_node *__node = node; \ + __node->next = __elf_table_entry(name, key); \ + __elf_table_entry(name, key) = __node; \ +}) -static void rb_add(struct rb_root *tree, struct rb_node *node, - int (*cmp)(struct rb_node *, const struct rb_node *)) +static inline void __elf_hash_del(struct elf_hash_node *node, + struct elf_hash_node **head) { - struct rb_node **link = &tree->rb_node; - struct rb_node *parent = NULL; + struct elf_hash_node *cur, *prev; - while (*link) { - parent = *link; - if (cmp(node, parent) < 0) - link = &parent->rb_left; - else - link = &parent->rb_right; + if (node == *head) { + *head = node->next; + return; } - rb_link_node(node, parent, link); - rb_insert_color(node, tree); -} - -static struct rb_node *rb_find_first(const struct rb_root *tree, const void *key, - int (*cmp)(const void *key, const struct rb_node *)) -{ - struct rb_node *node = tree->rb_node; - struct rb_node *match = NULL; - - while (node) { - int c = cmp(key, node); - if (c <= 0) { - if (!c) - match = node; - node = node->rb_left; - } else if (c > 0) { - node = node->rb_right; + for (prev = NULL, cur = *head; cur; prev = cur, cur = cur->next) { + if (cur == node) { + prev->next = cur->next; + break; } } - - return match; } -static struct rb_node *rb_next_match(struct rb_node *node, const void *key, - int (*cmp)(const void *key, const struct rb_node *)) +#define elf_hash_del(name, node, key) \ + __elf_hash_del(node, &__elf_table_entry(name, key)) + +#define elf_list_entry(ptr, type, member) \ +({ \ + typeof(ptr) __ptr = (ptr); \ + __ptr ? container_of(__ptr, type, member) : NULL; \ +}) + +#define elf_hash_for_each_possible(name, obj, member, key) \ + for (obj = elf_list_entry(__elf_table_entry(name, key), typeof(*obj), member); \ + obj; \ + obj = elf_list_entry(obj->member.next, typeof(*(obj)), member)) + +#define elf_alloc_hash(name, size) \ +({ \ + __elf_bits(name) = max(10, ilog2(size)); \ + __elf_table(name) = mmap(NULL, sizeof(struct elf_hash_node *) << __elf_bits(name), \ + PROT_READ|PROT_WRITE, \ + MAP_PRIVATE|MAP_ANON, -1, 0); \ + if (__elf_table(name) == (void *)-1L) { \ + ERROR_GLIBC("mmap fail " #name); \ + __elf_table(name) = NULL; \ + } \ + __elf_table(name); \ +}) + +static inline unsigned long __sym_start(struct symbol *s) { - node = rb_next(node); - if (node && cmp(key, node)) - node = NULL; - return node; + return s->offset; } -#define rb_for_each(tree, node, key, cmp) \ - for ((node) = rb_find_first((tree), (key), (cmp)); \ - (node); (node) = rb_next_match((node), (key), (cmp))) - -static int symbol_to_offset(struct rb_node *a, const struct rb_node *b) +static inline unsigned long __sym_last(struct symbol *s) { - struct symbol *sa = rb_entry(a, struct symbol, node); - struct symbol *sb = rb_entry(b, struct symbol, node); - - if (sa->offset < sb->offset) - return -1; - if (sa->offset > sb->offset) - return 1; + return s->offset + s->len - 1; +} - if (sa->len < sb->len) - return -1; - if (sa->len > sb->len) - return 1; +INTERVAL_TREE_DEFINE(struct symbol, node, unsigned long, __subtree_last, + __sym_start, __sym_last, static, __sym) - sa->alias = sb; +#define __sym_for_each(_iter, _tree, _start, _end) \ + for (_iter = __sym_iter_first((_tree), (_start), (_end)); \ + _iter; _iter = __sym_iter_next(_iter, (_start), (_end))) - return 0; -} +struct symbol_hole { + unsigned long key; + const struct symbol *sym; +}; -static int symbol_by_offset(const void *key, const struct rb_node *node) +/* + * Find !section symbol where @offset is after it. + */ +static int symbol_hole_by_offset(const void *key, const struct rb_node *node) { const struct symbol *s = rb_entry(node, struct symbol, node); - const unsigned long *o = key; + struct symbol_hole *sh = (void *)key; - if (*o < s->offset) + if (sh->key < s->offset) return -1; - if (*o >= s->offset + s->len) + + if (sh->key >= s->offset + s->len) { + if (s->type != STT_SECTION) + sh->sym = s; return 1; + } return 0; } @@ -131,9 +131,10 @@ struct section *find_section_by_name(const struct elf *elf, const char *name) { struct section *sec; - elf_hash_for_each_possible(elf->section_name_hash, sec, name_hash, str_hash(name)) + elf_hash_for_each_possible(section_name, sec, name_hash, str_hash(name)) { if (!strcmp(sec->name, name)) return sec; + } return NULL; } @@ -143,9 +144,10 @@ static struct section *find_section_by_index(struct elf *elf, { struct section *sec; - elf_hash_for_each_possible(elf->section_hash, sec, hash, idx) + elf_hash_for_each_possible(section, sec, hash, idx) { if (sec->idx == idx) return sec; + } return NULL; } @@ -154,22 +156,22 @@ static struct symbol *find_symbol_by_index(struct elf *elf, unsigned int idx) { struct symbol *sym; - elf_hash_for_each_possible(elf->symbol_hash, sym, hash, idx) + elf_hash_for_each_possible(symbol, sym, hash, idx) { if (sym->idx == idx) return sym; + } return NULL; } struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset) { - struct rb_node *node; - - rb_for_each(&sec->symbol_tree, node, &offset, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *iter; - if (s->offset == offset && s->type != STT_SECTION) - return s; + __sym_for_each(iter, tree, offset, offset) { + if (iter->offset == offset && iter->type != STT_SECTION) + return iter; } return NULL; @@ -177,13 +179,12 @@ struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset) struct symbol *find_func_by_offset(struct section *sec, unsigned long offset) { - struct rb_node *node; - - rb_for_each(&sec->symbol_tree, node, &offset, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *iter; - if (s->offset == offset && s->type == STT_FUNC) - return s; + __sym_for_each(iter, tree, offset, offset) { + if (iter->offset == offset && iter->type == STT_FUNC) + return iter; } return NULL; @@ -191,27 +192,65 @@ struct symbol *find_func_by_offset(struct section *sec, unsigned long offset) struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset) { - struct rb_node *node; + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *iter; - rb_for_each(&sec->symbol_tree, node, &offset, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); - - if (s->type != STT_SECTION) - return s; + __sym_for_each(iter, tree, offset, offset) { + if (iter->type != STT_SECTION) + return iter; } return NULL; } -struct symbol *find_func_containing(struct section *sec, unsigned long offset) +/* + * Returns size of hole starting at @offset. + */ +int find_symbol_hole_containing(const struct section *sec, unsigned long offset) { - struct rb_node *node; + struct symbol_hole hole = { + .key = offset, + .sym = NULL, + }; + struct rb_node *n; + struct symbol *s; + + /* + * Find the rightmost symbol for which @offset is after it. + */ + n = rb_find(&hole, &sec->symbol_tree.rb_root, symbol_hole_by_offset); + + /* found a symbol that contains @offset */ + if (n) + return 0; /* not a hole */ + + /* + * @offset >= sym->offset + sym->len, find symbol after it. + * When hole.sym is empty, use the first node to compute the hole. + * If there is no symbol in the section, the first node will be NULL, + * in which case, -1 is returned to skip the whole section. + */ + if (hole.sym) + n = rb_next(&hole.sym->node); + else + n = rb_first_cached(&sec->symbol_tree); + + if (!n) + return -1; /* until end of address space */ + + /* hole until start of next symbol */ + s = rb_entry(n, struct symbol, node); + return s->offset - offset; +} - rb_for_each(&sec->symbol_tree, node, &offset, symbol_by_offset) { - struct symbol *s = rb_entry(node, struct symbol, node); +struct symbol *find_func_containing(struct section *sec, unsigned long offset) +{ + struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; + struct symbol *iter; - if (s->type == STT_FUNC) - return s; + __sym_for_each(iter, tree, offset, offset) { + if (iter->type == STT_FUNC) + return iter; } return NULL; @@ -221,33 +260,35 @@ struct symbol *find_symbol_by_name(const struct elf *elf, const char *name) { struct symbol *sym; - elf_hash_for_each_possible(elf->symbol_name_hash, sym, name_hash, str_hash(name)) + elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(name)) { if (!strcmp(sym->name, name)) return sym; + } return NULL; } -struct rela *find_rela_by_dest_range(const struct elf *elf, struct section *sec, +struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, unsigned long offset, unsigned int len) { - struct rela *rela, *r = NULL; + struct reloc *reloc, *r = NULL; + struct section *rsec; unsigned long o; - if (!sec->rela) + rsec = sec->rsec; + if (!rsec) return NULL; - sec = sec->rela; - for_offset_range(o, offset, offset + len) { - elf_hash_for_each_possible(elf->rela_hash, rela, hash, - sec_offset_hash(sec, o)) { - if (rela->sec != sec) + elf_hash_for_each_possible(reloc, reloc, hash, + sec_offset_hash(rsec, o)) { + if (reloc->sec != rsec) continue; - if (rela->offset >= offset && rela->offset < offset + len) { - if (!r || rela->offset < r->offset) - r = rela; + if (reloc_offset(reloc) >= offset && + reloc_offset(reloc) < offset + len) { + if (!r || reloc_offset(reloc) < reloc_offset(r)) + r = reloc; } } if (r) @@ -257,9 +298,14 @@ struct rela *find_rela_by_dest_range(const struct elf *elf, struct section *sec, return NULL; } -struct rela *find_rela_by_dest(const struct elf *elf, struct section *sec, unsigned long offset) +struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset) { - return find_rela_by_dest_range(elf, sec, offset, 1); + return find_reloc_by_dest_range(elf, sec, offset, 1); +} + +static bool is_dwarf_section(struct section *sec) +{ + return !strncmp(sec->name, ".debug_", 7); } static int read_sections(struct elf *elf) @@ -270,127 +316,177 @@ static int read_sections(struct elf *elf) int i; if (elf_getshdrnum(elf->elf, §ions_nr)) { - WARN_ELF("elf_getshdrnum"); + ERROR_ELF("elf_getshdrnum"); return -1; } if (elf_getshdrstrndx(elf->elf, &shstrndx)) { - WARN_ELF("elf_getshdrstrndx"); + ERROR_ELF("elf_getshdrstrndx"); return -1; } + if (!elf_alloc_hash(section, sections_nr) || + !elf_alloc_hash(section_name, sections_nr)) + return -1; + + elf->section_data = calloc(sections_nr, sizeof(*sec)); + if (!elf->section_data) { + ERROR_GLIBC("calloc"); + return -1; + } for (i = 0; i < sections_nr; i++) { - sec = malloc(sizeof(*sec)); - if (!sec) { - perror("malloc"); - return -1; - } - memset(sec, 0, sizeof(*sec)); + sec = &elf->section_data[i]; INIT_LIST_HEAD(&sec->symbol_list); - INIT_LIST_HEAD(&sec->rela_list); s = elf_getscn(elf->elf, i); if (!s) { - WARN_ELF("elf_getscn"); + ERROR_ELF("elf_getscn"); return -1; } sec->idx = elf_ndxscn(s); if (!gelf_getshdr(s, &sec->sh)) { - WARN_ELF("gelf_getshdr"); + ERROR_ELF("gelf_getshdr"); return -1; } sec->name = elf_strptr(elf->elf, shstrndx, sec->sh.sh_name); if (!sec->name) { - WARN_ELF("elf_strptr"); + ERROR_ELF("elf_strptr"); return -1; } - if (sec->sh.sh_size != 0) { + if (sec->sh.sh_size != 0 && !is_dwarf_section(sec)) { sec->data = elf_getdata(s, NULL); if (!sec->data) { - WARN_ELF("elf_getdata"); + ERROR_ELF("elf_getdata"); return -1; } if (sec->data->d_off != 0 || sec->data->d_size != sec->sh.sh_size) { - WARN("unexpected data attributes for %s", - sec->name); + ERROR("unexpected data attributes for %s", sec->name); return -1; } } - sec->len = sec->sh.sh_size; list_add_tail(&sec->list, &elf->sections); - elf_hash_add(elf->section_hash, &sec->hash, sec->idx); - elf_hash_add(elf->section_name_hash, &sec->name_hash, str_hash(sec->name)); + elf_hash_add(section, &sec->hash, sec->idx); + elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); + + if (is_reloc_sec(sec)) + elf->num_relocs += sec_num_entries(sec); } - if (stats) + if (opts.stats) { printf("nr_sections: %lu\n", (unsigned long)sections_nr); + printf("section_bits: %d\n", elf->section_bits); + } /* sanity check, one more call to elf_nextscn() should return NULL */ if (elf_nextscn(elf->elf, s)) { - WARN("section entry mismatch"); + ERROR("section entry mismatch"); return -1; } return 0; } +static void elf_add_symbol(struct elf *elf, struct symbol *sym) +{ + struct list_head *entry; + struct rb_node *pnode; + struct symbol *iter; + + INIT_LIST_HEAD(&sym->pv_target); + sym->alias = sym; + + sym->type = GELF_ST_TYPE(sym->sym.st_info); + sym->bind = GELF_ST_BIND(sym->sym.st_info); + + if (sym->type == STT_FILE) + elf->num_files++; + + sym->offset = sym->sym.st_value; + sym->len = sym->sym.st_size; + + __sym_for_each(iter, &sym->sec->symbol_tree, sym->offset, sym->offset) { + if (iter->offset == sym->offset && iter->type == sym->type) + iter->alias = sym; + } + + __sym_insert(sym, &sym->sec->symbol_tree); + pnode = rb_prev(&sym->node); + if (pnode) + entry = &rb_entry(pnode, struct symbol, node)->list; + else + entry = &sym->sec->symbol_list; + list_add(&sym->list, entry); + elf_hash_add(symbol, &sym->hash, sym->idx); + elf_hash_add(symbol_name, &sym->name_hash, str_hash(sym->name)); + + /* + * Don't store empty STT_NOTYPE symbols in the rbtree. They + * can exist within a function, confusing the sorting. + */ + if (!sym->len) + __sym_remove(sym, &sym->sec->symbol_tree); +} + static int read_symbols(struct elf *elf) { struct section *symtab, *symtab_shndx, *sec; struct symbol *sym, *pfunc; - struct list_head *entry; - struct rb_node *pnode; int symbols_nr, i; char *coldstr; Elf_Data *shndx_data = NULL; Elf32_Word shndx; symtab = find_section_by_name(elf, ".symtab"); - if (!symtab) { - WARN("missing symbol table"); - return -1; + if (symtab) { + symtab_shndx = find_section_by_name(elf, ".symtab_shndx"); + if (symtab_shndx) + shndx_data = symtab_shndx->data; + + symbols_nr = sec_num_entries(symtab); + } else { + /* + * A missing symbol table is actually possible if it's an empty + * .o file. This can happen for thunk_64.o. Make sure to at + * least allocate the symbol hash tables so we can do symbol + * lookups without crashing. + */ + symbols_nr = 0; } - symtab_shndx = find_section_by_name(elf, ".symtab_shndx"); - if (symtab_shndx) - shndx_data = symtab_shndx->data; - - symbols_nr = symtab->sh.sh_size / symtab->sh.sh_entsize; + if (!elf_alloc_hash(symbol, symbols_nr) || + !elf_alloc_hash(symbol_name, symbols_nr)) + return -1; + elf->symbol_data = calloc(symbols_nr, sizeof(*sym)); + if (!elf->symbol_data) { + ERROR_GLIBC("calloc"); + return -1; + } for (i = 0; i < symbols_nr; i++) { - sym = malloc(sizeof(*sym)); - if (!sym) { - perror("malloc"); - return -1; - } - memset(sym, 0, sizeof(*sym)); - sym->alias = sym; + sym = &elf->symbol_data[i]; sym->idx = i; if (!gelf_getsymshndx(symtab->data, shndx_data, i, &sym->sym, &shndx)) { - WARN_ELF("gelf_getsymshndx"); + ERROR_ELF("gelf_getsymshndx"); goto err; } sym->name = elf_strptr(elf->elf, symtab->sh.sh_link, sym->sym.st_name); if (!sym->name) { - WARN_ELF("elf_strptr"); + ERROR_ELF("elf_strptr"); goto err; } - sym->type = GELF_ST_TYPE(sym->sym.st_info); - sym->bind = GELF_ST_BIND(sym->sym.st_info); - if ((sym->sym.st_shndx > SHN_UNDEF && sym->sym.st_shndx < SHN_LORESERVE) || (shndx_data && sym->sym.st_shndx == SHN_XINDEX)) { @@ -399,60 +495,54 @@ static int read_symbols(struct elf *elf) sym->sec = find_section_by_index(elf, shndx); if (!sym->sec) { - WARN("couldn't find section for symbol %s", - sym->name); + ERROR("couldn't find section for symbol %s", sym->name); goto err; } - if (sym->type == STT_SECTION) { + if (GELF_ST_TYPE(sym->sym.st_info) == STT_SECTION) { sym->name = sym->sec->name; sym->sec->sym = sym; } } else sym->sec = find_section_by_index(elf, 0); - sym->offset = sym->sym.st_value; - sym->len = sym->sym.st_size; - - rb_add(&sym->sec->symbol_tree, &sym->node, symbol_to_offset); - pnode = rb_prev(&sym->node); - if (pnode) - entry = &rb_entry(pnode, struct symbol, node)->list; - else - entry = &sym->sec->symbol_list; - list_add(&sym->list, entry); - elf_hash_add(elf->symbol_hash, &sym->hash, sym->idx); - elf_hash_add(elf->symbol_name_hash, &sym->name_hash, str_hash(sym->name)); + elf_add_symbol(elf, sym); } - if (stats) + if (opts.stats) { printf("nr_symbols: %lu\n", (unsigned long)symbols_nr); + printf("symbol_bits: %d\n", elf->symbol_bits); + } /* Create parent/child links for any cold subfunctions */ list_for_each_entry(sec, &elf->sections, list) { - list_for_each_entry(sym, &sec->symbol_list, list) { - char pname[MAX_NAME_LEN + 1]; + sec_for_each_sym(sec, sym) { + char *pname; size_t pnamelen; if (sym->type != STT_FUNC) continue; - sym->pfunc = sym->cfunc = sym; + + if (sym->pfunc == NULL) + sym->pfunc = sym; + + if (sym->cfunc == NULL) + sym->cfunc = sym; + coldstr = strstr(sym->name, ".cold"); if (!coldstr) continue; pnamelen = coldstr - sym->name; - if (pnamelen > MAX_NAME_LEN) { - WARN("%s(): parent function name exceeds maximum length of %d characters", - sym->name, MAX_NAME_LEN); + pname = strndup(sym->name, pnamelen); + if (!pname) { + ERROR("%s(): failed to allocate memory", sym->name); return -1; } - strncpy(pname, sym->name, pnamelen); - pname[pnamelen] = '\0'; pfunc = find_symbol_by_name(elf, pname); + free(pname); if (!pfunc) { - WARN("%s(): can't find parent function", - sym->name); + ERROR("%s(): can't find parent function", sym->name); return -1; } @@ -482,71 +572,450 @@ err: return -1; } -void elf_add_rela(struct elf *elf, struct rela *rela) +static int mark_group_syms(struct elf *elf) { - struct section *sec = rela->sec; + struct section *symtab, *sec; + struct symbol *sym; - list_add_tail(&rela->list, &sec->rela_list); - elf_hash_add(elf->rela_hash, &rela->hash, rela_hash(rela)); + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("no .symtab"); + return -1; + } + + list_for_each_entry(sec, &elf->sections, list) { + if (sec->sh.sh_type == SHT_GROUP && + sec->sh.sh_link == symtab->idx) { + sym = find_symbol_by_index(elf, sec->sh.sh_info); + if (!sym) { + ERROR("%s: can't find SHT_GROUP signature symbol", + sec->name); + return -1; + } + + sym->group_sec = sec; + } + } + + return 0; } -static int read_relas(struct elf *elf) +/* + * @sym's idx has changed. Update the relocs which reference it. + */ +static int elf_update_sym_relocs(struct elf *elf, struct symbol *sym) { - struct section *sec; - struct rela *rela; - int i; - unsigned int symndx; - unsigned long nr_rela, max_rela = 0, tot_rela = 0; + struct reloc *reloc; - list_for_each_entry(sec, &elf->sections, list) { - if (sec->sh.sh_type != SHT_RELA) - continue; + for (reloc = sym->relocs; reloc; reloc = sym_next_reloc(reloc)) + set_reloc_sym(elf, reloc, reloc->sym->idx); - sec->base = find_section_by_name(elf, sec->name + 5); - if (!sec->base) { - WARN("can't find base section for rela section %s", - sec->name); + return 0; +} + +/* + * The libelf API is terrible; gelf_update_sym*() takes a data block relative + * index value, *NOT* the symbol index. As such, iterate the data blocks and + * adjust index until it fits. + * + * If no data block is found, allow adding a new data block provided the index + * is only one past the end. + */ +static int elf_update_symbol(struct elf *elf, struct section *symtab, + struct section *symtab_shndx, struct symbol *sym) +{ + Elf32_Word shndx = sym->sec ? sym->sec->idx : SHN_UNDEF; + Elf_Data *symtab_data = NULL, *shndx_data = NULL; + Elf64_Xword entsize = symtab->sh.sh_entsize; + int max_idx, idx = sym->idx; + Elf_Scn *s, *t = NULL; + bool is_special_shndx = sym->sym.st_shndx >= SHN_LORESERVE && + sym->sym.st_shndx != SHN_XINDEX; + + if (is_special_shndx) + shndx = sym->sym.st_shndx; + + s = elf_getscn(elf->elf, symtab->idx); + if (!s) { + ERROR_ELF("elf_getscn"); + return -1; + } + + if (symtab_shndx) { + t = elf_getscn(elf->elf, symtab_shndx->idx); + if (!t) { + ERROR_ELF("elf_getscn"); return -1; } + } - sec->base->rela = sec; + for (;;) { + /* get next data descriptor for the relevant sections */ + symtab_data = elf_getdata(s, symtab_data); + if (t) + shndx_data = elf_getdata(t, shndx_data); + + /* end-of-list */ + if (!symtab_data) { + /* + * Over-allocate to avoid O(n^2) symbol creation + * behaviour. The down side is that libelf doesn't + * like this; see elf_truncate_section() for the fixup. + */ + int num = max(1U, sym->idx/3); + void *buf; - nr_rela = 0; - for (i = 0; i < sec->sh.sh_size / sec->sh.sh_entsize; i++) { - rela = malloc(sizeof(*rela)); - if (!rela) { - perror("malloc"); + if (idx) { + /* we don't do holes in symbol tables */ + ERROR("index out of range"); return -1; } - memset(rela, 0, sizeof(*rela)); - if (!gelf_getrela(sec->data, i, &rela->rela)) { - WARN_ELF("gelf_getrela"); + /* if @idx == 0, it's the next contiguous entry, create it */ + symtab_data = elf_newdata(s); + if (t) + shndx_data = elf_newdata(t); + + buf = calloc(num, entsize); + if (!buf) { + ERROR_GLIBC("calloc"); return -1; } - rela->type = GELF_R_TYPE(rela->rela.r_info); - rela->addend = rela->rela.r_addend; - rela->offset = rela->rela.r_offset; - symndx = GELF_R_SYM(rela->rela.r_info); - rela->sym = find_symbol_by_index(elf, symndx); - rela->sec = sec; - if (!rela->sym) { - WARN("can't find rela entry symbol %d for %s", - symndx, sec->name); + symtab_data->d_buf = buf; + symtab_data->d_size = num * entsize; + symtab_data->d_align = 1; + symtab_data->d_type = ELF_T_SYM; + + mark_sec_changed(elf, symtab, true); + symtab->truncate = true; + + if (t) { + buf = calloc(num, sizeof(Elf32_Word)); + if (!buf) { + ERROR_GLIBC("calloc"); + return -1; + } + + shndx_data->d_buf = buf; + shndx_data->d_size = num * sizeof(Elf32_Word); + shndx_data->d_align = sizeof(Elf32_Word); + shndx_data->d_type = ELF_T_WORD; + + mark_sec_changed(elf, symtab_shndx, true); + symtab_shndx->truncate = true; + } + + break; + } + + /* empty blocks should not happen */ + if (!symtab_data->d_size) { + ERROR("zero size data"); + return -1; + } + + /* is this the right block? */ + max_idx = symtab_data->d_size / entsize; + if (idx < max_idx) + break; + + /* adjust index and try again */ + idx -= max_idx; + } + + /* something went side-ways */ + if (idx < 0) { + ERROR("negative index"); + return -1; + } + + /* setup extended section index magic and write the symbol */ + if ((shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) || is_special_shndx) { + sym->sym.st_shndx = shndx; + if (!shndx_data) + shndx = 0; + } else { + sym->sym.st_shndx = SHN_XINDEX; + if (!shndx_data) { + ERROR("no .symtab_shndx"); + return -1; + } + } + + if (!gelf_update_symshndx(symtab_data, shndx_data, idx, &sym->sym, shndx)) { + ERROR_ELF("gelf_update_symshndx"); + return -1; + } + + return 0; +} + +static struct symbol * +__elf_create_symbol(struct elf *elf, struct symbol *sym) +{ + struct section *symtab, *symtab_shndx; + Elf32_Word first_non_local, new_idx; + struct symbol *old; + + symtab = find_section_by_name(elf, ".symtab"); + if (symtab) { + symtab_shndx = find_section_by_name(elf, ".symtab_shndx"); + } else { + ERROR("no .symtab"); + return NULL; + } + + new_idx = sec_num_entries(symtab); + + if (GELF_ST_BIND(sym->sym.st_info) != STB_LOCAL) + goto non_local; + + /* + * Move the first global symbol, as per sh_info, into a new, higher + * symbol index. This frees up a spot for a new local symbol. + */ + first_non_local = symtab->sh.sh_info; + old = find_symbol_by_index(elf, first_non_local); + if (old) { + + elf_hash_del(symbol, &old->hash, old->idx); + elf_hash_add(symbol, &old->hash, new_idx); + old->idx = new_idx; + + if (elf_update_symbol(elf, symtab, symtab_shndx, old)) { + ERROR("elf_update_symbol move"); + return NULL; + } + + if (elf_update_sym_relocs(elf, old)) + return NULL; + + if (old->group_sec) { + old->group_sec->sh.sh_info = new_idx; + mark_sec_changed(elf, old->group_sec, true); + } + + new_idx = first_non_local; + } + + /* + * Either way, we will add a LOCAL symbol. + */ + symtab->sh.sh_info += 1; + +non_local: + sym->idx = new_idx; + if (elf_update_symbol(elf, symtab, symtab_shndx, sym)) { + ERROR("elf_update_symbol"); + return NULL; + } + + symtab->sh.sh_size += symtab->sh.sh_entsize; + mark_sec_changed(elf, symtab, true); + + if (symtab_shndx) { + symtab_shndx->sh.sh_size += sizeof(Elf32_Word); + mark_sec_changed(elf, symtab_shndx, true); + } + + return sym; +} + +static struct symbol * +elf_create_section_symbol(struct elf *elf, struct section *sec) +{ + struct symbol *sym = calloc(1, sizeof(*sym)); + + if (!sym) { + ERROR_GLIBC("malloc"); + return NULL; + } + + sym->name = sec->name; + sym->sec = sec; + + // st_name 0 + sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION); + // st_other 0 + // st_value 0 + // st_size 0 + + sym = __elf_create_symbol(elf, sym); + if (sym) + elf_add_symbol(elf, sym); + + return sym; +} + +static int elf_add_string(struct elf *elf, struct section *strtab, char *str); + +struct symbol * +elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, long size) +{ + struct symbol *sym = calloc(1, sizeof(*sym)); + size_t namelen = strlen(orig->name) + sizeof("__pfx_"); + char *name = malloc(namelen); + + if (!sym || !name) { + ERROR_GLIBC("malloc"); + return NULL; + } + + snprintf(name, namelen, "__pfx_%s", orig->name); + + sym->name = name; + sym->sec = orig->sec; + + sym->sym.st_name = elf_add_string(elf, NULL, name); + sym->sym.st_info = orig->sym.st_info; + sym->sym.st_value = orig->sym.st_value - size; + sym->sym.st_size = size; + + sym = __elf_create_symbol(elf, sym); + if (sym) + elf_add_symbol(elf, sym); + + return sym; +} + +static 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 *reloc, empty = { 0 }; + + if (reloc_idx >= sec_num_entries(rsec)) { + ERROR("%s: bad reloc_idx %u for %s with %d relocs", + __func__, reloc_idx, rsec->name, sec_num_entries(rsec)); + return NULL; + } + + reloc = &rsec->relocs[reloc_idx]; + + if (memcmp(reloc, &empty, sizeof(empty))) { + ERROR("%s: %s: reloc %d already initialized!", + __func__, rsec->name, reloc_idx); + return NULL; + } + + reloc->sec = rsec; + reloc->sym = sym; + + set_reloc_offset(elf, reloc, offset); + set_reloc_sym(elf, reloc, sym->idx); + set_reloc_type(elf, reloc, type); + set_reloc_addend(elf, reloc, addend); + + elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); + set_sym_next_reloc(reloc, sym->relocs); + sym->relocs = reloc; + + return reloc; +} + +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 symbol *sym = insn_sec->sym; + int addend = insn_off; + + if (!(insn_sec->sh.sh_flags & SHF_EXECINSTR)) { + ERROR("bad call to %s() for data symbol %s", __func__, sym->name); + return NULL; + } + + if (!sym) { + /* + * Due to how weak functions work, we must use section based + * relocations. Symbol based relocations would result in the + * weak and non-weak function annotations being overlaid on the + * non-weak function after linking. + */ + sym = elf_create_section_symbol(elf, insn_sec); + if (!sym) + return NULL; + + insn_sec->sym = sym; + } + + return elf_init_reloc(elf, sec->rsec, reloc_idx, offset, sym, addend, + elf_text_rela_type(elf)); +} + +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) +{ + if (sym->sec && (sec->sh.sh_flags & SHF_EXECINSTR)) { + ERROR("bad call to %s() for text symbol %s", __func__, sym->name); + return NULL; + } + + return elf_init_reloc(elf, sec->rsec, reloc_idx, offset, sym, addend, + elf_data_rela_type(elf)); +} + +static int read_relocs(struct elf *elf) +{ + unsigned long nr_reloc, max_reloc = 0; + struct section *rsec; + struct reloc *reloc; + unsigned int symndx; + struct symbol *sym; + int i; + + if (!elf_alloc_hash(reloc, elf->num_relocs)) + return -1; + + list_for_each_entry(rsec, &elf->sections, list) { + if (!is_reloc_sec(rsec)) + continue; + + rsec->base = find_section_by_index(elf, rsec->sh.sh_info); + if (!rsec->base) { + ERROR("can't find base section for reloc section %s", rsec->name); + return -1; + } + + rsec->base->rsec = rsec; + + nr_reloc = 0; + rsec->relocs = calloc(sec_num_entries(rsec), sizeof(*reloc)); + if (!rsec->relocs) { + ERROR_GLIBC("calloc"); + return -1; + } + for (i = 0; i < sec_num_entries(rsec); i++) { + reloc = &rsec->relocs[i]; + + reloc->sec = rsec; + symndx = reloc_sym(reloc); + reloc->sym = sym = find_symbol_by_index(elf, symndx); + if (!reloc->sym) { + ERROR("can't find reloc entry symbol %d for %s", symndx, rsec->name); return -1; } - elf_add_rela(elf, rela); - nr_rela++; + elf_hash_add(reloc, &reloc->hash, reloc_hash(reloc)); + set_sym_next_reloc(reloc, sym->relocs); + sym->relocs = reloc; + + nr_reloc++; } - max_rela = max(max_rela, nr_rela); - tot_rela += nr_rela; + max_reloc = max(max_reloc, nr_reloc); } - if (stats) { - printf("max_rela: %lu\n", max_rela); - printf("tot_rela: %lu\n", tot_rela); + if (opts.stats) { + printf("max_reloc: %lu\n", max_reloc); + printf("num_relocs: %lu\n", elf->num_relocs); + printf("reloc_bits: %d\n", elf->reloc_bits); } return 0; @@ -561,19 +1030,13 @@ struct elf *elf_open_read(const char *name, int flags) elf = malloc(sizeof(*elf)); if (!elf) { - perror("malloc"); + ERROR_GLIBC("malloc"); return NULL; } - memset(elf, 0, offsetof(struct elf, sections)); + memset(elf, 0, sizeof(*elf)); INIT_LIST_HEAD(&elf->sections); - elf_hash_init(elf->symbol_hash); - elf_hash_init(elf->symbol_name_hash); - elf_hash_init(elf->section_hash); - elf_hash_init(elf->section_name_hash); - elf_hash_init(elf->rela_hash); - elf->fd = open(name, flags); if (elf->fd == -1) { fprintf(stderr, "objtool: Can't open '%s': %s\n", @@ -590,12 +1053,12 @@ struct elf *elf_open_read(const char *name, int flags) elf->elf = elf_begin(elf->fd, cmd, NULL); if (!elf->elf) { - WARN_ELF("elf_begin"); + ERROR_ELF("elf_begin"); goto err; } if (!gelf_getehdr(elf->elf, &elf->ehdr)) { - WARN_ELF("gelf_getehdr"); + ERROR_ELF("gelf_getehdr"); goto err; } @@ -605,7 +1068,10 @@ struct elf *elf_open_read(const char *name, int flags) if (read_symbols(elf)) goto err; - if (read_relas(elf)) + if (mark_group_syms(elf)) + goto err; + + if (read_relocs(elf)) goto err; return elf; @@ -615,43 +1081,76 @@ err: return NULL; } +static int elf_add_string(struct elf *elf, struct section *strtab, char *str) +{ + Elf_Data *data; + Elf_Scn *s; + int len; + + if (!strtab) + strtab = find_section_by_name(elf, ".strtab"); + if (!strtab) { + ERROR("can't find .strtab section"); + return -1; + } + + s = elf_getscn(elf->elf, strtab->idx); + if (!s) { + ERROR_ELF("elf_getscn"); + return -1; + } + + data = elf_newdata(s); + if (!data) { + ERROR_ELF("elf_newdata"); + return -1; + } + + data->d_buf = str; + data->d_size = strlen(str) + 1; + data->d_align = 1; + + len = strtab->sh.sh_size; + strtab->sh.sh_size += data->d_size; + + mark_sec_changed(elf, strtab, true); + + return len; +} + struct section *elf_create_section(struct elf *elf, const char *name, - size_t entsize, int nr) + size_t entsize, unsigned int nr) { struct section *sec, *shstrtab; size_t size = entsize * nr; Elf_Scn *s; - Elf_Data *data; sec = malloc(sizeof(*sec)); if (!sec) { - perror("malloc"); + ERROR_GLIBC("malloc"); return NULL; } memset(sec, 0, sizeof(*sec)); INIT_LIST_HEAD(&sec->symbol_list); - INIT_LIST_HEAD(&sec->rela_list); s = elf_newscn(elf->elf); if (!s) { - WARN_ELF("elf_newscn"); + ERROR_ELF("elf_newscn"); return NULL; } sec->name = strdup(name); if (!sec->name) { - perror("strdup"); + ERROR_GLIBC("strdup"); return NULL; } sec->idx = elf_ndxscn(s); - sec->len = size; - sec->changed = true; sec->data = elf_newdata(s); if (!sec->data) { - WARN_ELF("elf_newdata"); + ERROR_ELF("elf_newdata"); return NULL; } @@ -661,14 +1160,14 @@ struct section *elf_create_section(struct elf *elf, const char *name, if (size) { sec->data->d_buf = malloc(size); if (!sec->data->d_buf) { - perror("malloc"); + ERROR_GLIBC("malloc"); return NULL; } memset(sec->data->d_buf, 0, size); } if (!gelf_getshdr(s, &sec->sh)) { - WARN_ELF("gelf_getshdr"); + ERROR_ELF("gelf_getshdr"); return NULL; } @@ -678,124 +1177,178 @@ struct section *elf_create_section(struct elf *elf, const char *name, sec->sh.sh_addralign = 1; sec->sh.sh_flags = SHF_ALLOC; - /* Add section name to .shstrtab (or .strtab for Clang) */ shstrtab = find_section_by_name(elf, ".shstrtab"); if (!shstrtab) shstrtab = find_section_by_name(elf, ".strtab"); if (!shstrtab) { - WARN("can't find .shstrtab or .strtab section"); + ERROR("can't find .shstrtab or .strtab section"); return NULL; } - - s = elf_getscn(elf->elf, shstrtab->idx); - if (!s) { - WARN_ELF("elf_getscn"); + sec->sh.sh_name = elf_add_string(elf, shstrtab, sec->name); + if (sec->sh.sh_name == -1) return NULL; - } - data = elf_newdata(s); - if (!data) { - WARN_ELF("elf_newdata"); + list_add_tail(&sec->list, &elf->sections); + elf_hash_add(section, &sec->hash, sec->idx); + elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name)); + + mark_sec_changed(elf, sec, true); + + return sec; +} + +static struct section *elf_create_rela_section(struct elf *elf, + struct section *sec, + unsigned int reloc_nr) +{ + struct section *rsec; + char *rsec_name; + + rsec_name = malloc(strlen(sec->name) + strlen(".rela") + 1); + if (!rsec_name) { + ERROR_GLIBC("malloc"); return NULL; } + strcpy(rsec_name, ".rela"); + strcat(rsec_name, sec->name); - data->d_buf = sec->name; - data->d_size = strlen(name) + 1; - data->d_align = 1; + rsec = elf_create_section(elf, rsec_name, elf_rela_size(elf), reloc_nr); + free(rsec_name); + if (!rsec) + return NULL; - sec->sh.sh_name = shstrtab->len; + rsec->data->d_type = ELF_T_RELA; + rsec->sh.sh_type = SHT_RELA; + rsec->sh.sh_addralign = elf_addr_size(elf); + rsec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; + rsec->sh.sh_info = sec->idx; + rsec->sh.sh_flags = SHF_INFO_LINK; - shstrtab->len += strlen(name) + 1; - shstrtab->changed = true; + rsec->relocs = calloc(sec_num_entries(rsec), sizeof(struct reloc)); + if (!rsec->relocs) { + ERROR_GLIBC("calloc"); + return NULL; + } - list_add_tail(&sec->list, &elf->sections); - elf_hash_add(elf->section_hash, &sec->hash, sec->idx); - elf_hash_add(elf->section_name_hash, &sec->name_hash, str_hash(sec->name)); + sec->rsec = rsec; + rsec->base = sec; - return sec; + return rsec; } -struct section *elf_create_rela_section(struct elf *elf, struct section *base) +struct section *elf_create_section_pair(struct elf *elf, const char *name, + size_t entsize, unsigned int nr, + unsigned int reloc_nr) { - char *relaname; struct section *sec; - relaname = malloc(strlen(base->name) + strlen(".rela") + 1); - if (!relaname) { - perror("malloc"); - return NULL; - } - strcpy(relaname, ".rela"); - strcat(relaname, base->name); - - sec = elf_create_section(elf, relaname, sizeof(GElf_Rela), 0); - free(relaname); + sec = elf_create_section(elf, name, entsize, nr); if (!sec) return NULL; - base->rela = sec; - sec->base = base; - - sec->sh.sh_type = SHT_RELA; - sec->sh.sh_addralign = 8; - sec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; - sec->sh.sh_info = base->idx; - sec->sh.sh_flags = SHF_INFO_LINK; + if (!elf_create_rela_section(elf, sec, reloc_nr)) + return NULL; return sec; } -int elf_rebuild_rela_section(struct section *sec) +int elf_write_insn(struct elf *elf, struct section *sec, + unsigned long offset, unsigned int len, + const char *insn) { - struct rela *rela; - int nr, idx = 0, size; - GElf_Rela *relas; - - nr = 0; - list_for_each_entry(rela, &sec->rela_list, list) - nr++; + Elf_Data *data = sec->data; - size = nr * sizeof(*relas); - relas = malloc(size); - if (!relas) { - perror("malloc"); + if (data->d_type != ELF_T_BYTE || data->d_off) { + ERROR("write to unexpected data for section: %s", sec->name); return -1; } - sec->data->d_buf = relas; - sec->data->d_size = size; + memcpy(data->d_buf + offset, insn, len); - sec->sh.sh_size = size; + mark_sec_changed(elf, sec, true); - idx = 0; - list_for_each_entry(rela, &sec->rela_list, list) { - relas[idx].r_offset = rela->offset; - relas[idx].r_addend = rela->addend; - relas[idx].r_info = GELF_R_INFO(rela->sym->idx, rela->type); - idx++; + return 0; +} + +/* + * When Elf_Scn::sh_size is smaller than the combined Elf_Data::d_size + * do you: + * + * A) adhere to the section header and truncate the data, or + * B) ignore the section header and write out all the data you've got? + * + * Yes, libelf sucks and we need to manually truncate if we over-allocate data. + */ +static int elf_truncate_section(struct elf *elf, struct section *sec) +{ + u64 size = sec->sh.sh_size; + bool truncated = false; + Elf_Data *data = NULL; + Elf_Scn *s; + + s = elf_getscn(elf->elf, sec->idx); + if (!s) { + ERROR_ELF("elf_getscn"); + return -1; } - return 0; + for (;;) { + /* get next data descriptor for the relevant section */ + data = elf_getdata(s, data); + + if (!data) { + if (size) { + ERROR("end of section data but non-zero size left\n"); + return -1; + } + return 0; + } + + if (truncated) { + /* when we remove symbols */ + ERROR("truncated; but more data\n"); + return -1; + } + + if (!data->d_size) { + ERROR("zero size data"); + return -1; + } + + if (data->d_size > size) { + truncated = true; + data->d_size = size; + } + + size -= data->d_size; + } } -int elf_write(const struct elf *elf) +int elf_write(struct elf *elf) { struct section *sec; Elf_Scn *s; - /* Update section headers for changed sections: */ + /* Update changed relocation sections and section headers: */ list_for_each_entry(sec, &elf->sections, list) { - if (sec->changed) { + if (sec->truncate) + elf_truncate_section(elf, sec); + + if (sec_changed(sec)) { s = elf_getscn(elf->elf, sec->idx); if (!s) { - WARN_ELF("elf_getscn"); + ERROR_ELF("elf_getscn"); return -1; } + + /* Note this also flags the section dirty */ if (!gelf_update_shdr(s, &sec->sh)) { - WARN_ELF("gelf_update_shdr"); + ERROR_ELF("gelf_update_shdr"); return -1; } + + mark_sec_changed(elf, sec, false); } } @@ -804,39 +1357,25 @@ int elf_write(const struct elf *elf) /* Write all changes to the file. */ if (elf_update(elf->elf, ELF_C_WRITE) < 0) { - WARN_ELF("elf_update"); + ERROR_ELF("elf_update"); return -1; } + elf->changed = false; + return 0; } void elf_close(struct elf *elf) { - struct section *sec, *tmpsec; - struct symbol *sym, *tmpsym; - struct rela *rela, *tmprela; - if (elf->elf) elf_end(elf->elf); if (elf->fd > 0) close(elf->fd); - list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) { - list_for_each_entry_safe(sym, tmpsym, &sec->symbol_list, list) { - list_del(&sym->list); - hash_del(&sym->hash); - free(sym); - } - list_for_each_entry_safe(rela, tmprela, &sec->rela_list, list) { - list_del(&rela->list); - hash_del(&rela->hash); - free(rela); - } - list_del(&sec->list); - free(sec); - } - - free(elf); + /* + * NOTE: All remaining allocations are leaked on purpose. Objtool is + * about to exit anyway. + */ } diff --git a/tools/objtool/elf.h b/tools/objtool/elf.h deleted file mode 100644 index f4fe1d6ea392..000000000000 --- a/tools/objtool/elf.h +++ /dev/null @@ -1,138 +0,0 @@ -/* 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/list.h> -#include <linux/hashtable.h> -#include <linux/rbtree.h> -#include <linux/jhash.h> - -#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 section { - struct list_head list; - struct hlist_node hash; - struct hlist_node name_hash; - GElf_Shdr sh; - struct rb_root symbol_tree; - struct list_head symbol_list; - struct list_head rela_list; - struct section *base, *rela; - struct symbol *sym; - Elf_Data *data; - char *name; - int idx; - unsigned int len; - bool changed, text, rodata, noinstr; -}; - -struct symbol { - struct list_head list; - struct rb_node node; - struct hlist_node hash; - struct hlist_node name_hash; - GElf_Sym sym; - struct section *sec; - char *name; - unsigned int idx; - unsigned char bind, type; - unsigned long offset; - unsigned int len; - struct symbol *pfunc, *cfunc, *alias; - bool uaccess_safe; -}; - -struct rela { - struct list_head list; - struct hlist_node hash; - GElf_Rela rela; - struct section *sec; - struct symbol *sym; - unsigned int type; - unsigned long offset; - int addend; - bool jump_table_start; -}; - -#define ELF_HASH_BITS 20 - -struct elf { - Elf *elf; - GElf_Ehdr ehdr; - int fd; - char *name; - struct list_head sections; - DECLARE_HASHTABLE(symbol_hash, ELF_HASH_BITS); - DECLARE_HASHTABLE(symbol_name_hash, ELF_HASH_BITS); - DECLARE_HASHTABLE(section_hash, ELF_HASH_BITS); - DECLARE_HASHTABLE(section_name_hash, ELF_HASH_BITS); - DECLARE_HASHTABLE(rela_hash, ELF_HASH_BITS); -}; - -#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 rela_hash(struct rela *rela) -{ - return sec_offset_hash(rela->sec, rela->offset); -} - -struct elf *elf_open_read(const char *name, int flags); -struct section *elf_create_section(struct elf *elf, const char *name, size_t entsize, int nr); -struct section *elf_create_rela_section(struct elf *elf, struct section *base); -void elf_add_rela(struct elf *elf, struct rela *rela); -int elf_write(const struct elf *elf); -void 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_symbol_containing(const struct section *sec, unsigned long offset); -struct rela *find_rela_by_dest(const struct elf *elf, struct section *sec, unsigned long offset); -struct rela *find_rela_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); -int elf_rebuild_rela_section(struct section *sec); - -#define for_each_sec(file, sec) \ - list_for_each_entry(sec, &file->elf->sections, list) - -#endif /* _OBJTOOL_ELF_H */ diff --git a/tools/objtool/arch.h b/tools/objtool/include/objtool/arch.h index eda15a5a285e..01ef6f415adf 100644 --- a/tools/objtool/arch.h +++ b/tools/objtool/include/objtool/arch.h @@ -8,10 +8,8 @@ #include <stdbool.h> #include <linux/list.h> -#include "objtool.h" -#include "cfi.h" - -#include <asm/orc_types.h> +#include <objtool/objtool.h> +#include <objtool/cfi.h> enum insn_type { INSN_JUMP_CONDITIONAL, @@ -21,13 +19,17 @@ enum insn_type { INSN_CALL, INSN_CALL_DYNAMIC, INSN_RETURN, - INSN_CONTEXT_SWITCH, + 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, }; @@ -37,7 +39,6 @@ enum op_dest_type { OP_DEST_MEM, OP_DEST_PUSH, OP_DEST_PUSHF, - OP_DEST_LEAVE, }; struct op_dest { @@ -63,25 +64,41 @@ struct op_src { }; struct stack_op { + struct stack_op *next; struct op_dest dest; struct op_src src; - struct list_head list; }; struct instruction; +int arch_ftrace_match(char *name); + void arch_initial_func_cfi_state(struct cfi_init_state *state); -int arch_decode_instruction(const struct elf *elf, const struct section *sec, +int arch_decode_instruction(struct objtool_file *file, const struct section *sec, unsigned long offset, unsigned int maxlen, - unsigned int *len, enum insn_type *type, - unsigned long *immediate, - struct list_head *ops_list); + struct instruction *insn); bool arch_callee_saved_reg(unsigned char reg); unsigned long arch_jump_destination(struct instruction *insn); -unsigned long arch_dest_rela_offset(int addend); +unsigned long arch_dest_reloc_offset(int addend); + +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); + +unsigned int arch_reloc_size(struct reloc *reloc); +unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table); #endif /* _ARCH_H */ diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h new file mode 100644 index 000000000000..6b08666fa69d --- /dev/null +++ b/tools/objtool/include/objtool/builtin.h @@ -0,0 +1,52 @@ +/* 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 dump_orc; + bool hack_jump_label; + bool hack_noinstr; + bool hack_skylake; + bool ibt; + bool mcount; + bool noinstr; + bool orc; + bool retpoline; + bool rethunk; + bool unret; + bool sls; + bool stackval; + bool static_call; + bool uaccess; + int prefix; + bool cfi; + + /* options: */ + bool backtrace; + bool dryrun; + bool link; + bool mnop; + bool module; + bool no_unreachable; + const char *output; + bool sec_address; + bool stats; + bool verbose; + bool werror; +}; + +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); + +void print_args(void); + +#endif /* _BUILTIN_H */ diff --git a/tools/objtool/cfi.h b/tools/objtool/include/objtool/cfi.h index c7c59c6a44ee..c8a6bec4f6b9 100644 --- a/tools/objtool/cfi.h +++ b/tools/objtool/include/objtool/cfi.h @@ -6,7 +6,8 @@ #ifndef _OBJTOOL_CFI_H #define _OBJTOOL_CFI_H -#include "cfi_regs.h" +#include <arch/cfi_regs.h> +#include <linux/list.h> #define CFI_UNDEFINED -1 #define CFI_CFA -2 @@ -24,6 +25,7 @@ struct cfi_init_state { }; 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; @@ -32,7 +34,9 @@ struct cfi_state { unsigned char type; bool bp_scratch; bool drap; + bool signal; bool end; + bool force_undefined; }; #endif /* _OBJTOOL_CFI_H */ diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h new file mode 100644 index 000000000000..00fb745e7233 --- /dev/null +++ b/tools/objtool/include/objtool/check.h @@ -0,0 +1,128 @@ +/* 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; +}; + +#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, + dead_end : 1, + ignore_alts : 1, + hint : 1, + save : 1, + restore : 1, + retpoline_safe : 1, + noendbr : 1, + unret : 1, + visited : 4, + no_reloc : 1; + /* 10 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; +}; + +static inline struct symbol *insn_func(struct instruction *insn) +{ + struct symbol *sym = insn->sym; + + 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); +} + +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); + +#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)) + +#endif /* _CHECK_H */ diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h new file mode 100644 index 000000000000..0a2fa3ac0079 --- /dev/null +++ b/tools/objtool/include/objtool/elf.h @@ -0,0 +1,379 @@ +/* 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/list.h> +#include <linux/hashtable.h> +#include <linux/rbtree.h> +#include <linux/jhash.h> +#include <arch/elf.h> + +#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; + char *name; + int idx; + bool _changed, text, rodata, noinstr, init, truncate; + struct reloc *relocs; +}; + +struct symbol { + struct list_head list; + struct rb_node node; + struct elf_hash_node hash; + struct elf_hash_node name_hash; + GElf_Sym sym; + struct section *sec; + char *name; + unsigned int idx, len; + unsigned long offset; + unsigned long __subtree_last; + struct symbol *pfunc, *cfunc, *alias; + 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; + struct list_head pv_target; + struct reloc *relocs; + struct section *group_sec; +}; + +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; + char *name; + unsigned int num_files; + struct list_head sections; + 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; +}; + +struct elf *elf_open_read(const char *name, int flags); + +struct section *elf_create_section(struct elf *elf, const char *name, + size_t entsize, unsigned int nr); +struct section *elf_create_section_pair(struct elf *elf, const char *name, + size_t entsize, unsigned int nr, + unsigned int reloc_nr); + +struct symbol *elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, long size); + +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_insn(struct elf *elf, struct section *sec, + unsigned long offset, unsigned int len, + const char *insn); +int elf_write(struct elf *elf); +void 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_symbol_containing(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_reloc_sec(struct section *sec) +{ + return sec->sh.sh_type == SHT_RELA || sec->sh.sh_type == SHT_REL; +} + +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; +} + +#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); +} + +#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(file, sec) \ + list_for_each_entry(sec, &file->elf->sections, list) + +#define sec_for_each_sym(sec, sym) \ + list_for_each_entry(sym, &sec->symbol_list, list) + +#define for_each_sym(file, sym) \ + for (struct section *__sec, *__fake = (struct section *)1; \ + __fake; __fake = NULL) \ + for_each_sec(file, __sec) \ + sec_for_each_sym(__sec, sym) + +#define for_each_reloc(rsec, reloc) \ + for (int __i = 0, __fake = 1; __fake; __fake = 0) \ + for (reloc = rsec->relocs; \ + __i < sec_num_entries(rsec); \ + __i++, reloc++) + +#define for_each_reloc_from(rsec, reloc) \ + for (int __i = reloc_idx(reloc); \ + __i < sec_num_entries(rsec); \ + __i++, reloc++) + +#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 */ diff --git a/tools/objtool/include/objtool/endianness.h b/tools/objtool/include/objtool/endianness.h new file mode 100644 index 000000000000..4d2aa9b0fe2f --- /dev/null +++ b/tools/objtool/include/objtool/endianness.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _OBJTOOL_ENDIANNESS_H +#define _OBJTOOL_ENDIANNESS_H + +#include <linux/kernel.h> +#include <endian.h> +#include <objtool/elf.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(struct elf *elf) +{ + return (__BYTE_ORDER == __LITTLE_ENDIAN) ^ + (elf->ehdr.e_ident[EI_DATA] == ELFDATA2LSB); +} + +#define bswap_if_needed(elf, val) \ +({ \ + __typeof__(val) __ret; \ + bool __need_bswap = need_bswap(elf); \ + 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 */ diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h new file mode 100644 index 000000000000..c0dc86a78ff6 --- /dev/null +++ b/tools/objtool/include/objtool/objtool.h @@ -0,0 +1,50 @@ +/* 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; + + 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; +}; + +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 */ diff --git a/tools/objtool/include/objtool/orc.h b/tools/objtool/include/objtool/orc.h new file mode 100644 index 000000000000..15a32def1071 --- /dev/null +++ b/tools/objtool/include/objtool/orc.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 */ diff --git a/tools/objtool/special.h b/tools/objtool/include/objtool/special.h index 35061530e46e..72d09c0adf1a 100644 --- a/tools/objtool/special.h +++ b/tools/objtool/include/objtool/special.h @@ -7,15 +7,17 @@ #define _SPECIAL_H #include <stdbool.h> -#include "elf.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 skip_orig; - bool skip_alt; bool jump_or_nop; + u8 key_addend; struct section *orig_sec; unsigned long orig_off; @@ -28,4 +30,12 @@ struct special_alt { 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); #endif /* _SPECIAL_H */ diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h new file mode 100644 index 000000000000..cb8fe846d9dd --- /dev/null +++ b/tools/objtool/include/objtool/warn.h @@ -0,0 +1,105 @@ +/* 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->sym->warned) \ + WARN_FUNC(_insn->sec, _insn->offset, format, \ + ##__VA_ARGS__); \ + if (_insn->sym) \ + _insn->sym->warned = 1; \ +}) + +#define BT_INSN(insn, format, ...) \ +({ \ + if (opts.verbose || opts.backtrace) { \ + struct instruction *_insn = (insn); \ + char *_str = offstr(_insn->sec, _insn->offset); \ + WARN(" %s: " format, _str, ##__VA_ARGS__); \ + free(_str); \ + } \ +}) + +#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, ...) WARN_FUNC(insn->sec, insn->offset, format, ##__VA_ARGS__) + +#endif /* _WARN_H */ diff --git a/tools/objtool/noreturns.h b/tools/objtool/noreturns.h new file mode 100644 index 000000000000..eacfe3b0a8d1 --- /dev/null +++ b/tools/objtool/noreturns.h @@ -0,0 +1,54 @@ +/* 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(nmi_panic_self_stop) +NORETURN(panic) +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(snp_abort) +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) diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c index 58fdda510653..5c8b974ad0f9 100644 --- a/tools/objtool/objtool.c +++ b/tools/objtool/objtool.c @@ -3,104 +3,72 @@ * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com> */ -/* - * objtool: - * - * The 'check' subcmd analyzes every .o file and ensures the validity of its - * stack trace metadata. It enforces a set of rules on asm code and C inline - * assembly code so that stack traces can be reliable. - * - * For more information, see tools/objtool/Documentation/stack-validation.txt. - */ - #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 "builtin.h" - -struct cmd_struct { - const char *name; - int (*fn)(int, const char **); - const char *help; -}; - -static const char objtool_usage_string[] = - "objtool COMMAND [ARGS]"; - -static struct cmd_struct objtool_cmds[] = { - {"check", cmd_check, "Perform stack metadata validation on an object file" }, - {"orc", cmd_orc, "Generate in-place ORC unwind tables for an object file" }, -}; +#include <objtool/builtin.h> +#include <objtool/objtool.h> +#include <objtool/warn.h> bool help; -static void cmd_usage(void) -{ - unsigned int i, longest = 0; - - printf("\n usage: %s\n\n", objtool_usage_string); - - for (i = 0; i < ARRAY_SIZE(objtool_cmds); i++) { - if (longest < strlen(objtool_cmds[i].name)) - longest = strlen(objtool_cmds[i].name); - } - - puts(" Commands:"); - for (i = 0; i < ARRAY_SIZE(objtool_cmds); i++) { - printf(" %-*s ", longest, objtool_cmds[i].name); - puts(objtool_cmds[i].help); - } - - printf("\n"); - - if (!help) - exit(129); - exit(0); -} +static struct objtool_file file; -static void handle_options(int *argc, const char ***argv) +struct objtool_file *objtool_open_read(const char *filename) { - while (*argc > 0) { - const char *cmd = (*argv)[0]; - - if (cmd[0] != '-') - break; - - if (!strcmp(cmd, "--help") || !strcmp(cmd, "-h")) { - help = true; - break; - } else { - fprintf(stderr, "Unknown option: %s\n", cmd); - cmd_usage(); - } - - (*argv)++; - (*argc)--; + 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; } -static void handle_internal_command(int argc, const char **argv) +int objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func) { - const char *cmd = argv[0]; - unsigned int i, ret; - - for (i = 0; i < ARRAY_SIZE(objtool_cmds); i++) { - struct cmd_struct *p = objtool_cmds+i; + if (!opts.noinstr) + return 0; - if (strcmp(p->name, cmd)) - continue; + if (!f->pv_ops) { + ERROR("paravirt confusion"); + return -1; + } - ret = p->fn(argc, argv); + /* + * 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; - exit(ret); - } + /* already added this function */ + if (!list_empty(&func->pv_target)) + return 0; - cmd_usage(); + list_add(&func->pv_target, &f->pv_ops[idx].targets); + f->pv_ops[idx].clean = false; + return 0; } int main(int argc, const char **argv) @@ -111,14 +79,5 @@ int main(int argc, const char **argv) exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED); pager_init(UNUSED); - argv++; - argc--; - handle_options(&argc, &argv); - - if (!argc || help) - cmd_usage(); - - handle_internal_command(argc, argv); - - return 0; + return objtool_run(argc, argv); } diff --git a/tools/objtool/objtool.h b/tools/objtool/objtool.h deleted file mode 100644 index 528028a66816..000000000000 --- a/tools/objtool/objtool.h +++ /dev/null @@ -1,27 +0,0 @@ -/* 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 "elf.h" - -struct objtool_file { - struct elf *elf; - struct list_head insn_list; - DECLARE_HASHTABLE(insn_hash, 20); - bool ignore_unreachables, c_file, hints, rodata; -}; - -int check(const char *objname, bool orc); -int orc_dump(const char *objname); -int create_orc(struct objtool_file *file); -int create_orc_sections(struct objtool_file *file); - -#endif /* _OBJTOOL_H */ diff --git a/tools/objtool/orc_dump.c b/tools/objtool/orc_dump.c index fca46e006fc2..1dd9fc18fe62 100644 --- a/tools/objtool/orc_dump.c +++ b/tools/objtool/orc_dump.c @@ -5,62 +5,12 @@ #include <unistd.h> #include <asm/orc_types.h> -#include "objtool.h" -#include "warn.h" +#include <objtool/objtool.h> +#include <objtool/orc.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> -static const char *reg_name(unsigned int reg) -{ - switch (reg) { - case ORC_REG_PREV_SP: - return "prevsp"; - case ORC_REG_DX: - return "dx"; - case ORC_REG_DI: - return "di"; - case ORC_REG_BP: - return "bp"; - case ORC_REG_SP: - return "sp"; - case ORC_REG_R10: - return "r10"; - case ORC_REG_R13: - return "r13"; - case ORC_REG_BP_INDIRECT: - return "bp(ind)"; - case ORC_REG_SP_INDIRECT: - return "sp(ind)"; - default: - return "?"; - } -} - -static const char *orc_type_name(unsigned int type) -{ - switch (type) { - case ORC_TYPE_CALL: - return "call"; - case ORC_TYPE_REGS: - return "regs"; - case ORC_TYPE_REGS_IRET: - return "iret"; - 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); -} - -int orc_dump(const char *_objname) +int orc_dump(const char *filename) { int fd, nr_entries, i, *orc_ip = NULL, orc_size = 0; struct orc_entry *orc = NULL; @@ -74,13 +24,11 @@ int orc_dump(const char *_objname) GElf_Rela rela; GElf_Sym sym; Elf_Data *data, *symtab = NULL, *rela_orc_ip = NULL; - - - objname = _objname; + struct elf dummy_elf = {}; elf_version(EV_CURRENT); - fd = open(objname, O_RDONLY); + fd = open(filename, O_RDONLY); if (fd == -1) { perror("open"); return -1; @@ -88,41 +36,47 @@ int orc_dump(const char *_objname) elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); if (!elf) { - WARN_ELF("elf_begin"); + 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)) { - WARN_ELF("elf_getshdrnum"); + ERROR_ELF("elf_getshdrnum"); return -1; } if (elf_getshdrstrndx(elf, &shstrtab_idx)) { - WARN_ELF("elf_getshdrstrndx"); + ERROR_ELF("elf_getshdrstrndx"); return -1; } for (i = 0; i < nr_sections; i++) { scn = elf_getscn(elf, i); if (!scn) { - WARN_ELF("elf_getscn"); + ERROR_ELF("elf_getscn"); return -1; } if (!gelf_getshdr(scn, &sh)) { - WARN_ELF("gelf_getshdr"); + ERROR_ELF("gelf_getshdr"); return -1; } name = elf_strptr(elf, shstrtab_idx, sh.sh_name); if (!name) { - WARN_ELF("elf_strptr"); + ERROR_ELF("elf_strptr"); return -1; } data = elf_getdata(scn, NULL); if (!data) { - WARN_ELF("elf_getdata"); + ERROR_ELF("elf_getdata"); return -1; } @@ -145,7 +99,7 @@ int orc_dump(const char *_objname) return 0; if (orc_size % sizeof(*orc) != 0) { - WARN("bad .orc_unwind section size"); + ERROR("bad .orc_unwind section size"); return -1; } @@ -153,36 +107,36 @@ int orc_dump(const char *_objname) for (i = 0; i < nr_entries; i++) { if (rela_orc_ip) { if (!gelf_getrela(rela_orc_ip, i, &rela)) { - WARN_ELF("gelf_getrela"); + ERROR_ELF("gelf_getrela"); return -1; } if (!gelf_getsym(symtab, GELF_R_SYM(rela.r_info), &sym)) { - WARN_ELF("gelf_getsym"); + ERROR_ELF("gelf_getsym"); return -1; } if (GELF_ST_TYPE(sym.st_info) == STT_SECTION) { scn = elf_getscn(elf, sym.st_shndx); if (!scn) { - WARN_ELF("elf_getscn"); + ERROR_ELF("elf_getscn"); return -1; } if (!gelf_getshdr(scn, &sh)) { - WARN_ELF("gelf_getshdr"); + ERROR_ELF("gelf_getshdr"); return -1; } name = elf_strptr(elf, shstrtab_idx, sh.sh_name); if (!name) { - WARN_ELF("elf_strptr"); + ERROR_ELF("elf_strptr"); return -1; } } else { name = elf_strptr(elf, strtab_idx, sym.st_name); if (!name) { - WARN_ELF("elf_strptr"); + ERROR_ELF("elf_strptr"); return -1; } } @@ -193,17 +147,7 @@ int orc_dump(const char *_objname) printf("%llx:", (unsigned long long)(orc_ip_addr + (i * sizeof(int)) + orc_ip[i])); } - - printf(" sp:"); - - print_reg(orc[i].sp_reg, orc[i].sp_offset); - - printf(" bp:"); - - print_reg(orc[i].bp_reg, orc[i].bp_offset); - - printf(" type:%s end:%d\n", - orc_type_name(orc[i].type), orc[i].end); + orc_print_dump(&dummy_elf, orc, i); } elf_end(elf); diff --git a/tools/objtool/orc_gen.c b/tools/objtool/orc_gen.c index c9549988121a..922e6aac7cea 100644 --- a/tools/objtool/orc_gen.c +++ b/tools/objtool/orc_gen.c @@ -6,224 +6,142 @@ #include <stdlib.h> #include <string.h> -#include "check.h" -#include "warn.h" - -int create_orc(struct objtool_file *file) -{ - struct instruction *insn; - - for_each_insn(file, insn) { - struct orc_entry *orc = &insn->orc; - struct cfi_reg *cfa = &insn->cfi.cfa; - struct cfi_reg *bp = &insn->cfi.regs[CFI_BP]; - - orc->end = insn->cfi.end; - - if (cfa->base == CFI_UNDEFINED) { - orc->sp_reg = ORC_REG_UNDEFINED; - continue; - } - - switch (cfa->base) { - case CFI_SP: - orc->sp_reg = ORC_REG_SP; - break; - case CFI_SP_INDIRECT: - orc->sp_reg = ORC_REG_SP_INDIRECT; - break; - case CFI_BP: - orc->sp_reg = ORC_REG_BP; - break; - case CFI_BP_INDIRECT: - orc->sp_reg = ORC_REG_BP_INDIRECT; - break; - case CFI_R10: - orc->sp_reg = ORC_REG_R10; - break; - case CFI_R13: - orc->sp_reg = ORC_REG_R13; - break; - case CFI_DI: - orc->sp_reg = ORC_REG_DI; - break; - case CFI_DX: - orc->sp_reg = ORC_REG_DX; - break; - default: - WARN_FUNC("unknown CFA base reg %d", - insn->sec, insn->offset, 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: - WARN_FUNC("unknown BP base reg %d", - insn->sec, insn->offset, bp->base); - return -1; - } - - orc->sp_offset = cfa->offset; - orc->bp_offset = bp->offset; - orc->type = insn->cfi.type; - } - - return 0; -} - -static int create_orc_entry(struct elf *elf, struct section *u_sec, struct section *ip_relasec, - unsigned int idx, struct section *insn_sec, - unsigned long insn_off, struct orc_entry *o) +#include <linux/objtool_types.h> +#include <asm/orc_types.h> + +#include <objtool/check.h> +#include <objtool/orc.h> +#include <objtool/warn.h> +#include <objtool/endianness.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_entry *orc; - struct rela *rela; - - /* populate ORC data */ - orc = (struct orc_entry *)u_sec->data->d_buf + idx; - memcpy(orc, o, sizeof(*orc)); + struct orc_list_entry *entry = malloc(sizeof(*entry)); - /* populate rela for ip */ - rela = malloc(sizeof(*rela)); - if (!rela) { - perror("malloc"); + if (!entry) { + WARN("malloc failed"); return -1; } - memset(rela, 0, sizeof(*rela)); - - if (insn_sec->sym) { - rela->sym = insn_sec->sym; - rela->addend = insn_off; - } else { - /* - * The Clang assembler doesn't produce section symbols, so we - * have to reference the function symbol instead: - */ - rela->sym = find_symbol_containing(insn_sec, insn_off); - if (!rela->sym) { - /* - * Hack alert. This happens when we need to reference - * the NOP pad insn immediately after the function. - */ - rela->sym = find_symbol_containing(insn_sec, - insn_off - 1); - } - if (!rela->sym) { - WARN("missing symbol for insn at offset 0x%lx\n", - insn_off); - return -1; - } - - rela->addend = insn_off - rela->sym->offset; - } - rela->type = R_X86_64_PC32; - rela->offset = idx * sizeof(int); - rela->sec = ip_relasec; - - elf_add_rela(elf, rela); + entry->orc = *orc; + entry->insn_sec = sec; + entry->insn_off = offset; + list_add_tail(&entry->list, orc_list); return 0; } -int create_orc_sections(struct objtool_file *file) +static unsigned long alt_group_len(struct alt_group *alt_group) { - struct instruction *insn, *prev_insn; - struct section *sec, *u_sec, *ip_relasec; - unsigned int idx; + return alt_group->last_insn->offset + + alt_group->last_insn->len - + alt_group->first_insn->offset; +} - struct orc_entry empty = { - .sp_reg = ORC_REG_UNDEFINED, - .bp_reg = ORC_REG_UNDEFINED, - .type = ORC_TYPE_CALL, - }; +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; - sec = find_section_by_name(file->elf, ".orc_unwind"); - if (sec) { - WARN("file already has .orc_unwind section, skipping"); - return -1; - } + struct orc_entry null = { .type = ORC_TYPE_UNDEFINED }; - /* count the number of needed orcs */ - idx = 0; + /* Build a deduplicated list of ORC entries: */ + INIT_LIST_HEAD(&orc_list); for_each_sec(file, sec) { - if (!sec->text) - continue; - - prev_insn = NULL; - sec_for_each_insn(file, sec, insn) { - if (!prev_insn || - memcmp(&insn->orc, &prev_insn->orc, - sizeof(struct orc_entry))) { - idx++; - } - prev_insn = insn; - } - - /* section terminator */ - if (prev_insn) - idx++; - } - if (!idx) - return -1; - + struct orc_entry orc, prev_orc = {0}; + struct instruction *insn; + bool empty = true; - /* create .orc_unwind_ip and .rela.orc_unwind_ip sections */ - sec = elf_create_section(file->elf, ".orc_unwind_ip", sizeof(int), idx); - if (!sec) - return -1; - - ip_relasec = elf_create_rela_section(file->elf, sec); - if (!ip_relasec) - return -1; - - /* create .orc_unwind section */ - u_sec = elf_create_section(file->elf, ".orc_unwind", - sizeof(struct orc_entry), idx); - - /* populate sections */ - idx = 0; - for_each_sec(file, sec) { if (!sec->text) continue; - prev_insn = NULL; sec_for_each_insn(file, sec, insn) { - if (!prev_insn || memcmp(&insn->orc, &prev_insn->orc, - sizeof(struct orc_entry))) { + struct alt_group *alt_group = insn->alt_group; + int i; - if (create_orc_entry(file->elf, u_sec, ip_relasec, idx, - insn->sec, insn->offset, - &insn->orc)) + 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; + } - idx++; + /* + * 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; } - prev_insn = insn; - } - /* section terminator */ - if (prev_insn) { - if (create_orc_entry(file->elf, u_sec, ip_relasec, idx, - prev_insn->sec, - prev_insn->offset + prev_insn->len, - &empty)) - return -1; + /* Skip to the end of the alt_group */ + insn = alt_group->last_insn; + } - idx++; + /* Add a section terminator */ + if (!empty) { + orc_list_add(&orc_list, &null, sec, sec->sh.sh_size); + nr++; } } + if (!nr) + return 0; - if (elf_rebuild_rela_section(ip_relasec)) + /* 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", + sizeof(struct orc_entry), nr); + 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; } diff --git a/tools/objtool/special.c b/tools/objtool/special.c index e74e0189de22..c80fed8a840e 100644 --- a/tools/objtool/special.c +++ b/tools/objtool/special.c @@ -11,27 +11,11 @@ #include <stdlib.h> #include <string.h> -#include "builtin.h" -#include "special.h" -#include "warn.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 ALT_ENTRY_SIZE 13 -#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 - -#define X86_FEATURE_POPCNT (4*32+23) -#define X86_FEATURE_SMAP (9*32+20) +#include <arch/special.h> +#include <objtool/builtin.h> +#include <objtool/special.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> struct special_entry { const char *sec; @@ -39,9 +23,10 @@ struct special_entry { 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 */ }; -struct special_entry entries[] = { +static const struct special_entry entries[] = { { .sec = ".altinstructions", .group = true, @@ -58,6 +43,7 @@ struct special_entry entries[] = { .size = JUMP_ENTRY_SIZE, .orig = JUMP_ORIG_OFFSET, .new = JUMP_NEW_OFFSET, + .key = JUMP_KEY_OFFSET, }, { .sec = "__ex_table", @@ -68,11 +54,22 @@ struct special_entry entries[] = { {}, }; -static int get_alt_entry(struct elf *elf, struct special_entry *entry, +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 rela *orig_rela, *new_rela; + struct reloc *orig_reloc, *new_reloc; unsigned long offset; offset = idx * entry->size; @@ -87,67 +84,41 @@ static int get_alt_entry(struct elf *elf, struct special_entry *entry, entry->new_len); } - if (entry->feature) { - unsigned short feature; - - feature = *(unsigned short *)(sec->data->d_buf + offset + - entry->feature); - - /* - * It has been requested that we don't validate the !POPCNT - * feature path which is a "very very small percentage of - * machines". - */ - if (feature == X86_FEATURE_POPCNT) - alt->skip_orig = true; - - /* - * If UACCESS validation is enabled; force that alternative; - * otherwise force it the other way. - * - * What we want to avoid is having both the original and the - * alternative code flow at the same time, in that case we can - * find paths that see the STAC but take the NOP instead of - * CLAC and the other way around. - */ - if (feature == X86_FEATURE_SMAP) { - if (uaccess) - alt->skip_orig = true; - else - alt->skip_alt = true; - } - } - - orig_rela = find_rela_by_dest(elf, sec, offset + entry->orig); - if (!orig_rela) { - WARN_FUNC("can't find orig rela", sec, offset + entry->orig); - return -1; - } - if (orig_rela->sym->type != STT_SECTION) { - WARN_FUNC("don't know how to handle non-section rela symbol %s", - sec, offset + entry->orig, orig_rela->sym->name); + 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; } - alt->orig_sec = orig_rela->sym->sec; - alt->orig_off = orig_rela->addend; + reloc_to_sec_off(orig_reloc, &alt->orig_sec, &alt->orig_off); + + arch_handle_alternative(alt); if (!entry->group || alt->new_len) { - new_rela = find_rela_by_dest(elf, sec, offset + entry->new); - if (!new_rela) { - WARN_FUNC("can't find new rela", - sec, offset + entry->new); + 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; } - alt->new_sec = new_rela->sym->sec; - alt->new_off = (unsigned int)new_rela->addend; + 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; } @@ -158,7 +129,7 @@ static int get_alt_entry(struct elf *elf, struct special_entry *entry, */ int special_get_alts(struct elf *elf, struct list_head *alts) { - struct special_entry *entry; + const struct special_entry *entry; struct section *sec; unsigned int nr_entries; struct special_alt *alt; @@ -171,24 +142,25 @@ int special_get_alts(struct elf *elf, struct list_head *alts) if (!sec) continue; - if (sec->len % entry->size != 0) { - WARN("%s size not a multiple of %d", - sec->name, entry->size); + if (sec->sh.sh_size % entry->size != 0) { + ERROR("%s size not a multiple of %d", sec->name, entry->size); return -1; } - nr_entries = sec->len / entry->size; + nr_entries = sec->sh.sh_size / entry->size; for (idx = 0; idx < nr_entries; idx++) { alt = malloc(sizeof(*alt)); if (!alt) { - WARN("malloc failed"); + ERROR_GLIBC("malloc failed"); return -1; } memset(alt, 0, sizeof(*alt)); ret = get_alt_entry(elf, entry, sec, idx, alt); - if (ret) + if (ret > 0) + continue; + if (ret < 0) return ret; list_add_tail(&alt->list, alts); diff --git a/tools/objtool/sync-check.sh b/tools/objtool/sync-check.sh index 2a1261bfbb62..81d120d05442 100755 --- a/tools/objtool/sync-check.sh +++ b/tools/objtool/sync-check.sh @@ -1,13 +1,31 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 -FILES=' +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/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 @@ -40,11 +58,18 @@ fi cd ../.. -for i in $FILES; do - check $i -done +while read -r file_entry; do + if [ -z "$file_entry" ]; then + continue + fi + + check $file_entry +done <<EOF +$FILES +EOF -check arch/x86/include/asm/inat.h '-I "^#include [\"<]\(asm/\)*inat_types.h[\">]"' -check arch/x86/include/asm/insn.h '-I "^#include [\"<]\(asm/\)*inat.h[\">]"' -check arch/x86/lib/inat.c '-I "^#include [\"<]\(../include/\)*asm/insn.h[\">]"' -check arch/x86/lib/insn.c '-I "^#include [\"<]\(../include/\)*asm/in\(at\|sn\).h[\">]" -I "^#include [\"<]\(../include/\)*asm/emulate_prefix.h[\">]"' +if [ "$SRCARCH" = "x86" ]; then + for i in $SYNC_CHECK_FILES; do + check $i '-I "^.*\/\*.*__ignore_sync_check__.*\*\/.*$"' + done +fi diff --git a/tools/objtool/warn.h b/tools/objtool/warn.h deleted file mode 100644 index 7799f60de80a..000000000000 --- a/tools/objtool/warn.h +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 "elf.h" - -extern const char *objname; - -static inline char *offstr(struct section *sec, unsigned long offset) -{ - struct symbol *func; - char *name, *str; - unsigned long name_off; - - func = find_func_containing(sec, offset); - if (func) { - name = func->name; - name_off = offset - func->offset; - } else { - name = sec->name; - name_off = offset; - } - - str = malloc(strlen(name) + 20); - - if (func) - sprintf(str, "%s()+0x%lx", name, name_off); - else - sprintf(str, "%s+0x%lx", name, name_off); - - return str; -} - -#define WARN(format, ...) \ - fprintf(stderr, \ - "%s: warning: objtool: " format "\n", \ - objname, ##__VA_ARGS__) - -#define WARN_FUNC(format, sec, offset, ...) \ -({ \ - char *_str = offstr(sec, offset); \ - WARN("%s: " format, _str, ##__VA_ARGS__); \ - free(_str); \ -}) - -#define BT_FUNC(format, insn, ...) \ -({ \ - struct instruction *_insn = (insn); \ - char *_str = offstr(_insn->sec, _insn->offset); \ - WARN(" %s: " format, _str, ##__VA_ARGS__); \ - free(_str); \ -}) - -#define WARN_ELF(format, ...) \ - WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1)) - -#endif /* _WARN_H */ diff --git a/tools/objtool/weak.c b/tools/objtool/weak.c index 942ea5e8ac36..d83f607733b0 100644 --- a/tools/objtool/weak.c +++ b/tools/objtool/weak.c @@ -7,9 +7,7 @@ #include <stdbool.h> #include <errno.h> -#include "objtool.h" - -#define __weak __attribute__((weak)) +#include <objtool/objtool.h> #define UNSUPPORTED(name) \ ({ \ @@ -17,24 +15,12 @@ return ENOSYS; \ }) -const char __weak *objname; - -int __weak check(const char *_objname, bool orc) -{ - UNSUPPORTED("check subcommand"); -} - int __weak orc_dump(const char *_objname) { - UNSUPPORTED("orc"); -} - -int __weak create_orc(struct objtool_file *file) -{ - UNSUPPORTED("orc"); + UNSUPPORTED("ORC"); } -int __weak create_orc_sections(struct objtool_file *file) +int __weak orc_create(struct objtool_file *file) { - UNSUPPORTED("orc"); + UNSUPPORTED("ORC"); } |