Objtool changes for this cycle were:
- Comprehensive interface overhaul: ================================= Objtool's interface has some issues: - Several features are done unconditionally, without any way to turn them off. Some of them might be surprising. This makes objtool tricky to use, and prevents porting individual features to other arches. - The config dependencies are too coarse-grained. Objtool enablement is tied to CONFIG_STACK_VALIDATION, but it has several other features independent of that. - The objtool subcmds ("check" and "orc") are clumsy: "check" is really a subset of "orc", so it has all the same options. The subcmd model has never really worked for objtool, as it only has a single purpose: "do some combination of things on an object file". - The '--lto' and '--vmlinux' options are nonsensical and have surprising behavior. Overhaul the interface: - get rid of subcmds - make all features individually selectable - remove and/or clarify confusing/obsolete options - update the documentation - fix some bugs found along the way - Fix x32 regression - Fix Kbuild cleanup bugs - Add scripts/objdump-func helper script to disassemble a single function from an object file. - Rewrite scripts/faddr2line to be section-aware, by basing it on 'readelf', moving it away from 'nm', which doesn't handle multiple sections well, which can result in decoding failure. - Rewrite & fix symbol handling - which had a number of bugs wrt. object files that don't have global symbols - which is rare but possible. Also fix a bunch of symbol handling bugs found along the way. Signed-off-by: Ingo Molnar <mingo@kernel.org> -----BEGIN PGP SIGNATURE----- iQJFBAABCgAvFiEEBpT5eoXrXCwVQwEKEnMQ0APhK1gFAmKLtcURHG1pbmdvQGtl cm5lbC5vcmcACgkQEnMQ0APhK1jVQg//QM8nCNadJAVS9exVGX1DZI9pnf3OJaA9 gOFML7Lv3MC+Lwdxt6Iv020rFVaeAnOcjPsis3dppFz62FZzzMWoemn5irg2BFiJ dp++UtJWTfKxgU2BHydU9uXD0kcJkD4AjBCIaFsgmTjAz8QvMGa9j0smuUm3cDSL 0Bdid+LhkQqW3P2FiLWsSAzh4vqZmdwpXgERZRql8qD3NYk5hV4QDKs3gMguktat 9gos4kGt0uwKfiEvmeNEXkoAwUsTvE/vqaOy9cVxxCqcWrrC+yQeBpwSoqhHK526 dyHlwlYvBaPFqZnmquVUv21iv1MU6dUBJPhNIChke0NDTwVzSXdI75207FARyk5J 3igSFEfJcU9zMvhAAsAjzD/uQP2ATowg5qa/V2xyWwtyaRgBleRffYiDsbhgDoNc R4/vI+vn/fQXouMhmmjPNYzu9uHQ+k89wQCJIY8Bswf7oNu6nKL3jJb/a/a7xhsH ZNqv+M0KEENTZcjBU2UHGyImApmkTlsp2mxUiiHs7QoV1hTfz+TcTXKPM1mIuJB8 /HrVpv64CZ3S7p4JyGBUTNpci4mBjgBmwwAf16+dtaxyxxfoqReVWh3+bzsZbH+B kRjezWHh7/yCsoyDm7/LPgyPKEbozLLzMsTsjVJeWgeTgZ+xuqku3PTVctyzAI21 DVL5oZe3iK4= =ARdm -----END PGP SIGNATURE----- Merge tag 'objtool-core-2022-05-23' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip Pull objtool updates from Ingo Molnar: - Comprehensive interface overhaul: ================================= Objtool's interface has some issues: - Several features are done unconditionally, without any way to turn them off. Some of them might be surprising. This makes objtool tricky to use, and prevents porting individual features to other arches. - The config dependencies are too coarse-grained. Objtool enablement is tied to CONFIG_STACK_VALIDATION, but it has several other features independent of that. - The objtool subcmds ("check" and "orc") are clumsy: "check" is really a subset of "orc", so it has all the same options. The subcmd model has never really worked for objtool, as it only has a single purpose: "do some combination of things on an object file". - The '--lto' and '--vmlinux' options are nonsensical and have surprising behavior. Overhaul the interface: - get rid of subcmds - make all features individually selectable - remove and/or clarify confusing/obsolete options - update the documentation - fix some bugs found along the way - Fix x32 regression - Fix Kbuild cleanup bugs - Add scripts/objdump-func helper script to disassemble a single function from an object file. - Rewrite scripts/faddr2line to be section-aware, by basing it on 'readelf', moving it away from 'nm', which doesn't handle multiple sections well, which can result in decoding failure. - Rewrite & fix symbol handling - which had a number of bugs wrt. object files that don't have global symbols - which is rare but possible. Also fix a bunch of symbol handling bugs found along the way. * tag 'objtool-core-2022-05-23' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (23 commits) objtool: Fix objtool regression on x32 systems objtool: Fix symbol creation scripts/faddr2line: Fix overlapping text section failures scripts: Create objdump-func helper script objtool: Remove libsubcmd.a when make clean objtool: Remove inat-tables.c when make clean objtool: Update documentation objtool: Remove --lto and --vmlinux in favor of --link objtool: Add HAVE_NOINSTR_VALIDATION objtool: Rename "VMLINUX_VALIDATION" -> "NOINSTR_VALIDATION" objtool: Make noinstr hacks optional objtool: Make jump label hack optional objtool: Make static call annotation optional objtool: Make stack validation frame-pointer-specific objtool: Add CONFIG_OBJTOOL objtool: Extricate sls from stack validation objtool: Rework ibt and extricate from stack validation objtool: Make stack validation optional objtool: Add option to print section addresses objtool: Don't print parentheses in function addresses ...
This commit is contained in:
commit
22922deae1
2
Makefile
2
Makefile
|
@ -1302,7 +1302,7 @@ install: sub_make_done :=
|
|||
# ---------------------------------------------------------------------------
|
||||
# Tools
|
||||
|
||||
ifdef CONFIG_STACK_VALIDATION
|
||||
ifdef CONFIG_OBJTOOL
|
||||
prepare: tools/objtool
|
||||
endif
|
||||
|
||||
|
|
18
arch/Kconfig
18
arch/Kconfig
|
@ -54,6 +54,7 @@ config JUMP_LABEL
|
|||
bool "Optimize very unlikely/likely branches"
|
||||
depends on HAVE_ARCH_JUMP_LABEL
|
||||
depends on CC_HAS_ASM_GOTO
|
||||
select OBJTOOL if HAVE_JUMP_LABEL_HACK
|
||||
help
|
||||
This option enables a transparent branch optimization that
|
||||
makes certain almost-always-true or almost-always-false branch
|
||||
|
@ -1034,11 +1035,23 @@ config ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT
|
|||
depends on MMU
|
||||
select ARCH_HAS_ELF_RANDOMIZE
|
||||
|
||||
config HAVE_OBJTOOL
|
||||
bool
|
||||
|
||||
config HAVE_JUMP_LABEL_HACK
|
||||
bool
|
||||
|
||||
config HAVE_NOINSTR_HACK
|
||||
bool
|
||||
|
||||
config HAVE_NOINSTR_VALIDATION
|
||||
bool
|
||||
|
||||
config HAVE_STACK_VALIDATION
|
||||
bool
|
||||
help
|
||||
Architecture supports the 'objtool check' host tool command, which
|
||||
performs compile-time stack metadata validation.
|
||||
Architecture supports objtool compile-time frame pointer rule
|
||||
validation.
|
||||
|
||||
config HAVE_RELIABLE_STACKTRACE
|
||||
bool
|
||||
|
@ -1308,6 +1321,7 @@ config HAVE_STATIC_CALL
|
|||
config HAVE_STATIC_CALL_INLINE
|
||||
bool
|
||||
depends on HAVE_STATIC_CALL
|
||||
select OBJTOOL
|
||||
|
||||
config HAVE_PREEMPT_DYNAMIC
|
||||
bool
|
||||
|
|
|
@ -188,7 +188,7 @@ config X86
|
|||
select HAVE_CONTEXT_TRACKING if X86_64
|
||||
select HAVE_CONTEXT_TRACKING_OFFSTACK if HAVE_CONTEXT_TRACKING
|
||||
select HAVE_C_RECORDMCOUNT
|
||||
select HAVE_OBJTOOL_MCOUNT if STACK_VALIDATION
|
||||
select HAVE_OBJTOOL_MCOUNT if HAVE_OBJTOOL
|
||||
select HAVE_BUILDTIME_MCOUNT_SORT
|
||||
select HAVE_DEBUG_KMEMLEAK
|
||||
select HAVE_DMA_CONTIGUOUS
|
||||
|
@ -212,6 +212,7 @@ config X86
|
|||
select HAVE_IOREMAP_PROT
|
||||
select HAVE_IRQ_EXIT_ON_IRQ_STACK if X86_64
|
||||
select HAVE_IRQ_TIME_ACCOUNTING
|
||||
select HAVE_JUMP_LABEL_HACK if HAVE_OBJTOOL
|
||||
select HAVE_KERNEL_BZIP2
|
||||
select HAVE_KERNEL_GZIP
|
||||
select HAVE_KERNEL_LZ4
|
||||
|
@ -230,7 +231,10 @@ config X86
|
|||
select HAVE_MOD_ARCH_SPECIFIC
|
||||
select HAVE_MOVE_PMD
|
||||
select HAVE_MOVE_PUD
|
||||
select HAVE_NOINSTR_HACK if HAVE_OBJTOOL
|
||||
select HAVE_NMI
|
||||
select HAVE_NOINSTR_VALIDATION if HAVE_OBJTOOL
|
||||
select HAVE_OBJTOOL if X86_64
|
||||
select HAVE_OPTPROBES
|
||||
select HAVE_PCSPKR_PLATFORM
|
||||
select HAVE_PERF_EVENTS
|
||||
|
@ -239,17 +243,17 @@ config X86
|
|||
select HAVE_PCI
|
||||
select HAVE_PERF_REGS
|
||||
select HAVE_PERF_USER_STACK_DUMP
|
||||
select MMU_GATHER_RCU_TABLE_FREE if PARAVIRT
|
||||
select MMU_GATHER_RCU_TABLE_FREE if PARAVIRT
|
||||
select HAVE_POSIX_CPU_TIMERS_TASK_WORK
|
||||
select HAVE_REGS_AND_STACK_ACCESS_API
|
||||
select HAVE_RELIABLE_STACKTRACE if X86_64 && (UNWINDER_FRAME_POINTER || UNWINDER_ORC) && STACK_VALIDATION
|
||||
select HAVE_RELIABLE_STACKTRACE if UNWINDER_ORC || STACK_VALIDATION
|
||||
select HAVE_FUNCTION_ARG_ACCESS_API
|
||||
select HAVE_SETUP_PER_CPU_AREA
|
||||
select HAVE_SOFTIRQ_ON_OWN_STACK
|
||||
select HAVE_STACKPROTECTOR if CC_HAS_SANE_STACKPROTECTOR
|
||||
select HAVE_STACK_VALIDATION if X86_64
|
||||
select HAVE_STACK_VALIDATION if HAVE_OBJTOOL
|
||||
select HAVE_STATIC_CALL
|
||||
select HAVE_STATIC_CALL_INLINE if HAVE_STACK_VALIDATION
|
||||
select HAVE_STATIC_CALL_INLINE if HAVE_OBJTOOL
|
||||
select HAVE_PREEMPT_DYNAMIC_CALL
|
||||
select HAVE_RSEQ
|
||||
select HAVE_SYSCALL_TRACEPOINTS
|
||||
|
@ -268,7 +272,6 @@ config X86
|
|||
select RTC_MC146818_LIB
|
||||
select SPARSE_IRQ
|
||||
select SRCU
|
||||
select STACK_VALIDATION if HAVE_STACK_VALIDATION && (HAVE_STATIC_CALL_INLINE || RETPOLINE)
|
||||
select SYSCTL_EXCEPTION_TRACE
|
||||
select THREAD_INFO_IN_TASK
|
||||
select TRACE_IRQFLAGS_SUPPORT
|
||||
|
@ -459,6 +462,7 @@ config GOLDFISH
|
|||
|
||||
config RETPOLINE
|
||||
bool "Avoid speculative indirect branches in kernel"
|
||||
select OBJTOOL if HAVE_OBJTOOL
|
||||
default y
|
||||
help
|
||||
Compile kernel with the retpoline compiler options to guard against
|
||||
|
@ -472,6 +476,7 @@ config CC_HAS_SLS
|
|||
config SLS
|
||||
bool "Mitigate Straight-Line-Speculation"
|
||||
depends on CC_HAS_SLS && X86_64
|
||||
select OBJTOOL if HAVE_OBJTOOL
|
||||
default n
|
||||
help
|
||||
Compile the kernel with straight-line-speculation options to guard
|
||||
|
@ -1859,9 +1864,10 @@ config CC_HAS_IBT
|
|||
config X86_KERNEL_IBT
|
||||
prompt "Indirect Branch Tracking"
|
||||
bool
|
||||
depends on X86_64 && CC_HAS_IBT && STACK_VALIDATION
|
||||
depends on X86_64 && CC_HAS_IBT && HAVE_OBJTOOL
|
||||
# https://github.com/llvm/llvm-project/commit/9d7001eba9c4cb311e03cd8cdc231f9e579f2d0f
|
||||
depends on !LD_IS_LLD || LLD_VERSION >= 140000
|
||||
select OBJTOOL
|
||||
help
|
||||
Build the kernel with support for Indirect Branch Tracking, a
|
||||
hardware support course-grain forward-edge Control Flow Integrity
|
||||
|
|
|
@ -237,7 +237,7 @@ choice
|
|||
config UNWINDER_ORC
|
||||
bool "ORC unwinder"
|
||||
depends on X86_64
|
||||
select STACK_VALIDATION
|
||||
select OBJTOOL
|
||||
help
|
||||
This option enables the ORC (Oops Rewind Capability) unwinder for
|
||||
unwinding kernel stack traces. It uses a custom data format which is
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
_ASM_PTR "%c0 + %c1 - .\n\t" \
|
||||
".popsection \n\t"
|
||||
|
||||
#ifdef CONFIG_STACK_VALIDATION
|
||||
#ifdef CONFIG_HAVE_JUMP_LABEL_HACK
|
||||
|
||||
static __always_inline bool arch_static_branch(struct static_key *key, bool branch)
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ static __always_inline bool arch_static_branch(struct static_key *key, bool bran
|
|||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
#else /* !CONFIG_HAVE_JUMP_LABEL_HACK */
|
||||
|
||||
static __always_inline bool arch_static_branch(struct static_key * const key, const bool branch)
|
||||
{
|
||||
|
@ -48,7 +48,7 @@ static __always_inline bool arch_static_branch(struct static_key * const key, co
|
|||
return true;
|
||||
}
|
||||
|
||||
#endif /* STACK_VALIDATION */
|
||||
#endif /* CONFIG_HAVE_JUMP_LABEL_HACK */
|
||||
|
||||
static __always_inline bool arch_static_branch_jump(struct static_key * const key, const bool branch)
|
||||
{
|
||||
|
|
|
@ -338,7 +338,7 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
|
|||
}
|
||||
}
|
||||
|
||||
#if defined(CONFIG_RETPOLINE) && defined(CONFIG_STACK_VALIDATION)
|
||||
#if defined(CONFIG_RETPOLINE) && defined(CONFIG_OBJTOOL)
|
||||
|
||||
/*
|
||||
* CALL/JMP *%\reg
|
||||
|
@ -507,11 +507,11 @@ void __init_or_module noinline apply_retpolines(s32 *start, s32 *end)
|
|||
}
|
||||
}
|
||||
|
||||
#else /* !RETPOLINES || !CONFIG_STACK_VALIDATION */
|
||||
#else /* !CONFIG_RETPOLINE || !CONFIG_OBJTOOL */
|
||||
|
||||
void __init_or_module noinline apply_retpolines(s32 *start, s32 *end) { }
|
||||
|
||||
#endif /* CONFIG_RETPOLINE && CONFIG_STACK_VALIDATION */
|
||||
#endif /* CONFIG_RETPOLINE && CONFIG_OBJTOOL */
|
||||
|
||||
#ifdef CONFIG_X86_KERNEL_IBT
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
|
|||
#endif
|
||||
|
||||
/* Unreachable code */
|
||||
#ifdef CONFIG_STACK_VALIDATION
|
||||
#ifdef CONFIG_OBJTOOL
|
||||
/*
|
||||
* These macros help objtool understand GCC code flow for unreachable code.
|
||||
* The __COUNTER__ based labels are a hack to make each instance of the macros
|
||||
|
@ -128,10 +128,10 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
|
|||
/* Annotate a C jump table to allow objtool to follow the code flow */
|
||||
#define __annotate_jump_table __section(".rodata..c_jump_table")
|
||||
|
||||
#else
|
||||
#else /* !CONFIG_OBJTOOL */
|
||||
#define annotate_unreachable()
|
||||
#define __annotate_jump_table
|
||||
#endif
|
||||
#endif /* CONFIG_OBJTOOL */
|
||||
|
||||
#ifndef unreachable
|
||||
# define unreachable() do { \
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#ifndef __LINUX_INSTRUMENTATION_H
|
||||
#define __LINUX_INSTRUMENTATION_H
|
||||
|
||||
#if defined(CONFIG_DEBUG_ENTRY) && defined(CONFIG_STACK_VALIDATION)
|
||||
#ifdef CONFIG_NOINSTR_VALIDATION
|
||||
|
||||
#include <linux/stringify.h>
|
||||
|
||||
|
@ -53,9 +53,9 @@
|
|||
".popsection\n\t" : : "i" (c)); \
|
||||
})
|
||||
#define instrumentation_end() __instrumentation_end(__COUNTER__)
|
||||
#else
|
||||
#else /* !CONFIG_NOINSTR_VALIDATION */
|
||||
# define instrumentation_begin() do { } while(0)
|
||||
# define instrumentation_end() do { } while(0)
|
||||
#endif
|
||||
#endif /* CONFIG_NOINSTR_VALIDATION */
|
||||
|
||||
#endif /* __LINUX_INSTRUMENTATION_H */
|
||||
|
|
|
@ -38,7 +38,7 @@ struct unwind_hint {
|
|||
#define UNWIND_HINT_TYPE_REGS_PARTIAL 2
|
||||
#define UNWIND_HINT_TYPE_FUNC 3
|
||||
|
||||
#ifdef CONFIG_STACK_VALIDATION
|
||||
#ifdef CONFIG_OBJTOOL
|
||||
|
||||
#include <asm/asm.h>
|
||||
|
||||
|
@ -159,7 +159,7 @@ struct unwind_hint {
|
|||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#else /* !CONFIG_STACK_VALIDATION */
|
||||
#else /* !CONFIG_OBJTOOL */
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
|
||||
|
@ -181,6 +181,6 @@ struct unwind_hint {
|
|||
.endm
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_STACK_VALIDATION */
|
||||
#endif /* CONFIG_OBJTOOL */
|
||||
|
||||
#endif /* _LINUX_OBJTOOL_H */
|
||||
|
|
|
@ -729,6 +729,7 @@ config FTRACE_MCOUNT_USE_OBJTOOL
|
|||
depends on !FTRACE_MCOUNT_USE_PATCHABLE_FUNCTION_ENTRY
|
||||
depends on !FTRACE_MCOUNT_USE_CC
|
||||
depends on FTRACE_MCOUNT_RECORD
|
||||
select OBJTOOL
|
||||
|
||||
config FTRACE_MCOUNT_USE_RECORDMCOUNT
|
||||
def_bool y
|
||||
|
|
|
@ -485,24 +485,25 @@ config FRAME_POINTER
|
|||
larger and slower, but it gives very useful debugging information
|
||||
in case of kernel bugs. (precise oopses/stacktraces/warnings)
|
||||
|
||||
config OBJTOOL
|
||||
bool
|
||||
|
||||
config STACK_VALIDATION
|
||||
bool "Compile-time stack metadata validation"
|
||||
depends on HAVE_STACK_VALIDATION
|
||||
depends on HAVE_STACK_VALIDATION && UNWINDER_FRAME_POINTER
|
||||
select OBJTOOL
|
||||
default n
|
||||
help
|
||||
Add compile-time checks to validate stack metadata, including frame
|
||||
pointers (if CONFIG_FRAME_POINTER is enabled). This helps ensure
|
||||
that runtime stack traces are more reliable.
|
||||
|
||||
This is also a prerequisite for generation of ORC unwind data, which
|
||||
is needed for CONFIG_UNWINDER_ORC.
|
||||
Validate frame pointer rules at compile-time. This helps ensure that
|
||||
runtime stack traces are more reliable.
|
||||
|
||||
For more information, see
|
||||
tools/objtool/Documentation/stack-validation.txt.
|
||||
|
||||
config VMLINUX_VALIDATION
|
||||
config NOINSTR_VALIDATION
|
||||
bool
|
||||
depends on STACK_VALIDATION && DEBUG_ENTRY
|
||||
depends on HAVE_NOINSTR_VALIDATION && DEBUG_ENTRY
|
||||
select OBJTOOL
|
||||
default y
|
||||
|
||||
config VMLINUX_MAP
|
||||
|
@ -2035,10 +2036,11 @@ config KCOV
|
|||
bool "Code coverage for fuzzing"
|
||||
depends on ARCH_HAS_KCOV
|
||||
depends on CC_HAS_SANCOV_TRACE_PC || GCC_PLUGINS
|
||||
depends on !ARCH_WANTS_NO_INSTR || STACK_VALIDATION || \
|
||||
depends on !ARCH_WANTS_NO_INSTR || HAVE_NOINSTR_HACK || \
|
||||
GCC_VERSION >= 120000 || CLANG_VERSION >= 130000
|
||||
select DEBUG_FS
|
||||
select GCC_PLUGIN_SANCOV if !CC_HAS_SANCOV_TRACE_PC
|
||||
select OBJTOOL if HAVE_NOINSTR_HACK
|
||||
help
|
||||
KCOV exposes kernel code coverage information in a form suitable
|
||||
for coverage-guided fuzzing (randomized testing).
|
||||
|
|
|
@ -187,7 +187,9 @@ config KCSAN_WEAK_MEMORY
|
|||
# We can either let objtool nop __tsan_func_{entry,exit}() and builtin
|
||||
# atomics instrumentation in .noinstr.text, or use a compiler that can
|
||||
# implement __no_kcsan to really remove all instrumentation.
|
||||
depends on STACK_VALIDATION || CC_IS_GCC || CLANG_VERSION >= 140000
|
||||
depends on !ARCH_WANTS_NO_INSTR || HAVE_NOINSTR_HACK || \
|
||||
CC_IS_GCC || CLANG_VERSION >= 140000
|
||||
select OBJTOOL if HAVE_NOINSTR_HACK
|
||||
help
|
||||
Enable support for modeling a subset of weak memory, which allows
|
||||
detecting a subset of data races due to missing memory barriers.
|
||||
|
|
|
@ -94,7 +94,7 @@ config UBSAN_UNREACHABLE
|
|||
bool "Perform checking for unreachable code"
|
||||
# objtool already handles unreachable checking and gets angry about
|
||||
# seeing UBSan instrumentation located in unreachable places.
|
||||
depends on !STACK_VALIDATION
|
||||
depends on !(OBJTOOL && (STACK_VALIDATION || UNWINDER_ORC || X86_SMAP))
|
||||
depends on $(cc-option,-fsanitize=unreachable)
|
||||
help
|
||||
This option enables -fsanitize=unreachable which checks for control
|
||||
|
|
|
@ -222,25 +222,29 @@ cmd_record_mcount = $(if $(findstring $(strip $(CC_FLAGS_FTRACE)),$(_c_flags)),
|
|||
$(sub_cmd_record_mcount))
|
||||
endif # CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT
|
||||
|
||||
ifdef CONFIG_STACK_VALIDATION
|
||||
ifdef CONFIG_OBJTOOL
|
||||
|
||||
objtool := $(objtree)/tools/objtool/objtool
|
||||
|
||||
objtool_args = \
|
||||
$(if $(CONFIG_UNWINDER_ORC),orc generate,check) \
|
||||
$(if $(part-of-module), --module) \
|
||||
$(if $(CONFIG_X86_KERNEL_IBT), --lto --ibt) \
|
||||
$(if $(CONFIG_FRAME_POINTER),, --no-fp) \
|
||||
$(if $(CONFIG_GCOV_KERNEL), --no-unreachable) \
|
||||
$(if $(CONFIG_RETPOLINE), --retpoline) \
|
||||
--uaccess \
|
||||
$(if $(CONFIG_HAVE_JUMP_LABEL_HACK), --hacks=jump_label) \
|
||||
$(if $(CONFIG_HAVE_NOINSTR_HACK), --hacks=noinstr) \
|
||||
$(if $(CONFIG_X86_KERNEL_IBT), --ibt) \
|
||||
$(if $(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL), --mcount) \
|
||||
$(if $(CONFIG_SLS), --sls)
|
||||
$(if $(CONFIG_UNWINDER_ORC), --orc) \
|
||||
$(if $(CONFIG_RETPOLINE), --retpoline) \
|
||||
$(if $(CONFIG_SLS), --sls) \
|
||||
$(if $(CONFIG_STACK_VALIDATION), --stackval) \
|
||||
$(if $(CONFIG_HAVE_STATIC_CALL_INLINE), --static-call) \
|
||||
--uaccess \
|
||||
$(if $(linked-object), --link) \
|
||||
$(if $(part-of-module), --module) \
|
||||
$(if $(CONFIG_GCOV_KERNEL), --no-unreachable)
|
||||
|
||||
cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool_args) $@)
|
||||
cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$@: $$(wildcard $(objtool))' ; } >> $(dot-target).cmd)
|
||||
|
||||
endif # CONFIG_STACK_VALIDATION
|
||||
endif # CONFIG_OBJTOOL
|
||||
|
||||
ifneq ($(CONFIG_LTO_CLANG)$(CONFIG_X86_KERNEL_IBT),)
|
||||
|
||||
|
@ -303,6 +307,7 @@ quiet_cmd_cc_prelink_modules = LD [M] $@
|
|||
# modules into native code
|
||||
$(obj)/%.prelink.o: objtool-enabled = y
|
||||
$(obj)/%.prelink.o: part-of-module := y
|
||||
$(obj)/%.prelink.o: linked-object := y
|
||||
|
||||
$(obj)/%.prelink.o: $(obj)/%.o FORCE
|
||||
$(call if_changed,cc_prelink_modules)
|
||||
|
|
|
@ -44,17 +44,6 @@
|
|||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
READELF="${CROSS_COMPILE:-}readelf"
|
||||
ADDR2LINE="${CROSS_COMPILE:-}addr2line"
|
||||
SIZE="${CROSS_COMPILE:-}size"
|
||||
NM="${CROSS_COMPILE:-}nm"
|
||||
|
||||
command -v awk >/dev/null 2>&1 || die "awk isn't installed"
|
||||
command -v ${READELF} >/dev/null 2>&1 || die "readelf isn't installed"
|
||||
command -v ${ADDR2LINE} >/dev/null 2>&1 || die "addr2line isn't installed"
|
||||
command -v ${SIZE} >/dev/null 2>&1 || die "size isn't installed"
|
||||
command -v ${NM} >/dev/null 2>&1 || die "nm isn't installed"
|
||||
|
||||
usage() {
|
||||
echo "usage: faddr2line [--list] <object file> <func+offset> <func+offset>..." >&2
|
||||
exit 1
|
||||
|
@ -69,6 +58,14 @@ die() {
|
|||
exit 1
|
||||
}
|
||||
|
||||
READELF="${CROSS_COMPILE:-}readelf"
|
||||
ADDR2LINE="${CROSS_COMPILE:-}addr2line"
|
||||
AWK="awk"
|
||||
|
||||
command -v ${AWK} >/dev/null 2>&1 || die "${AWK} isn't installed"
|
||||
command -v ${READELF} >/dev/null 2>&1 || die "${READELF} isn't installed"
|
||||
command -v ${ADDR2LINE} >/dev/null 2>&1 || die "${ADDR2LINE} isn't installed"
|
||||
|
||||
# Try to figure out the source directory prefix so we can remove it from the
|
||||
# addr2line output. HACK ALERT: This assumes that start_kernel() is in
|
||||
# init/main.c! This only works for vmlinux. Otherwise it falls back to
|
||||
|
@ -76,7 +73,7 @@ die() {
|
|||
find_dir_prefix() {
|
||||
local objfile=$1
|
||||
|
||||
local start_kernel_addr=$(${READELF} -sW $objfile | awk '$8 == "start_kernel" {printf "0x%s", $2}')
|
||||
local start_kernel_addr=$(${READELF} --symbols --wide $objfile | ${AWK} '$8 == "start_kernel" {printf "0x%s", $2}')
|
||||
[[ -z $start_kernel_addr ]] && return
|
||||
|
||||
local file_line=$(${ADDR2LINE} -e $objfile $start_kernel_addr)
|
||||
|
@ -97,86 +94,133 @@ __faddr2line() {
|
|||
local dir_prefix=$3
|
||||
local print_warnings=$4
|
||||
|
||||
local func=${func_addr%+*}
|
||||
local sym_name=${func_addr%+*}
|
||||
local offset=${func_addr#*+}
|
||||
offset=${offset%/*}
|
||||
local size=
|
||||
[[ $func_addr =~ "/" ]] && size=${func_addr#*/}
|
||||
local user_size=
|
||||
[[ $func_addr =~ "/" ]] && user_size=${func_addr#*/}
|
||||
|
||||
if [[ -z $func ]] || [[ -z $offset ]] || [[ $func = $func_addr ]]; then
|
||||
if [[ -z $sym_name ]] || [[ -z $offset ]] || [[ $sym_name = $func_addr ]]; then
|
||||
warn "bad func+offset $func_addr"
|
||||
DONE=1
|
||||
return
|
||||
fi
|
||||
|
||||
# Go through each of the object's symbols which match the func name.
|
||||
# In rare cases there might be duplicates.
|
||||
file_end=$(${SIZE} -Ax $objfile | awk '$1 == ".text" {print $2}')
|
||||
while read symbol; do
|
||||
local fields=($symbol)
|
||||
local sym_base=0x${fields[0]}
|
||||
local sym_type=${fields[1]}
|
||||
local sym_end=${fields[3]}
|
||||
# In rare cases there might be duplicates, in which case we print all
|
||||
# matches.
|
||||
while read line; do
|
||||
local fields=($line)
|
||||
local sym_addr=0x${fields[1]}
|
||||
local sym_elf_size=${fields[2]}
|
||||
local sym_sec=${fields[6]}
|
||||
|
||||
# calculate the size
|
||||
local sym_size=$(($sym_end - $sym_base))
|
||||
if [[ -z $sym_size ]] || [[ $sym_size -le 0 ]]; then
|
||||
warn "bad symbol size: base: $sym_base end: $sym_end"
|
||||
# Get the section size:
|
||||
local sec_size=$(${READELF} --section-headers --wide $objfile |
|
||||
sed 's/\[ /\[/' |
|
||||
${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print "0x" $6; exit }')
|
||||
|
||||
if [[ -z $sec_size ]]; then
|
||||
warn "bad section size: section: $sym_sec"
|
||||
DONE=1
|
||||
return
|
||||
fi
|
||||
|
||||
# Calculate the symbol size.
|
||||
#
|
||||
# Unfortunately we can't use the ELF size, because kallsyms
|
||||
# also includes the padding bytes in its size calculation. For
|
||||
# kallsyms, the size calculation is the distance between the
|
||||
# symbol and the next symbol in a sorted list.
|
||||
local sym_size
|
||||
local cur_sym_addr
|
||||
local found=0
|
||||
while read line; do
|
||||
local fields=($line)
|
||||
cur_sym_addr=0x${fields[1]}
|
||||
local cur_sym_elf_size=${fields[2]}
|
||||
local cur_sym_name=${fields[7]:-}
|
||||
|
||||
if [[ $cur_sym_addr = $sym_addr ]] &&
|
||||
[[ $cur_sym_elf_size = $sym_elf_size ]] &&
|
||||
[[ $cur_sym_name = $sym_name ]]; then
|
||||
found=1
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ $found = 1 ]]; then
|
||||
sym_size=$(($cur_sym_addr - $sym_addr))
|
||||
[[ $sym_size -lt $sym_elf_size ]] && continue;
|
||||
found=2
|
||||
break
|
||||
fi
|
||||
done < <(${READELF} --symbols --wide $objfile | ${AWK} -v sec=$sym_sec '$7 == sec' | sort --key=2)
|
||||
|
||||
if [[ $found = 0 ]]; then
|
||||
warn "can't find symbol: sym_name: $sym_name sym_sec: $sym_sec sym_addr: $sym_addr sym_elf_size: $sym_elf_size"
|
||||
DONE=1
|
||||
return
|
||||
fi
|
||||
|
||||
# If nothing was found after the symbol, assume it's the last
|
||||
# symbol in the section.
|
||||
[[ $found = 1 ]] && sym_size=$(($sec_size - $sym_addr))
|
||||
|
||||
if [[ -z $sym_size ]] || [[ $sym_size -le 0 ]]; then
|
||||
warn "bad symbol size: sym_addr: $sym_addr cur_sym_addr: $cur_sym_addr"
|
||||
DONE=1
|
||||
return
|
||||
fi
|
||||
|
||||
sym_size=0x$(printf %x $sym_size)
|
||||
|
||||
# calculate the address
|
||||
local addr=$(($sym_base + $offset))
|
||||
# Calculate the section address from user-supplied offset:
|
||||
local addr=$(($sym_addr + $offset))
|
||||
if [[ -z $addr ]] || [[ $addr = 0 ]]; then
|
||||
warn "bad address: $sym_base + $offset"
|
||||
warn "bad address: $sym_addr + $offset"
|
||||
DONE=1
|
||||
return
|
||||
fi
|
||||
addr=0x$(printf %x $addr)
|
||||
|
||||
# weed out non-function symbols
|
||||
if [[ $sym_type != t ]] && [[ $sym_type != T ]]; then
|
||||
# If the user provided a size, make sure it matches the symbol's size:
|
||||
if [[ -n $user_size ]] && [[ $user_size -ne $sym_size ]]; then
|
||||
[[ $print_warnings = 1 ]] &&
|
||||
echo "skipping $func address at $addr due to non-function symbol of type '$sym_type'"
|
||||
continue
|
||||
fi
|
||||
|
||||
# if the user provided a size, make sure it matches the symbol's size
|
||||
if [[ -n $size ]] && [[ $size -ne $sym_size ]]; then
|
||||
[[ $print_warnings = 1 ]] &&
|
||||
echo "skipping $func address at $addr due to size mismatch ($size != $sym_size)"
|
||||
echo "skipping $sym_name address at $addr due to size mismatch ($user_size != $sym_size)"
|
||||
continue;
|
||||
fi
|
||||
|
||||
# make sure the provided offset is within the symbol's range
|
||||
# Make sure the provided offset is within the symbol's range:
|
||||
if [[ $offset -gt $sym_size ]]; then
|
||||
[[ $print_warnings = 1 ]] &&
|
||||
echo "skipping $func address at $addr due to size mismatch ($offset > $sym_size)"
|
||||
echo "skipping $sym_name address at $addr due to size mismatch ($offset > $sym_size)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# separate multiple entries with a blank line
|
||||
# In case of duplicates or multiple addresses specified on the
|
||||
# cmdline, separate multiple entries with a blank line:
|
||||
[[ $FIRST = 0 ]] && echo
|
||||
FIRST=0
|
||||
|
||||
# pass real address to addr2line
|
||||
echo "$func+$offset/$sym_size:"
|
||||
local file_lines=$(${ADDR2LINE} -fpie $objfile $addr | sed "s; $dir_prefix\(\./\)*; ;")
|
||||
[[ -z $file_lines ]] && return
|
||||
echo "$sym_name+$offset/$sym_size:"
|
||||
|
||||
# Pass section address to addr2line and strip absolute paths
|
||||
# from the output:
|
||||
local output=$(${ADDR2LINE} -fpie $objfile $addr | sed "s; $dir_prefix\(\./\)*; ;")
|
||||
[[ -z $output ]] && continue
|
||||
|
||||
# Default output (non --list):
|
||||
if [[ $LIST = 0 ]]; then
|
||||
echo "$file_lines" | while read -r line
|
||||
echo "$output" | while read -r line
|
||||
do
|
||||
echo $line
|
||||
done
|
||||
DONE=1;
|
||||
return
|
||||
continue
|
||||
fi
|
||||
|
||||
# show each line with context
|
||||
echo "$file_lines" | while read -r line
|
||||
# For --list, show each line with its corresponding source code:
|
||||
echo "$output" | while read -r line
|
||||
do
|
||||
echo
|
||||
echo $line
|
||||
|
@ -184,12 +228,12 @@ __faddr2line() {
|
|||
n1=$[$n-5]
|
||||
n2=$[$n+5]
|
||||
f=$(echo $line | sed 's/.*at \(.\+\):.*/\1/g')
|
||||
awk 'NR>=strtonum("'$n1'") && NR<=strtonum("'$n2'") { if (NR=='$n') printf(">%d<", NR); else printf(" %d ", NR); printf("\t%s\n", $0)}' $f
|
||||
${AWK} 'NR>=strtonum("'$n1'") && NR<=strtonum("'$n2'") { if (NR=='$n') printf(">%d<", NR); else printf(" %d ", NR); printf("\t%s\n", $0)}' $f
|
||||
done
|
||||
|
||||
DONE=1
|
||||
|
||||
done < <(${NM} -n $objfile | awk -v fn=$func -v end=$file_end '$3 == fn { found=1; line=$0; start=$1; next } found == 1 { found=0; print line, "0x"$1 } END {if (found == 1) print line, end; }')
|
||||
done < <(${READELF} --symbols --wide $objfile | ${AWK} -v fn=$sym_name '$4 == "FUNC" && $8 == fn')
|
||||
}
|
||||
|
||||
[[ $# -lt 2 ]] && usage
|
||||
|
|
|
@ -108,16 +108,22 @@ objtool_link()
|
|||
local objtoolcmd;
|
||||
local objtoolopt;
|
||||
|
||||
if is_enabled CONFIG_STACK_VALIDATION && \
|
||||
( is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT ); then
|
||||
if ! is_enabled CONFIG_OBJTOOL; then
|
||||
return;
|
||||
fi
|
||||
|
||||
# Don't perform vmlinux validation unless explicitly requested,
|
||||
# but run objtool on vmlinux.o now that we have an object file.
|
||||
if is_enabled CONFIG_UNWINDER_ORC; then
|
||||
objtoolcmd="orc generate"
|
||||
if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT; then
|
||||
|
||||
# For LTO and IBT, objtool doesn't run on individual
|
||||
# translation units. Run everything on vmlinux instead.
|
||||
|
||||
if is_enabled CONFIG_HAVE_JUMP_LABEL_HACK; then
|
||||
objtoolopt="${objtoolopt} --hacks=jump_label"
|
||||
fi
|
||||
|
||||
objtoolopt="${objtoolopt} --lto"
|
||||
if is_enabled CONFIG_HAVE_NOINSTR_HACK; then
|
||||
objtoolopt="${objtoolopt} --hacks=noinstr"
|
||||
fi
|
||||
|
||||
if is_enabled CONFIG_X86_KERNEL_IBT; then
|
||||
objtoolopt="${objtoolopt} --ibt"
|
||||
|
@ -126,34 +132,44 @@ objtool_link()
|
|||
if is_enabled CONFIG_FTRACE_MCOUNT_USE_OBJTOOL; then
|
||||
objtoolopt="${objtoolopt} --mcount"
|
||||
fi
|
||||
fi
|
||||
|
||||
if is_enabled CONFIG_VMLINUX_VALIDATION; then
|
||||
objtoolopt="${objtoolopt} --noinstr"
|
||||
fi
|
||||
if is_enabled CONFIG_UNWINDER_ORC; then
|
||||
objtoolopt="${objtoolopt} --orc"
|
||||
fi
|
||||
|
||||
if [ -n "${objtoolopt}" ]; then
|
||||
if [ -z "${objtoolcmd}" ]; then
|
||||
objtoolcmd="check"
|
||||
fi
|
||||
objtoolopt="${objtoolopt} --vmlinux"
|
||||
if ! is_enabled CONFIG_FRAME_POINTER; then
|
||||
objtoolopt="${objtoolopt} --no-fp"
|
||||
fi
|
||||
if is_enabled CONFIG_GCOV_KERNEL; then
|
||||
objtoolopt="${objtoolopt} --no-unreachable"
|
||||
fi
|
||||
if is_enabled CONFIG_RETPOLINE; then
|
||||
objtoolopt="${objtoolopt} --retpoline"
|
||||
fi
|
||||
|
||||
objtoolopt="${objtoolopt} --uaccess"
|
||||
|
||||
if is_enabled CONFIG_SLS; then
|
||||
objtoolopt="${objtoolopt} --sls"
|
||||
fi
|
||||
|
||||
if is_enabled CONFIG_STACK_VALIDATION; then
|
||||
objtoolopt="${objtoolopt} --stackval"
|
||||
fi
|
||||
|
||||
if is_enabled CONFIG_HAVE_STATIC_CALL_INLINE; then
|
||||
objtoolopt="${objtoolopt} --static-call"
|
||||
fi
|
||||
|
||||
objtoolopt="${objtoolopt} --uaccess"
|
||||
fi
|
||||
|
||||
if is_enabled CONFIG_NOINSTR_VALIDATION; then
|
||||
objtoolopt="${objtoolopt} --noinstr"
|
||||
fi
|
||||
|
||||
if [ -n "${objtoolopt}" ]; then
|
||||
|
||||
if is_enabled CONFIG_GCOV_KERNEL; then
|
||||
objtoolopt="${objtoolopt} --no-unreachable"
|
||||
fi
|
||||
|
||||
objtoolopt="${objtoolopt} --link"
|
||||
|
||||
info OBJTOOL ${1}
|
||||
tools/objtool/objtool ${objtoolcmd} ${objtoolopt} ${1}
|
||||
tools/objtool/objtool ${objtoolopt} ${1}
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Disassemble a single function.
|
||||
#
|
||||
# usage: objdump-func <file> <func>
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
OBJDUMP="${CROSS_COMPILE:-}objdump"
|
||||
|
||||
command -v gawk >/dev/null 2>&1 || die "gawk isn't installed"
|
||||
|
||||
usage() {
|
||||
echo "usage: objdump-func <file> <func>" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -lt 2 ]] && usage
|
||||
|
||||
OBJ=$1; shift
|
||||
FUNC=$1; shift
|
||||
|
||||
# Secret feature to allow adding extra objdump args at the end
|
||||
EXTRA_ARGS=$@
|
||||
|
||||
# Note this also matches compiler-added suffixes like ".cold", etc
|
||||
${OBJDUMP} -wdr $EXTRA_ARGS $OBJ | gawk -M -v f=$FUNC '/^$/ { P=0; } $0 ~ "<" f "(\\..*)?>:" { P=1; O=strtonum("0x" $1); } { if (P) { o=strtonum("0x" $1); printf("%04x ", o-O); print $0; } }'
|
|
@ -67,7 +67,7 @@ deploy_kernel_headers () {
|
|||
) > debian/hdrsrcfiles
|
||||
|
||||
{
|
||||
if is_enabled CONFIG_STACK_VALIDATION; then
|
||||
if is_enabled CONFIG_OBJTOOL; then
|
||||
echo tools/objtool/objtool
|
||||
fi
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ struct unwind_hint {
|
|||
#define UNWIND_HINT_TYPE_REGS_PARTIAL 2
|
||||
#define UNWIND_HINT_TYPE_FUNC 3
|
||||
|
||||
#ifdef CONFIG_STACK_VALIDATION
|
||||
#ifdef CONFIG_OBJTOOL
|
||||
|
||||
#include <asm/asm.h>
|
||||
|
||||
|
@ -159,7 +159,7 @@ struct unwind_hint {
|
|||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#else /* !CONFIG_STACK_VALIDATION */
|
||||
#else /* !CONFIG_OBJTOOL */
|
||||
|
||||
#ifndef __ASSEMBLY__
|
||||
|
||||
|
@ -181,6 +181,6 @@ struct unwind_hint {
|
|||
.endm
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_STACK_VALIDATION */
|
||||
#endif /* CONFIG_OBJTOOL */
|
||||
|
||||
#endif /* _LINUX_OBJTOOL_H */
|
||||
|
|
|
@ -806,9 +806,9 @@ static int option__cmp(const void *va, const void *vb)
|
|||
|
||||
static struct option *options__order(const struct option *opts)
|
||||
{
|
||||
int nr_opts = 0, len;
|
||||
int nr_opts = 0, nr_group = 0, len;
|
||||
const struct option *o = opts;
|
||||
struct option *ordered;
|
||||
struct option *opt, *ordered, *group;
|
||||
|
||||
for (o = opts; o->type != OPTION_END; o++)
|
||||
++nr_opts;
|
||||
|
@ -819,7 +819,18 @@ static struct option *options__order(const struct option *opts)
|
|||
goto out;
|
||||
memcpy(ordered, opts, len);
|
||||
|
||||
qsort(ordered, nr_opts, sizeof(*o), option__cmp);
|
||||
/* sort each option group individually */
|
||||
for (opt = group = ordered; opt->type != OPTION_END; opt++) {
|
||||
if (opt->type == OPTION_GROUP) {
|
||||
qsort(group, nr_group, sizeof(*opt), option__cmp);
|
||||
group = opt + 1;
|
||||
nr_group = 0;
|
||||
continue;
|
||||
}
|
||||
nr_group++;
|
||||
}
|
||||
qsort(group, nr_group, sizeof(*opt), option__cmp);
|
||||
|
||||
out:
|
||||
return ordered;
|
||||
}
|
||||
|
|
|
@ -2,17 +2,15 @@ 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
|
||||
|
|
|
@ -1,15 +1,103 @@
|
|||
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.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
Overview
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
- 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 +108,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
|
||||
|
@ -113,9 +193,6 @@ 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
|
||||
|
@ -177,7 +254,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
|
||||
|
@ -358,3 +436,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.
|
|
@ -39,15 +39,13 @@ CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED)
|
|||
|
||||
AWK = awk
|
||||
|
||||
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
|
||||
export BUILD_ORC
|
||||
export srctree OUTPUT CFLAGS SRCARCH AWK
|
||||
include $(srctree)/tools/build/Makefile.include
|
||||
|
||||
|
@ -65,7 +63,7 @@ $(LIBSUBCMD): fixdep FORCE
|
|||
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 $(LIBSUBCMD)
|
||||
|
||||
FORCE:
|
||||
|
||||
|
|
|
@ -581,7 +581,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
|
|||
break;
|
||||
|
||||
case 0xc7: /* mov imm, r/m */
|
||||
if (!noinstr)
|
||||
if (!opts.noinstr)
|
||||
break;
|
||||
|
||||
if (insn.length == 3+4+4 && !strncmp(sec->name, ".init.text", 10)) {
|
||||
|
|
|
@ -20,7 +20,7 @@ void arch_handle_alternative(unsigned short feature, struct special_alt *alt)
|
|||
* find paths that see the STAC but take the NOP instead of
|
||||
* CLAC and the other way around.
|
||||
*/
|
||||
if (uaccess)
|
||||
if (opts.uaccess)
|
||||
alt->skip_orig = true;
|
||||
else
|
||||
alt->skip_alt = true;
|
||||
|
|
|
@ -3,28 +3,21 @@
|
|||
* 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 <stdlib.h>
|
||||
#include <objtool/builtin.h>
|
||||
#include <objtool/objtool.h>
|
||||
|
||||
bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats,
|
||||
lto, vmlinux, mcount, noinstr, backup, sls, dryrun,
|
||||
ibt;
|
||||
#define ERROR(format, ...) \
|
||||
fprintf(stderr, \
|
||||
"error: objtool: " format "\n", \
|
||||
##__VA_ARGS__)
|
||||
|
||||
struct opts opts;
|
||||
|
||||
static const char * const check_usage[] = {
|
||||
"objtool check [<options>] file.o",
|
||||
"objtool <actions> [<options>] file.o",
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -33,22 +26,64 @@ static const char * const env_usage[] = {
|
|||
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;
|
||||
}
|
||||
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
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(0, "lto", <o, "whole-archive like runs"),
|
||||
OPT_BOOLEAN('n', "noinstr", &noinstr, "noinstr validation for vmlinux.o"),
|
||||
OPT_BOOLEAN('l', "vmlinux", &vmlinux, "vmlinux.o validation"),
|
||||
OPT_BOOLEAN('M', "mcount", &mcount, "generate __mcount_loc"),
|
||||
OPT_BOOLEAN('B', "backup", &backup, "create .orig files before modification"),
|
||||
OPT_BOOLEAN('S', "sls", &sls, "validate straight-line-speculation"),
|
||||
OPT_BOOLEAN(0, "dry-run", &dryrun, "don't write the modifications"),
|
||||
OPT_BOOLEAN(0, "ibt", &ibt, "validate ENDBR placement"),
|
||||
OPT_GROUP("Actions:"),
|
||||
OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr", "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('o', "orc", &opts.orc, "generate ORC metadata"),
|
||||
OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"),
|
||||
OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"),
|
||||
OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate frame pointer rules"),
|
||||
OPT_BOOLEAN('t', "static-call", &opts.static_call, "annotate static calls"),
|
||||
OPT_BOOLEAN('u', "uaccess", &opts.uaccess, "validate uaccess rules for SMAP"),
|
||||
OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump),
|
||||
|
||||
OPT_GROUP("Options:"),
|
||||
OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"),
|
||||
OPT_BOOLEAN(0, "backup", &opts.backup, "create .orig files before modification"),
|
||||
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, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"),
|
||||
OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"),
|
||||
OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"),
|
||||
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
|
@ -79,7 +114,59 @@ int cmd_parse_options(int argc, const char **argv, const char * const usage[])
|
|||
return argc;
|
||||
}
|
||||
|
||||
int cmd_check(int argc, const char **argv)
|
||||
static bool opts_valid(void)
|
||||
{
|
||||
if (opts.hack_jump_label ||
|
||||
opts.hack_noinstr ||
|
||||
opts.ibt ||
|
||||
opts.mcount ||
|
||||
opts.noinstr ||
|
||||
opts.orc ||
|
||||
opts.retpoline ||
|
||||
opts.sls ||
|
||||
opts.stackval ||
|
||||
opts.static_call ||
|
||||
opts.uaccess) {
|
||||
if (opts.dump_orc) {
|
||||
ERROR("--dump can't be combined with other options");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (opts.dump_orc)
|
||||
return true;
|
||||
|
||||
ERROR("At least one command required");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool link_opts_valid(struct objtool_file *file)
|
||||
{
|
||||
if (opts.link)
|
||||
return true;
|
||||
|
||||
if (has_multiple_files(file->elf)) {
|
||||
ERROR("Linked object detected, forcing --link");
|
||||
opts.link = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (opts.noinstr) {
|
||||
ERROR("--noinstr requires --link");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.ibt) {
|
||||
ERROR("--ibt requires --link");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int objtool_run(int argc, const char **argv)
|
||||
{
|
||||
const char *objname;
|
||||
struct objtool_file *file;
|
||||
|
@ -88,10 +175,19 @@ int cmd_check(int argc, const char **argv)
|
|||
argc = cmd_parse_options(argc, argv, check_usage);
|
||||
objname = argv[0];
|
||||
|
||||
if (!opts_valid())
|
||||
return 1;
|
||||
|
||||
if (opts.dump_orc)
|
||||
return orc_dump(objname);
|
||||
|
||||
file = objtool_open_read(objname);
|
||||
if (!file)
|
||||
return 1;
|
||||
|
||||
if (!link_opts_valid(file))
|
||||
return 1;
|
||||
|
||||
ret = check(file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
|
@ -1,73 +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 <objtool/builtin.h>
|
||||
#include <objtool/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)) {
|
||||
struct objtool_file *file;
|
||||
int ret;
|
||||
|
||||
argc = cmd_parse_options(argc, argv, orc_usage);
|
||||
objname = argv[0];
|
||||
|
||||
file = objtool_open_read(objname);
|
||||
if (!file)
|
||||
return 1;
|
||||
|
||||
ret = check(file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (list_empty(&file->insn_list))
|
||||
return 0;
|
||||
|
||||
ret = orc_create(file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!file->elf->changed)
|
||||
return 0;
|
||||
|
||||
return elf_write(file->elf);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <arch/elf.h>
|
||||
|
@ -263,7 +264,8 @@ 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);
|
||||
|
@ -273,7 +275,7 @@ static void init_insn_state(struct insn_state *state, struct section *sec)
|
|||
* not correctly determine insn->call_dest->sec (external symbols do
|
||||
* not have a section).
|
||||
*/
|
||||
if (vmlinux && noinstr && sec)
|
||||
if (opts.link && opts.noinstr && sec)
|
||||
state->noinstr = sec->noinstr;
|
||||
}
|
||||
|
||||
|
@ -339,7 +341,7 @@ static void *cfi_hash_alloc(unsigned long size)
|
|||
if (cfi_hash == (void *)-1L) {
|
||||
WARN("mmap fail cfi_hash");
|
||||
cfi_hash = NULL;
|
||||
} else if (stats) {
|
||||
} else if (opts.stats) {
|
||||
printf("cfi_bits: %d\n", cfi_bits);
|
||||
}
|
||||
|
||||
|
@ -434,7 +436,7 @@ static int decode_instructions(struct objtool_file *file)
|
|||
}
|
||||
}
|
||||
|
||||
if (stats)
|
||||
if (opts.stats)
|
||||
printf("nr_insns: %lu\n", nr_insns);
|
||||
|
||||
return 0;
|
||||
|
@ -497,7 +499,7 @@ static int init_pv_ops(struct objtool_file *file)
|
|||
struct symbol *sym;
|
||||
int idx, nr;
|
||||
|
||||
if (!noinstr)
|
||||
if (!opts.noinstr)
|
||||
return 0;
|
||||
|
||||
file->pv_ops = NULL;
|
||||
|
@ -560,12 +562,12 @@ static int add_dead_ends(struct objtool_file *file)
|
|||
else if (reloc->addend == reloc->sym->sec->sh.sh_size) {
|
||||
insn = find_last_insn(file, reloc->sym->sec);
|
||||
if (!insn) {
|
||||
WARN("can't find unreachable insn at %s+0x%lx",
|
||||
WARN("can't find unreachable insn at %s+0x%" PRIx64,
|
||||
reloc->sym->sec->name, reloc->addend);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
WARN("can't find unreachable insn at %s+0x%lx",
|
||||
WARN("can't find unreachable insn at %s+0x%" PRIx64,
|
||||
reloc->sym->sec->name, reloc->addend);
|
||||
return -1;
|
||||
}
|
||||
|
@ -595,12 +597,12 @@ static int add_dead_ends(struct objtool_file *file)
|
|||
else if (reloc->addend == reloc->sym->sec->sh.sh_size) {
|
||||
insn = find_last_insn(file, reloc->sym->sec);
|
||||
if (!insn) {
|
||||
WARN("can't find reachable insn at %s+0x%lx",
|
||||
WARN("can't find reachable insn at %s+0x%" PRIx64,
|
||||
reloc->sym->sec->name, reloc->addend);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
WARN("can't find reachable insn at %s+0x%lx",
|
||||
WARN("can't find reachable insn at %s+0x%" PRIx64,
|
||||
reloc->sym->sec->name, reloc->addend);
|
||||
return -1;
|
||||
}
|
||||
|
@ -668,7 +670,7 @@ static int create_static_call_sections(struct objtool_file *file)
|
|||
|
||||
key_sym = find_symbol_by_name(file->elf, tmp);
|
||||
if (!key_sym) {
|
||||
if (!module) {
|
||||
if (!opts.module) {
|
||||
WARN("static_call: can't find static_call_key symbol: %s", tmp);
|
||||
return -1;
|
||||
}
|
||||
|
@ -761,7 +763,7 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file)
|
|||
list_for_each_entry(insn, &file->endbr_list, call_node)
|
||||
idx++;
|
||||
|
||||
if (stats) {
|
||||
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);
|
||||
|
@ -1028,7 +1030,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++) {
|
||||
|
@ -1144,7 +1146,7 @@ static void annotate_call_site(struct objtool_file *file,
|
|||
* attribute so they need a little help, NOP out any such calls from
|
||||
* noinstr text.
|
||||
*/
|
||||
if (insn->sec->noinstr && sym->profiling_func) {
|
||||
if (opts.hack_noinstr && insn->sec->noinstr && sym->profiling_func) {
|
||||
if (reloc) {
|
||||
reloc->type = R_NONE;
|
||||
elf_write_reloc(file->elf, reloc);
|
||||
|
@ -1170,7 +1172,7 @@ static void annotate_call_site(struct objtool_file *file,
|
|||
return;
|
||||
}
|
||||
|
||||
if (mcount && sym->fentry) {
|
||||
if (opts.mcount && sym->fentry) {
|
||||
if (sibling)
|
||||
WARN_FUNC("Tail call to __fentry__ !?!?", insn->sec, insn->offset);
|
||||
|
||||
|
@ -1256,7 +1258,7 @@ static bool is_first_func_insn(struct objtool_file *file, struct instruction *in
|
|||
if (insn->offset == insn->func->offset)
|
||||
return true;
|
||||
|
||||
if (ibt) {
|
||||
if (opts.ibt) {
|
||||
struct instruction *prev = prev_insn_same_sym(file, insn);
|
||||
|
||||
if (prev && prev->type == INSN_ENDBR &&
|
||||
|
@ -1592,7 +1594,7 @@ static int handle_jump_alt(struct objtool_file *file,
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (special_alt->key_addend & 2) {
|
||||
if (opts.hack_jump_label && special_alt->key_addend & 2) {
|
||||
struct reloc *reloc = insn_reloc(file, orig_insn);
|
||||
|
||||
if (reloc) {
|
||||
|
@ -1699,7 +1701,7 @@ static int add_special_section_alts(struct objtool_file *file)
|
|||
free(special_alt);
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
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);
|
||||
|
@ -1945,7 +1947,7 @@ static int read_unwind_hints(struct objtool_file *file)
|
|||
|
||||
insn->hint = true;
|
||||
|
||||
if (ibt && hint->type == UNWIND_HINT_TYPE_REGS_PARTIAL) {
|
||||
if (opts.ibt && hint->type == UNWIND_HINT_TYPE_REGS_PARTIAL) {
|
||||
struct symbol *sym = find_symbol_by_offset(insn->sec, insn->offset);
|
||||
|
||||
if (sym && sym->bind == STB_GLOBAL &&
|
||||
|
@ -2806,7 +2808,7 @@ static int update_cfi_state(struct instruction *insn,
|
|||
}
|
||||
|
||||
/* 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 && op->src.reg == CFI_BP &&
|
||||
cfa->base != CFI_BP)
|
||||
cfi->bp_scratch = true;
|
||||
break;
|
||||
|
@ -3182,114 +3184,6 @@ static struct instruction *next_insn_to_validate(struct objtool_file *file,
|
|||
return next_insn_same_sec(file, insn);
|
||||
}
|
||||
|
||||
static struct instruction *
|
||||
validate_ibt_reloc(struct objtool_file *file, struct reloc *reloc)
|
||||
{
|
||||
struct instruction *dest;
|
||||
struct section *sec;
|
||||
unsigned long off;
|
||||
|
||||
sec = reloc->sym->sec;
|
||||
off = reloc->sym->offset;
|
||||
|
||||
if ((reloc->sec->base->sh.sh_flags & SHF_EXECINSTR) &&
|
||||
(reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32))
|
||||
off += arch_dest_reloc_offset(reloc->addend);
|
||||
else
|
||||
off += reloc->addend;
|
||||
|
||||
dest = find_insn(file, sec, off);
|
||||
if (!dest)
|
||||
return NULL;
|
||||
|
||||
if (dest->type == INSN_ENDBR) {
|
||||
if (!list_empty(&dest->call_node))
|
||||
list_del_init(&dest->call_node);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (reloc->sym->static_call_tramp)
|
||||
return NULL;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
static void warn_noendbr(const char *msg, struct section *sec, unsigned long offset,
|
||||
struct instruction *dest)
|
||||
{
|
||||
WARN_FUNC("%srelocation to !ENDBR: %s", sec, offset, msg,
|
||||
offstr(dest->sec, dest->offset));
|
||||
}
|
||||
|
||||
static void validate_ibt_dest(struct objtool_file *file, struct instruction *insn,
|
||||
struct instruction *dest)
|
||||
{
|
||||
if (dest->func && dest->func == insn->func) {
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
if (dest->noendbr)
|
||||
return;
|
||||
|
||||
warn_noendbr("", insn->sec, insn->offset, dest);
|
||||
}
|
||||
|
||||
static void validate_ibt_insn(struct objtool_file *file, struct instruction *insn)
|
||||
{
|
||||
struct instruction *dest;
|
||||
struct reloc *reloc;
|
||||
|
||||
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:
|
||||
/*
|
||||
* We're looking for code references setting up indirect code
|
||||
* flow. As such, ignore direct code flow and the actual
|
||||
* dynamic branches.
|
||||
*/
|
||||
return;
|
||||
|
||||
case INSN_NOP:
|
||||
/*
|
||||
* handle_group_alt() will create INSN_NOP instruction that
|
||||
* don't belong to any section, ignore all NOP since they won't
|
||||
* carry a (useful) relocation anyway.
|
||||
*/
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (reloc = insn_reloc(file, insn);
|
||||
reloc;
|
||||
reloc = find_reloc_by_dest_range(file->elf, insn->sec,
|
||||
reloc->offset + 1,
|
||||
(insn->offset + insn->len) - (reloc->offset + 1))) {
|
||||
dest = validate_ibt_reloc(file, reloc);
|
||||
if (dest)
|
||||
validate_ibt_dest(file, insn, dest);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Follow the branch starting at the given instruction, and recursively follow
|
||||
* any other branches (jumps). Meanwhile, track the frame pointer state at
|
||||
|
@ -3363,7 +3257,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
|
|||
|
||||
ret = validate_branch(file, func, alt->insn, state);
|
||||
if (ret) {
|
||||
if (backtrace)
|
||||
if (opts.backtrace)
|
||||
BT_FUNC("(alt)", insn);
|
||||
return ret;
|
||||
}
|
||||
|
@ -3379,11 +3273,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
|
|||
switch (insn->type) {
|
||||
|
||||
case INSN_RETURN:
|
||||
if (sls && !insn->retpoline_safe &&
|
||||
next_insn && next_insn->type != INSN_TRAP) {
|
||||
WARN_FUNC("missing int3 after ret",
|
||||
insn->sec, insn->offset);
|
||||
}
|
||||
return validate_return(func, insn, &state);
|
||||
|
||||
case INSN_CALL:
|
||||
|
@ -3392,7 +3281,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!no_fp && func && !is_fentry_call(insn) &&
|
||||
if (opts.stackval && func && !is_fentry_call(insn) &&
|
||||
!has_valid_stack_frame(&state)) {
|
||||
WARN_FUNC("call without frame pointer save/setup",
|
||||
sec, insn->offset);
|
||||
|
@ -3415,7 +3304,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)
|
||||
if (opts.backtrace)
|
||||
BT_FUNC("(branch)", insn);
|
||||
return ret;
|
||||
}
|
||||
|
@ -3427,13 +3316,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
|
|||
break;
|
||||
|
||||
case INSN_JUMP_DYNAMIC:
|
||||
if (sls && !insn->retpoline_safe &&
|
||||
next_insn && next_insn->type != INSN_TRAP) {
|
||||
WARN_FUNC("missing int3 after indirect jump",
|
||||
insn->sec, insn->offset);
|
||||
}
|
||||
|
||||
/* fallthrough */
|
||||
case INSN_JUMP_DYNAMIC_CONDITIONAL:
|
||||
if (is_sibling_call(insn)) {
|
||||
ret = validate_sibling_call(file, insn, &state);
|
||||
|
@ -3499,9 +3381,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
|
|||
break;
|
||||
}
|
||||
|
||||
if (ibt)
|
||||
validate_ibt_insn(file, insn);
|
||||
|
||||
if (insn->dead_end)
|
||||
return 0;
|
||||
|
||||
|
@ -3528,7 +3407,7 @@ static int validate_unwind_hints(struct objtool_file *file, struct section *sec)
|
|||
if (!file->hints)
|
||||
return 0;
|
||||
|
||||
init_insn_state(&state, sec);
|
||||
init_insn_state(file, &state, sec);
|
||||
|
||||
if (sec) {
|
||||
insn = find_insn(file, sec, 0);
|
||||
|
@ -3541,7 +3420,7 @@ static int validate_unwind_hints(struct objtool_file *file, struct section *sec)
|
|||
while (&insn->list != &file->insn_list && (!sec || insn->sec == sec)) {
|
||||
if (insn->hint && !insn->visited && !insn->ignore) {
|
||||
ret = validate_branch(file, insn->func, insn, state);
|
||||
if (ret && backtrace)
|
||||
if (ret && opts.backtrace)
|
||||
BT_FUNC("<=== (hint)", insn);
|
||||
warnings += ret;
|
||||
}
|
||||
|
@ -3571,7 +3450,7 @@ static int validate_retpoline(struct objtool_file *file)
|
|||
* loaded late, they very much do need retpoline in their
|
||||
* .init.text
|
||||
*/
|
||||
if (!strcmp(insn->sec->name, ".init.text") && !module)
|
||||
if (!strcmp(insn->sec->name, ".init.text") && !opts.module)
|
||||
continue;
|
||||
|
||||
WARN_FUNC("indirect %s found in RETPOLINE build",
|
||||
|
@ -3614,14 +3493,14 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio
|
|||
return true;
|
||||
|
||||
/*
|
||||
* Whole archive runs might encounder dead code from weak symbols.
|
||||
* 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 (!insn->func && lto) {
|
||||
if (opts.link && !insn->func) {
|
||||
int size = find_symbol_hole_containing(insn->sec, insn->offset);
|
||||
unsigned long end = insn->offset + size;
|
||||
|
||||
|
@ -3728,7 +3607,7 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
|
|||
state->uaccess = sym->uaccess_safe;
|
||||
|
||||
ret = validate_branch(file, insn->func, insn, *state);
|
||||
if (ret && backtrace)
|
||||
if (ret && opts.backtrace)
|
||||
BT_FUNC("<=== (sym)", insn);
|
||||
return ret;
|
||||
}
|
||||
|
@ -3743,7 +3622,7 @@ static int validate_section(struct objtool_file *file, struct section *sec)
|
|||
if (func->type != STT_FUNC)
|
||||
continue;
|
||||
|
||||
init_insn_state(&state, sec);
|
||||
init_insn_state(file, &state, sec);
|
||||
set_func_state(&state.cfi);
|
||||
|
||||
warnings += validate_symbol(file, sec, func, &state);
|
||||
|
@ -3752,7 +3631,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;
|
||||
|
@ -3787,48 +3666,208 @@ static int validate_functions(struct objtool_file *file)
|
|||
return warnings;
|
||||
}
|
||||
|
||||
static void mark_endbr_used(struct instruction *insn)
|
||||
{
|
||||
if (!list_empty(&insn->call_node))
|
||||
list_del_init(&insn->call_node);
|
||||
}
|
||||
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (reloc = insn_reloc(file, insn);
|
||||
reloc;
|
||||
reloc = find_reloc_by_dest_range(file->elf, insn->sec,
|
||||
reloc->offset + 1,
|
||||
(insn->offset + insn->len) - (reloc->offset + 1))) {
|
||||
|
||||
/*
|
||||
* static_call_update() references the trampoline, which
|
||||
* doesn't have (or need) ENDBR. Skip warning in that case.
|
||||
*/
|
||||
if (reloc->sym->static_call_tramp)
|
||||
continue;
|
||||
|
||||
off = reloc->sym->offset;
|
||||
if (reloc->type == R_X86_64_PC32 || reloc->type == R_X86_64_PLT32)
|
||||
off += arch_dest_reloc_offset(reloc->addend);
|
||||
else
|
||||
off += reloc->addend;
|
||||
|
||||
dest = find_insn(file, reloc->sym->sec, off);
|
||||
if (!dest)
|
||||
continue;
|
||||
|
||||
if (dest->type == INSN_ENDBR) {
|
||||
mark_endbr_used(dest);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dest->func && dest->func == insn->func) {
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dest->noendbr)
|
||||
continue;
|
||||
|
||||
WARN_FUNC("relocation to !ENDBR: %s",
|
||||
insn->sec, insn->offset,
|
||||
offstr(dest->sec, dest->offset));
|
||||
|
||||
warnings++;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!dest)
|
||||
return 0;
|
||||
|
||||
if (dest->type == INSN_ENDBR) {
|
||||
mark_endbr_used(dest);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (dest->noendbr)
|
||||
return 0;
|
||||
|
||||
WARN_FUNC("data relocation to !ENDBR: %s",
|
||||
reloc->sec->base, reloc->offset,
|
||||
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) {
|
||||
bool is_data;
|
||||
|
||||
/* already done in validate_branch() */
|
||||
/* Already done by validate_ibt_insn() */
|
||||
if (sec->sh.sh_flags & SHF_EXECINSTR)
|
||||
continue;
|
||||
|
||||
if (!sec->reloc)
|
||||
continue;
|
||||
|
||||
if (!strncmp(sec->name, ".orc", 4))
|
||||
/*
|
||||
* These sections can reference text addresses, but not with
|
||||
* the intent to indirect branch to them.
|
||||
*/
|
||||
if (!strncmp(sec->name, ".discard", 8) ||
|
||||
!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, "__tracepoints"))
|
||||
continue;
|
||||
|
||||
if (!strncmp(sec->name, ".discard", 8))
|
||||
list_for_each_entry(reloc, &sec->reloc->reloc_list, list)
|
||||
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;
|
||||
|
||||
if (!strncmp(sec->name, ".debug", 6))
|
||||
continue;
|
||||
switch (insn->type) {
|
||||
case INSN_RETURN:
|
||||
if (!next_insn || next_insn->type != INSN_TRAP) {
|
||||
WARN_FUNC("missing int3 after ret",
|
||||
insn->sec, insn->offset);
|
||||
warnings++;
|
||||
}
|
||||
|
||||
if (!strcmp(sec->name, "_error_injection_whitelist"))
|
||||
continue;
|
||||
|
||||
if (!strcmp(sec->name, "_kprobe_blacklist"))
|
||||
continue;
|
||||
|
||||
is_data = strstr(sec->name, ".data") || strstr(sec->name, ".rodata");
|
||||
|
||||
list_for_each_entry(reloc, &sec->reloc->reloc_list, list) {
|
||||
struct instruction *dest;
|
||||
|
||||
dest = validate_ibt_reloc(file, reloc);
|
||||
if (is_data && dest && !dest->noendbr)
|
||||
warn_noendbr("data ", sec, reloc->offset, dest);
|
||||
break;
|
||||
case INSN_JUMP_DYNAMIC:
|
||||
if (!next_insn || next_insn->type != INSN_TRAP) {
|
||||
WARN_FUNC("missing int3 after indirect jump",
|
||||
insn->sec, insn->offset);
|
||||
warnings++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return warnings;
|
||||
}
|
||||
|
||||
static int validate_reachable_instructions(struct objtool_file *file)
|
||||
|
@ -3853,16 +3892,6 @@ int check(struct objtool_file *file)
|
|||
{
|
||||
int ret, warnings = 0;
|
||||
|
||||
if (lto && !(vmlinux || module)) {
|
||||
fprintf(stderr, "--lto requires: --vmlinux or --module\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ibt && !lto) {
|
||||
fprintf(stderr, "--ibt requires: --lto\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
arch_initial_func_cfi_state(&initial_func_cfi);
|
||||
init_cfi_state(&init_cfi);
|
||||
init_cfi_state(&func_cfi);
|
||||
|
@ -3883,73 +3912,89 @@ int check(struct objtool_file *file)
|
|||
if (list_empty(&file->insn_list))
|
||||
goto out;
|
||||
|
||||
if (vmlinux && !lto) {
|
||||
ret = validate_vmlinux_functions(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
warnings += ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (retpoline) {
|
||||
if (opts.retpoline) {
|
||||
ret = validate_retpoline(file);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
ret = validate_functions(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
if (opts.stackval || opts.orc || opts.uaccess) {
|
||||
ret = validate_functions(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
|
||||
ret = validate_unwind_hints(file, NULL);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
ret = validate_unwind_hints(file, NULL);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
|
||||
if (ibt) {
|
||||
if (!warnings) {
|
||||
ret = validate_reachable_instructions(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
} else if (opts.noinstr) {
|
||||
ret = validate_noinstr_sections(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
if (opts.ibt) {
|
||||
ret = validate_ibt(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
if (!warnings) {
|
||||
ret = validate_reachable_instructions(file);
|
||||
if (opts.sls) {
|
||||
ret = validate_sls(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
ret = create_static_call_sections(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
if (opts.static_call) {
|
||||
ret = create_static_call_sections(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
if (retpoline) {
|
||||
if (opts.retpoline) {
|
||||
ret = create_retpoline_sites_sections(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
if (mcount) {
|
||||
if (opts.mcount) {
|
||||
ret = create_mcount_loc_sections(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
if (ibt) {
|
||||
if (opts.ibt) {
|
||||
ret = create_ibt_endbr_seal_sections(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
if (opts.orc && !list_empty(&file->insn_list)) {
|
||||
ret = orc_create(file);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
warnings += ret;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
|
|
@ -355,7 +355,7 @@ static int read_sections(struct elf *elf)
|
|||
elf_hash_add(section_name, &sec->name_hash, str_hash(sec->name));
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
if (opts.stats) {
|
||||
printf("nr_sections: %lu\n", (unsigned long)sections_nr);
|
||||
printf("section_bits: %d\n", elf->section_bits);
|
||||
}
|
||||
|
@ -374,9 +374,15 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym)
|
|||
struct list_head *entry;
|
||||
struct rb_node *pnode;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -435,8 +441,6 @@ static int read_symbols(struct elf *elf)
|
|||
return -1;
|
||||
}
|
||||
memset(sym, 0, sizeof(*sym));
|
||||
INIT_LIST_HEAD(&sym->pv_target);
|
||||
sym->alias = sym;
|
||||
|
||||
sym->idx = i;
|
||||
|
||||
|
@ -475,7 +479,7 @@ static int read_symbols(struct elf *elf)
|
|||
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);
|
||||
}
|
||||
|
@ -546,7 +550,7 @@ static struct section *elf_create_reloc_section(struct elf *elf,
|
|||
int reltype);
|
||||
|
||||
int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset,
|
||||
unsigned int type, struct symbol *sym, long addend)
|
||||
unsigned int type, struct symbol *sym, s64 addend)
|
||||
{
|
||||
struct reloc *reloc;
|
||||
|
||||
|
@ -600,24 +604,21 @@ static void elf_dirty_reloc_sym(struct elf *elf, struct symbol *sym)
|
|||
}
|
||||
|
||||
/*
|
||||
* Move the first global symbol, as per sh_info, into a new, higher symbol
|
||||
* index. This fees up the shndx for a new local symbol.
|
||||
* 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_move_global_symbol(struct elf *elf, struct section *symtab,
|
||||
struct section *symtab_shndx)
|
||||
static int elf_update_symbol(struct elf *elf, struct section *symtab,
|
||||
struct section *symtab_shndx, struct symbol *sym)
|
||||
{
|
||||
Elf_Data *data, *shndx_data = NULL;
|
||||
Elf32_Word first_non_local;
|
||||
struct symbol *sym;
|
||||
Elf_Scn *s;
|
||||
|
||||
first_non_local = symtab->sh.sh_info;
|
||||
|
||||
sym = find_symbol_by_index(elf, first_non_local);
|
||||
if (!sym) {
|
||||
WARN("no non-local symbols !?");
|
||||
return first_non_local;
|
||||
}
|
||||
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;
|
||||
|
||||
s = elf_getscn(elf->elf, symtab->idx);
|
||||
if (!s) {
|
||||
|
@ -625,79 +626,124 @@ static int elf_move_global_symbol(struct elf *elf, struct section *symtab,
|
|||
return -1;
|
||||
}
|
||||
|
||||
data = elf_newdata(s);
|
||||
if (!data) {
|
||||
WARN_ELF("elf_newdata");
|
||||
return -1;
|
||||
}
|
||||
|
||||
data->d_buf = &sym->sym;
|
||||
data->d_size = sizeof(sym->sym);
|
||||
data->d_align = 1;
|
||||
data->d_type = ELF_T_SYM;
|
||||
|
||||
sym->idx = symtab->sh.sh_size / sizeof(sym->sym);
|
||||
elf_dirty_reloc_sym(elf, sym);
|
||||
|
||||
symtab->sh.sh_info += 1;
|
||||
symtab->sh.sh_size += data->d_size;
|
||||
symtab->changed = true;
|
||||
|
||||
if (symtab_shndx) {
|
||||
s = elf_getscn(elf->elf, symtab_shndx->idx);
|
||||
if (!s) {
|
||||
t = elf_getscn(elf->elf, symtab_shndx->idx);
|
||||
if (!t) {
|
||||
WARN_ELF("elf_getscn");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
shndx_data = elf_newdata(s);
|
||||
if (!shndx_data) {
|
||||
WARN_ELF("elf_newshndx_data");
|
||||
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) {
|
||||
void *buf;
|
||||
|
||||
if (idx) {
|
||||
/* we don't do holes in symbol tables */
|
||||
WARN("index out of range");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 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(1, entsize);
|
||||
if (!buf) {
|
||||
WARN("malloc");
|
||||
return -1;
|
||||
}
|
||||
|
||||
symtab_data->d_buf = buf;
|
||||
symtab_data->d_size = entsize;
|
||||
symtab_data->d_align = 1;
|
||||
symtab_data->d_type = ELF_T_SYM;
|
||||
|
||||
symtab->sh.sh_size += entsize;
|
||||
symtab->changed = true;
|
||||
|
||||
if (t) {
|
||||
shndx_data->d_buf = &sym->sec->idx;
|
||||
shndx_data->d_size = sizeof(Elf32_Word);
|
||||
shndx_data->d_align = sizeof(Elf32_Word);
|
||||
shndx_data->d_type = ELF_T_WORD;
|
||||
|
||||
symtab_shndx->sh.sh_size += sizeof(Elf32_Word);
|
||||
symtab_shndx->changed = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* empty blocks should not happen */
|
||||
if (!symtab_data->d_size) {
|
||||
WARN("zero size data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
shndx_data->d_buf = &sym->sec->idx;
|
||||
shndx_data->d_size = sizeof(Elf32_Word);
|
||||
shndx_data->d_align = 4;
|
||||
shndx_data->d_type = ELF_T_WORD;
|
||||
/* is this the right block? */
|
||||
max_idx = symtab_data->d_size / entsize;
|
||||
if (idx < max_idx)
|
||||
break;
|
||||
|
||||
symtab_shndx->sh.sh_size += 4;
|
||||
symtab_shndx->changed = true;
|
||||
/* adjust index and try again */
|
||||
idx -= max_idx;
|
||||
}
|
||||
|
||||
return first_non_local;
|
||||
/* something went side-ways */
|
||||
if (idx < 0) {
|
||||
WARN("negative index");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* setup extended section index magic and write the symbol */
|
||||
if (shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) {
|
||||
sym->sym.st_shndx = shndx;
|
||||
if (!shndx_data)
|
||||
shndx = 0;
|
||||
} else {
|
||||
sym->sym.st_shndx = SHN_XINDEX;
|
||||
if (!shndx_data) {
|
||||
WARN("no .symtab_shndx");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gelf_update_symshndx(symtab_data, shndx_data, idx, &sym->sym, shndx)) {
|
||||
WARN_ELF("gelf_update_symshndx");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct symbol *
|
||||
elf_create_section_symbol(struct elf *elf, struct section *sec)
|
||||
{
|
||||
struct section *symtab, *symtab_shndx;
|
||||
Elf_Data *shndx_data = NULL;
|
||||
struct symbol *sym;
|
||||
Elf32_Word shndx;
|
||||
Elf32_Word first_non_local, new_idx;
|
||||
struct symbol *sym, *old;
|
||||
|
||||
symtab = find_section_by_name(elf, ".symtab");
|
||||
if (symtab) {
|
||||
symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
|
||||
if (symtab_shndx)
|
||||
shndx_data = symtab_shndx->data;
|
||||
} else {
|
||||
WARN("no .symtab");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sym = malloc(sizeof(*sym));
|
||||
sym = calloc(1, sizeof(*sym));
|
||||
if (!sym) {
|
||||
perror("malloc");
|
||||
return NULL;
|
||||
}
|
||||
memset(sym, 0, sizeof(*sym));
|
||||
|
||||
sym->idx = elf_move_global_symbol(elf, symtab, symtab_shndx);
|
||||
if (sym->idx < 0) {
|
||||
WARN("elf_move_global_symbol");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sym->name = sec->name;
|
||||
sym->sec = sec;
|
||||
|
@ -707,24 +753,41 @@ elf_create_section_symbol(struct elf *elf, struct section *sec)
|
|||
// st_other 0
|
||||
// st_value 0
|
||||
// st_size 0
|
||||
shndx = sec->idx;
|
||||
if (shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) {
|
||||
sym->sym.st_shndx = shndx;
|
||||
if (!shndx_data)
|
||||
shndx = 0;
|
||||
} else {
|
||||
sym->sym.st_shndx = SHN_XINDEX;
|
||||
if (!shndx_data) {
|
||||
WARN("no .symtab_shndx");
|
||||
|
||||
/*
|
||||
* Move the first global symbol, as per sh_info, into a new, higher
|
||||
* symbol index. This fees up a spot for a new local symbol.
|
||||
*/
|
||||
first_non_local = symtab->sh.sh_info;
|
||||
new_idx = symtab->sh.sh_size / symtab->sh.sh_entsize;
|
||||
old = find_symbol_by_index(elf, first_non_local);
|
||||
if (old) {
|
||||
old->idx = new_idx;
|
||||
|
||||
hlist_del(&old->hash);
|
||||
elf_hash_add(symbol, &old->hash, old->idx);
|
||||
|
||||
elf_dirty_reloc_sym(elf, old);
|
||||
|
||||
if (elf_update_symbol(elf, symtab, symtab_shndx, old)) {
|
||||
WARN("elf_update_symbol move");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
new_idx = first_non_local;
|
||||
}
|
||||
|
||||
if (!gelf_update_symshndx(symtab->data, shndx_data, sym->idx, &sym->sym, shndx)) {
|
||||
WARN_ELF("gelf_update_symshndx");
|
||||
sym->idx = new_idx;
|
||||
if (elf_update_symbol(elf, symtab, symtab_shndx, sym)) {
|
||||
WARN("elf_update_symbol");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Either way, we added a LOCAL symbol.
|
||||
*/
|
||||
symtab->sh.sh_info += 1;
|
||||
|
||||
elf_add_symbol(elf, sym);
|
||||
|
||||
return sym;
|
||||
|
@ -843,7 +906,7 @@ static int read_relocs(struct elf *elf)
|
|||
tot_reloc += nr_reloc;
|
||||
}
|
||||
|
||||
if (stats) {
|
||||
if (opts.stats) {
|
||||
printf("max_reloc: %lu\n", max_reloc);
|
||||
printf("tot_reloc: %lu\n", tot_reloc);
|
||||
printf("reloc_bits: %d\n", elf->reloc_bits);
|
||||
|
@ -1222,7 +1285,7 @@ int elf_write(struct elf *elf)
|
|||
struct section *sec;
|
||||
Elf_Scn *s;
|
||||
|
||||
if (dryrun)
|
||||
if (opts.dryrun)
|
||||
return 0;
|
||||
|
||||
/* Update changed relocation sections and section headers: */
|
||||
|
|
|
@ -8,13 +8,37 @@
|
|||
#include <subcmd/parse-options.h>
|
||||
|
||||
extern const struct option check_options[];
|
||||
extern bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess, stats,
|
||||
lto, vmlinux, mcount, noinstr, backup, sls, dryrun,
|
||||
ibt;
|
||||
|
||||
struct opts {
|
||||
/* actions: */
|
||||
bool dump_orc;
|
||||
bool hack_jump_label;
|
||||
bool hack_noinstr;
|
||||
bool ibt;
|
||||
bool mcount;
|
||||
bool noinstr;
|
||||
bool orc;
|
||||
bool retpoline;
|
||||
bool sls;
|
||||
bool stackval;
|
||||
bool static_call;
|
||||
bool uaccess;
|
||||
|
||||
/* options: */
|
||||
bool backtrace;
|
||||
bool backup;
|
||||
bool dryrun;
|
||||
bool link;
|
||||
bool module;
|
||||
bool no_unreachable;
|
||||
bool sec_address;
|
||||
bool stats;
|
||||
};
|
||||
|
||||
extern struct opts opts;
|
||||
|
||||
extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]);
|
||||
|
||||
extern int cmd_check(int argc, const char **argv);
|
||||
extern int cmd_orc(int argc, const char **argv);
|
||||
extern int objtool_run(int argc, const char **argv);
|
||||
|
||||
#endif /* _BUILTIN_H */
|
||||
|
|
|
@ -73,7 +73,7 @@ struct reloc {
|
|||
struct symbol *sym;
|
||||
unsigned long offset;
|
||||
unsigned int type;
|
||||
long addend;
|
||||
s64 addend;
|
||||
int idx;
|
||||
bool jump_table_start;
|
||||
};
|
||||
|
@ -86,7 +86,7 @@ struct elf {
|
|||
int fd;
|
||||
bool changed;
|
||||
char *name;
|
||||
unsigned int text_size;
|
||||
unsigned int text_size, num_files;
|
||||
struct list_head sections;
|
||||
|
||||
int symbol_bits;
|
||||
|
@ -131,11 +131,21 @@ static inline u32 reloc_hash(struct reloc *reloc)
|
|||
return sec_offset_hash(reloc->sec, reloc->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;
|
||||
}
|
||||
|
||||
struct elf *elf_open_read(const char *name, int flags);
|
||||
struct section *elf_create_section(struct elf *elf, const char *name, unsigned int sh_flags, size_t entsize, int nr);
|
||||
|
||||
int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset,
|
||||
unsigned int type, struct symbol *sym, long addend);
|
||||
unsigned int type, struct symbol *sym, s64 addend);
|
||||
int elf_add_reloc_to_insn(struct elf *elf, struct section *sec,
|
||||
unsigned long offset, unsigned int type,
|
||||
struct section *insn_sec, unsigned long insn_off);
|
||||
|
|
|
@ -11,34 +11,33 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <objtool/builtin.h>
|
||||
#include <objtool/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;
|
||||
bool is_text = (sec->sh.sh_flags & SHF_EXECINSTR);
|
||||
struct symbol *sym = NULL;
|
||||
char *str;
|
||||
int len;
|
||||
|
||||
func = find_func_containing(sec, offset);
|
||||
if (!func)
|
||||
func = find_symbol_containing(sec, offset);
|
||||
if (func) {
|
||||
name = func->name;
|
||||
name_off = offset - func->offset;
|
||||
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 {
|
||||
name = sec->name;
|
||||
name_off = offset;
|
||||
str = malloc(strlen(sec->name) + 20);
|
||||
sprintf(str, "%s+0x%lx", sec->name, 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,6 @@
|
|||
* 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>
|
||||
|
@ -26,20 +16,6 @@
|
|||
#include <objtool/objtool.h>
|
||||
#include <objtool/warn.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" },
|
||||
};
|
||||
|
||||
bool help;
|
||||
|
||||
const char *objname;
|
||||
|
@ -118,7 +94,7 @@ struct objtool_file *objtool_open_read(const char *_objname)
|
|||
if (!file.elf)
|
||||
return NULL;
|
||||
|
||||
if (backup && !objtool_create_backup(objname)) {
|
||||
if (opts.backup && !objtool_create_backup(objname)) {
|
||||
WARN("can't create backup file");
|
||||
return NULL;
|
||||
}
|
||||
|
@ -129,7 +105,7 @@ struct objtool_file *objtool_open_read(const char *_objname)
|
|||
INIT_LIST_HEAD(&file.static_call_list);
|
||||
INIT_LIST_HEAD(&file.mcount_loc_list);
|
||||
INIT_LIST_HEAD(&file.endbr_list);
|
||||
file.ignore_unreachables = no_unreachable;
|
||||
file.ignore_unreachables = opts.no_unreachable;
|
||||
file.hints = false;
|
||||
|
||||
return &file;
|
||||
|
@ -137,7 +113,7 @@ struct objtool_file *objtool_open_read(const char *_objname)
|
|||
|
||||
void objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func)
|
||||
{
|
||||
if (!noinstr)
|
||||
if (!opts.noinstr)
|
||||
return;
|
||||
|
||||
if (!f->pv_ops) {
|
||||
|
@ -161,70 +137,6 @@ void objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func)
|
|||
f->pv_ops[idx].clean = false;
|
||||
}
|
||||
|
||||
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 void handle_options(int *argc, const char ***argv)
|
||||
{
|
||||
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)--;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_internal_command(int argc, const char **argv)
|
||||
{
|
||||
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 (strcmp(p->name, cmd))
|
||||
continue;
|
||||
|
||||
ret = p->fn(argc, argv);
|
||||
|
||||
exit(ret);
|
||||
}
|
||||
|
||||
cmd_usage();
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
static const char *UNUSED = "OBJTOOL_NOT_IMPLEMENTED";
|
||||
|
@ -233,14 +145,7 @@ 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);
|
||||
objtool_run(argc, argv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -15,17 +15,12 @@
|
|||
return ENOSYS; \
|
||||
})
|
||||
|
||||
int __weak check(struct objtool_file *file)
|
||||
{
|
||||
UNSUPPORTED("check subcommand");
|
||||
}
|
||||
|
||||
int __weak orc_dump(const char *_objname)
|
||||
{
|
||||
UNSUPPORTED("orc");
|
||||
UNSUPPORTED("ORC");
|
||||
}
|
||||
|
||||
int __weak orc_create(struct objtool_file *file)
|
||||
{
|
||||
UNSUPPORTED("orc");
|
||||
UNSUPPORTED("ORC");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue