Merge branch 'core-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull core fixes from Thomas Gleixner:

 - A collection of objtool fixes which address recent fallout partially
   exposed by newer toolchains, clang, BPF and general code changes.

 - Force USER_DS for user stack traces

[ Note: the "objtool fixes" are not all to objtool itself, but for
  kernel code that triggers objtool warnings.

  Things like missing function size annotations, or code that confuses
  the unwinder etc.   - Linus]

* 'core-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (27 commits)
  objtool: Support conditional retpolines
  objtool: Convert insn type to enum
  objtool: Fix seg fault on bad switch table entry
  objtool: Support repeated uses of the same C jump table
  objtool: Refactor jump table code
  objtool: Refactor sibling call detection logic
  objtool: Do frame pointer check before dead end check
  objtool: Change dead_end_function() to return boolean
  objtool: Warn on zero-length functions
  objtool: Refactor function alias logic
  objtool: Track original function across branches
  objtool: Add mcsafe_handle_tail() to the uaccess safe list
  bpf: Disable GCC -fgcse optimization for ___bpf_prog_run()
  x86/uaccess: Remove redundant CLACs in getuser/putuser error paths
  x86/uaccess: Don't leak AC flag into fentry from mcsafe_handle_tail()
  x86/uaccess: Remove ELF function annotation from copy_user_handle_tail()
  x86/head/64: Annotate start_cpu0() as non-callable
  x86/entry: Fix thunk function ELF sizes
  x86/kvm: Don't call kvm_spurious_fault() from .fixup
  x86/kvm: Replace vmx_vmenter()'s call to kvm_spurious_fault() with UD2
  ...
This commit is contained in:
Linus Torvalds 2019-07-20 10:45:15 -07:00
commit e6023adc5c
22 changed files with 312 additions and 246 deletions

View File

@ -12,9 +12,7 @@
/* rdi: arg1 ... normal C conventions. rax is saved/restored. */ /* rdi: arg1 ... normal C conventions. rax is saved/restored. */
.macro THUNK name, func, put_ret_addr_in_rdi=0 .macro THUNK name, func, put_ret_addr_in_rdi=0
.globl \name ENTRY(\name)
.type \name, @function
\name:
pushq %rbp pushq %rbp
movq %rsp, %rbp movq %rsp, %rbp
@ -35,6 +33,7 @@
call \func call \func
jmp .L_restore jmp .L_restore
ENDPROC(\name)
_ASM_NOKPROBE(\name) _ASM_NOKPROBE(\name)
.endm .endm

View File

@ -1496,25 +1496,29 @@ enum {
#define kvm_arch_vcpu_memslots_id(vcpu) ((vcpu)->arch.hflags & HF_SMM_MASK ? 1 : 0) #define kvm_arch_vcpu_memslots_id(vcpu) ((vcpu)->arch.hflags & HF_SMM_MASK ? 1 : 0)
#define kvm_memslots_for_spte_role(kvm, role) __kvm_memslots(kvm, (role).smm) #define kvm_memslots_for_spte_role(kvm, role) __kvm_memslots(kvm, (role).smm)
asmlinkage void __noreturn kvm_spurious_fault(void);
/* /*
* Hardware virtualization extension instructions may fault if a * Hardware virtualization extension instructions may fault if a
* reboot turns off virtualization while processes are running. * reboot turns off virtualization while processes are running.
* Trap the fault and ignore the instruction if that happens. * Usually after catching the fault we just panic; during reboot
* instead the instruction is ignored.
*/ */
asmlinkage void kvm_spurious_fault(void); #define ____kvm_handle_fault_on_reboot(insn, cleanup_insn) \
"666: \n\t" \
#define ____kvm_handle_fault_on_reboot(insn, cleanup_insn) \ insn "\n\t" \
"666: " insn "\n\t" \ "jmp 668f \n\t" \
"668: \n\t" \ "667: \n\t" \
".pushsection .fixup, \"ax\" \n" \ "call kvm_spurious_fault \n\t" \
"667: \n\t" \ "668: \n\t" \
cleanup_insn "\n\t" \ ".pushsection .fixup, \"ax\" \n\t" \
"cmpb $0, kvm_rebooting \n\t" \ "700: \n\t" \
"jne 668b \n\t" \ cleanup_insn "\n\t" \
__ASM_SIZE(push) " $666b \n\t" \ "cmpb $0, kvm_rebooting\n\t" \
"jmp kvm_spurious_fault \n\t" \ "je 667b \n\t" \
".popsection \n\t" \ "jmp 668b \n\t" \
_ASM_EXTABLE(666b, 667b) ".popsection \n\t" \
_ASM_EXTABLE(666b, 700b)
#define __kvm_handle_fault_on_reboot(insn) \ #define __kvm_handle_fault_on_reboot(insn) \
____kvm_handle_fault_on_reboot(insn, "") ____kvm_handle_fault_on_reboot(insn, "")

View File

@ -746,6 +746,7 @@ bool __raw_callee_save___native_vcpu_is_preempted(long cpu);
PV_RESTORE_ALL_CALLER_REGS \ PV_RESTORE_ALL_CALLER_REGS \
FRAME_END \ FRAME_END \
"ret;" \ "ret;" \
".size " PV_THUNK_NAME(func) ", .-" PV_THUNK_NAME(func) ";" \
".popsection") ".popsection")
/* Get a reference to a callee-save function */ /* Get a reference to a callee-save function */

View File

@ -253,10 +253,10 @@ END(secondary_startup_64)
* start_secondary() via .Ljump_to_C_code. * start_secondary() via .Ljump_to_C_code.
*/ */
ENTRY(start_cpu0) ENTRY(start_cpu0)
movq initial_stack(%rip), %rsp
UNWIND_HINT_EMPTY UNWIND_HINT_EMPTY
movq initial_stack(%rip), %rsp
jmp .Ljump_to_C_code jmp .Ljump_to_C_code
ENDPROC(start_cpu0) END(start_cpu0)
#endif #endif
/* Both SMP bootup and ACPI suspend change these variables */ /* Both SMP bootup and ACPI suspend change these variables */

View File

@ -838,6 +838,7 @@ asm(
"cmpb $0, " __stringify(KVM_STEAL_TIME_preempted) "+steal_time(%rax);" "cmpb $0, " __stringify(KVM_STEAL_TIME_preempted) "+steal_time(%rax);"
"setne %al;" "setne %al;"
"ret;" "ret;"
".size __raw_callee_save___kvm_vcpu_is_preempted, .-__raw_callee_save___kvm_vcpu_is_preempted;"
".popsection"); ".popsection");
#endif #endif

View File

@ -312,29 +312,42 @@ static void invalidate_registers(struct x86_emulate_ctxt *ctxt)
static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *)); static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *));
#define FOP_FUNC(name) \ #define __FOP_FUNC(name) \
".align " __stringify(FASTOP_SIZE) " \n\t" \ ".align " __stringify(FASTOP_SIZE) " \n\t" \
".type " name ", @function \n\t" \ ".type " name ", @function \n\t" \
name ":\n\t" name ":\n\t"
#define FOP_RET "ret \n\t" #define FOP_FUNC(name) \
__FOP_FUNC(#name)
#define __FOP_RET(name) \
"ret \n\t" \
".size " name ", .-" name "\n\t"
#define FOP_RET(name) \
__FOP_RET(#name)
#define FOP_START(op) \ #define FOP_START(op) \
extern void em_##op(struct fastop *fake); \ extern void em_##op(struct fastop *fake); \
asm(".pushsection .text, \"ax\" \n\t" \ asm(".pushsection .text, \"ax\" \n\t" \
".global em_" #op " \n\t" \ ".global em_" #op " \n\t" \
FOP_FUNC("em_" #op) ".align " __stringify(FASTOP_SIZE) " \n\t" \
"em_" #op ":\n\t"
#define FOP_END \ #define FOP_END \
".popsection") ".popsection")
#define __FOPNOP(name) \
__FOP_FUNC(name) \
__FOP_RET(name)
#define FOPNOP() \ #define FOPNOP() \
FOP_FUNC(__stringify(__UNIQUE_ID(nop))) \ __FOPNOP(__stringify(__UNIQUE_ID(nop)))
FOP_RET
#define FOP1E(op, dst) \ #define FOP1E(op, dst) \
FOP_FUNC(#op "_" #dst) \ __FOP_FUNC(#op "_" #dst) \
"10: " #op " %" #dst " \n\t" FOP_RET "10: " #op " %" #dst " \n\t" \
__FOP_RET(#op "_" #dst)
#define FOP1EEX(op, dst) \ #define FOP1EEX(op, dst) \
FOP1E(op, dst) _ASM_EXTABLE(10b, kvm_fastop_exception) FOP1E(op, dst) _ASM_EXTABLE(10b, kvm_fastop_exception)
@ -366,8 +379,9 @@ static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *));
FOP_END FOP_END
#define FOP2E(op, dst, src) \ #define FOP2E(op, dst, src) \
FOP_FUNC(#op "_" #dst "_" #src) \ __FOP_FUNC(#op "_" #dst "_" #src) \
#op " %" #src ", %" #dst " \n\t" FOP_RET #op " %" #src ", %" #dst " \n\t" \
__FOP_RET(#op "_" #dst "_" #src)
#define FASTOP2(op) \ #define FASTOP2(op) \
FOP_START(op) \ FOP_START(op) \
@ -405,8 +419,9 @@ static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *));
FOP_END FOP_END
#define FOP3E(op, dst, src, src2) \ #define FOP3E(op, dst, src, src2) \
FOP_FUNC(#op "_" #dst "_" #src "_" #src2) \ __FOP_FUNC(#op "_" #dst "_" #src "_" #src2) \
#op " %" #src2 ", %" #src ", %" #dst " \n\t" FOP_RET #op " %" #src2 ", %" #src ", %" #dst " \n\t"\
__FOP_RET(#op "_" #dst "_" #src "_" #src2)
/* 3-operand, word-only, src2=cl */ /* 3-operand, word-only, src2=cl */
#define FASTOP3WCL(op) \ #define FASTOP3WCL(op) \
@ -423,7 +438,7 @@ static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *));
".type " #op ", @function \n\t" \ ".type " #op ", @function \n\t" \
#op ": \n\t" \ #op ": \n\t" \
#op " %al \n\t" \ #op " %al \n\t" \
FOP_RET __FOP_RET(#op)
asm(".pushsection .fixup, \"ax\"\n" asm(".pushsection .fixup, \"ax\"\n"
".global kvm_fastop_exception \n" ".global kvm_fastop_exception \n"
@ -449,7 +464,10 @@ FOP_SETCC(setle)
FOP_SETCC(setnle) FOP_SETCC(setnle)
FOP_END; FOP_END;
FOP_START(salc) "pushf; sbb %al, %al; popf \n\t" FOP_RET FOP_START(salc)
FOP_FUNC(salc)
"pushf; sbb %al, %al; popf \n\t"
FOP_RET(salc)
FOP_END; FOP_END;
/* /*

View File

@ -54,9 +54,9 @@ ENTRY(vmx_vmenter)
ret ret
3: cmpb $0, kvm_rebooting 3: cmpb $0, kvm_rebooting
jne 4f je 4f
call kvm_spurious_fault ret
4: ret 4: ud2
.pushsection .fixup, "ax" .pushsection .fixup, "ax"
5: jmp 3b 5: jmp 3b

View File

@ -239,7 +239,7 @@ copy_user_handle_tail:
ret ret
_ASM_EXTABLE_UA(1b, 2b) _ASM_EXTABLE_UA(1b, 2b)
ENDPROC(copy_user_handle_tail) END(copy_user_handle_tail)
/* /*
* copy_user_nocache - Uncached memory copy with exception handling * copy_user_nocache - Uncached memory copy with exception handling

View File

@ -115,29 +115,29 @@ ENDPROC(__get_user_8)
EXPORT_SYMBOL(__get_user_8) EXPORT_SYMBOL(__get_user_8)
bad_get_user_clac:
ASM_CLAC
bad_get_user: bad_get_user:
xor %edx,%edx xor %edx,%edx
mov $(-EFAULT),%_ASM_AX mov $(-EFAULT),%_ASM_AX
ASM_CLAC
ret ret
END(bad_get_user)
#ifdef CONFIG_X86_32 #ifdef CONFIG_X86_32
bad_get_user_8_clac:
ASM_CLAC
bad_get_user_8: bad_get_user_8:
xor %edx,%edx xor %edx,%edx
xor %ecx,%ecx xor %ecx,%ecx
mov $(-EFAULT),%_ASM_AX mov $(-EFAULT),%_ASM_AX
ASM_CLAC
ret ret
END(bad_get_user_8)
#endif #endif
_ASM_EXTABLE_UA(1b, bad_get_user) _ASM_EXTABLE_UA(1b, bad_get_user_clac)
_ASM_EXTABLE_UA(2b, bad_get_user) _ASM_EXTABLE_UA(2b, bad_get_user_clac)
_ASM_EXTABLE_UA(3b, bad_get_user) _ASM_EXTABLE_UA(3b, bad_get_user_clac)
#ifdef CONFIG_X86_64 #ifdef CONFIG_X86_64
_ASM_EXTABLE_UA(4b, bad_get_user) _ASM_EXTABLE_UA(4b, bad_get_user_clac)
#else #else
_ASM_EXTABLE_UA(4b, bad_get_user_8) _ASM_EXTABLE_UA(4b, bad_get_user_8_clac)
_ASM_EXTABLE_UA(5b, bad_get_user_8) _ASM_EXTABLE_UA(5b, bad_get_user_8_clac)
#endif #endif

View File

@ -32,8 +32,6 @@
*/ */
#define ENTER mov PER_CPU_VAR(current_task), %_ASM_BX #define ENTER mov PER_CPU_VAR(current_task), %_ASM_BX
#define EXIT ASM_CLAC ; \
ret
.text .text
ENTRY(__put_user_1) ENTRY(__put_user_1)
@ -43,7 +41,8 @@ ENTRY(__put_user_1)
ASM_STAC ASM_STAC
1: movb %al,(%_ASM_CX) 1: movb %al,(%_ASM_CX)
xor %eax,%eax xor %eax,%eax
EXIT ASM_CLAC
ret
ENDPROC(__put_user_1) ENDPROC(__put_user_1)
EXPORT_SYMBOL(__put_user_1) EXPORT_SYMBOL(__put_user_1)
@ -56,7 +55,8 @@ ENTRY(__put_user_2)
ASM_STAC ASM_STAC
2: movw %ax,(%_ASM_CX) 2: movw %ax,(%_ASM_CX)
xor %eax,%eax xor %eax,%eax
EXIT ASM_CLAC
ret
ENDPROC(__put_user_2) ENDPROC(__put_user_2)
EXPORT_SYMBOL(__put_user_2) EXPORT_SYMBOL(__put_user_2)
@ -69,7 +69,8 @@ ENTRY(__put_user_4)
ASM_STAC ASM_STAC
3: movl %eax,(%_ASM_CX) 3: movl %eax,(%_ASM_CX)
xor %eax,%eax xor %eax,%eax
EXIT ASM_CLAC
ret
ENDPROC(__put_user_4) ENDPROC(__put_user_4)
EXPORT_SYMBOL(__put_user_4) EXPORT_SYMBOL(__put_user_4)
@ -85,19 +86,21 @@ ENTRY(__put_user_8)
5: movl %edx,4(%_ASM_CX) 5: movl %edx,4(%_ASM_CX)
#endif #endif
xor %eax,%eax xor %eax,%eax
EXIT ASM_CLAC
RET
ENDPROC(__put_user_8) ENDPROC(__put_user_8)
EXPORT_SYMBOL(__put_user_8) EXPORT_SYMBOL(__put_user_8)
bad_put_user_clac:
ASM_CLAC
bad_put_user: bad_put_user:
movl $-EFAULT,%eax movl $-EFAULT,%eax
EXIT RET
END(bad_put_user)
_ASM_EXTABLE_UA(1b, bad_put_user) _ASM_EXTABLE_UA(1b, bad_put_user_clac)
_ASM_EXTABLE_UA(2b, bad_put_user) _ASM_EXTABLE_UA(2b, bad_put_user_clac)
_ASM_EXTABLE_UA(3b, bad_put_user) _ASM_EXTABLE_UA(3b, bad_put_user_clac)
_ASM_EXTABLE_UA(4b, bad_put_user) _ASM_EXTABLE_UA(4b, bad_put_user_clac)
#ifdef CONFIG_X86_32 #ifdef CONFIG_X86_32
_ASM_EXTABLE_UA(5b, bad_put_user) _ASM_EXTABLE_UA(5b, bad_put_user_clac)
#endif #endif

View File

@ -60,7 +60,7 @@ EXPORT_SYMBOL(clear_user);
* but reuse __memcpy_mcsafe in case a new read error is encountered. * but reuse __memcpy_mcsafe in case a new read error is encountered.
* clac() is handled in _copy_to_iter_mcsafe(). * clac() is handled in _copy_to_iter_mcsafe().
*/ */
__visible unsigned long __visible notrace unsigned long
mcsafe_handle_tail(char *to, char *from, unsigned len) mcsafe_handle_tail(char *to, char *from, unsigned len)
{ {
for (; len; --len, to++, from++) { for (; len; --len, to++, from++) {

View File

@ -170,3 +170,5 @@
#else #else
#define __diag_GCC_8(s) #define __diag_GCC_8(s)
#endif #endif
#define __no_fgcse __attribute__((optimize("-fno-gcse")))

View File

@ -116,9 +116,14 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
".pushsection .discard.unreachable\n\t" \ ".pushsection .discard.unreachable\n\t" \
".long 999b - .\n\t" \ ".long 999b - .\n\t" \
".popsection\n\t" ".popsection\n\t"
/* Annotate a C jump table to allow objtool to follow the code flow */
#define __annotate_jump_table __section(".rodata..c_jump_table")
#else #else
#define annotate_reachable() #define annotate_reachable()
#define annotate_unreachable() #define annotate_unreachable()
#define __annotate_jump_table
#endif #endif
#ifndef ASM_UNREACHABLE #ifndef ASM_UNREACHABLE

View File

@ -189,6 +189,10 @@ struct ftrace_likely_data {
#define asm_volatile_goto(x...) asm goto(x) #define asm_volatile_goto(x...) asm goto(x)
#endif #endif
#ifndef __no_fgcse
# define __no_fgcse
#endif
/* Are two types/vars the same type (ignoring qualifiers)? */ /* Are two types/vars the same type (ignoring qualifiers)? */
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

View File

@ -1295,11 +1295,11 @@ bool bpf_opcode_in_insntable(u8 code)
* *
* Decode and execute eBPF instructions. * Decode and execute eBPF instructions.
*/ */
static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn, u64 *stack) static u64 __no_fgcse ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn, u64 *stack)
{ {
#define BPF_INSN_2_LBL(x, y) [BPF_##x | BPF_##y] = &&x##_##y #define BPF_INSN_2_LBL(x, y) [BPF_##x | BPF_##y] = &&x##_##y
#define BPF_INSN_3_LBL(x, y, z) [BPF_##x | BPF_##y | BPF_##z] = &&x##_##y##_##z #define BPF_INSN_3_LBL(x, y, z) [BPF_##x | BPF_##y | BPF_##z] = &&x##_##y##_##z
static const void *jumptable[256] = { static const void * const jumptable[256] __annotate_jump_table = {
[0 ... 255] = &&default_label, [0 ... 255] = &&default_label,
/* Now overwrite non-defaults ... */ /* Now overwrite non-defaults ... */
BPF_INSN_MAP(BPF_INSN_2_LBL, BPF_INSN_3_LBL), BPF_INSN_MAP(BPF_INSN_2_LBL, BPF_INSN_3_LBL),
@ -1558,7 +1558,6 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn, u64 *stack)
BUG_ON(1); BUG_ON(1);
return 0; return 0;
} }
STACK_FRAME_NON_STANDARD(___bpf_prog_run); /* jump table */
#define PROG_NAME(stack_size) __bpf_prog_run##stack_size #define PROG_NAME(stack_size) __bpf_prog_run##stack_size
#define DEFINE_BPF_PROG_RUN(stack_size) \ #define DEFINE_BPF_PROG_RUN(stack_size) \

View File

@ -226,12 +226,17 @@ unsigned int stack_trace_save_user(unsigned long *store, unsigned int size)
.store = store, .store = store,
.size = size, .size = size,
}; };
mm_segment_t fs;
/* Trace user stack if not a kernel thread */ /* Trace user stack if not a kernel thread */
if (current->flags & PF_KTHREAD) if (current->flags & PF_KTHREAD)
return 0; return 0;
fs = get_fs();
set_fs(USER_DS);
arch_stack_walk_user(consume_entry, &c, task_pt_regs(current)); arch_stack_walk_user(consume_entry, &c, task_pt_regs(current));
set_fs(fs);
return c.len; return c.len;
} }
#endif #endif

View File

@ -11,22 +11,24 @@
#include "elf.h" #include "elf.h"
#include "cfi.h" #include "cfi.h"
#define INSN_JUMP_CONDITIONAL 1 enum insn_type {
#define INSN_JUMP_UNCONDITIONAL 2 INSN_JUMP_CONDITIONAL,
#define INSN_JUMP_DYNAMIC 3 INSN_JUMP_UNCONDITIONAL,
#define INSN_CALL 4 INSN_JUMP_DYNAMIC,
#define INSN_CALL_DYNAMIC 5 INSN_JUMP_DYNAMIC_CONDITIONAL,
#define INSN_RETURN 6 INSN_CALL,
#define INSN_CONTEXT_SWITCH 7 INSN_CALL_DYNAMIC,
#define INSN_STACK 8 INSN_RETURN,
#define INSN_BUG 9 INSN_CONTEXT_SWITCH,
#define INSN_NOP 10 INSN_STACK,
#define INSN_STAC 11 INSN_BUG,
#define INSN_CLAC 12 INSN_NOP,
#define INSN_STD 13 INSN_STAC,
#define INSN_CLD 14 INSN_CLAC,
#define INSN_OTHER 15 INSN_STD,
#define INSN_LAST INSN_OTHER INSN_CLD,
INSN_OTHER,
};
enum op_dest_type { enum op_dest_type {
OP_DEST_REG, OP_DEST_REG,
@ -68,7 +70,7 @@ void arch_initial_func_cfi_state(struct cfi_state *state);
int arch_decode_instruction(struct elf *elf, struct section *sec, int arch_decode_instruction(struct elf *elf, struct section *sec,
unsigned long offset, unsigned int maxlen, unsigned long offset, unsigned int maxlen,
unsigned int *len, unsigned char *type, unsigned int *len, enum insn_type *type,
unsigned long *immediate, struct stack_op *op); unsigned long *immediate, struct stack_op *op);
bool arch_callee_saved_reg(unsigned char reg); bool arch_callee_saved_reg(unsigned char reg);

View File

@ -68,7 +68,7 @@ bool arch_callee_saved_reg(unsigned char reg)
int arch_decode_instruction(struct elf *elf, struct section *sec, int arch_decode_instruction(struct elf *elf, struct section *sec,
unsigned long offset, unsigned int maxlen, unsigned long offset, unsigned int maxlen,
unsigned int *len, unsigned char *type, unsigned int *len, enum insn_type *type,
unsigned long *immediate, struct stack_op *op) unsigned long *immediate, struct stack_op *op)
{ {
struct insn insn; struct insn insn;

View File

@ -18,6 +18,8 @@
#define FAKE_JUMP_OFFSET -1 #define FAKE_JUMP_OFFSET -1
#define C_JUMP_TABLE_SECTION ".rodata..c_jump_table"
struct alternative { struct alternative {
struct list_head list; struct list_head list;
struct instruction *insn; struct instruction *insn;
@ -95,6 +97,20 @@ static struct instruction *next_insn_same_func(struct objtool_file *file,
for (insn = next_insn_same_sec(file, insn); insn; \ for (insn = next_insn_same_sec(file, insn); insn; \
insn = next_insn_same_sec(file, insn)) insn = next_insn_same_sec(file, insn))
static bool is_sibling_call(struct instruction *insn)
{
/* An indirect jump is either a sibling call or a jump to a table. */
if (insn->type == INSN_JUMP_DYNAMIC)
return list_empty(&insn->alts);
if (insn->type != INSN_JUMP_CONDITIONAL &&
insn->type != INSN_JUMP_UNCONDITIONAL)
return false;
/* add_jump_destinations() sets insn->call_dest for sibling calls. */
return !!insn->call_dest;
}
/* /*
* This checks to see if the given function is a "noreturn" function. * This checks to see if the given function is a "noreturn" function.
* *
@ -103,14 +119,9 @@ static struct instruction *next_insn_same_func(struct objtool_file *file,
* *
* For local functions, we have to detect them manually by simply looking for * For local functions, we have to detect them manually by simply looking for
* the lack of a return instruction. * the lack of a return instruction.
*
* Returns:
* -1: error
* 0: no dead end
* 1: dead end
*/ */
static int __dead_end_function(struct objtool_file *file, struct symbol *func, static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
int recursion) int recursion)
{ {
int i; int i;
struct instruction *insn; struct instruction *insn;
@ -136,30 +147,33 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
"rewind_stack_do_exit", "rewind_stack_do_exit",
}; };
if (!func)
return false;
if (func->bind == STB_WEAK) if (func->bind == STB_WEAK)
return 0; return false;
if (func->bind == STB_GLOBAL) if (func->bind == STB_GLOBAL)
for (i = 0; i < ARRAY_SIZE(global_noreturns); i++) for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)
if (!strcmp(func->name, global_noreturns[i])) if (!strcmp(func->name, global_noreturns[i]))
return 1; return true;
if (!func->len) if (!func->len)
return 0; return false;
insn = find_insn(file, func->sec, func->offset); insn = find_insn(file, func->sec, func->offset);
if (!insn->func) if (!insn->func)
return 0; return false;
func_for_each_insn_all(file, func, insn) { func_for_each_insn_all(file, func, insn) {
empty = false; empty = false;
if (insn->type == INSN_RETURN) if (insn->type == INSN_RETURN)
return 0; return false;
} }
if (empty) if (empty)
return 0; return false;
/* /*
* A function can have a sibling call instead of a return. In that * A function can have a sibling call instead of a return. In that
@ -167,40 +181,31 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func,
* of the sibling call returns. * of the sibling call returns.
*/ */
func_for_each_insn_all(file, func, insn) { func_for_each_insn_all(file, func, insn) {
if (insn->type == INSN_JUMP_UNCONDITIONAL) { if (is_sibling_call(insn)) {
struct instruction *dest = insn->jump_dest; struct instruction *dest = insn->jump_dest;
if (!dest) if (!dest)
/* sibling call to another file */ /* sibling call to another file */
return 0; return false;
if (dest->func && dest->func->pfunc != insn->func->pfunc) { /* local sibling call */
if (recursion == 5) {
/* local sibling call */ /*
if (recursion == 5) { * Infinite recursion: two functions have
/* * sibling calls to each other. This is a very
* Infinite recursion: two functions * rare case. It means they aren't dead ends.
* have sibling calls to each other. */
* This is a very rare case. It means return false;
* they aren't dead ends.
*/
return 0;
}
return __dead_end_function(file, dest->func,
recursion + 1);
} }
}
if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts)) return __dead_end_function(file, dest->func, recursion+1);
/* sibling call */ }
return 0;
} }
return 1; return true;
} }
static int dead_end_function(struct objtool_file *file, struct symbol *func) static bool dead_end_function(struct objtool_file *file, struct symbol *func)
{ {
return __dead_end_function(file, func, 0); return __dead_end_function(file, func, 0);
} }
@ -262,19 +267,12 @@ static int decode_instructions(struct objtool_file *file)
if (ret) if (ret)
goto err; goto err;
if (!insn->type || insn->type > INSN_LAST) {
WARN_FUNC("invalid instruction type %d",
insn->sec, insn->offset, insn->type);
ret = -1;
goto err;
}
hash_add(file->insn_hash, &insn->hash, insn->offset); hash_add(file->insn_hash, &insn->hash, insn->offset);
list_add_tail(&insn->list, &file->insn_list); list_add_tail(&insn->list, &file->insn_list);
} }
list_for_each_entry(func, &sec->symbol_list, list) { list_for_each_entry(func, &sec->symbol_list, list) {
if (func->type != STT_FUNC) if (func->type != STT_FUNC || func->alias != func)
continue; continue;
if (!find_insn(file, sec, func->offset)) { if (!find_insn(file, sec, func->offset)) {
@ -284,8 +282,7 @@ static int decode_instructions(struct objtool_file *file)
} }
func_for_each_insn(file, func, insn) func_for_each_insn(file, func, insn)
if (!insn->func) insn->func = func;
insn->func = func;
} }
} }
@ -488,6 +485,7 @@ static const char *uaccess_safe_builtin[] = {
/* misc */ /* misc */
"csum_partial_copy_generic", "csum_partial_copy_generic",
"__memcpy_mcsafe", "__memcpy_mcsafe",
"mcsafe_handle_tail",
"ftrace_likely_update", /* CONFIG_TRACE_BRANCH_PROFILING */ "ftrace_likely_update", /* CONFIG_TRACE_BRANCH_PROFILING */
NULL NULL
}; };
@ -505,7 +503,7 @@ static void add_uaccess_safe(struct objtool_file *file)
if (!func) if (!func)
continue; continue;
func->alias->uaccess_safe = true; func->uaccess_safe = true;
} }
} }
@ -577,13 +575,16 @@ static int add_jump_destinations(struct objtool_file *file)
* Retpoline jumps are really dynamic jumps in * Retpoline jumps are really dynamic jumps in
* disguise, so convert them accordingly. * disguise, so convert them accordingly.
*/ */
insn->type = INSN_JUMP_DYNAMIC; if (insn->type == INSN_JUMP_UNCONDITIONAL)
insn->type = INSN_JUMP_DYNAMIC;
else
insn->type = INSN_JUMP_DYNAMIC_CONDITIONAL;
insn->retpoline_safe = true; insn->retpoline_safe = true;
continue; continue;
} else { } else {
/* sibling call */ /* external sibling call */
insn->call_dest = rela->sym; insn->call_dest = rela->sym;
insn->jump_dest = NULL;
continue; continue;
} }
@ -623,7 +624,7 @@ static int add_jump_destinations(struct objtool_file *file)
* However this code can't completely replace the * However this code can't completely replace the
* read_symbols() code because this doesn't detect the * read_symbols() code because this doesn't detect the
* case where the parent function's only reference to a * case where the parent function's only reference to a
* subfunction is through a switch table. * subfunction is through a jump table.
*/ */
if (!strstr(insn->func->name, ".cold.") && if (!strstr(insn->func->name, ".cold.") &&
strstr(insn->jump_dest->func->name, ".cold.")) { strstr(insn->jump_dest->func->name, ".cold.")) {
@ -633,9 +634,8 @@ static int add_jump_destinations(struct objtool_file *file)
} else if (insn->jump_dest->func->pfunc != insn->func->pfunc && } else if (insn->jump_dest->func->pfunc != insn->func->pfunc &&
insn->jump_dest->offset == insn->jump_dest->func->offset) { insn->jump_dest->offset == insn->jump_dest->func->offset) {
/* sibling class */ /* internal sibling call */
insn->call_dest = insn->jump_dest->func; insn->call_dest = insn->jump_dest->func;
insn->jump_dest = NULL;
} }
} }
} }
@ -896,20 +896,26 @@ static int add_special_section_alts(struct objtool_file *file)
return ret; return ret;
} }
static int add_switch_table(struct objtool_file *file, struct instruction *insn, static int add_jump_table(struct objtool_file *file, struct instruction *insn,
struct rela *table, struct rela *next_table) struct rela *table)
{ {
struct rela *rela = table; struct rela *rela = table;
struct instruction *alt_insn; struct instruction *dest_insn;
struct alternative *alt; struct alternative *alt;
struct symbol *pfunc = insn->func->pfunc; struct symbol *pfunc = insn->func->pfunc;
unsigned int prev_offset = 0; unsigned int prev_offset = 0;
list_for_each_entry_from(rela, &table->rela_sec->rela_list, list) { /*
if (rela == next_table) * Each @rela is a switch table relocation which points to the target
* instruction.
*/
list_for_each_entry_from(rela, &table->sec->rela_list, list) {
/* Check for the end of the table: */
if (rela != table && rela->jump_table_start)
break; break;
/* Make sure the switch table entries are consecutive: */ /* Make sure the table entries are consecutive: */
if (prev_offset && rela->offset != prev_offset + 8) if (prev_offset && rela->offset != prev_offset + 8)
break; break;
@ -918,12 +924,12 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
rela->addend == pfunc->offset) rela->addend == pfunc->offset)
break; break;
alt_insn = find_insn(file, rela->sym->sec, rela->addend); dest_insn = find_insn(file, rela->sym->sec, rela->addend);
if (!alt_insn) if (!dest_insn)
break; break;
/* Make sure the jmp dest is in the function or subfunction: */ /* Make sure the destination is in the same function: */
if (alt_insn->func->pfunc != pfunc) if (!dest_insn->func || dest_insn->func->pfunc != pfunc)
break; break;
alt = malloc(sizeof(*alt)); alt = malloc(sizeof(*alt));
@ -932,7 +938,7 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
return -1; return -1;
} }
alt->insn = alt_insn; alt->insn = dest_insn;
list_add_tail(&alt->list, &insn->alts); list_add_tail(&alt->list, &insn->alts);
prev_offset = rela->offset; prev_offset = rela->offset;
} }
@ -947,7 +953,7 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
} }
/* /*
* find_switch_table() - Given a dynamic jump, find the switch jump table in * find_jump_table() - Given a dynamic jump, find the switch jump table in
* .rodata associated with it. * .rodata associated with it.
* *
* There are 3 basic patterns: * There are 3 basic patterns:
@ -989,13 +995,13 @@ static int add_switch_table(struct objtool_file *file, struct instruction *insn,
* *
* NOTE: RETPOLINE made it harder still to decode dynamic jumps. * NOTE: RETPOLINE made it harder still to decode dynamic jumps.
*/ */
static struct rela *find_switch_table(struct objtool_file *file, static struct rela *find_jump_table(struct objtool_file *file,
struct symbol *func, struct symbol *func,
struct instruction *insn) struct instruction *insn)
{ {
struct rela *text_rela, *rodata_rela; struct rela *text_rela, *table_rela;
struct instruction *orig_insn = insn; struct instruction *orig_insn = insn;
struct section *rodata_sec; struct section *table_sec;
unsigned long table_offset; unsigned long table_offset;
/* /*
@ -1028,42 +1034,52 @@ static struct rela *find_switch_table(struct objtool_file *file,
continue; continue;
table_offset = text_rela->addend; table_offset = text_rela->addend;
rodata_sec = text_rela->sym->sec; table_sec = text_rela->sym->sec;
if (text_rela->type == R_X86_64_PC32) if (text_rela->type == R_X86_64_PC32)
table_offset += 4; table_offset += 4;
/* /*
* Make sure the .rodata address isn't associated with a * Make sure the .rodata address isn't associated with a
* symbol. gcc jump tables are anonymous data. * symbol. GCC jump tables are anonymous data.
*
* Also support C jump tables which are in the same format as
* switch jump tables. For objtool to recognize them, they
* need to be placed in the C_JUMP_TABLE_SECTION section. They
* have symbols associated with them.
*/ */
if (find_symbol_containing(rodata_sec, table_offset)) if (find_symbol_containing(table_sec, table_offset) &&
strcmp(table_sec->name, C_JUMP_TABLE_SECTION))
continue; continue;
rodata_rela = find_rela_by_dest(rodata_sec, table_offset); /* Each table entry has a rela associated with it. */
if (rodata_rela) { table_rela = find_rela_by_dest(table_sec, table_offset);
/* if (!table_rela)
* Use of RIP-relative switch jumps is quite rare, and continue;
* indicates a rare GCC quirk/bug which can leave dead
* code behind.
*/
if (text_rela->type == R_X86_64_PC32)
file->ignore_unreachables = true;
return rodata_rela; /*
} * Use of RIP-relative switch jumps is quite rare, and
* indicates a rare GCC quirk/bug which can leave dead code
* behind.
*/
if (text_rela->type == R_X86_64_PC32)
file->ignore_unreachables = true;
return table_rela;
} }
return NULL; return NULL;
} }
/*
static int add_func_switch_tables(struct objtool_file *file, * First pass: Mark the head of each jump table so that in the next pass,
struct symbol *func) * we know when a given jump table ends and the next one starts.
*/
static void mark_func_jump_tables(struct objtool_file *file,
struct symbol *func)
{ {
struct instruction *insn, *last = NULL, *prev_jump = NULL; struct instruction *insn, *last = NULL;
struct rela *rela, *prev_rela = NULL; struct rela *rela;
int ret;
func_for_each_insn_all(file, func, insn) { func_for_each_insn_all(file, func, insn) {
if (!last) if (!last)
@ -1071,7 +1087,7 @@ static int add_func_switch_tables(struct objtool_file *file,
/* /*
* Store back-pointers for unconditional forward jumps such * Store back-pointers for unconditional forward jumps such
* that find_switch_table() can back-track using those and * that find_jump_table() can back-track using those and
* avoid some potentially confusing code. * avoid some potentially confusing code.
*/ */
if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest && if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest &&
@ -1086,27 +1102,25 @@ static int add_func_switch_tables(struct objtool_file *file,
if (insn->type != INSN_JUMP_DYNAMIC) if (insn->type != INSN_JUMP_DYNAMIC)
continue; continue;
rela = find_switch_table(file, func, insn); rela = find_jump_table(file, func, insn);
if (!rela) if (rela) {
rela->jump_table_start = true;
insn->jump_table = rela;
}
}
}
static int add_func_jump_tables(struct objtool_file *file,
struct symbol *func)
{
struct instruction *insn;
int ret;
func_for_each_insn_all(file, func, insn) {
if (!insn->jump_table)
continue; continue;
/* ret = add_jump_table(file, insn, insn->jump_table);
* We found a switch table, but we don't know yet how big it
* is. Don't add it until we reach the end of the function or
* the beginning of another switch table in the same function.
*/
if (prev_jump) {
ret = add_switch_table(file, prev_jump, prev_rela, rela);
if (ret)
return ret;
}
prev_jump = insn;
prev_rela = rela;
}
if (prev_jump) {
ret = add_switch_table(file, prev_jump, prev_rela, NULL);
if (ret) if (ret)
return ret; return ret;
} }
@ -1119,7 +1133,7 @@ static int add_func_switch_tables(struct objtool_file *file,
* section which contains a list of addresses within the function to jump to. * section which contains a list of addresses within the function to jump to.
* This finds these jump tables and adds them to the insn->alts lists. * This finds these jump tables and adds them to the insn->alts lists.
*/ */
static int add_switch_table_alts(struct objtool_file *file) static int add_jump_table_alts(struct objtool_file *file)
{ {
struct section *sec; struct section *sec;
struct symbol *func; struct symbol *func;
@ -1133,7 +1147,8 @@ static int add_switch_table_alts(struct objtool_file *file)
if (func->type != STT_FUNC) if (func->type != STT_FUNC)
continue; continue;
ret = add_func_switch_tables(file, func); mark_func_jump_tables(file, func);
ret = add_func_jump_tables(file, func);
if (ret) if (ret)
return ret; return ret;
} }
@ -1277,13 +1292,18 @@ static void mark_rodata(struct objtool_file *file)
bool found = false; bool found = false;
/* /*
* This searches for the .rodata section or multiple .rodata.func_name * Search for the following rodata sections, each of which can
* sections if -fdata-sections is being used. The .str.1.1 and .str.1.8 * potentially contain jump tables:
* rodata sections are ignored as they don't contain jump tables. *
* - .rodata: can contain GCC switch tables
* - .rodata.<func>: same, if -fdata-sections is being used
* - .rodata..c_jump_table: contains C annotated jump tables
*
* .rodata.str1.* sections are ignored; they don't contain jump tables.
*/ */
for_each_sec(file, sec) { for_each_sec(file, sec) {
if (!strncmp(sec->name, ".rodata", 7) && if ((!strncmp(sec->name, ".rodata", 7) && !strstr(sec->name, ".str1.")) ||
!strstr(sec->name, ".str1.")) { !strcmp(sec->name, C_JUMP_TABLE_SECTION)) {
sec->rodata = true; sec->rodata = true;
found = true; found = true;
} }
@ -1325,7 +1345,7 @@ static int decode_sections(struct objtool_file *file)
if (ret) if (ret)
return ret; return ret;
ret = add_switch_table_alts(file); ret = add_jump_table_alts(file);
if (ret) if (ret)
return ret; return ret;
@ -1873,12 +1893,12 @@ static bool insn_state_match(struct instruction *insn, struct insn_state *state)
static inline bool func_uaccess_safe(struct symbol *func) static inline bool func_uaccess_safe(struct symbol *func)
{ {
if (func) if (func)
return func->alias->uaccess_safe; return func->uaccess_safe;
return false; return false;
} }
static inline const char *insn_dest_name(struct instruction *insn) static inline const char *call_dest_name(struct instruction *insn)
{ {
if (insn->call_dest) if (insn->call_dest)
return insn->call_dest->name; return insn->call_dest->name;
@ -1890,13 +1910,13 @@ static int validate_call(struct instruction *insn, struct insn_state *state)
{ {
if (state->uaccess && !func_uaccess_safe(insn->call_dest)) { if (state->uaccess && !func_uaccess_safe(insn->call_dest)) {
WARN_FUNC("call to %s() with UACCESS enabled", WARN_FUNC("call to %s() with UACCESS enabled",
insn->sec, insn->offset, insn_dest_name(insn)); insn->sec, insn->offset, call_dest_name(insn));
return 1; return 1;
} }
if (state->df) { if (state->df) {
WARN_FUNC("call to %s() with DF set", WARN_FUNC("call to %s() with DF set",
insn->sec, insn->offset, insn_dest_name(insn)); insn->sec, insn->offset, call_dest_name(insn));
return 1; return 1;
} }
@ -1920,13 +1940,12 @@ static int validate_sibling_call(struct instruction *insn, struct insn_state *st
* each instruction and validate all the rules described in * each instruction and validate all the rules described in
* tools/objtool/Documentation/stack-validation.txt. * tools/objtool/Documentation/stack-validation.txt.
*/ */
static int validate_branch(struct objtool_file *file, struct instruction *first, static int validate_branch(struct objtool_file *file, struct symbol *func,
struct insn_state state) struct instruction *first, struct insn_state state)
{ {
struct alternative *alt; struct alternative *alt;
struct instruction *insn, *next_insn; struct instruction *insn, *next_insn;
struct section *sec; struct section *sec;
struct symbol *func = NULL;
int ret; int ret;
insn = first; insn = first;
@ -1947,9 +1966,6 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
return 1; return 1;
} }
if (insn->func)
func = insn->func->pfunc;
if (func && insn->ignore) { if (func && insn->ignore) {
WARN_FUNC("BUG: why am I validating an ignored function?", WARN_FUNC("BUG: why am I validating an ignored function?",
sec, insn->offset); sec, insn->offset);
@ -1971,7 +1987,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
i = insn; i = insn;
save_insn = NULL; save_insn = NULL;
func_for_each_insn_continue_reverse(file, insn->func, i) { func_for_each_insn_continue_reverse(file, func, i) {
if (i->save) { if (i->save) {
save_insn = i; save_insn = i;
break; break;
@ -2017,7 +2033,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
if (alt->skip_orig) if (alt->skip_orig)
skip_orig = true; skip_orig = true;
ret = validate_branch(file, alt->insn, state); ret = validate_branch(file, func, alt->insn, state);
if (ret) { if (ret) {
if (backtrace) if (backtrace)
BT_FUNC("(alt)", insn); BT_FUNC("(alt)", insn);
@ -2055,7 +2071,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
if (state.bp_scratch) { if (state.bp_scratch) {
WARN("%s uses BP as a scratch register", WARN("%s uses BP as a scratch register",
insn->func->name); func->name);
return 1; return 1;
} }
@ -2067,36 +2083,28 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
if (ret) if (ret)
return ret; return ret;
if (insn->type == INSN_CALL) { if (!no_fp && func && !is_fentry_call(insn) &&
if (is_fentry_call(insn)) !has_valid_stack_frame(&state)) {
break;
ret = dead_end_function(file, insn->call_dest);
if (ret == 1)
return 0;
if (ret == -1)
return 1;
}
if (!no_fp && func && !has_valid_stack_frame(&state)) {
WARN_FUNC("call without frame pointer save/setup", WARN_FUNC("call without frame pointer save/setup",
sec, insn->offset); sec, insn->offset);
return 1; return 1;
} }
if (dead_end_function(file, insn->call_dest))
return 0;
break; break;
case INSN_JUMP_CONDITIONAL: case INSN_JUMP_CONDITIONAL:
case INSN_JUMP_UNCONDITIONAL: case INSN_JUMP_UNCONDITIONAL:
if (func && !insn->jump_dest) { if (func && is_sibling_call(insn)) {
ret = validate_sibling_call(insn, &state); ret = validate_sibling_call(insn, &state);
if (ret) if (ret)
return ret; return ret;
} else if (insn->jump_dest && } else if (insn->jump_dest) {
(!func || !insn->jump_dest->func || ret = validate_branch(file, func,
insn->jump_dest->func->pfunc == func)) { insn->jump_dest, state);
ret = validate_branch(file, insn->jump_dest,
state);
if (ret) { if (ret) {
if (backtrace) if (backtrace)
BT_FUNC("(branch)", insn); BT_FUNC("(branch)", insn);
@ -2110,13 +2118,17 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
break; break;
case INSN_JUMP_DYNAMIC: case INSN_JUMP_DYNAMIC:
if (func && list_empty(&insn->alts)) { case INSN_JUMP_DYNAMIC_CONDITIONAL:
if (func && is_sibling_call(insn)) {
ret = validate_sibling_call(insn, &state); ret = validate_sibling_call(insn, &state);
if (ret) if (ret)
return ret; return ret;
} }
return 0; if (insn->type == INSN_JUMP_DYNAMIC)
return 0;
break;
case INSN_CONTEXT_SWITCH: case INSN_CONTEXT_SWITCH:
if (func && (!next_insn || !next_insn->hint)) { if (func && (!next_insn || !next_insn->hint)) {
@ -2162,7 +2174,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
break; break;
case INSN_CLAC: case INSN_CLAC:
if (!state.uaccess && insn->func) { if (!state.uaccess && func) {
WARN_FUNC("redundant UACCESS disable", sec, insn->offset); WARN_FUNC("redundant UACCESS disable", sec, insn->offset);
return 1; return 1;
} }
@ -2183,7 +2195,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
break; break;
case INSN_CLD: case INSN_CLD:
if (!state.df && insn->func) if (!state.df && func)
WARN_FUNC("redundant CLD", sec, insn->offset); WARN_FUNC("redundant CLD", sec, insn->offset);
state.df = false; state.df = false;
@ -2222,7 +2234,7 @@ static int validate_unwind_hints(struct objtool_file *file)
for_each_insn(file, insn) { for_each_insn(file, insn) {
if (insn->hint && !insn->visited) { if (insn->hint && !insn->visited) {
ret = validate_branch(file, insn, state); ret = validate_branch(file, insn->func, insn, state);
if (ret && backtrace) if (ret && backtrace)
BT_FUNC("<=== (hint)", insn); BT_FUNC("<=== (hint)", insn);
warnings += ret; warnings += ret;
@ -2345,16 +2357,25 @@ static int validate_functions(struct objtool_file *file)
for_each_sec(file, sec) { for_each_sec(file, sec) {
list_for_each_entry(func, &sec->symbol_list, list) { list_for_each_entry(func, &sec->symbol_list, list) {
if (func->type != STT_FUNC || func->pfunc != func) if (func->type != STT_FUNC)
continue;
if (!func->len) {
WARN("%s() is missing an ELF size annotation",
func->name);
warnings++;
}
if (func->pfunc != func || func->alias != func)
continue; continue;
insn = find_insn(file, sec, func->offset); insn = find_insn(file, sec, func->offset);
if (!insn || insn->ignore) if (!insn || insn->ignore || insn->visited)
continue; continue;
state.uaccess = func->alias->uaccess_safe; state.uaccess = func->uaccess_safe;
ret = validate_branch(file, insn, state); ret = validate_branch(file, func, insn, state);
if (ret && backtrace) if (ret && backtrace)
BT_FUNC("<=== (func)", insn); BT_FUNC("<=== (func)", insn);
warnings += ret; warnings += ret;
@ -2407,7 +2428,7 @@ int check(const char *_objname, bool orc)
objname = _objname; objname = _objname;
file.elf = elf_open(objname, orc ? O_RDWR : O_RDONLY); file.elf = elf_read(objname, orc ? O_RDWR : O_RDONLY);
if (!file.elf) if (!file.elf)
return 1; return 1;

View File

@ -31,13 +31,14 @@ struct instruction {
struct section *sec; struct section *sec;
unsigned long offset; unsigned long offset;
unsigned int len; unsigned int len;
unsigned char type; enum insn_type type;
unsigned long immediate; unsigned long immediate;
bool alt_group, visited, dead_end, ignore, hint, save, restore, ignore_alts; bool alt_group, visited, dead_end, ignore, hint, save, restore, ignore_alts;
bool retpoline_safe; bool retpoline_safe;
struct symbol *call_dest; struct symbol *call_dest;
struct instruction *jump_dest; struct instruction *jump_dest;
struct instruction *first_jump_src; struct instruction *first_jump_src;
struct rela *jump_table;
struct list_head alts; struct list_head alts;
struct symbol *func; struct symbol *func;
struct stack_op stack_op; struct stack_op stack_op;

View File

@ -278,7 +278,7 @@ static int read_symbols(struct elf *elf)
} }
if (sym->offset == s->offset) { if (sym->offset == s->offset) {
if (sym->len == s->len && alias == sym) if (sym->len && sym->len == s->len && alias == sym)
alias = s; alias = s;
if (sym->len >= s->len) { if (sym->len >= s->len) {
@ -385,7 +385,7 @@ static int read_relas(struct elf *elf)
rela->offset = rela->rela.r_offset; rela->offset = rela->rela.r_offset;
symndx = GELF_R_SYM(rela->rela.r_info); symndx = GELF_R_SYM(rela->rela.r_info);
rela->sym = find_symbol_by_index(elf, symndx); rela->sym = find_symbol_by_index(elf, symndx);
rela->rela_sec = sec; rela->sec = sec;
if (!rela->sym) { if (!rela->sym) {
WARN("can't find rela entry symbol %d for %s", WARN("can't find rela entry symbol %d for %s",
symndx, sec->name); symndx, sec->name);
@ -401,7 +401,7 @@ static int read_relas(struct elf *elf)
return 0; return 0;
} }
struct elf *elf_open(const char *name, int flags) struct elf *elf_read(const char *name, int flags)
{ {
struct elf *elf; struct elf *elf;
Elf_Cmd cmd; Elf_Cmd cmd;
@ -463,7 +463,7 @@ struct section *elf_create_section(struct elf *elf, const char *name,
{ {
struct section *sec, *shstrtab; struct section *sec, *shstrtab;
size_t size = entsize * nr; size_t size = entsize * nr;
struct Elf_Scn *s; Elf_Scn *s;
Elf_Data *data; Elf_Data *data;
sec = malloc(sizeof(*sec)); sec = malloc(sizeof(*sec));

View File

@ -57,11 +57,12 @@ struct rela {
struct list_head list; struct list_head list;
struct hlist_node hash; struct hlist_node hash;
GElf_Rela rela; GElf_Rela rela;
struct section *rela_sec; struct section *sec;
struct symbol *sym; struct symbol *sym;
unsigned int type; unsigned int type;
unsigned long offset; unsigned long offset;
int addend; int addend;
bool jump_table_start;
}; };
struct elf { struct elf {
@ -74,7 +75,7 @@ struct elf {
}; };
struct elf *elf_open(const char *name, int flags); struct elf *elf_read(const char *name, int flags);
struct section *find_section_by_name(struct elf *elf, const char *name); struct section *find_section_by_name(struct elf *elf, const char *name);
struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset);
struct symbol *find_symbol_by_name(struct elf *elf, const char *name); struct symbol *find_symbol_by_name(struct elf *elf, const char *name);