Merge branch 'x86/asm' into locking/core
We need the ASM_UNREACHABLE() macro for a dependent patch. Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
@@ -305,8 +305,6 @@ config DEBUG_ENTRY
|
|||||||
Some of these sanity checks may slow down kernel entries and
|
Some of these sanity checks may slow down kernel entries and
|
||||||
exits or otherwise impact performance.
|
exits or otherwise impact performance.
|
||||||
|
|
||||||
This is currently used to help test NMI code.
|
|
||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
config DEBUG_NMI_SELFTEST
|
config DEBUG_NMI_SELFTEST
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# Makefile for the x86 low level entry code
|
# Makefile for the x86 low level entry code
|
||||||
#
|
#
|
||||||
|
|
||||||
OBJECT_FILES_NON_STANDARD_entry_$(BITS).o := y
|
|
||||||
OBJECT_FILES_NON_STANDARD_entry_64_compat.o := y
|
OBJECT_FILES_NON_STANDARD_entry_64_compat.o := y
|
||||||
|
|
||||||
CFLAGS_syscall_64.o += $(call cc-option,-Wno-override-init,)
|
CFLAGS_syscall_64.o += $(call cc-option,-Wno-override-init,)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <linux/jump_label.h>
|
#include <linux/jump_label.h>
|
||||||
|
#include <asm/unwind_hints.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -112,6 +113,7 @@ For 32-bit we have the following conventions - kernel is built with
|
|||||||
movq %rdx, 12*8+\offset(%rsp)
|
movq %rdx, 12*8+\offset(%rsp)
|
||||||
movq %rsi, 13*8+\offset(%rsp)
|
movq %rsi, 13*8+\offset(%rsp)
|
||||||
movq %rdi, 14*8+\offset(%rsp)
|
movq %rdi, 14*8+\offset(%rsp)
|
||||||
|
UNWIND_HINT_REGS offset=\offset extra=0
|
||||||
.endm
|
.endm
|
||||||
.macro SAVE_C_REGS offset=0
|
.macro SAVE_C_REGS offset=0
|
||||||
SAVE_C_REGS_HELPER \offset, 1, 1, 1, 1
|
SAVE_C_REGS_HELPER \offset, 1, 1, 1, 1
|
||||||
@@ -136,6 +138,7 @@ For 32-bit we have the following conventions - kernel is built with
|
|||||||
movq %r12, 3*8+\offset(%rsp)
|
movq %r12, 3*8+\offset(%rsp)
|
||||||
movq %rbp, 4*8+\offset(%rsp)
|
movq %rbp, 4*8+\offset(%rsp)
|
||||||
movq %rbx, 5*8+\offset(%rsp)
|
movq %rbx, 5*8+\offset(%rsp)
|
||||||
|
UNWIND_HINT_REGS offset=\offset
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
.macro RESTORE_EXTRA_REGS offset=0
|
.macro RESTORE_EXTRA_REGS offset=0
|
||||||
@@ -145,6 +148,7 @@ For 32-bit we have the following conventions - kernel is built with
|
|||||||
movq 3*8+\offset(%rsp), %r12
|
movq 3*8+\offset(%rsp), %r12
|
||||||
movq 4*8+\offset(%rsp), %rbp
|
movq 4*8+\offset(%rsp), %rbp
|
||||||
movq 5*8+\offset(%rsp), %rbx
|
movq 5*8+\offset(%rsp), %rbx
|
||||||
|
UNWIND_HINT_REGS offset=\offset extra=0
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
.macro RESTORE_C_REGS_HELPER rstor_rax=1, rstor_rcx=1, rstor_r11=1, rstor_r8910=1, rstor_rdx=1
|
.macro RESTORE_C_REGS_HELPER rstor_rax=1, rstor_rcx=1, rstor_r11=1, rstor_r8910=1, rstor_rdx=1
|
||||||
@@ -167,6 +171,7 @@ For 32-bit we have the following conventions - kernel is built with
|
|||||||
.endif
|
.endif
|
||||||
movq 13*8(%rsp), %rsi
|
movq 13*8(%rsp), %rsi
|
||||||
movq 14*8(%rsp), %rdi
|
movq 14*8(%rsp), %rdi
|
||||||
|
UNWIND_HINT_IRET_REGS offset=16*8
|
||||||
.endm
|
.endm
|
||||||
.macro RESTORE_C_REGS
|
.macro RESTORE_C_REGS
|
||||||
RESTORE_C_REGS_HELPER 1,1,1,1,1
|
RESTORE_C_REGS_HELPER 1,1,1,1,1
|
||||||
|
|||||||
+140
-30
@@ -36,6 +36,7 @@
|
|||||||
#include <asm/smap.h>
|
#include <asm/smap.h>
|
||||||
#include <asm/pgtable_types.h>
|
#include <asm/pgtable_types.h>
|
||||||
#include <asm/export.h>
|
#include <asm/export.h>
|
||||||
|
#include <asm/frame.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
|
|
||||||
.code64
|
.code64
|
||||||
@@ -43,9 +44,10 @@
|
|||||||
|
|
||||||
#ifdef CONFIG_PARAVIRT
|
#ifdef CONFIG_PARAVIRT
|
||||||
ENTRY(native_usergs_sysret64)
|
ENTRY(native_usergs_sysret64)
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
swapgs
|
swapgs
|
||||||
sysretq
|
sysretq
|
||||||
ENDPROC(native_usergs_sysret64)
|
END(native_usergs_sysret64)
|
||||||
#endif /* CONFIG_PARAVIRT */
|
#endif /* CONFIG_PARAVIRT */
|
||||||
|
|
||||||
.macro TRACE_IRQS_IRETQ
|
.macro TRACE_IRQS_IRETQ
|
||||||
@@ -134,6 +136,7 @@ ENDPROC(native_usergs_sysret64)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
ENTRY(entry_SYSCALL_64)
|
ENTRY(entry_SYSCALL_64)
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
/*
|
/*
|
||||||
* Interrupts are off on entry.
|
* Interrupts are off on entry.
|
||||||
* We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
|
* We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
|
||||||
@@ -169,6 +172,7 @@ GLOBAL(entry_SYSCALL_64_after_swapgs)
|
|||||||
pushq %r10 /* pt_regs->r10 */
|
pushq %r10 /* pt_regs->r10 */
|
||||||
pushq %r11 /* pt_regs->r11 */
|
pushq %r11 /* pt_regs->r11 */
|
||||||
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
|
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
|
||||||
|
UNWIND_HINT_REGS extra=0
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we need to do entry work or if we guess we'll need to do
|
* If we need to do entry work or if we guess we'll need to do
|
||||||
@@ -223,6 +227,7 @@ entry_SYSCALL_64_fastpath:
|
|||||||
movq EFLAGS(%rsp), %r11
|
movq EFLAGS(%rsp), %r11
|
||||||
RESTORE_C_REGS_EXCEPT_RCX_R11
|
RESTORE_C_REGS_EXCEPT_RCX_R11
|
||||||
movq RSP(%rsp), %rsp
|
movq RSP(%rsp), %rsp
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
USERGS_SYSRET64
|
USERGS_SYSRET64
|
||||||
|
|
||||||
1:
|
1:
|
||||||
@@ -316,6 +321,7 @@ syscall_return_via_sysret:
|
|||||||
/* rcx and r11 are already restored (see code above) */
|
/* rcx and r11 are already restored (see code above) */
|
||||||
RESTORE_C_REGS_EXCEPT_RCX_R11
|
RESTORE_C_REGS_EXCEPT_RCX_R11
|
||||||
movq RSP(%rsp), %rsp
|
movq RSP(%rsp), %rsp
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
USERGS_SYSRET64
|
USERGS_SYSRET64
|
||||||
|
|
||||||
opportunistic_sysret_failed:
|
opportunistic_sysret_failed:
|
||||||
@@ -343,6 +349,7 @@ ENTRY(stub_ptregs_64)
|
|||||||
DISABLE_INTERRUPTS(CLBR_ANY)
|
DISABLE_INTERRUPTS(CLBR_ANY)
|
||||||
TRACE_IRQS_OFF
|
TRACE_IRQS_OFF
|
||||||
popq %rax
|
popq %rax
|
||||||
|
UNWIND_HINT_REGS extra=0
|
||||||
jmp entry_SYSCALL64_slow_path
|
jmp entry_SYSCALL64_slow_path
|
||||||
|
|
||||||
1:
|
1:
|
||||||
@@ -351,6 +358,7 @@ END(stub_ptregs_64)
|
|||||||
|
|
||||||
.macro ptregs_stub func
|
.macro ptregs_stub func
|
||||||
ENTRY(ptregs_\func)
|
ENTRY(ptregs_\func)
|
||||||
|
UNWIND_HINT_FUNC
|
||||||
leaq \func(%rip), %rax
|
leaq \func(%rip), %rax
|
||||||
jmp stub_ptregs_64
|
jmp stub_ptregs_64
|
||||||
END(ptregs_\func)
|
END(ptregs_\func)
|
||||||
@@ -367,6 +375,7 @@ END(ptregs_\func)
|
|||||||
* %rsi: next task
|
* %rsi: next task
|
||||||
*/
|
*/
|
||||||
ENTRY(__switch_to_asm)
|
ENTRY(__switch_to_asm)
|
||||||
|
UNWIND_HINT_FUNC
|
||||||
/*
|
/*
|
||||||
* Save callee-saved registers
|
* Save callee-saved registers
|
||||||
* This must match the order in inactive_task_frame
|
* This must match the order in inactive_task_frame
|
||||||
@@ -406,6 +415,7 @@ END(__switch_to_asm)
|
|||||||
* r12: kernel thread arg
|
* r12: kernel thread arg
|
||||||
*/
|
*/
|
||||||
ENTRY(ret_from_fork)
|
ENTRY(ret_from_fork)
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
movq %rax, %rdi
|
movq %rax, %rdi
|
||||||
call schedule_tail /* rdi: 'prev' task parameter */
|
call schedule_tail /* rdi: 'prev' task parameter */
|
||||||
|
|
||||||
@@ -413,6 +423,7 @@ ENTRY(ret_from_fork)
|
|||||||
jnz 1f /* kernel threads are uncommon */
|
jnz 1f /* kernel threads are uncommon */
|
||||||
|
|
||||||
2:
|
2:
|
||||||
|
UNWIND_HINT_REGS
|
||||||
movq %rsp, %rdi
|
movq %rsp, %rdi
|
||||||
call syscall_return_slowpath /* returns with IRQs disabled */
|
call syscall_return_slowpath /* returns with IRQs disabled */
|
||||||
TRACE_IRQS_ON /* user mode is traced as IRQS on */
|
TRACE_IRQS_ON /* user mode is traced as IRQS on */
|
||||||
@@ -440,13 +451,102 @@ END(ret_from_fork)
|
|||||||
ENTRY(irq_entries_start)
|
ENTRY(irq_entries_start)
|
||||||
vector=FIRST_EXTERNAL_VECTOR
|
vector=FIRST_EXTERNAL_VECTOR
|
||||||
.rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)
|
.rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR)
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
pushq $(~vector+0x80) /* Note: always in signed byte range */
|
pushq $(~vector+0x80) /* Note: always in signed byte range */
|
||||||
vector=vector+1
|
|
||||||
jmp common_interrupt
|
jmp common_interrupt
|
||||||
.align 8
|
.align 8
|
||||||
|
vector=vector+1
|
||||||
.endr
|
.endr
|
||||||
END(irq_entries_start)
|
END(irq_entries_start)
|
||||||
|
|
||||||
|
.macro DEBUG_ENTRY_ASSERT_IRQS_OFF
|
||||||
|
#ifdef CONFIG_DEBUG_ENTRY
|
||||||
|
pushfq
|
||||||
|
testl $X86_EFLAGS_IF, (%rsp)
|
||||||
|
jz .Lokay_\@
|
||||||
|
ud2
|
||||||
|
.Lokay_\@:
|
||||||
|
addq $8, %rsp
|
||||||
|
#endif
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enters the IRQ stack if we're not already using it. NMI-safe. Clobbers
|
||||||
|
* flags and puts old RSP into old_rsp, and leaves all other GPRs alone.
|
||||||
|
* Requires kernel GSBASE.
|
||||||
|
*
|
||||||
|
* The invariant is that, if irq_count != -1, then the IRQ stack is in use.
|
||||||
|
*/
|
||||||
|
.macro ENTER_IRQ_STACK regs=1 old_rsp
|
||||||
|
DEBUG_ENTRY_ASSERT_IRQS_OFF
|
||||||
|
movq %rsp, \old_rsp
|
||||||
|
|
||||||
|
.if \regs
|
||||||
|
UNWIND_HINT_REGS base=\old_rsp
|
||||||
|
.endif
|
||||||
|
|
||||||
|
incl PER_CPU_VAR(irq_count)
|
||||||
|
jnz .Lirq_stack_push_old_rsp_\@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Right now, if we just incremented irq_count to zero, we've
|
||||||
|
* claimed the IRQ stack but we haven't switched to it yet.
|
||||||
|
*
|
||||||
|
* If anything is added that can interrupt us here without using IST,
|
||||||
|
* it must be *extremely* careful to limit its stack usage. This
|
||||||
|
* could include kprobes and a hypothetical future IST-less #DB
|
||||||
|
* handler.
|
||||||
|
*
|
||||||
|
* The OOPS unwinder relies on the word at the top of the IRQ
|
||||||
|
* stack linking back to the previous RSP for the entire time we're
|
||||||
|
* on the IRQ stack. For this to work reliably, we need to write
|
||||||
|
* it before we actually move ourselves to the IRQ stack.
|
||||||
|
*/
|
||||||
|
|
||||||
|
movq \old_rsp, PER_CPU_VAR(irq_stack_union + IRQ_STACK_SIZE - 8)
|
||||||
|
movq PER_CPU_VAR(irq_stack_ptr), %rsp
|
||||||
|
|
||||||
|
#ifdef CONFIG_DEBUG_ENTRY
|
||||||
|
/*
|
||||||
|
* If the first movq above becomes wrong due to IRQ stack layout
|
||||||
|
* changes, the only way we'll notice is if we try to unwind right
|
||||||
|
* here. Assert that we set up the stack right to catch this type
|
||||||
|
* of bug quickly.
|
||||||
|
*/
|
||||||
|
cmpq -8(%rsp), \old_rsp
|
||||||
|
je .Lirq_stack_okay\@
|
||||||
|
ud2
|
||||||
|
.Lirq_stack_okay\@:
|
||||||
|
#endif
|
||||||
|
|
||||||
|
.Lirq_stack_push_old_rsp_\@:
|
||||||
|
pushq \old_rsp
|
||||||
|
|
||||||
|
.if \regs
|
||||||
|
UNWIND_HINT_REGS indirect=1
|
||||||
|
.endif
|
||||||
|
.endm
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Undoes ENTER_IRQ_STACK.
|
||||||
|
*/
|
||||||
|
.macro LEAVE_IRQ_STACK regs=1
|
||||||
|
DEBUG_ENTRY_ASSERT_IRQS_OFF
|
||||||
|
/* We need to be off the IRQ stack before decrementing irq_count. */
|
||||||
|
popq %rsp
|
||||||
|
|
||||||
|
.if \regs
|
||||||
|
UNWIND_HINT_REGS
|
||||||
|
.endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As in ENTER_IRQ_STACK, irq_count == 0, we are still claiming
|
||||||
|
* the irq stack but we're not on it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
decl PER_CPU_VAR(irq_count)
|
||||||
|
.endm
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Interrupt entry/exit.
|
* Interrupt entry/exit.
|
||||||
*
|
*
|
||||||
@@ -485,17 +585,7 @@ END(irq_entries_start)
|
|||||||
CALL_enter_from_user_mode
|
CALL_enter_from_user_mode
|
||||||
|
|
||||||
1:
|
1:
|
||||||
/*
|
ENTER_IRQ_STACK old_rsp=%rdi
|
||||||
* Save previous stack pointer, optionally switch to interrupt stack.
|
|
||||||
* irq_count is used to check if a CPU is already on an interrupt stack
|
|
||||||
* or not. While this is essentially redundant with preempt_count it is
|
|
||||||
* a little cheaper to use a separate counter in the PDA (short of
|
|
||||||
* moving irq_enter into assembly, which would be too much work)
|
|
||||||
*/
|
|
||||||
movq %rsp, %rdi
|
|
||||||
incl PER_CPU_VAR(irq_count)
|
|
||||||
cmovzq PER_CPU_VAR(irq_stack_ptr), %rsp
|
|
||||||
pushq %rdi
|
|
||||||
/* We entered an interrupt context - irqs are off: */
|
/* We entered an interrupt context - irqs are off: */
|
||||||
TRACE_IRQS_OFF
|
TRACE_IRQS_OFF
|
||||||
|
|
||||||
@@ -515,10 +605,8 @@ common_interrupt:
|
|||||||
ret_from_intr:
|
ret_from_intr:
|
||||||
DISABLE_INTERRUPTS(CLBR_ANY)
|
DISABLE_INTERRUPTS(CLBR_ANY)
|
||||||
TRACE_IRQS_OFF
|
TRACE_IRQS_OFF
|
||||||
decl PER_CPU_VAR(irq_count)
|
|
||||||
|
|
||||||
/* Restore saved previous stack */
|
LEAVE_IRQ_STACK
|
||||||
popq %rsp
|
|
||||||
|
|
||||||
testb $3, CS(%rsp)
|
testb $3, CS(%rsp)
|
||||||
jz retint_kernel
|
jz retint_kernel
|
||||||
@@ -561,6 +649,7 @@ restore_c_regs_and_iret:
|
|||||||
INTERRUPT_RETURN
|
INTERRUPT_RETURN
|
||||||
|
|
||||||
ENTRY(native_iret)
|
ENTRY(native_iret)
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
/*
|
/*
|
||||||
* Are we returning to a stack segment from the LDT? Note: in
|
* Are we returning to a stack segment from the LDT? Note: in
|
||||||
* 64-bit mode SS:RSP on the exception stack is always valid.
|
* 64-bit mode SS:RSP on the exception stack is always valid.
|
||||||
@@ -633,6 +722,7 @@ native_irq_return_ldt:
|
|||||||
orq PER_CPU_VAR(espfix_stack), %rax
|
orq PER_CPU_VAR(espfix_stack), %rax
|
||||||
SWAPGS
|
SWAPGS
|
||||||
movq %rax, %rsp
|
movq %rax, %rsp
|
||||||
|
UNWIND_HINT_IRET_REGS offset=8
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* At this point, we cannot write to the stack any more, but we can
|
* At this point, we cannot write to the stack any more, but we can
|
||||||
@@ -654,6 +744,7 @@ END(common_interrupt)
|
|||||||
*/
|
*/
|
||||||
.macro apicinterrupt3 num sym do_sym
|
.macro apicinterrupt3 num sym do_sym
|
||||||
ENTRY(\sym)
|
ENTRY(\sym)
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
ASM_CLAC
|
ASM_CLAC
|
||||||
pushq $~(\num)
|
pushq $~(\num)
|
||||||
.Lcommon_\sym:
|
.Lcommon_\sym:
|
||||||
@@ -740,6 +831,8 @@ apicinterrupt IRQ_WORK_VECTOR irq_work_interrupt smp_irq_work_interrupt
|
|||||||
|
|
||||||
.macro idtentry sym do_sym has_error_code:req paranoid=0 shift_ist=-1
|
.macro idtentry sym do_sym has_error_code:req paranoid=0 shift_ist=-1
|
||||||
ENTRY(\sym)
|
ENTRY(\sym)
|
||||||
|
UNWIND_HINT_IRET_REGS offset=8
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
.if \shift_ist != -1 && \paranoid == 0
|
.if \shift_ist != -1 && \paranoid == 0
|
||||||
.error "using shift_ist requires paranoid=1"
|
.error "using shift_ist requires paranoid=1"
|
||||||
@@ -763,6 +856,7 @@ ENTRY(\sym)
|
|||||||
.else
|
.else
|
||||||
call error_entry
|
call error_entry
|
||||||
.endif
|
.endif
|
||||||
|
UNWIND_HINT_REGS
|
||||||
/* returned flag: ebx=0: need swapgs on exit, ebx=1: don't need it */
|
/* returned flag: ebx=0: need swapgs on exit, ebx=1: don't need it */
|
||||||
|
|
||||||
.if \paranoid
|
.if \paranoid
|
||||||
@@ -860,6 +954,7 @@ idtentry simd_coprocessor_error do_simd_coprocessor_error has_error_code=0
|
|||||||
* edi: new selector
|
* edi: new selector
|
||||||
*/
|
*/
|
||||||
ENTRY(native_load_gs_index)
|
ENTRY(native_load_gs_index)
|
||||||
|
FRAME_BEGIN
|
||||||
pushfq
|
pushfq
|
||||||
DISABLE_INTERRUPTS(CLBR_ANY & ~CLBR_RDI)
|
DISABLE_INTERRUPTS(CLBR_ANY & ~CLBR_RDI)
|
||||||
SWAPGS
|
SWAPGS
|
||||||
@@ -868,8 +963,9 @@ ENTRY(native_load_gs_index)
|
|||||||
2: ALTERNATIVE "", "mfence", X86_BUG_SWAPGS_FENCE
|
2: ALTERNATIVE "", "mfence", X86_BUG_SWAPGS_FENCE
|
||||||
SWAPGS
|
SWAPGS
|
||||||
popfq
|
popfq
|
||||||
|
FRAME_END
|
||||||
ret
|
ret
|
||||||
END(native_load_gs_index)
|
ENDPROC(native_load_gs_index)
|
||||||
EXPORT_SYMBOL(native_load_gs_index)
|
EXPORT_SYMBOL(native_load_gs_index)
|
||||||
|
|
||||||
_ASM_EXTABLE(.Lgs_change, bad_gs)
|
_ASM_EXTABLE(.Lgs_change, bad_gs)
|
||||||
@@ -892,14 +988,12 @@ bad_gs:
|
|||||||
ENTRY(do_softirq_own_stack)
|
ENTRY(do_softirq_own_stack)
|
||||||
pushq %rbp
|
pushq %rbp
|
||||||
mov %rsp, %rbp
|
mov %rsp, %rbp
|
||||||
incl PER_CPU_VAR(irq_count)
|
ENTER_IRQ_STACK regs=0 old_rsp=%r11
|
||||||
cmove PER_CPU_VAR(irq_stack_ptr), %rsp
|
|
||||||
push %rbp /* frame pointer backlink */
|
|
||||||
call __do_softirq
|
call __do_softirq
|
||||||
|
LEAVE_IRQ_STACK regs=0
|
||||||
leaveq
|
leaveq
|
||||||
decl PER_CPU_VAR(irq_count)
|
|
||||||
ret
|
ret
|
||||||
END(do_softirq_own_stack)
|
ENDPROC(do_softirq_own_stack)
|
||||||
|
|
||||||
#ifdef CONFIG_XEN
|
#ifdef CONFIG_XEN
|
||||||
idtentry xen_hypervisor_callback xen_do_hypervisor_callback has_error_code=0
|
idtentry xen_hypervisor_callback xen_do_hypervisor_callback has_error_code=0
|
||||||
@@ -923,14 +1017,14 @@ ENTRY(xen_do_hypervisor_callback) /* do_hypervisor_callback(struct *pt_regs) */
|
|||||||
* Since we don't modify %rdi, evtchn_do_upall(struct *pt_regs) will
|
* Since we don't modify %rdi, evtchn_do_upall(struct *pt_regs) will
|
||||||
* see the correct pointer to the pt_regs
|
* see the correct pointer to the pt_regs
|
||||||
*/
|
*/
|
||||||
|
UNWIND_HINT_FUNC
|
||||||
movq %rdi, %rsp /* we don't return, adjust the stack frame */
|
movq %rdi, %rsp /* we don't return, adjust the stack frame */
|
||||||
11: incl PER_CPU_VAR(irq_count)
|
UNWIND_HINT_REGS
|
||||||
movq %rsp, %rbp
|
|
||||||
cmovzq PER_CPU_VAR(irq_stack_ptr), %rsp
|
ENTER_IRQ_STACK old_rsp=%r10
|
||||||
pushq %rbp /* frame pointer backlink */
|
|
||||||
call xen_evtchn_do_upcall
|
call xen_evtchn_do_upcall
|
||||||
popq %rsp
|
LEAVE_IRQ_STACK
|
||||||
decl PER_CPU_VAR(irq_count)
|
|
||||||
#ifndef CONFIG_PREEMPT
|
#ifndef CONFIG_PREEMPT
|
||||||
call xen_maybe_preempt_hcall
|
call xen_maybe_preempt_hcall
|
||||||
#endif
|
#endif
|
||||||
@@ -951,6 +1045,7 @@ END(xen_do_hypervisor_callback)
|
|||||||
* with its current contents: any discrepancy means we in category 1.
|
* with its current contents: any discrepancy means we in category 1.
|
||||||
*/
|
*/
|
||||||
ENTRY(xen_failsafe_callback)
|
ENTRY(xen_failsafe_callback)
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
movl %ds, %ecx
|
movl %ds, %ecx
|
||||||
cmpw %cx, 0x10(%rsp)
|
cmpw %cx, 0x10(%rsp)
|
||||||
jne 1f
|
jne 1f
|
||||||
@@ -970,11 +1065,13 @@ ENTRY(xen_failsafe_callback)
|
|||||||
pushq $0 /* RIP */
|
pushq $0 /* RIP */
|
||||||
pushq %r11
|
pushq %r11
|
||||||
pushq %rcx
|
pushq %rcx
|
||||||
|
UNWIND_HINT_IRET_REGS offset=8
|
||||||
jmp general_protection
|
jmp general_protection
|
||||||
1: /* Segment mismatch => Category 1 (Bad segment). Retry the IRET. */
|
1: /* Segment mismatch => Category 1 (Bad segment). Retry the IRET. */
|
||||||
movq (%rsp), %rcx
|
movq (%rsp), %rcx
|
||||||
movq 8(%rsp), %r11
|
movq 8(%rsp), %r11
|
||||||
addq $0x30, %rsp
|
addq $0x30, %rsp
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
pushq $-1 /* orig_ax = -1 => not a system call */
|
pushq $-1 /* orig_ax = -1 => not a system call */
|
||||||
ALLOC_PT_GPREGS_ON_STACK
|
ALLOC_PT_GPREGS_ON_STACK
|
||||||
SAVE_C_REGS
|
SAVE_C_REGS
|
||||||
@@ -1020,6 +1117,7 @@ idtentry machine_check has_error_code=0 paranoid=1 do_sym=*machine_check_vec
|
|||||||
* Return: ebx=0: need swapgs on exit, ebx=1: otherwise
|
* Return: ebx=0: need swapgs on exit, ebx=1: otherwise
|
||||||
*/
|
*/
|
||||||
ENTRY(paranoid_entry)
|
ENTRY(paranoid_entry)
|
||||||
|
UNWIND_HINT_FUNC
|
||||||
cld
|
cld
|
||||||
SAVE_C_REGS 8
|
SAVE_C_REGS 8
|
||||||
SAVE_EXTRA_REGS 8
|
SAVE_EXTRA_REGS 8
|
||||||
@@ -1047,6 +1145,7 @@ END(paranoid_entry)
|
|||||||
* On entry, ebx is "no swapgs" flag (1: don't need swapgs, 0: need it)
|
* On entry, ebx is "no swapgs" flag (1: don't need swapgs, 0: need it)
|
||||||
*/
|
*/
|
||||||
ENTRY(paranoid_exit)
|
ENTRY(paranoid_exit)
|
||||||
|
UNWIND_HINT_REGS
|
||||||
DISABLE_INTERRUPTS(CLBR_ANY)
|
DISABLE_INTERRUPTS(CLBR_ANY)
|
||||||
TRACE_IRQS_OFF_DEBUG
|
TRACE_IRQS_OFF_DEBUG
|
||||||
testl %ebx, %ebx /* swapgs needed? */
|
testl %ebx, %ebx /* swapgs needed? */
|
||||||
@@ -1068,6 +1167,7 @@ END(paranoid_exit)
|
|||||||
* Return: EBX=0: came from user mode; EBX=1: otherwise
|
* Return: EBX=0: came from user mode; EBX=1: otherwise
|
||||||
*/
|
*/
|
||||||
ENTRY(error_entry)
|
ENTRY(error_entry)
|
||||||
|
UNWIND_HINT_FUNC
|
||||||
cld
|
cld
|
||||||
SAVE_C_REGS 8
|
SAVE_C_REGS 8
|
||||||
SAVE_EXTRA_REGS 8
|
SAVE_EXTRA_REGS 8
|
||||||
@@ -1152,6 +1252,7 @@ END(error_entry)
|
|||||||
* 0: user gsbase is loaded, we need SWAPGS and standard preparation for return to usermode
|
* 0: user gsbase is loaded, we need SWAPGS and standard preparation for return to usermode
|
||||||
*/
|
*/
|
||||||
ENTRY(error_exit)
|
ENTRY(error_exit)
|
||||||
|
UNWIND_HINT_REGS
|
||||||
DISABLE_INTERRUPTS(CLBR_ANY)
|
DISABLE_INTERRUPTS(CLBR_ANY)
|
||||||
TRACE_IRQS_OFF
|
TRACE_IRQS_OFF
|
||||||
testl %ebx, %ebx
|
testl %ebx, %ebx
|
||||||
@@ -1161,6 +1262,7 @@ END(error_exit)
|
|||||||
|
|
||||||
/* Runs on exception stack */
|
/* Runs on exception stack */
|
||||||
ENTRY(nmi)
|
ENTRY(nmi)
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
/*
|
/*
|
||||||
* Fix up the exception frame if we're on Xen.
|
* Fix up the exception frame if we're on Xen.
|
||||||
* PARAVIRT_ADJUST_EXCEPTION_FRAME is guaranteed to push at most
|
* PARAVIRT_ADJUST_EXCEPTION_FRAME is guaranteed to push at most
|
||||||
@@ -1232,11 +1334,13 @@ ENTRY(nmi)
|
|||||||
cld
|
cld
|
||||||
movq %rsp, %rdx
|
movq %rsp, %rdx
|
||||||
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
|
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
|
||||||
|
UNWIND_HINT_IRET_REGS base=%rdx offset=8
|
||||||
pushq 5*8(%rdx) /* pt_regs->ss */
|
pushq 5*8(%rdx) /* pt_regs->ss */
|
||||||
pushq 4*8(%rdx) /* pt_regs->rsp */
|
pushq 4*8(%rdx) /* pt_regs->rsp */
|
||||||
pushq 3*8(%rdx) /* pt_regs->flags */
|
pushq 3*8(%rdx) /* pt_regs->flags */
|
||||||
pushq 2*8(%rdx) /* pt_regs->cs */
|
pushq 2*8(%rdx) /* pt_regs->cs */
|
||||||
pushq 1*8(%rdx) /* pt_regs->rip */
|
pushq 1*8(%rdx) /* pt_regs->rip */
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
pushq $-1 /* pt_regs->orig_ax */
|
pushq $-1 /* pt_regs->orig_ax */
|
||||||
pushq %rdi /* pt_regs->di */
|
pushq %rdi /* pt_regs->di */
|
||||||
pushq %rsi /* pt_regs->si */
|
pushq %rsi /* pt_regs->si */
|
||||||
@@ -1253,6 +1357,7 @@ ENTRY(nmi)
|
|||||||
pushq %r13 /* pt_regs->r13 */
|
pushq %r13 /* pt_regs->r13 */
|
||||||
pushq %r14 /* pt_regs->r14 */
|
pushq %r14 /* pt_regs->r14 */
|
||||||
pushq %r15 /* pt_regs->r15 */
|
pushq %r15 /* pt_regs->r15 */
|
||||||
|
UNWIND_HINT_REGS
|
||||||
ENCODE_FRAME_POINTER
|
ENCODE_FRAME_POINTER
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1407,6 +1512,7 @@ first_nmi:
|
|||||||
.rept 5
|
.rept 5
|
||||||
pushq 11*8(%rsp)
|
pushq 11*8(%rsp)
|
||||||
.endr
|
.endr
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
|
|
||||||
/* Everything up to here is safe from nested NMIs */
|
/* Everything up to here is safe from nested NMIs */
|
||||||
|
|
||||||
@@ -1422,6 +1528,7 @@ first_nmi:
|
|||||||
pushq $__KERNEL_CS /* CS */
|
pushq $__KERNEL_CS /* CS */
|
||||||
pushq $1f /* RIP */
|
pushq $1f /* RIP */
|
||||||
INTERRUPT_RETURN /* continues at repeat_nmi below */
|
INTERRUPT_RETURN /* continues at repeat_nmi below */
|
||||||
|
UNWIND_HINT_IRET_REGS
|
||||||
1:
|
1:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1471,6 +1578,7 @@ end_repeat_nmi:
|
|||||||
* exceptions might do.
|
* exceptions might do.
|
||||||
*/
|
*/
|
||||||
call paranoid_entry
|
call paranoid_entry
|
||||||
|
UNWIND_HINT_REGS
|
||||||
|
|
||||||
/* paranoidentry do_nmi, 0; without TRACE_IRQS_OFF */
|
/* paranoidentry do_nmi, 0; without TRACE_IRQS_OFF */
|
||||||
movq %rsp, %rdi
|
movq %rsp, %rdi
|
||||||
@@ -1508,17 +1616,19 @@ nmi_restore:
|
|||||||
END(nmi)
|
END(nmi)
|
||||||
|
|
||||||
ENTRY(ignore_sysret)
|
ENTRY(ignore_sysret)
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
mov $-ENOSYS, %eax
|
mov $-ENOSYS, %eax
|
||||||
sysret
|
sysret
|
||||||
END(ignore_sysret)
|
END(ignore_sysret)
|
||||||
|
|
||||||
ENTRY(rewind_stack_do_exit)
|
ENTRY(rewind_stack_do_exit)
|
||||||
|
UNWIND_HINT_FUNC
|
||||||
/* Prevent any naive code from trying to unwind to our caller. */
|
/* Prevent any naive code from trying to unwind to our caller. */
|
||||||
xorl %ebp, %ebp
|
xorl %ebp, %ebp
|
||||||
|
|
||||||
movq PER_CPU_VAR(cpu_current_top_of_stack), %rax
|
movq PER_CPU_VAR(cpu_current_top_of_stack), %rax
|
||||||
leaq -TOP_OF_KERNEL_STACK_PADDING-PTREGS_SIZE(%rax), %rsp
|
leaq -PTREGS_SIZE(%rax), %rsp
|
||||||
|
UNWIND_HINT_FUNC sp_offset=PTREGS_SIZE
|
||||||
|
|
||||||
call do_exit
|
call do_exit
|
||||||
1: jmp 1b
|
|
||||||
END(rewind_stack_do_exit)
|
END(rewind_stack_do_exit)
|
||||||
|
|||||||
+47
-51
@@ -69,6 +69,9 @@ build_mmio_write(__writeb, "b", unsigned char, "q", )
|
|||||||
build_mmio_write(__writew, "w", unsigned short, "r", )
|
build_mmio_write(__writew, "w", unsigned short, "r", )
|
||||||
build_mmio_write(__writel, "l", unsigned int, "r", )
|
build_mmio_write(__writel, "l", unsigned int, "r", )
|
||||||
|
|
||||||
|
#define readb readb
|
||||||
|
#define readw readw
|
||||||
|
#define readl readl
|
||||||
#define readb_relaxed(a) __readb(a)
|
#define readb_relaxed(a) __readb(a)
|
||||||
#define readw_relaxed(a) __readw(a)
|
#define readw_relaxed(a) __readw(a)
|
||||||
#define readl_relaxed(a) __readl(a)
|
#define readl_relaxed(a) __readl(a)
|
||||||
@@ -76,6 +79,9 @@ build_mmio_write(__writel, "l", unsigned int, "r", )
|
|||||||
#define __raw_readw __readw
|
#define __raw_readw __readw
|
||||||
#define __raw_readl __readl
|
#define __raw_readl __readl
|
||||||
|
|
||||||
|
#define writeb writeb
|
||||||
|
#define writew writew
|
||||||
|
#define writel writel
|
||||||
#define writeb_relaxed(v, a) __writeb(v, a)
|
#define writeb_relaxed(v, a) __writeb(v, a)
|
||||||
#define writew_relaxed(v, a) __writew(v, a)
|
#define writew_relaxed(v, a) __writew(v, a)
|
||||||
#define writel_relaxed(v, a) __writel(v, a)
|
#define writel_relaxed(v, a) __writel(v, a)
|
||||||
@@ -88,13 +94,15 @@ build_mmio_write(__writel, "l", unsigned int, "r", )
|
|||||||
#ifdef CONFIG_X86_64
|
#ifdef CONFIG_X86_64
|
||||||
|
|
||||||
build_mmio_read(readq, "q", unsigned long, "=r", :"memory")
|
build_mmio_read(readq, "q", unsigned long, "=r", :"memory")
|
||||||
|
build_mmio_read(__readq, "q", unsigned long, "=r", )
|
||||||
build_mmio_write(writeq, "q", unsigned long, "r", :"memory")
|
build_mmio_write(writeq, "q", unsigned long, "r", :"memory")
|
||||||
|
build_mmio_write(__writeq, "q", unsigned long, "r", )
|
||||||
|
|
||||||
#define readq_relaxed(a) readq(a)
|
#define readq_relaxed(a) __readq(a)
|
||||||
#define writeq_relaxed(v, a) writeq(v, a)
|
#define writeq_relaxed(v, a) __writeq(v, a)
|
||||||
|
|
||||||
#define __raw_readq(a) readq(a)
|
#define __raw_readq __readq
|
||||||
#define __raw_writeq(val, addr) writeq(val, addr)
|
#define __raw_writeq __writeq
|
||||||
|
|
||||||
/* Let people know that we have them */
|
/* Let people know that we have them */
|
||||||
#define readq readq
|
#define readq readq
|
||||||
@@ -119,6 +127,7 @@ static inline phys_addr_t virt_to_phys(volatile void *address)
|
|||||||
{
|
{
|
||||||
return __pa(address);
|
return __pa(address);
|
||||||
}
|
}
|
||||||
|
#define virt_to_phys virt_to_phys
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* phys_to_virt - map physical address to virtual
|
* phys_to_virt - map physical address to virtual
|
||||||
@@ -137,6 +146,7 @@ static inline void *phys_to_virt(phys_addr_t address)
|
|||||||
{
|
{
|
||||||
return __va(address);
|
return __va(address);
|
||||||
}
|
}
|
||||||
|
#define phys_to_virt phys_to_virt
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Change "struct page" to physical address.
|
* Change "struct page" to physical address.
|
||||||
@@ -169,11 +179,14 @@ static inline unsigned int isa_virt_to_bus(volatile void *address)
|
|||||||
* else, you probably want one of the following.
|
* else, you probably want one of the following.
|
||||||
*/
|
*/
|
||||||
extern void __iomem *ioremap_nocache(resource_size_t offset, unsigned long size);
|
extern void __iomem *ioremap_nocache(resource_size_t offset, unsigned long size);
|
||||||
|
#define ioremap_nocache ioremap_nocache
|
||||||
extern void __iomem *ioremap_uc(resource_size_t offset, unsigned long size);
|
extern void __iomem *ioremap_uc(resource_size_t offset, unsigned long size);
|
||||||
#define ioremap_uc ioremap_uc
|
#define ioremap_uc ioremap_uc
|
||||||
|
|
||||||
extern void __iomem *ioremap_cache(resource_size_t offset, unsigned long size);
|
extern void __iomem *ioremap_cache(resource_size_t offset, unsigned long size);
|
||||||
|
#define ioremap_cache ioremap_cache
|
||||||
extern void __iomem *ioremap_prot(resource_size_t offset, unsigned long size, unsigned long prot_val);
|
extern void __iomem *ioremap_prot(resource_size_t offset, unsigned long size, unsigned long prot_val);
|
||||||
|
#define ioremap_prot ioremap_prot
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ioremap - map bus memory into CPU space
|
* ioremap - map bus memory into CPU space
|
||||||
@@ -193,8 +206,10 @@ static inline void __iomem *ioremap(resource_size_t offset, unsigned long size)
|
|||||||
{
|
{
|
||||||
return ioremap_nocache(offset, size);
|
return ioremap_nocache(offset, size);
|
||||||
}
|
}
|
||||||
|
#define ioremap ioremap
|
||||||
|
|
||||||
extern void iounmap(volatile void __iomem *addr);
|
extern void iounmap(volatile void __iomem *addr);
|
||||||
|
#define iounmap iounmap
|
||||||
|
|
||||||
extern void set_iounmap_nonlazy(void);
|
extern void set_iounmap_nonlazy(void);
|
||||||
|
|
||||||
@@ -202,53 +217,6 @@ extern void set_iounmap_nonlazy(void);
|
|||||||
|
|
||||||
#include <asm-generic/iomap.h>
|
#include <asm-generic/iomap.h>
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a virtual cached pointer to an uncached pointer
|
|
||||||
*/
|
|
||||||
#define xlate_dev_kmem_ptr(p) p
|
|
||||||
|
|
||||||
/**
|
|
||||||
* memset_io Set a range of I/O memory to a constant value
|
|
||||||
* @addr: The beginning of the I/O-memory range to set
|
|
||||||
* @val: The value to set the memory to
|
|
||||||
* @count: The number of bytes to set
|
|
||||||
*
|
|
||||||
* Set a range of I/O memory to a given value.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
memset_io(volatile void __iomem *addr, unsigned char val, size_t count)
|
|
||||||
{
|
|
||||||
memset((void __force *)addr, val, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* memcpy_fromio Copy a block of data from I/O memory
|
|
||||||
* @dst: The (RAM) destination for the copy
|
|
||||||
* @src: The (I/O memory) source for the data
|
|
||||||
* @count: The number of bytes to copy
|
|
||||||
*
|
|
||||||
* Copy a block of data from I/O memory.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
memcpy_fromio(void *dst, const volatile void __iomem *src, size_t count)
|
|
||||||
{
|
|
||||||
memcpy(dst, (const void __force *)src, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* memcpy_toio Copy a block of data into I/O memory
|
|
||||||
* @dst: The (I/O memory) destination for the copy
|
|
||||||
* @src: The (RAM) source for the data
|
|
||||||
* @count: The number of bytes to copy
|
|
||||||
*
|
|
||||||
* Copy a block of data to I/O memory.
|
|
||||||
*/
|
|
||||||
static inline void
|
|
||||||
memcpy_toio(volatile void __iomem *dst, const void *src, size_t count)
|
|
||||||
{
|
|
||||||
memcpy((void __force *)dst, src, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ISA space is 'always mapped' on a typical x86 system, no need to
|
* ISA space is 'always mapped' on a typical x86 system, no need to
|
||||||
* explicitly ioremap() it. The fact that the ISA IO space is mapped
|
* explicitly ioremap() it. The fact that the ISA IO space is mapped
|
||||||
@@ -341,13 +309,38 @@ BUILDIO(b, b, char)
|
|||||||
BUILDIO(w, w, short)
|
BUILDIO(w, w, short)
|
||||||
BUILDIO(l, , int)
|
BUILDIO(l, , int)
|
||||||
|
|
||||||
|
#define inb inb
|
||||||
|
#define inw inw
|
||||||
|
#define inl inl
|
||||||
|
#define inb_p inb_p
|
||||||
|
#define inw_p inw_p
|
||||||
|
#define inl_p inl_p
|
||||||
|
#define insb insb
|
||||||
|
#define insw insw
|
||||||
|
#define insl insl
|
||||||
|
|
||||||
|
#define outb outb
|
||||||
|
#define outw outw
|
||||||
|
#define outl outl
|
||||||
|
#define outb_p outb_p
|
||||||
|
#define outw_p outw_p
|
||||||
|
#define outl_p outl_p
|
||||||
|
#define outsb outsb
|
||||||
|
#define outsw outsw
|
||||||
|
#define outsl outsl
|
||||||
|
|
||||||
extern void *xlate_dev_mem_ptr(phys_addr_t phys);
|
extern void *xlate_dev_mem_ptr(phys_addr_t phys);
|
||||||
extern void unxlate_dev_mem_ptr(phys_addr_t phys, void *addr);
|
extern void unxlate_dev_mem_ptr(phys_addr_t phys, void *addr);
|
||||||
|
|
||||||
|
#define xlate_dev_mem_ptr xlate_dev_mem_ptr
|
||||||
|
#define unxlate_dev_mem_ptr unxlate_dev_mem_ptr
|
||||||
|
|
||||||
extern int ioremap_change_attr(unsigned long vaddr, unsigned long size,
|
extern int ioremap_change_attr(unsigned long vaddr, unsigned long size,
|
||||||
enum page_cache_mode pcm);
|
enum page_cache_mode pcm);
|
||||||
extern void __iomem *ioremap_wc(resource_size_t offset, unsigned long size);
|
extern void __iomem *ioremap_wc(resource_size_t offset, unsigned long size);
|
||||||
|
#define ioremap_wc ioremap_wc
|
||||||
extern void __iomem *ioremap_wt(resource_size_t offset, unsigned long size);
|
extern void __iomem *ioremap_wt(resource_size_t offset, unsigned long size);
|
||||||
|
#define ioremap_wt ioremap_wt
|
||||||
|
|
||||||
extern bool is_early_ioremap_ptep(pte_t *ptep);
|
extern bool is_early_ioremap_ptep(pte_t *ptep);
|
||||||
|
|
||||||
@@ -365,6 +358,9 @@ extern bool xen_biovec_phys_mergeable(const struct bio_vec *vec1,
|
|||||||
|
|
||||||
#define IO_SPACE_LIMIT 0xffff
|
#define IO_SPACE_LIMIT 0xffff
|
||||||
|
|
||||||
|
#include <asm-generic/io.h>
|
||||||
|
#undef PCI_IOBASE
|
||||||
|
|
||||||
#ifdef CONFIG_MTRR
|
#ifdef CONFIG_MTRR
|
||||||
extern int __must_check arch_phys_wc_index(int handle);
|
extern int __must_check arch_phys_wc_index(int handle);
|
||||||
#define arch_phys_wc_index arch_phys_wc_index
|
#define arch_phys_wc_index arch_phys_wc_index
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ORC_TYPES_H
|
||||||
|
#define _ORC_TYPES_H
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/compiler.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ORC_REG_* registers are base registers which are used to find other
|
||||||
|
* registers on the stack.
|
||||||
|
*
|
||||||
|
* ORC_REG_PREV_SP, also known as DWARF Call Frame Address (CFA), is the
|
||||||
|
* address of the previous frame: the caller's SP before it called the current
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* ORC_REG_UNDEFINED means the corresponding register's value didn't change in
|
||||||
|
* the current frame.
|
||||||
|
*
|
||||||
|
* The most commonly used base registers are SP and BP -- which the previous SP
|
||||||
|
* is usually based on -- and PREV_SP and UNDEFINED -- which the previous BP is
|
||||||
|
* usually based on.
|
||||||
|
*
|
||||||
|
* The rest of the base registers are needed for special cases like entry code
|
||||||
|
* and GCC realigned stacks.
|
||||||
|
*/
|
||||||
|
#define ORC_REG_UNDEFINED 0
|
||||||
|
#define ORC_REG_PREV_SP 1
|
||||||
|
#define ORC_REG_DX 2
|
||||||
|
#define ORC_REG_DI 3
|
||||||
|
#define ORC_REG_BP 4
|
||||||
|
#define ORC_REG_SP 5
|
||||||
|
#define ORC_REG_R10 6
|
||||||
|
#define ORC_REG_R13 7
|
||||||
|
#define ORC_REG_BP_INDIRECT 8
|
||||||
|
#define ORC_REG_SP_INDIRECT 9
|
||||||
|
#define ORC_REG_MAX 15
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ORC_TYPE_CALL: Indicates that sp_reg+sp_offset resolves to PREV_SP (the
|
||||||
|
* caller's SP right before it made the call). Used for all callable
|
||||||
|
* functions, i.e. all C code and all callable asm functions.
|
||||||
|
*
|
||||||
|
* ORC_TYPE_REGS: Used in entry code to indicate that sp_reg+sp_offset points
|
||||||
|
* to a fully populated pt_regs from a syscall, interrupt, or exception.
|
||||||
|
*
|
||||||
|
* ORC_TYPE_REGS_IRET: Used in entry code to indicate that sp_reg+sp_offset
|
||||||
|
* points to the iret return frame.
|
||||||
|
*
|
||||||
|
* The UNWIND_HINT macros are used only for the unwind_hint struct. They
|
||||||
|
* aren't used in struct orc_entry due to size and complexity constraints.
|
||||||
|
* Objtool converts them to real types when it converts the hints to orc
|
||||||
|
* entries.
|
||||||
|
*/
|
||||||
|
#define ORC_TYPE_CALL 0
|
||||||
|
#define ORC_TYPE_REGS 1
|
||||||
|
#define ORC_TYPE_REGS_IRET 2
|
||||||
|
#define UNWIND_HINT_TYPE_SAVE 3
|
||||||
|
#define UNWIND_HINT_TYPE_RESTORE 4
|
||||||
|
|
||||||
|
#ifndef __ASSEMBLY__
|
||||||
|
/*
|
||||||
|
* This struct is more or less a vastly simplified version of the DWARF Call
|
||||||
|
* Frame Information standard. It contains only the necessary parts of DWARF
|
||||||
|
* CFI, simplified for ease of access by the in-kernel unwinder. It tells the
|
||||||
|
* unwinder how to find the previous SP and BP (and sometimes entry regs) on
|
||||||
|
* the stack for a given code address. Each instance of the struct corresponds
|
||||||
|
* to one or more code locations.
|
||||||
|
*/
|
||||||
|
struct orc_entry {
|
||||||
|
s16 sp_offset;
|
||||||
|
s16 bp_offset;
|
||||||
|
unsigned sp_reg:4;
|
||||||
|
unsigned bp_reg:4;
|
||||||
|
unsigned type:2;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This struct is used by asm and inline asm code to manually annotate the
|
||||||
|
* location of registers on the stack for the ORC unwinder.
|
||||||
|
*
|
||||||
|
* Type can be either ORC_TYPE_* or UNWIND_HINT_TYPE_*.
|
||||||
|
*/
|
||||||
|
struct unwind_hint {
|
||||||
|
u32 ip;
|
||||||
|
s16 sp_offset;
|
||||||
|
u8 sp_reg;
|
||||||
|
u8 type;
|
||||||
|
};
|
||||||
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
||||||
|
#endif /* _ORC_TYPES_H */
|
||||||
@@ -22,6 +22,7 @@ struct vm86;
|
|||||||
#include <asm/nops.h>
|
#include <asm/nops.h>
|
||||||
#include <asm/special_insns.h>
|
#include <asm/special_insns.h>
|
||||||
#include <asm/fpu/types.h>
|
#include <asm/fpu/types.h>
|
||||||
|
#include <asm/unwind_hints.h>
|
||||||
|
|
||||||
#include <linux/personality.h>
|
#include <linux/personality.h>
|
||||||
#include <linux/cache.h>
|
#include <linux/cache.h>
|
||||||
@@ -684,6 +685,7 @@ static inline void sync_core(void)
|
|||||||
unsigned int tmp;
|
unsigned int tmp;
|
||||||
|
|
||||||
asm volatile (
|
asm volatile (
|
||||||
|
UNWIND_HINT_SAVE
|
||||||
"mov %%ss, %0\n\t"
|
"mov %%ss, %0\n\t"
|
||||||
"pushq %q0\n\t"
|
"pushq %q0\n\t"
|
||||||
"pushq %%rsp\n\t"
|
"pushq %%rsp\n\t"
|
||||||
@@ -693,6 +695,7 @@ static inline void sync_core(void)
|
|||||||
"pushq %q0\n\t"
|
"pushq %q0\n\t"
|
||||||
"pushq $1f\n\t"
|
"pushq $1f\n\t"
|
||||||
"iretq\n\t"
|
"iretq\n\t"
|
||||||
|
UNWIND_HINT_RESTORE
|
||||||
"1:"
|
"1:"
|
||||||
: "=&r" (tmp), "+r" (__sp) : : "cc", "memory");
|
: "=&r" (tmp), "+r" (__sp) : : "cc", "memory");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,45 +1,56 @@
|
|||||||
#ifndef _ASM_X86_RMWcc
|
#ifndef _ASM_X86_RMWcc
|
||||||
#define _ASM_X86_RMWcc
|
#define _ASM_X86_RMWcc
|
||||||
|
|
||||||
|
#define __CLOBBERS_MEM "memory"
|
||||||
|
#define __CLOBBERS_MEM_CC_CX "memory", "cc", "cx"
|
||||||
|
|
||||||
#if !defined(__GCC_ASM_FLAG_OUTPUTS__) && defined(CC_HAVE_ASM_GOTO)
|
#if !defined(__GCC_ASM_FLAG_OUTPUTS__) && defined(CC_HAVE_ASM_GOTO)
|
||||||
|
|
||||||
/* Use asm goto */
|
/* Use asm goto */
|
||||||
|
|
||||||
#define __GEN_RMWcc(fullop, var, cc, ...) \
|
#define __GEN_RMWcc(fullop, var, cc, clobbers, ...) \
|
||||||
do { \
|
do { \
|
||||||
asm_volatile_goto (fullop "; j" #cc " %l[cc_label]" \
|
asm_volatile_goto (fullop "; j" #cc " %l[cc_label]" \
|
||||||
: : "m" (var), ## __VA_ARGS__ \
|
: : [counter] "m" (var), ## __VA_ARGS__ \
|
||||||
: "memory" : cc_label); \
|
: clobbers : cc_label); \
|
||||||
return 0; \
|
return 0; \
|
||||||
cc_label: \
|
cc_label: \
|
||||||
return 1; \
|
return 1; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define GEN_UNARY_RMWcc(op, var, arg0, cc) \
|
#define __BINARY_RMWcc_ARG " %1, "
|
||||||
__GEN_RMWcc(op " " arg0, var, cc)
|
|
||||||
|
|
||||||
#define GEN_BINARY_RMWcc(op, var, vcon, val, arg0, cc) \
|
|
||||||
__GEN_RMWcc(op " %1, " arg0, var, cc, vcon (val))
|
|
||||||
|
|
||||||
#else /* defined(__GCC_ASM_FLAG_OUTPUTS__) || !defined(CC_HAVE_ASM_GOTO) */
|
#else /* defined(__GCC_ASM_FLAG_OUTPUTS__) || !defined(CC_HAVE_ASM_GOTO) */
|
||||||
|
|
||||||
/* Use flags output or a set instruction */
|
/* Use flags output or a set instruction */
|
||||||
|
|
||||||
#define __GEN_RMWcc(fullop, var, cc, ...) \
|
#define __GEN_RMWcc(fullop, var, cc, clobbers, ...) \
|
||||||
do { \
|
do { \
|
||||||
bool c; \
|
bool c; \
|
||||||
asm volatile (fullop ";" CC_SET(cc) \
|
asm volatile (fullop ";" CC_SET(cc) \
|
||||||
: "+m" (var), CC_OUT(cc) (c) \
|
: [counter] "+m" (var), CC_OUT(cc) (c) \
|
||||||
: __VA_ARGS__ : "memory"); \
|
: __VA_ARGS__ : clobbers); \
|
||||||
return c; \
|
return c; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define GEN_UNARY_RMWcc(op, var, arg0, cc) \
|
#define __BINARY_RMWcc_ARG " %2, "
|
||||||
__GEN_RMWcc(op " " arg0, var, cc)
|
|
||||||
|
|
||||||
#define GEN_BINARY_RMWcc(op, var, vcon, val, arg0, cc) \
|
|
||||||
__GEN_RMWcc(op " %2, " arg0, var, cc, vcon (val))
|
|
||||||
|
|
||||||
#endif /* defined(__GCC_ASM_FLAG_OUTPUTS__) || !defined(CC_HAVE_ASM_GOTO) */
|
#endif /* defined(__GCC_ASM_FLAG_OUTPUTS__) || !defined(CC_HAVE_ASM_GOTO) */
|
||||||
|
|
||||||
|
#define GEN_UNARY_RMWcc(op, var, arg0, cc) \
|
||||||
|
__GEN_RMWcc(op " " arg0, var, cc, __CLOBBERS_MEM)
|
||||||
|
|
||||||
|
#define GEN_UNARY_SUFFIXED_RMWcc(op, suffix, var, arg0, cc) \
|
||||||
|
__GEN_RMWcc(op " " arg0 "\n\t" suffix, var, cc, \
|
||||||
|
__CLOBBERS_MEM_CC_CX)
|
||||||
|
|
||||||
|
#define GEN_BINARY_RMWcc(op, var, vcon, val, arg0, cc) \
|
||||||
|
__GEN_RMWcc(op __BINARY_RMWcc_ARG arg0, var, cc, \
|
||||||
|
__CLOBBERS_MEM, vcon (val))
|
||||||
|
|
||||||
|
#define GEN_BINARY_SUFFIXED_RMWcc(op, suffix, var, vcon, val, arg0, cc) \
|
||||||
|
__GEN_RMWcc(op __BINARY_RMWcc_ARG arg0 "\n\t" suffix, var, cc, \
|
||||||
|
__CLOBBERS_MEM_CC_CX, vcon (val))
|
||||||
|
|
||||||
#endif /* _ASM_X86_RMWcc */
|
#endif /* _ASM_X86_RMWcc */
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#ifndef _ASM_X86_UNWIND_HINTS_H
|
||||||
|
#define _ASM_X86_UNWIND_HINTS_H
|
||||||
|
|
||||||
|
#include "orc_types.h"
|
||||||
|
|
||||||
|
#ifdef __ASSEMBLY__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In asm, there are two kinds of code: normal C-type callable functions and
|
||||||
|
* the rest. The normal callable functions can be called by other code, and
|
||||||
|
* don't do anything unusual with the stack. Such normal callable functions
|
||||||
|
* are annotated with the ENTRY/ENDPROC macros. Most asm code falls in this
|
||||||
|
* category. In this case, no special debugging annotations are needed because
|
||||||
|
* objtool can automatically generate the ORC data for the ORC unwinder to read
|
||||||
|
* at runtime.
|
||||||
|
*
|
||||||
|
* Anything which doesn't fall into the above category, such as syscall and
|
||||||
|
* interrupt handlers, tends to not be called directly by other functions, and
|
||||||
|
* often does unusual non-C-function-type things with the stack pointer. Such
|
||||||
|
* code needs to be annotated such that objtool can understand it. The
|
||||||
|
* following CFI hint macros are for this type of code.
|
||||||
|
*
|
||||||
|
* These macros provide hints to objtool about the state of the stack at each
|
||||||
|
* instruction. Objtool starts from the hints and follows the code flow,
|
||||||
|
* making automatic CFI adjustments when it sees pushes and pops, filling out
|
||||||
|
* the debuginfo as necessary. It will also warn if it sees any
|
||||||
|
* inconsistencies.
|
||||||
|
*/
|
||||||
|
.macro UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=0 type=ORC_TYPE_CALL
|
||||||
|
#ifdef CONFIG_STACK_VALIDATION
|
||||||
|
.Lunwind_hint_ip_\@:
|
||||||
|
.pushsection .discard.unwind_hints
|
||||||
|
/* struct unwind_hint */
|
||||||
|
.long .Lunwind_hint_ip_\@ - .
|
||||||
|
.short \sp_offset
|
||||||
|
.byte \sp_reg
|
||||||
|
.byte \type
|
||||||
|
.popsection
|
||||||
|
#endif
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro UNWIND_HINT_EMPTY
|
||||||
|
UNWIND_HINT sp_reg=ORC_REG_UNDEFINED
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro UNWIND_HINT_REGS base=%rsp offset=0 indirect=0 extra=1 iret=0
|
||||||
|
.if \base == %rsp && \indirect
|
||||||
|
.set sp_reg, ORC_REG_SP_INDIRECT
|
||||||
|
.elseif \base == %rsp
|
||||||
|
.set sp_reg, ORC_REG_SP
|
||||||
|
.elseif \base == %rbp
|
||||||
|
.set sp_reg, ORC_REG_BP
|
||||||
|
.elseif \base == %rdi
|
||||||
|
.set sp_reg, ORC_REG_DI
|
||||||
|
.elseif \base == %rdx
|
||||||
|
.set sp_reg, ORC_REG_DX
|
||||||
|
.elseif \base == %r10
|
||||||
|
.set sp_reg, ORC_REG_R10
|
||||||
|
.else
|
||||||
|
.error "UNWIND_HINT_REGS: bad base register"
|
||||||
|
.endif
|
||||||
|
|
||||||
|
.set sp_offset, \offset
|
||||||
|
|
||||||
|
.if \iret
|
||||||
|
.set type, ORC_TYPE_REGS_IRET
|
||||||
|
.elseif \extra == 0
|
||||||
|
.set type, ORC_TYPE_REGS_IRET
|
||||||
|
.set sp_offset, \offset + (16*8)
|
||||||
|
.else
|
||||||
|
.set type, ORC_TYPE_REGS
|
||||||
|
.endif
|
||||||
|
|
||||||
|
UNWIND_HINT sp_reg=sp_reg sp_offset=sp_offset type=type
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro UNWIND_HINT_IRET_REGS base=%rsp offset=0
|
||||||
|
UNWIND_HINT_REGS base=\base offset=\offset iret=1
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro UNWIND_HINT_FUNC sp_offset=8
|
||||||
|
UNWIND_HINT sp_offset=\sp_offset
|
||||||
|
.endm
|
||||||
|
|
||||||
|
#else /* !__ASSEMBLY__ */
|
||||||
|
|
||||||
|
#define UNWIND_HINT(sp_reg, sp_offset, type) \
|
||||||
|
"987: \n\t" \
|
||||||
|
".pushsection .discard.unwind_hints\n\t" \
|
||||||
|
/* struct unwind_hint */ \
|
||||||
|
".long 987b - .\n\t" \
|
||||||
|
".short " __stringify(sp_offset) "\n\t" \
|
||||||
|
".byte " __stringify(sp_reg) "\n\t" \
|
||||||
|
".byte " __stringify(type) "\n\t" \
|
||||||
|
".popsection\n\t"
|
||||||
|
|
||||||
|
#define UNWIND_HINT_SAVE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_SAVE)
|
||||||
|
|
||||||
|
#define UNWIND_HINT_RESTORE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_RESTORE)
|
||||||
|
|
||||||
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
||||||
|
#endif /* _ASM_X86_UNWIND_HINTS_H */
|
||||||
@@ -94,6 +94,9 @@ void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
|
|||||||
if (stack_name)
|
if (stack_name)
|
||||||
printk("%s <%s>\n", log_lvl, stack_name);
|
printk("%s <%s>\n", log_lvl, stack_name);
|
||||||
|
|
||||||
|
if (regs && on_stack(&stack_info, regs, sizeof(*regs)))
|
||||||
|
__show_regs(regs, 0);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scan the stack, printing any text addresses we find. At the
|
* Scan the stack, printing any text addresses we find. At the
|
||||||
* same time, follow proper stack frames with the unwinder.
|
* same time, follow proper stack frames with the unwinder.
|
||||||
@@ -118,10 +121,8 @@ void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
|
|||||||
* Don't print regs->ip again if it was already printed
|
* Don't print regs->ip again if it was already printed
|
||||||
* by __show_regs() below.
|
* by __show_regs() below.
|
||||||
*/
|
*/
|
||||||
if (regs && stack == ®s->ip) {
|
if (regs && stack == ®s->ip)
|
||||||
unwind_next_frame(&state);
|
goto next;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stack == ret_addr_p)
|
if (stack == ret_addr_p)
|
||||||
reliable = 1;
|
reliable = 1;
|
||||||
@@ -144,6 +145,7 @@ void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
|
|||||||
if (!reliable)
|
if (!reliable)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
next:
|
||||||
/*
|
/*
|
||||||
* Get the next frame from the unwinder. No need to
|
* Get the next frame from the unwinder. No need to
|
||||||
* check for an error: if anything goes wrong, the rest
|
* check for an error: if anything goes wrong, the rest
|
||||||
@@ -153,7 +155,7 @@ void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
|
|||||||
|
|
||||||
/* if the frame has entry regs, print them */
|
/* if the frame has entry regs, print them */
|
||||||
regs = unwind_get_entry_regs(&state);
|
regs = unwind_get_entry_regs(&state);
|
||||||
if (regs)
|
if (regs && on_stack(&stack_info, regs, sizeof(*regs)))
|
||||||
__show_regs(regs, 0);
|
__show_regs(regs, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ static bool in_hardirq_stack(unsigned long *stack, struct stack_info *info)
|
|||||||
* This is a software stack, so 'end' can be a valid stack pointer.
|
* This is a software stack, so 'end' can be a valid stack pointer.
|
||||||
* It just means the stack is empty.
|
* It just means the stack is empty.
|
||||||
*/
|
*/
|
||||||
if (stack < begin || stack > end)
|
if (stack <= begin || stack > end)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
info->type = STACK_TYPE_IRQ;
|
info->type = STACK_TYPE_IRQ;
|
||||||
@@ -62,7 +62,7 @@ static bool in_softirq_stack(unsigned long *stack, struct stack_info *info)
|
|||||||
* This is a software stack, so 'end' can be a valid stack pointer.
|
* This is a software stack, so 'end' can be a valid stack pointer.
|
||||||
* It just means the stack is empty.
|
* It just means the stack is empty.
|
||||||
*/
|
*/
|
||||||
if (stack < begin || stack > end)
|
if (stack <= begin || stack > end)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
info->type = STACK_TYPE_SOFTIRQ;
|
info->type = STACK_TYPE_SOFTIRQ;
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ static bool in_exception_stack(unsigned long *stack, struct stack_info *info)
|
|||||||
begin = end - (exception_stack_sizes[k] / sizeof(long));
|
begin = end - (exception_stack_sizes[k] / sizeof(long));
|
||||||
regs = (struct pt_regs *)end - 1;
|
regs = (struct pt_regs *)end - 1;
|
||||||
|
|
||||||
if (stack < begin || stack >= end)
|
if (stack <= begin || stack >= end)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
info->type = STACK_TYPE_EXCEPTION + k;
|
info->type = STACK_TYPE_EXCEPTION + k;
|
||||||
@@ -78,7 +78,7 @@ static bool in_irq_stack(unsigned long *stack, struct stack_info *info)
|
|||||||
* This is a software stack, so 'end' can be a valid stack pointer.
|
* This is a software stack, so 'end' can be a valid stack pointer.
|
||||||
* It just means the stack is empty.
|
* It just means the stack is empty.
|
||||||
*/
|
*/
|
||||||
if (stack < begin || stack > end)
|
if (stack <= begin || stack > end)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
info->type = STACK_TYPE_IRQ;
|
info->type = STACK_TYPE_IRQ;
|
||||||
|
|||||||
@@ -279,6 +279,9 @@ __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
|
|||||||
struct tss_struct *tss = &per_cpu(cpu_tss, cpu);
|
struct tss_struct *tss = &per_cpu(cpu_tss, cpu);
|
||||||
unsigned prev_fsindex, prev_gsindex;
|
unsigned prev_fsindex, prev_gsindex;
|
||||||
|
|
||||||
|
WARN_ON_ONCE(IS_ENABLED(CONFIG_DEBUG_ENTRY) &&
|
||||||
|
this_cpu_read(irq_count) != -1);
|
||||||
|
|
||||||
switch_fpu_prepare(prev_fpu, cpu);
|
switch_fpu_prepare(prev_fpu, cpu);
|
||||||
|
|
||||||
/* We must save %fs and %gs before load_TLS() because
|
/* We must save %fs and %gs before load_TLS() because
|
||||||
|
|||||||
@@ -915,6 +915,9 @@ extern void ioport_unmap(void __iomem *p);
|
|||||||
#endif /* CONFIG_GENERIC_IOMAP */
|
#endif /* CONFIG_GENERIC_IOMAP */
|
||||||
#endif /* CONFIG_HAS_IOPORT_MAP */
|
#endif /* CONFIG_HAS_IOPORT_MAP */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a virtual cached pointer to an uncached pointer
|
||||||
|
*/
|
||||||
#ifndef xlate_dev_kmem_ptr
|
#ifndef xlate_dev_kmem_ptr
|
||||||
#define xlate_dev_kmem_ptr xlate_dev_kmem_ptr
|
#define xlate_dev_kmem_ptr xlate_dev_kmem_ptr
|
||||||
static inline void *xlate_dev_kmem_ptr(void *addr)
|
static inline void *xlate_dev_kmem_ptr(void *addr)
|
||||||
@@ -954,6 +957,14 @@ static inline void *bus_to_virt(unsigned long address)
|
|||||||
|
|
||||||
#ifndef memset_io
|
#ifndef memset_io
|
||||||
#define memset_io memset_io
|
#define memset_io memset_io
|
||||||
|
/**
|
||||||
|
* memset_io Set a range of I/O memory to a constant value
|
||||||
|
* @addr: The beginning of the I/O-memory range to set
|
||||||
|
* @val: The value to set the memory to
|
||||||
|
* @count: The number of bytes to set
|
||||||
|
*
|
||||||
|
* Set a range of I/O memory to a given value.
|
||||||
|
*/
|
||||||
static inline void memset_io(volatile void __iomem *addr, int value,
|
static inline void memset_io(volatile void __iomem *addr, int value,
|
||||||
size_t size)
|
size_t size)
|
||||||
{
|
{
|
||||||
@@ -963,6 +974,14 @@ static inline void memset_io(volatile void __iomem *addr, int value,
|
|||||||
|
|
||||||
#ifndef memcpy_fromio
|
#ifndef memcpy_fromio
|
||||||
#define memcpy_fromio memcpy_fromio
|
#define memcpy_fromio memcpy_fromio
|
||||||
|
/**
|
||||||
|
* memcpy_fromio Copy a block of data from I/O memory
|
||||||
|
* @dst: The (RAM) destination for the copy
|
||||||
|
* @src: The (I/O memory) source for the data
|
||||||
|
* @count: The number of bytes to copy
|
||||||
|
*
|
||||||
|
* Copy a block of data from I/O memory.
|
||||||
|
*/
|
||||||
static inline void memcpy_fromio(void *buffer,
|
static inline void memcpy_fromio(void *buffer,
|
||||||
const volatile void __iomem *addr,
|
const volatile void __iomem *addr,
|
||||||
size_t size)
|
size_t size)
|
||||||
@@ -973,6 +992,14 @@ static inline void memcpy_fromio(void *buffer,
|
|||||||
|
|
||||||
#ifndef memcpy_toio
|
#ifndef memcpy_toio
|
||||||
#define memcpy_toio memcpy_toio
|
#define memcpy_toio memcpy_toio
|
||||||
|
/**
|
||||||
|
* memcpy_toio Copy a block of data into I/O memory
|
||||||
|
* @dst: The (I/O memory) destination for the copy
|
||||||
|
* @src: The (RAM) source for the data
|
||||||
|
* @count: The number of bytes to copy
|
||||||
|
*
|
||||||
|
* Copy a block of data to I/O memory.
|
||||||
|
*/
|
||||||
static inline void memcpy_toio(volatile void __iomem *addr, const void *buffer,
|
static inline void memcpy_toio(volatile void __iomem *addr, const void *buffer,
|
||||||
size_t size)
|
size_t size)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -203,11 +203,16 @@
|
|||||||
|
|
||||||
#ifdef CONFIG_STACK_VALIDATION
|
#ifdef CONFIG_STACK_VALIDATION
|
||||||
#define annotate_unreachable() ({ \
|
#define annotate_unreachable() ({ \
|
||||||
asm("%c0:\t\n" \
|
asm("%c0:\n\t" \
|
||||||
".pushsection .discard.unreachable\t\n" \
|
".pushsection .discard.unreachable\n\t" \
|
||||||
".long %c0b - .\t\n" \
|
".long %c0b - .\n\t" \
|
||||||
".popsection\t\n" : : "i" (__LINE__)); \
|
".popsection\n\t" : : "i" (__LINE__)); \
|
||||||
})
|
})
|
||||||
|
#define ASM_UNREACHABLE \
|
||||||
|
"999:\n\t" \
|
||||||
|
".pushsection .discard.unreachable\n\t" \
|
||||||
|
".long 999b - .\n\t" \
|
||||||
|
".popsection\n\t"
|
||||||
#else
|
#else
|
||||||
#define annotate_unreachable()
|
#define annotate_unreachable()
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -185,6 +185,9 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Unreachable code */
|
/* Unreachable code */
|
||||||
|
#ifndef ASM_UNREACHABLE
|
||||||
|
# define ASM_UNREACHABLE
|
||||||
|
#endif
|
||||||
#ifndef unreachable
|
#ifndef unreachable
|
||||||
# define unreachable() do { } while (1)
|
# define unreachable() do { } while (1)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -262,6 +262,9 @@ objtool_args = check
|
|||||||
ifndef CONFIG_FRAME_POINTER
|
ifndef CONFIG_FRAME_POINTER
|
||||||
objtool_args += --no-fp
|
objtool_args += --no-fp
|
||||||
endif
|
endif
|
||||||
|
ifdef CONFIG_GCOV_KERNEL
|
||||||
|
objtool_args += --no-unreachable
|
||||||
|
endif
|
||||||
|
|
||||||
# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
|
# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
|
||||||
# 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file
|
# 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
objtool-y += arch/$(SRCARCH)/
|
objtool-y += arch/$(SRCARCH)/
|
||||||
objtool-y += builtin-check.o
|
objtool-y += builtin-check.o
|
||||||
|
objtool-y += builtin-orc.o
|
||||||
objtool-y += check.o
|
objtool-y += check.o
|
||||||
|
objtool-y += orc_gen.o
|
||||||
|
objtool-y += orc_dump.o
|
||||||
objtool-y += elf.o
|
objtool-y += elf.o
|
||||||
objtool-y += special.o
|
objtool-y += special.o
|
||||||
objtool-y += objtool.o
|
objtool-y += objtool.o
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ 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
|
It enforces a set of rules on asm code and C inline assembly code so
|
||||||
that stack traces can be reliable.
|
that stack traces can be reliable.
|
||||||
|
|
||||||
Currently it only checks frame pointer usage, but there are plans to add
|
|
||||||
CFI validation for C files and CFI generation for asm files.
|
|
||||||
|
|
||||||
For each function, it recursively follows all possible code paths and
|
For each function, it recursively follows all possible code paths and
|
||||||
validates the correct frame pointer state at each instruction.
|
validates the correct frame pointer state at each instruction.
|
||||||
|
|
||||||
@@ -23,6 +20,10 @@ alternative execution paths to a given instruction (or set of
|
|||||||
instructions). Similarly, it knows how to follow switch statements, for
|
instructions). Similarly, it knows how to follow switch statements, for
|
||||||
which gcc sometimes uses jump tables.
|
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.txt in the
|
||||||
|
kernel tree for more details.)
|
||||||
|
|
||||||
|
|
||||||
Why do we need stack metadata validation?
|
Why do we need stack metadata validation?
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
@@ -93,37 +94,14 @@ a) More reliable stack traces for frame pointer enabled kernels
|
|||||||
or at the very end of the function after the stack frame has been
|
or at the very end of the function after the stack frame has been
|
||||||
destroyed. This is an inherent limitation of frame pointers.
|
destroyed. This is an inherent limitation of frame pointers.
|
||||||
|
|
||||||
b) 100% reliable stack traces for DWARF enabled kernels
|
b) ORC (Oops Rewind Capability) unwind table generation
|
||||||
|
|
||||||
(NOTE: This is not yet implemented)
|
An alternative to frame pointers and DWARF, ORC unwind data can be
|
||||||
|
used to walk the stack. Unlike frame pointers, ORC data is out of
|
||||||
|
band. So it doesn't affect runtime performance and it can be
|
||||||
|
reliable even when interrupts or exceptions are involved.
|
||||||
|
|
||||||
As an alternative to frame pointers, DWARF Call Frame Information
|
For more details, see Documentation/x86/orc-unwinder.txt.
|
||||||
(CFI) metadata can be used to walk the stack. Unlike frame pointers,
|
|
||||||
CFI metadata is out of band. So it doesn't affect runtime
|
|
||||||
performance and it can be reliable even when interrupts or exceptions
|
|
||||||
are involved.
|
|
||||||
|
|
||||||
For C code, gcc automatically generates DWARF CFI metadata. But for
|
|
||||||
asm code, generating CFI is a tedious manual approach which requires
|
|
||||||
manually placed .cfi assembler macros to be scattered throughout the
|
|
||||||
code. It's clumsy and very easy to get wrong, and it makes the real
|
|
||||||
code harder to read.
|
|
||||||
|
|
||||||
Stacktool will improve this situation in several ways. For code
|
|
||||||
which already has CFI annotations, it will validate them. For code
|
|
||||||
which doesn't have CFI annotations, it will generate them. So an
|
|
||||||
architecture can opt to strip out all the manual .cfi annotations
|
|
||||||
from their asm code and have objtool generate them instead.
|
|
||||||
|
|
||||||
We might also add a runtime stack validation debug option where we
|
|
||||||
periodically walk the stack from schedule() and/or an NMI to ensure
|
|
||||||
that the stack metadata is sane and that we reach the bottom of the
|
|
||||||
stack.
|
|
||||||
|
|
||||||
So the benefit of objtool here will be that external tooling should
|
|
||||||
always show perfect stack traces. And the same will be true for
|
|
||||||
kernel warning/oops traces if the architecture has a runtime DWARF
|
|
||||||
unwinder.
|
|
||||||
|
|
||||||
c) Higher live patching compatibility rate
|
c) Higher live patching compatibility rate
|
||||||
|
|
||||||
@@ -211,7 +189,7 @@ they mean, and suggestions for how to fix them.
|
|||||||
function, add proper frame pointer logic using the FRAME_BEGIN and
|
function, add proper frame pointer logic using the FRAME_BEGIN and
|
||||||
FRAME_END macros. Otherwise, if it's not a callable function, remove
|
FRAME_END macros. Otherwise, if it's not a callable function, remove
|
||||||
its ELF function annotation by changing ENDPROC to END, and instead
|
its ELF function annotation by changing ENDPROC to END, and instead
|
||||||
use the manual CFI hint macros in asm/undwarf.h.
|
use the manual unwind hint macros in asm/unwind_hints.h.
|
||||||
|
|
||||||
If it's a GCC-compiled .c file, the error may be because the function
|
If it's a GCC-compiled .c file, the error may be because the function
|
||||||
uses an inline asm() statement which has a "call" instruction. An
|
uses an inline asm() statement which has a "call" instruction. An
|
||||||
@@ -231,8 +209,8 @@ they mean, and suggestions for how to fix them.
|
|||||||
If the error is for an asm file, and the instruction is inside (or
|
If the error is for an asm file, and the instruction is inside (or
|
||||||
reachable from) a callable function, the function should be annotated
|
reachable from) a callable function, the function should be annotated
|
||||||
with the ENTRY/ENDPROC macros (ENDPROC is the important one).
|
with the ENTRY/ENDPROC macros (ENDPROC is the important one).
|
||||||
Otherwise, the code should probably be annotated with the CFI hint
|
Otherwise, the code should probably be annotated with the unwind hint
|
||||||
macros in asm/undwarf.h so objtool and the unwinder can know the
|
macros in asm/unwind_hints.h so objtool and the unwinder can know the
|
||||||
stack state associated with the code.
|
stack state associated with the code.
|
||||||
|
|
||||||
If you're 100% sure the code won't affect stack traces, or if you're
|
If you're 100% sure the code won't affect stack traces, or if you're
|
||||||
@@ -258,7 +236,7 @@ they mean, and suggestions for how to fix them.
|
|||||||
instructions aren't allowed in a callable function, and are most
|
instructions aren't allowed in a callable function, and are most
|
||||||
likely part of the kernel entry code. They should usually not have
|
likely part of the kernel entry code. They should usually not have
|
||||||
the callable function annotation (ENDPROC) and should always be
|
the callable function annotation (ENDPROC) and should always be
|
||||||
annotated with the CFI hint macros in asm/undwarf.h.
|
annotated with the unwind hint macros in asm/unwind_hints.h.
|
||||||
|
|
||||||
|
|
||||||
6. file.o: warning: objtool: func()+0x26: sibling call from callable instruction with modified stack frame
|
6. file.o: warning: objtool: func()+0x26: sibling call from callable instruction with modified stack frame
|
||||||
@@ -272,7 +250,7 @@ they mean, and suggestions for how to fix them.
|
|||||||
|
|
||||||
If the instruction is not actually in a callable function (e.g.
|
If the instruction is not actually in a callable function (e.g.
|
||||||
kernel entry code), change ENDPROC to END and annotate manually with
|
kernel entry code), change ENDPROC to END and annotate manually with
|
||||||
the CFI hint macros in asm/undwarf.h.
|
the unwind hint macros in asm/unwind_hints.h.
|
||||||
|
|
||||||
|
|
||||||
7. file: warning: objtool: func()+0x5c: stack state mismatch
|
7. file: warning: objtool: func()+0x5c: stack state mismatch
|
||||||
@@ -288,8 +266,8 @@ they mean, and suggestions for how to fix them.
|
|||||||
|
|
||||||
Another possibility is that the code has some asm or inline asm which
|
Another possibility is that the code has some asm or inline asm which
|
||||||
does some unusual things to the stack or the frame pointer. In such
|
does some unusual things to the stack or the frame pointer. In such
|
||||||
cases it's probably appropriate to use the CFI hint macros in
|
cases it's probably appropriate to use the unwind hint macros in
|
||||||
asm/undwarf.h.
|
asm/unwind_hints.h.
|
||||||
|
|
||||||
|
|
||||||
8. file.o: warning: objtool: funcA() falls through to next function funcB()
|
8. file.o: warning: objtool: funcA() falls through to next function funcB()
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ $(OBJTOOL): $(LIBSUBCMD) $(OBJTOOL_IN)
|
|||||||
diff -I'^#include' arch/x86/insn/inat.h ../../arch/x86/include/asm/inat.h >/dev/null && \
|
diff -I'^#include' arch/x86/insn/inat.h ../../arch/x86/include/asm/inat.h >/dev/null && \
|
||||||
diff -I'^#include' arch/x86/insn/inat_types.h ../../arch/x86/include/asm/inat_types.h >/dev/null) \
|
diff -I'^#include' arch/x86/insn/inat_types.h ../../arch/x86/include/asm/inat_types.h >/dev/null) \
|
||||||
|| echo "warning: objtool: x86 instruction decoder differs from kernel" >&2 )) || true
|
|| echo "warning: objtool: x86 instruction decoder differs from kernel" >&2 )) || true
|
||||||
|
@(test -d ../../kernel -a -d ../../tools -a -d ../objtool && (( \
|
||||||
|
diff ../../arch/x86/include/asm/orc_types.h orc_types.h >/dev/null) \
|
||||||
|
|| echo "warning: objtool: orc_types.h differs from kernel" >&2 )) || true
|
||||||
$(QUIET_LINK)$(CC) $(OBJTOOL_IN) $(LDFLAGS) -o $@
|
$(QUIET_LINK)$(CC) $(OBJTOOL_IN) $(LDFLAGS) -o $@
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
|
|
||||||
bool nofp;
|
bool no_fp, no_unreachable;
|
||||||
|
|
||||||
static const char * const check_usage[] = {
|
static const char * const check_usage[] = {
|
||||||
"objtool check [<options>] file.o",
|
"objtool check [<options>] file.o",
|
||||||
@@ -37,7 +37,8 @@ static const char * const check_usage[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const struct option check_options[] = {
|
const struct option check_options[] = {
|
||||||
OPT_BOOLEAN('f', "no-fp", &nofp, "Skip frame pointer validation"),
|
OPT_BOOLEAN('f', "no-fp", &no_fp, "Skip frame pointer validation"),
|
||||||
|
OPT_BOOLEAN('u', "no-unreachable", &no_unreachable, "Skip 'unreachable instruction' warnings"),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,5 +53,5 @@ int cmd_check(int argc, const char **argv)
|
|||||||
|
|
||||||
objname = argv[0];
|
objname = argv[0];
|
||||||
|
|
||||||
return check(objname, nofp);
|
return check(objname, no_fp, no_unreachable, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 <subcmd/parse-options.h>
|
||||||
|
#include "builtin.h"
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const char *orc_usage[] = {
|
||||||
|
"objtool orc generate [<options>] file.o",
|
||||||
|
"objtool orc dump file.o",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const struct option check_options[];
|
||||||
|
extern bool no_fp, no_unreachable;
|
||||||
|
|
||||||
|
int cmd_orc(int argc, const char **argv)
|
||||||
|
{
|
||||||
|
const char *objname;
|
||||||
|
|
||||||
|
argc--; argv++;
|
||||||
|
if (!strncmp(argv[0], "gen", 3)) {
|
||||||
|
argc = parse_options(argc, argv, check_options, orc_usage, 0);
|
||||||
|
if (argc != 1)
|
||||||
|
usage_with_options(orc_usage, check_options);
|
||||||
|
|
||||||
|
objname = argv[0];
|
||||||
|
|
||||||
|
return check(objname, no_fp, no_unreachable, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(argv[0], "dump")) {
|
||||||
|
if (argc != 2)
|
||||||
|
usage_with_options(orc_usage, check_options);
|
||||||
|
|
||||||
|
objname = argv[1];
|
||||||
|
|
||||||
|
return orc_dump(objname);
|
||||||
|
}
|
||||||
|
|
||||||
|
usage_with_options(orc_usage, check_options);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -18,5 +18,6 @@
|
|||||||
#define _BUILTIN_H
|
#define _BUILTIN_H
|
||||||
|
|
||||||
extern int cmd_check(int argc, const char **argv);
|
extern int cmd_check(int argc, const char **argv);
|
||||||
|
extern int cmd_orc(int argc, const char **argv);
|
||||||
|
|
||||||
#endif /* _BUILTIN_H */
|
#endif /* _BUILTIN_H */
|
||||||
|
|||||||
+239
-42
@@ -33,11 +33,11 @@ struct alternative {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const char *objname;
|
const char *objname;
|
||||||
static bool nofp;
|
static bool no_fp;
|
||||||
struct cfi_state initial_func_cfi;
|
struct cfi_state initial_func_cfi;
|
||||||
|
|
||||||
static struct instruction *find_insn(struct objtool_file *file,
|
struct instruction *find_insn(struct objtool_file *file,
|
||||||
struct section *sec, unsigned long offset)
|
struct section *sec, unsigned long offset)
|
||||||
{
|
{
|
||||||
struct instruction *insn;
|
struct instruction *insn;
|
||||||
|
|
||||||
@@ -59,19 +59,6 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file,
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool gcov_enabled(struct objtool_file *file)
|
|
||||||
{
|
|
||||||
struct section *sec;
|
|
||||||
struct symbol *sym;
|
|
||||||
|
|
||||||
for_each_sec(file, sec)
|
|
||||||
list_for_each_entry(sym, &sec->symbol_list, list)
|
|
||||||
if (!strncmp(sym->name, "__gcov_.", 8))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define func_for_each_insn(file, func, insn) \
|
#define func_for_each_insn(file, func, insn) \
|
||||||
for (insn = find_insn(file, func->sec, func->offset); \
|
for (insn = find_insn(file, func->sec, func->offset); \
|
||||||
insn && &insn->list != &file->insn_list && \
|
insn && &insn->list != &file->insn_list && \
|
||||||
@@ -100,7 +87,6 @@ static bool gcov_enabled(struct objtool_file *file)
|
|||||||
static bool ignore_func(struct objtool_file *file, struct symbol *func)
|
static bool ignore_func(struct objtool_file *file, struct symbol *func)
|
||||||
{
|
{
|
||||||
struct rela *rela;
|
struct rela *rela;
|
||||||
struct instruction *insn;
|
|
||||||
|
|
||||||
/* check for STACK_FRAME_NON_STANDARD */
|
/* check for STACK_FRAME_NON_STANDARD */
|
||||||
if (file->whitelist && file->whitelist->rela)
|
if (file->whitelist && file->whitelist->rela)
|
||||||
@@ -113,11 +99,6 @@ static bool ignore_func(struct objtool_file *file, struct symbol *func)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check if it has a context switching instruction */
|
|
||||||
func_for_each_insn(file, func, insn)
|
|
||||||
if (insn->type == INSN_CONTEXT_SWITCH)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +240,11 @@ static int decode_instructions(struct objtool_file *file)
|
|||||||
if (!(sec->sh.sh_flags & SHF_EXECINSTR))
|
if (!(sec->sh.sh_flags & SHF_EXECINSTR))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (strcmp(sec->name, ".altinstr_replacement") &&
|
||||||
|
strcmp(sec->name, ".altinstr_aux") &&
|
||||||
|
strncmp(sec->name, ".discard.", 9))
|
||||||
|
sec->text = true;
|
||||||
|
|
||||||
for (offset = 0; offset < sec->len; offset += insn->len) {
|
for (offset = 0; offset < sec->len; offset += insn->len) {
|
||||||
insn = malloc(sizeof(*insn));
|
insn = malloc(sizeof(*insn));
|
||||||
if (!insn) {
|
if (!insn) {
|
||||||
@@ -874,6 +860,99 @@ static int add_switch_table_alts(struct objtool_file *file)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int read_unwind_hints(struct objtool_file *file)
|
||||||
|
{
|
||||||
|
struct section *sec, *relasec;
|
||||||
|
struct rela *rela;
|
||||||
|
struct unwind_hint *hint;
|
||||||
|
struct instruction *insn;
|
||||||
|
struct cfi_reg *cfa;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
sec = find_section_by_name(file->elf, ".discard.unwind_hints");
|
||||||
|
if (!sec)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
relasec = sec->rela;
|
||||||
|
if (!relasec) {
|
||||||
|
WARN("missing .rela.discard.unwind_hints section");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sec->len % sizeof(struct unwind_hint)) {
|
||||||
|
WARN("struct unwind_hint size mismatch");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
file->hints = true;
|
||||||
|
|
||||||
|
for (i = 0; i < sec->len / sizeof(struct unwind_hint); i++) {
|
||||||
|
hint = (struct unwind_hint *)sec->data->d_buf + i;
|
||||||
|
|
||||||
|
rela = find_rela_by_dest(sec, i * sizeof(*hint));
|
||||||
|
if (!rela) {
|
||||||
|
WARN("can't find rela for unwind_hints[%d]", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
insn = find_insn(file, rela->sym->sec, rela->addend);
|
||||||
|
if (!insn) {
|
||||||
|
WARN("can't find insn for unwind_hints[%d]", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfa = &insn->state.cfa;
|
||||||
|
|
||||||
|
if (hint->type == UNWIND_HINT_TYPE_SAVE) {
|
||||||
|
insn->save = true;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
} else if (hint->type == UNWIND_HINT_TYPE_RESTORE) {
|
||||||
|
insn->restore = true;
|
||||||
|
insn->hint = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
insn->hint = true;
|
||||||
|
|
||||||
|
switch (hint->sp_reg) {
|
||||||
|
case ORC_REG_UNDEFINED:
|
||||||
|
cfa->base = CFI_UNDEFINED;
|
||||||
|
break;
|
||||||
|
case ORC_REG_SP:
|
||||||
|
cfa->base = CFI_SP;
|
||||||
|
break;
|
||||||
|
case ORC_REG_BP:
|
||||||
|
cfa->base = CFI_BP;
|
||||||
|
break;
|
||||||
|
case ORC_REG_SP_INDIRECT:
|
||||||
|
cfa->base = CFI_SP_INDIRECT;
|
||||||
|
break;
|
||||||
|
case ORC_REG_R10:
|
||||||
|
cfa->base = CFI_R10;
|
||||||
|
break;
|
||||||
|
case ORC_REG_R13:
|
||||||
|
cfa->base = CFI_R13;
|
||||||
|
break;
|
||||||
|
case ORC_REG_DI:
|
||||||
|
cfa->base = CFI_DI;
|
||||||
|
break;
|
||||||
|
case ORC_REG_DX:
|
||||||
|
cfa->base = CFI_DX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
WARN_FUNC("unsupported unwind_hint sp base reg %d",
|
||||||
|
insn->sec, insn->offset, hint->sp_reg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfa->offset = hint->sp_offset;
|
||||||
|
insn->state.type = hint->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int decode_sections(struct objtool_file *file)
|
static int decode_sections(struct objtool_file *file)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
@@ -904,6 +983,10 @@ static int decode_sections(struct objtool_file *file)
|
|||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
ret = read_unwind_hints(file);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -947,6 +1030,30 @@ static bool has_valid_stack_frame(struct insn_state *state)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int update_insn_state_regs(struct instruction *insn, struct insn_state *state)
|
||||||
|
{
|
||||||
|
struct cfi_reg *cfa = &state->cfa;
|
||||||
|
struct stack_op *op = &insn->stack_op;
|
||||||
|
|
||||||
|
if (cfa->base != CFI_SP)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* push */
|
||||||
|
if (op->dest.type == OP_DEST_PUSH)
|
||||||
|
cfa->offset += 8;
|
||||||
|
|
||||||
|
/* pop */
|
||||||
|
if (op->src.type == OP_SRC_POP)
|
||||||
|
cfa->offset -= 8;
|
||||||
|
|
||||||
|
/* add immediate to sp */
|
||||||
|
if (op->dest.type == OP_DEST_REG && op->src.type == OP_SRC_ADD &&
|
||||||
|
op->dest.reg == CFI_SP && op->src.reg == CFI_SP)
|
||||||
|
cfa->offset -= op->src.offset;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void save_reg(struct insn_state *state, unsigned char reg, int base,
|
static void save_reg(struct insn_state *state, unsigned char reg, int base,
|
||||||
int offset)
|
int offset)
|
||||||
{
|
{
|
||||||
@@ -1032,6 +1139,9 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state->type == ORC_TYPE_REGS || state->type == ORC_TYPE_REGS_IRET)
|
||||||
|
return update_insn_state_regs(insn, state);
|
||||||
|
|
||||||
switch (op->dest.type) {
|
switch (op->dest.type) {
|
||||||
|
|
||||||
case OP_DEST_REG:
|
case OP_DEST_REG:
|
||||||
@@ -1051,7 +1161,7 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|||||||
regs[CFI_BP].base = CFI_BP;
|
regs[CFI_BP].base = CFI_BP;
|
||||||
regs[CFI_BP].offset = -state->stack_size;
|
regs[CFI_BP].offset = -state->stack_size;
|
||||||
state->bp_scratch = false;
|
state->bp_scratch = false;
|
||||||
} else if (!nofp) {
|
} else if (!no_fp) {
|
||||||
|
|
||||||
WARN_FUNC("unknown stack-related register move",
|
WARN_FUNC("unknown stack-related register move",
|
||||||
insn->sec, insn->offset);
|
insn->sec, insn->offset);
|
||||||
@@ -1222,7 +1332,7 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* detect when asm code uses rbp as a scratch register */
|
/* detect when asm code uses rbp as a scratch register */
|
||||||
if (!nofp && insn->func && op->src.reg == CFI_BP &&
|
if (!no_fp && insn->func && op->src.reg == CFI_BP &&
|
||||||
cfa->base != CFI_BP)
|
cfa->base != CFI_BP)
|
||||||
state->bp_scratch = true;
|
state->bp_scratch = true;
|
||||||
break;
|
break;
|
||||||
@@ -1323,6 +1433,10 @@ static bool insn_state_match(struct instruction *insn, struct insn_state *state)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (state1->type != state2->type) {
|
||||||
|
WARN_FUNC("stack state mismatch: type1=%d type2=%d",
|
||||||
|
insn->sec, insn->offset, state1->type, state2->type);
|
||||||
|
|
||||||
} else if (state1->drap != state2->drap ||
|
} else if (state1->drap != state2->drap ||
|
||||||
(state1->drap && state1->drap_reg != state2->drap_reg)) {
|
(state1->drap && state1->drap_reg != state2->drap_reg)) {
|
||||||
WARN_FUNC("stack state mismatch: drap1=%d(%d) drap2=%d(%d)",
|
WARN_FUNC("stack state mismatch: drap1=%d(%d) drap2=%d(%d)",
|
||||||
@@ -1346,7 +1460,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||||||
struct insn_state state)
|
struct insn_state state)
|
||||||
{
|
{
|
||||||
struct alternative *alt;
|
struct alternative *alt;
|
||||||
struct instruction *insn;
|
struct instruction *insn, *next_insn;
|
||||||
struct section *sec;
|
struct section *sec;
|
||||||
struct symbol *func = NULL;
|
struct symbol *func = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
@@ -1361,6 +1475,8 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
next_insn = next_insn_same_sec(file, insn);
|
||||||
|
|
||||||
if (file->c_file && insn->func) {
|
if (file->c_file && insn->func) {
|
||||||
if (func && func != insn->func) {
|
if (func && func != insn->func) {
|
||||||
WARN("%s() falls through to next function %s()",
|
WARN("%s() falls through to next function %s()",
|
||||||
@@ -1378,13 +1494,54 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (insn->visited) {
|
if (insn->visited) {
|
||||||
if (!!insn_state_match(insn, &state))
|
if (!insn->hint && !insn_state_match(insn, &state))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
insn->state = state;
|
if (insn->hint) {
|
||||||
|
if (insn->restore) {
|
||||||
|
struct instruction *save_insn, *i;
|
||||||
|
|
||||||
|
i = insn;
|
||||||
|
save_insn = NULL;
|
||||||
|
func_for_each_insn_continue_reverse(file, func, i) {
|
||||||
|
if (i->save) {
|
||||||
|
save_insn = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save_insn) {
|
||||||
|
WARN_FUNC("no corresponding CFI save for CFI restore",
|
||||||
|
sec, insn->offset);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save_insn->visited) {
|
||||||
|
/*
|
||||||
|
* Oops, no state to copy yet.
|
||||||
|
* Hopefully we can reach this
|
||||||
|
* instruction from another branch
|
||||||
|
* after the save insn has been
|
||||||
|
* visited.
|
||||||
|
*/
|
||||||
|
if (insn == first)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
WARN_FUNC("objtool isn't smart enough to handle this CFI save/restore combo",
|
||||||
|
sec, insn->offset);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
insn->state = save_insn->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = insn->state;
|
||||||
|
|
||||||
|
} else
|
||||||
|
insn->state = state;
|
||||||
|
|
||||||
insn->visited = true;
|
insn->visited = true;
|
||||||
|
|
||||||
@@ -1423,7 +1580,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||||||
|
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
case INSN_CALL_DYNAMIC:
|
case INSN_CALL_DYNAMIC:
|
||||||
if (!nofp && func && !has_valid_stack_frame(&state)) {
|
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;
|
||||||
@@ -1461,6 +1618,14 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
case INSN_CONTEXT_SWITCH:
|
||||||
|
if (func && (!next_insn || !next_insn->hint)) {
|
||||||
|
WARN_FUNC("unsupported instruction in callable function",
|
||||||
|
sec, insn->offset);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
case INSN_STACK:
|
case INSN_STACK:
|
||||||
if (update_insn_state(insn, &state))
|
if (update_insn_state(insn, &state))
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1474,7 +1639,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||||||
if (insn->dead_end)
|
if (insn->dead_end)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
insn = next_insn_same_sec(file, insn);
|
insn = next_insn;
|
||||||
if (!insn) {
|
if (!insn) {
|
||||||
WARN("%s: unexpected end of section", sec->name);
|
WARN("%s: unexpected end of section", sec->name);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1484,6 +1649,27 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int validate_unwind_hints(struct objtool_file *file)
|
||||||
|
{
|
||||||
|
struct instruction *insn;
|
||||||
|
int ret, warnings = 0;
|
||||||
|
struct insn_state state;
|
||||||
|
|
||||||
|
if (!file->hints)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
clear_insn_state(&state);
|
||||||
|
|
||||||
|
for_each_insn(file, insn) {
|
||||||
|
if (insn->hint && !insn->visited) {
|
||||||
|
ret = validate_branch(file, insn, state);
|
||||||
|
warnings += ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings;
|
||||||
|
}
|
||||||
|
|
||||||
static bool is_kasan_insn(struct instruction *insn)
|
static bool is_kasan_insn(struct instruction *insn)
|
||||||
{
|
{
|
||||||
return (insn->type == INSN_CALL &&
|
return (insn->type == INSN_CALL &&
|
||||||
@@ -1580,15 +1766,6 @@ static int validate_reachable_instructions(struct objtool_file *file)
|
|||||||
if (insn->visited || ignore_unreachable_insn(insn))
|
if (insn->visited || ignore_unreachable_insn(insn))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
|
||||||
* gcov produces a lot of unreachable instructions. If we get
|
|
||||||
* an unreachable warning and the file has gcov enabled, just
|
|
||||||
* ignore it, and all other such warnings for the file. Do
|
|
||||||
* this here because this is an expensive function.
|
|
||||||
*/
|
|
||||||
if (gcov_enabled(file))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
WARN_FUNC("unreachable instruction", insn->sec, insn->offset);
|
WARN_FUNC("unreachable instruction", insn->sec, insn->offset);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -1613,15 +1790,15 @@ static void cleanup(struct objtool_file *file)
|
|||||||
elf_close(file->elf);
|
elf_close(file->elf);
|
||||||
}
|
}
|
||||||
|
|
||||||
int check(const char *_objname, bool _nofp)
|
int check(const char *_objname, bool _no_fp, bool no_unreachable, bool orc)
|
||||||
{
|
{
|
||||||
struct objtool_file file;
|
struct objtool_file file;
|
||||||
int ret, warnings = 0;
|
int ret, warnings = 0;
|
||||||
|
|
||||||
objname = _objname;
|
objname = _objname;
|
||||||
nofp = _nofp;
|
no_fp = _no_fp;
|
||||||
|
|
||||||
file.elf = elf_open(objname);
|
file.elf = elf_open(objname, orc ? O_RDWR : O_RDONLY);
|
||||||
if (!file.elf)
|
if (!file.elf)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
@@ -1629,8 +1806,9 @@ int check(const char *_objname, bool _nofp)
|
|||||||
hash_init(file.insn_hash);
|
hash_init(file.insn_hash);
|
||||||
file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard");
|
file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard");
|
||||||
file.rodata = find_section_by_name(file.elf, ".rodata");
|
file.rodata = find_section_by_name(file.elf, ".rodata");
|
||||||
file.ignore_unreachables = false;
|
|
||||||
file.c_file = find_section_by_name(file.elf, ".comment");
|
file.c_file = find_section_by_name(file.elf, ".comment");
|
||||||
|
file.ignore_unreachables = no_unreachable;
|
||||||
|
file.hints = false;
|
||||||
|
|
||||||
arch_initial_func_cfi_state(&initial_func_cfi);
|
arch_initial_func_cfi_state(&initial_func_cfi);
|
||||||
|
|
||||||
@@ -1647,6 +1825,11 @@ int check(const char *_objname, bool _nofp)
|
|||||||
goto out;
|
goto out;
|
||||||
warnings += ret;
|
warnings += ret;
|
||||||
|
|
||||||
|
ret = validate_unwind_hints(&file);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
warnings += ret;
|
||||||
|
|
||||||
if (!warnings) {
|
if (!warnings) {
|
||||||
ret = validate_reachable_instructions(&file);
|
ret = validate_reachable_instructions(&file);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@@ -1654,6 +1837,20 @@ int check(const char *_objname, bool _nofp)
|
|||||||
warnings += ret;
|
warnings += ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (orc) {
|
||||||
|
ret = create_orc(&file);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ret = create_orc_sections(&file);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
ret = elf_write(file.elf);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
cleanup(&file);
|
cleanup(&file);
|
||||||
|
|
||||||
|
|||||||
+16
-3
@@ -22,12 +22,14 @@
|
|||||||
#include "elf.h"
|
#include "elf.h"
|
||||||
#include "cfi.h"
|
#include "cfi.h"
|
||||||
#include "arch.h"
|
#include "arch.h"
|
||||||
|
#include "orc.h"
|
||||||
#include <linux/hashtable.h>
|
#include <linux/hashtable.h>
|
||||||
|
|
||||||
struct insn_state {
|
struct insn_state {
|
||||||
struct cfi_reg cfa;
|
struct cfi_reg cfa;
|
||||||
struct cfi_reg regs[CFI_NUM_REGS];
|
struct cfi_reg regs[CFI_NUM_REGS];
|
||||||
int stack_size;
|
int stack_size;
|
||||||
|
unsigned char type;
|
||||||
bool bp_scratch;
|
bool bp_scratch;
|
||||||
bool drap;
|
bool drap;
|
||||||
int drap_reg;
|
int drap_reg;
|
||||||
@@ -41,13 +43,14 @@ struct instruction {
|
|||||||
unsigned int len;
|
unsigned int len;
|
||||||
unsigned char type;
|
unsigned char type;
|
||||||
unsigned long immediate;
|
unsigned long immediate;
|
||||||
bool alt_group, visited, dead_end, ignore;
|
bool alt_group, visited, dead_end, ignore, hint, save, restore;
|
||||||
struct symbol *call_dest;
|
struct symbol *call_dest;
|
||||||
struct instruction *jump_dest;
|
struct instruction *jump_dest;
|
||||||
struct list_head alts;
|
struct list_head alts;
|
||||||
struct symbol *func;
|
struct symbol *func;
|
||||||
struct stack_op stack_op;
|
struct stack_op stack_op;
|
||||||
struct insn_state state;
|
struct insn_state state;
|
||||||
|
struct orc_entry orc;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct objtool_file {
|
struct objtool_file {
|
||||||
@@ -55,12 +58,22 @@ struct objtool_file {
|
|||||||
struct list_head insn_list;
|
struct list_head insn_list;
|
||||||
DECLARE_HASHTABLE(insn_hash, 16);
|
DECLARE_HASHTABLE(insn_hash, 16);
|
||||||
struct section *rodata, *whitelist;
|
struct section *rodata, *whitelist;
|
||||||
bool ignore_unreachables, c_file;
|
bool ignore_unreachables, c_file, hints;
|
||||||
};
|
};
|
||||||
|
|
||||||
int check(const char *objname, bool nofp);
|
int check(const char *objname, bool no_fp, bool no_unreachable, bool orc);
|
||||||
|
|
||||||
|
struct instruction *find_insn(struct objtool_file *file,
|
||||||
|
struct section *sec, unsigned long offset);
|
||||||
|
|
||||||
#define for_each_insn(file, insn) \
|
#define for_each_insn(file, insn) \
|
||||||
list_for_each_entry(insn, &file->insn_list, list)
|
list_for_each_entry(insn, &file->insn_list, list)
|
||||||
|
|
||||||
|
#define sec_for_each_insn(file, sec, insn) \
|
||||||
|
for (insn = find_insn(file, sec, 0); \
|
||||||
|
insn && &insn->list != &file->insn_list && \
|
||||||
|
insn->sec == sec; \
|
||||||
|
insn = list_next_entry(insn, list))
|
||||||
|
|
||||||
|
|
||||||
#endif /* _CHECK_H */
|
#endif /* _CHECK_H */
|
||||||
|
|||||||
+199
-13
@@ -30,16 +30,6 @@
|
|||||||
#include "elf.h"
|
#include "elf.h"
|
||||||
#include "warn.h"
|
#include "warn.h"
|
||||||
|
|
||||||
/*
|
|
||||||
* Fallback for systems without this "read, mmaping if possible" cmd.
|
|
||||||
*/
|
|
||||||
#ifndef ELF_C_READ_MMAP
|
|
||||||
#define ELF_C_READ_MMAP ELF_C_READ
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define WARN_ELF(format, ...) \
|
|
||||||
WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1))
|
|
||||||
|
|
||||||
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 section *sec;
|
struct section *sec;
|
||||||
@@ -349,9 +339,10 @@ static int read_relas(struct elf *elf)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct elf *elf_open(const char *name)
|
struct elf *elf_open(const char *name, int flags)
|
||||||
{
|
{
|
||||||
struct elf *elf;
|
struct elf *elf;
|
||||||
|
Elf_Cmd cmd;
|
||||||
|
|
||||||
elf_version(EV_CURRENT);
|
elf_version(EV_CURRENT);
|
||||||
|
|
||||||
@@ -364,13 +355,20 @@ struct elf *elf_open(const char *name)
|
|||||||
|
|
||||||
INIT_LIST_HEAD(&elf->sections);
|
INIT_LIST_HEAD(&elf->sections);
|
||||||
|
|
||||||
elf->fd = open(name, O_RDONLY);
|
elf->fd = open(name, flags);
|
||||||
if (elf->fd == -1) {
|
if (elf->fd == -1) {
|
||||||
perror("open");
|
perror("open");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
elf->elf = elf_begin(elf->fd, ELF_C_READ_MMAP, NULL);
|
if ((flags & O_ACCMODE) == O_RDONLY)
|
||||||
|
cmd = ELF_C_READ_MMAP;
|
||||||
|
else if ((flags & O_ACCMODE) == O_RDWR)
|
||||||
|
cmd = ELF_C_RDWR;
|
||||||
|
else /* O_WRONLY */
|
||||||
|
cmd = ELF_C_WRITE;
|
||||||
|
|
||||||
|
elf->elf = elf_begin(elf->fd, cmd, NULL);
|
||||||
if (!elf->elf) {
|
if (!elf->elf) {
|
||||||
WARN_ELF("elf_begin");
|
WARN_ELF("elf_begin");
|
||||||
goto err;
|
goto err;
|
||||||
@@ -397,6 +395,194 @@ err:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct section *elf_create_section(struct elf *elf, const char *name,
|
||||||
|
size_t entsize, int nr)
|
||||||
|
{
|
||||||
|
struct section *sec, *shstrtab;
|
||||||
|
size_t size = entsize * nr;
|
||||||
|
struct Elf_Scn *s;
|
||||||
|
Elf_Data *data;
|
||||||
|
|
||||||
|
sec = malloc(sizeof(*sec));
|
||||||
|
if (!sec) {
|
||||||
|
perror("malloc");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memset(sec, 0, sizeof(*sec));
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&sec->symbol_list);
|
||||||
|
INIT_LIST_HEAD(&sec->rela_list);
|
||||||
|
hash_init(sec->rela_hash);
|
||||||
|
hash_init(sec->symbol_hash);
|
||||||
|
|
||||||
|
list_add_tail(&sec->list, &elf->sections);
|
||||||
|
|
||||||
|
s = elf_newscn(elf->elf);
|
||||||
|
if (!s) {
|
||||||
|
WARN_ELF("elf_newscn");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sec->name = strdup(name);
|
||||||
|
if (!sec->name) {
|
||||||
|
perror("strdup");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sec->idx = elf_ndxscn(s);
|
||||||
|
sec->len = size;
|
||||||
|
sec->changed = true;
|
||||||
|
|
||||||
|
sec->data = elf_newdata(s);
|
||||||
|
if (!sec->data) {
|
||||||
|
WARN_ELF("elf_newdata");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sec->data->d_size = size;
|
||||||
|
sec->data->d_align = 1;
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
sec->data->d_buf = malloc(size);
|
||||||
|
if (!sec->data->d_buf) {
|
||||||
|
perror("malloc");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memset(sec->data->d_buf, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gelf_getshdr(s, &sec->sh)) {
|
||||||
|
WARN_ELF("gelf_getshdr");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sec->sh.sh_size = size;
|
||||||
|
sec->sh.sh_entsize = entsize;
|
||||||
|
sec->sh.sh_type = SHT_PROGBITS;
|
||||||
|
sec->sh.sh_addralign = 1;
|
||||||
|
sec->sh.sh_flags = SHF_ALLOC;
|
||||||
|
|
||||||
|
|
||||||
|
/* Add section name to .shstrtab */
|
||||||
|
shstrtab = find_section_by_name(elf, ".shstrtab");
|
||||||
|
if (!shstrtab) {
|
||||||
|
WARN("can't find .shstrtab section");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = elf_getscn(elf->elf, shstrtab->idx);
|
||||||
|
if (!s) {
|
||||||
|
WARN_ELF("elf_getscn");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = elf_newdata(s);
|
||||||
|
if (!data) {
|
||||||
|
WARN_ELF("elf_newdata");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->d_buf = sec->name;
|
||||||
|
data->d_size = strlen(name) + 1;
|
||||||
|
data->d_align = 1;
|
||||||
|
|
||||||
|
sec->sh.sh_name = shstrtab->len;
|
||||||
|
|
||||||
|
shstrtab->len += strlen(name) + 1;
|
||||||
|
shstrtab->changed = true;
|
||||||
|
|
||||||
|
return sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct section *elf_create_rela_section(struct elf *elf, struct section *base)
|
||||||
|
{
|
||||||
|
char *relaname;
|
||||||
|
struct section *sec;
|
||||||
|
|
||||||
|
relaname = malloc(strlen(base->name) + strlen(".rela") + 1);
|
||||||
|
if (!relaname) {
|
||||||
|
perror("malloc");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
strcpy(relaname, ".rela");
|
||||||
|
strcat(relaname, base->name);
|
||||||
|
|
||||||
|
sec = elf_create_section(elf, relaname, sizeof(GElf_Rela), 0);
|
||||||
|
if (!sec)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
base->rela = sec;
|
||||||
|
sec->base = base;
|
||||||
|
|
||||||
|
sec->sh.sh_type = SHT_RELA;
|
||||||
|
sec->sh.sh_addralign = 8;
|
||||||
|
sec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx;
|
||||||
|
sec->sh.sh_info = base->idx;
|
||||||
|
sec->sh.sh_flags = SHF_INFO_LINK;
|
||||||
|
|
||||||
|
return sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
int elf_rebuild_rela_section(struct section *sec)
|
||||||
|
{
|
||||||
|
struct rela *rela;
|
||||||
|
int nr, idx = 0, size;
|
||||||
|
GElf_Rela *relas;
|
||||||
|
|
||||||
|
nr = 0;
|
||||||
|
list_for_each_entry(rela, &sec->rela_list, list)
|
||||||
|
nr++;
|
||||||
|
|
||||||
|
size = nr * sizeof(*relas);
|
||||||
|
relas = malloc(size);
|
||||||
|
if (!relas) {
|
||||||
|
perror("malloc");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sec->data->d_buf = relas;
|
||||||
|
sec->data->d_size = size;
|
||||||
|
|
||||||
|
sec->sh.sh_size = size;
|
||||||
|
|
||||||
|
idx = 0;
|
||||||
|
list_for_each_entry(rela, &sec->rela_list, list) {
|
||||||
|
relas[idx].r_offset = rela->offset;
|
||||||
|
relas[idx].r_addend = rela->addend;
|
||||||
|
relas[idx].r_info = GELF_R_INFO(rela->sym->idx, rela->type);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int elf_write(struct elf *elf)
|
||||||
|
{
|
||||||
|
struct section *sec;
|
||||||
|
Elf_Scn *s;
|
||||||
|
|
||||||
|
list_for_each_entry(sec, &elf->sections, list) {
|
||||||
|
if (sec->changed) {
|
||||||
|
s = elf_getscn(elf->elf, sec->idx);
|
||||||
|
if (!s) {
|
||||||
|
WARN_ELF("elf_getscn");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!gelf_update_shdr (s, &sec->sh)) {
|
||||||
|
WARN_ELF("gelf_update_shdr");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elf_update(elf->elf, ELF_C_WRITE) < 0) {
|
||||||
|
WARN_ELF("elf_update");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void elf_close(struct elf *elf)
|
void elf_close(struct elf *elf)
|
||||||
{
|
{
|
||||||
struct section *sec, *tmpsec;
|
struct section *sec, *tmpsec;
|
||||||
|
|||||||
+14
-1
@@ -28,6 +28,13 @@
|
|||||||
# define elf_getshdrstrndx elf_getshstrndx
|
# define elf_getshdrstrndx elf_getshstrndx
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fallback for systems without this "read, mmaping if possible" cmd.
|
||||||
|
*/
|
||||||
|
#ifndef ELF_C_READ_MMAP
|
||||||
|
#define ELF_C_READ_MMAP ELF_C_READ
|
||||||
|
#endif
|
||||||
|
|
||||||
struct section {
|
struct section {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
GElf_Shdr sh;
|
GElf_Shdr sh;
|
||||||
@@ -41,6 +48,7 @@ struct section {
|
|||||||
char *name;
|
char *name;
|
||||||
int idx;
|
int idx;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
|
bool changed, text;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct symbol {
|
struct symbol {
|
||||||
@@ -75,7 +83,7 @@ struct elf {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct elf *elf_open(const char *name);
|
struct elf *elf_open(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_containing(struct section *sec, unsigned long offset);
|
struct symbol *find_symbol_containing(struct section *sec, unsigned long offset);
|
||||||
@@ -83,6 +91,11 @@ struct rela *find_rela_by_dest(struct section *sec, unsigned long offset);
|
|||||||
struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset,
|
struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset,
|
||||||
unsigned int len);
|
unsigned int len);
|
||||||
struct symbol *find_containing_func(struct section *sec, unsigned long offset);
|
struct symbol *find_containing_func(struct section *sec, unsigned long offset);
|
||||||
|
struct section *elf_create_section(struct elf *elf, const char *name, size_t
|
||||||
|
entsize, int nr);
|
||||||
|
struct section *elf_create_rela_section(struct elf *elf, struct section *base);
|
||||||
|
int elf_rebuild_rela_section(struct section *sec);
|
||||||
|
int elf_write(struct elf *elf);
|
||||||
void elf_close(struct elf *elf);
|
void elf_close(struct elf *elf);
|
||||||
|
|
||||||
#define for_each_sec(file, sec) \
|
#define for_each_sec(file, sec) \
|
||||||
|
|||||||
@@ -42,10 +42,11 @@ struct cmd_struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char objtool_usage_string[] =
|
static const char objtool_usage_string[] =
|
||||||
"objtool [OPTIONS] COMMAND [ARGS]";
|
"objtool COMMAND [ARGS]";
|
||||||
|
|
||||||
static struct cmd_struct objtool_cmds[] = {
|
static struct cmd_struct objtool_cmds[] = {
|
||||||
{"check", cmd_check, "Perform stack metadata validation on an object file" },
|
{"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;
|
bool help;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ORC_H
|
||||||
|
#define _ORC_H
|
||||||
|
|
||||||
|
#include "orc_types.h"
|
||||||
|
|
||||||
|
struct objtool_file;
|
||||||
|
|
||||||
|
int create_orc(struct objtool_file *file);
|
||||||
|
int create_orc_sections(struct objtool_file *file);
|
||||||
|
|
||||||
|
int orc_dump(const char *objname);
|
||||||
|
|
||||||
|
#endif /* _ORC_H */
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "orc.h"
|
||||||
|
#include "warn.h"
|
||||||
|
|
||||||
|
static const char *reg_name(unsigned int reg)
|
||||||
|
{
|
||||||
|
switch (reg) {
|
||||||
|
case ORC_REG_PREV_SP:
|
||||||
|
return "prevsp";
|
||||||
|
case ORC_REG_DX:
|
||||||
|
return "dx";
|
||||||
|
case ORC_REG_DI:
|
||||||
|
return "di";
|
||||||
|
case ORC_REG_BP:
|
||||||
|
return "bp";
|
||||||
|
case ORC_REG_SP:
|
||||||
|
return "sp";
|
||||||
|
case ORC_REG_R10:
|
||||||
|
return "r10";
|
||||||
|
case ORC_REG_R13:
|
||||||
|
return "r13";
|
||||||
|
case ORC_REG_BP_INDIRECT:
|
||||||
|
return "bp(ind)";
|
||||||
|
case ORC_REG_SP_INDIRECT:
|
||||||
|
return "sp(ind)";
|
||||||
|
default:
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *orc_type_name(unsigned int type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case ORC_TYPE_CALL:
|
||||||
|
return "call";
|
||||||
|
case ORC_TYPE_REGS:
|
||||||
|
return "regs";
|
||||||
|
case ORC_TYPE_REGS_IRET:
|
||||||
|
return "iret";
|
||||||
|
default:
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_reg(unsigned int reg, int offset)
|
||||||
|
{
|
||||||
|
if (reg == ORC_REG_BP_INDIRECT)
|
||||||
|
printf("(bp%+d)", offset);
|
||||||
|
else if (reg == ORC_REG_SP_INDIRECT)
|
||||||
|
printf("(sp%+d)", offset);
|
||||||
|
else if (reg == ORC_REG_UNDEFINED)
|
||||||
|
printf("(und)");
|
||||||
|
else
|
||||||
|
printf("%s%+d", reg_name(reg), offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
int orc_dump(const char *_objname)
|
||||||
|
{
|
||||||
|
int fd, nr_entries, i, *orc_ip = NULL, orc_size = 0;
|
||||||
|
struct orc_entry *orc = NULL;
|
||||||
|
char *name;
|
||||||
|
unsigned long nr_sections, orc_ip_addr = 0;
|
||||||
|
size_t shstrtab_idx;
|
||||||
|
Elf *elf;
|
||||||
|
Elf_Scn *scn;
|
||||||
|
GElf_Shdr sh;
|
||||||
|
GElf_Rela rela;
|
||||||
|
GElf_Sym sym;
|
||||||
|
Elf_Data *data, *symtab = NULL, *rela_orc_ip = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
objname = _objname;
|
||||||
|
|
||||||
|
elf_version(EV_CURRENT);
|
||||||
|
|
||||||
|
fd = open(objname, O_RDONLY);
|
||||||
|
if (fd == -1) {
|
||||||
|
perror("open");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
|
||||||
|
if (!elf) {
|
||||||
|
WARN_ELF("elf_begin");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elf_getshdrnum(elf, &nr_sections)) {
|
||||||
|
WARN_ELF("elf_getshdrnum");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elf_getshdrstrndx(elf, &shstrtab_idx)) {
|
||||||
|
WARN_ELF("elf_getshdrstrndx");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < nr_sections; i++) {
|
||||||
|
scn = elf_getscn(elf, i);
|
||||||
|
if (!scn) {
|
||||||
|
WARN_ELF("elf_getscn");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gelf_getshdr(scn, &sh)) {
|
||||||
|
WARN_ELF("gelf_getshdr");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = elf_strptr(elf, shstrtab_idx, sh.sh_name);
|
||||||
|
if (!name) {
|
||||||
|
WARN_ELF("elf_strptr");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = elf_getdata(scn, NULL);
|
||||||
|
if (!data) {
|
||||||
|
WARN_ELF("elf_getdata");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(name, ".symtab")) {
|
||||||
|
symtab = data;
|
||||||
|
} else if (!strcmp(name, ".orc_unwind")) {
|
||||||
|
orc = data->d_buf;
|
||||||
|
orc_size = sh.sh_size;
|
||||||
|
} else if (!strcmp(name, ".orc_unwind_ip")) {
|
||||||
|
orc_ip = data->d_buf;
|
||||||
|
orc_ip_addr = sh.sh_addr;
|
||||||
|
} else if (!strcmp(name, ".rela.orc_unwind_ip")) {
|
||||||
|
rela_orc_ip = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!symtab || !orc || !orc_ip)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (orc_size % sizeof(*orc) != 0) {
|
||||||
|
WARN("bad .orc_unwind section size");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nr_entries = orc_size / sizeof(*orc);
|
||||||
|
for (i = 0; i < nr_entries; i++) {
|
||||||
|
if (rela_orc_ip) {
|
||||||
|
if (!gelf_getrela(rela_orc_ip, i, &rela)) {
|
||||||
|
WARN_ELF("gelf_getrela");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gelf_getsym(symtab, GELF_R_SYM(rela.r_info), &sym)) {
|
||||||
|
WARN_ELF("gelf_getsym");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
scn = elf_getscn(elf, sym.st_shndx);
|
||||||
|
if (!scn) {
|
||||||
|
WARN_ELF("elf_getscn");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gelf_getshdr(scn, &sh)) {
|
||||||
|
WARN_ELF("gelf_getshdr");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = elf_strptr(elf, shstrtab_idx, sh.sh_name);
|
||||||
|
if (!name || !*name) {
|
||||||
|
WARN_ELF("elf_strptr");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s+%lx:", name, rela.r_addend);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
printf("%lx:", orc_ip_addr + (i * sizeof(int)) + orc_ip[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
printf(" sp:");
|
||||||
|
|
||||||
|
print_reg(orc[i].sp_reg, orc[i].sp_offset);
|
||||||
|
|
||||||
|
printf(" bp:");
|
||||||
|
|
||||||
|
print_reg(orc[i].bp_reg, orc[i].bp_offset);
|
||||||
|
|
||||||
|
printf(" type:%s\n", orc_type_name(orc[i].type));
|
||||||
|
}
|
||||||
|
|
||||||
|
elf_end(elf);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "orc.h"
|
||||||
|
#include "check.h"
|
||||||
|
#include "warn.h"
|
||||||
|
|
||||||
|
int create_orc(struct objtool_file *file)
|
||||||
|
{
|
||||||
|
struct instruction *insn;
|
||||||
|
|
||||||
|
for_each_insn(file, insn) {
|
||||||
|
struct orc_entry *orc = &insn->orc;
|
||||||
|
struct cfi_reg *cfa = &insn->state.cfa;
|
||||||
|
struct cfi_reg *bp = &insn->state.regs[CFI_BP];
|
||||||
|
|
||||||
|
if (cfa->base == CFI_UNDEFINED) {
|
||||||
|
orc->sp_reg = ORC_REG_UNDEFINED;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cfa->base) {
|
||||||
|
case CFI_SP:
|
||||||
|
orc->sp_reg = ORC_REG_SP;
|
||||||
|
break;
|
||||||
|
case CFI_SP_INDIRECT:
|
||||||
|
orc->sp_reg = ORC_REG_SP_INDIRECT;
|
||||||
|
break;
|
||||||
|
case CFI_BP:
|
||||||
|
orc->sp_reg = ORC_REG_BP;
|
||||||
|
break;
|
||||||
|
case CFI_BP_INDIRECT:
|
||||||
|
orc->sp_reg = ORC_REG_BP_INDIRECT;
|
||||||
|
break;
|
||||||
|
case CFI_R10:
|
||||||
|
orc->sp_reg = ORC_REG_R10;
|
||||||
|
break;
|
||||||
|
case CFI_R13:
|
||||||
|
orc->sp_reg = ORC_REG_R13;
|
||||||
|
break;
|
||||||
|
case CFI_DI:
|
||||||
|
orc->sp_reg = ORC_REG_DI;
|
||||||
|
break;
|
||||||
|
case CFI_DX:
|
||||||
|
orc->sp_reg = ORC_REG_DX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
WARN_FUNC("unknown CFA base reg %d",
|
||||||
|
insn->sec, insn->offset, cfa->base);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(bp->base) {
|
||||||
|
case CFI_UNDEFINED:
|
||||||
|
orc->bp_reg = ORC_REG_UNDEFINED;
|
||||||
|
break;
|
||||||
|
case CFI_CFA:
|
||||||
|
orc->bp_reg = ORC_REG_PREV_SP;
|
||||||
|
break;
|
||||||
|
case CFI_BP:
|
||||||
|
orc->bp_reg = ORC_REG_BP;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
WARN_FUNC("unknown BP base reg %d",
|
||||||
|
insn->sec, insn->offset, bp->base);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
orc->sp_offset = cfa->offset;
|
||||||
|
orc->bp_offset = bp->offset;
|
||||||
|
orc->type = insn->state.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_orc_entry(struct section *u_sec, struct section *ip_relasec,
|
||||||
|
unsigned int idx, struct section *insn_sec,
|
||||||
|
unsigned long insn_off, struct orc_entry *o)
|
||||||
|
{
|
||||||
|
struct orc_entry *orc;
|
||||||
|
struct rela *rela;
|
||||||
|
|
||||||
|
/* populate ORC data */
|
||||||
|
orc = (struct orc_entry *)u_sec->data->d_buf + idx;
|
||||||
|
memcpy(orc, o, sizeof(*orc));
|
||||||
|
|
||||||
|
/* populate rela for ip */
|
||||||
|
rela = malloc(sizeof(*rela));
|
||||||
|
if (!rela) {
|
||||||
|
perror("malloc");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memset(rela, 0, sizeof(*rela));
|
||||||
|
|
||||||
|
rela->sym = insn_sec->sym;
|
||||||
|
rela->addend = insn_off;
|
||||||
|
rela->type = R_X86_64_PC32;
|
||||||
|
rela->offset = idx * sizeof(int);
|
||||||
|
|
||||||
|
list_add_tail(&rela->list, &ip_relasec->rela_list);
|
||||||
|
hash_add(ip_relasec->rela_hash, &rela->hash, rela->offset);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int create_orc_sections(struct objtool_file *file)
|
||||||
|
{
|
||||||
|
struct instruction *insn, *prev_insn;
|
||||||
|
struct section *sec, *u_sec, *ip_relasec;
|
||||||
|
unsigned int idx;
|
||||||
|
|
||||||
|
struct orc_entry empty = {
|
||||||
|
.sp_reg = ORC_REG_UNDEFINED,
|
||||||
|
.bp_reg = ORC_REG_UNDEFINED,
|
||||||
|
.type = ORC_TYPE_CALL,
|
||||||
|
};
|
||||||
|
|
||||||
|
sec = find_section_by_name(file->elf, ".orc_unwind");
|
||||||
|
if (sec) {
|
||||||
|
WARN("file already has .orc_unwind section, skipping");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* count the number of needed orcs */
|
||||||
|
idx = 0;
|
||||||
|
for_each_sec(file, sec) {
|
||||||
|
if (!sec->text)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
prev_insn = NULL;
|
||||||
|
sec_for_each_insn(file, sec, insn) {
|
||||||
|
if (!prev_insn ||
|
||||||
|
memcmp(&insn->orc, &prev_insn->orc,
|
||||||
|
sizeof(struct orc_entry))) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
prev_insn = insn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* section terminator */
|
||||||
|
if (prev_insn)
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
if (!idx)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
|
||||||
|
/* create .orc_unwind_ip and .rela.orc_unwind_ip sections */
|
||||||
|
sec = elf_create_section(file->elf, ".orc_unwind_ip", sizeof(int), idx);
|
||||||
|
|
||||||
|
ip_relasec = elf_create_rela_section(file->elf, sec);
|
||||||
|
if (!ip_relasec)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* create .orc_unwind section */
|
||||||
|
u_sec = elf_create_section(file->elf, ".orc_unwind",
|
||||||
|
sizeof(struct orc_entry), idx);
|
||||||
|
|
||||||
|
/* populate sections */
|
||||||
|
idx = 0;
|
||||||
|
for_each_sec(file, sec) {
|
||||||
|
if (!sec->text)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
prev_insn = NULL;
|
||||||
|
sec_for_each_insn(file, sec, insn) {
|
||||||
|
if (!prev_insn || memcmp(&insn->orc, &prev_insn->orc,
|
||||||
|
sizeof(struct orc_entry))) {
|
||||||
|
|
||||||
|
if (create_orc_entry(u_sec, ip_relasec, idx,
|
||||||
|
insn->sec, insn->offset,
|
||||||
|
&insn->orc))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
prev_insn = insn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* section terminator */
|
||||||
|
if (prev_insn) {
|
||||||
|
if (create_orc_entry(u_sec, ip_relasec, idx,
|
||||||
|
prev_insn->sec,
|
||||||
|
prev_insn->offset + prev_insn->len,
|
||||||
|
&empty))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elf_rebuild_rela_section(ip_relasec))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ORC_TYPES_H
|
||||||
|
#define _ORC_TYPES_H
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/compiler.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ORC_REG_* registers are base registers which are used to find other
|
||||||
|
* registers on the stack.
|
||||||
|
*
|
||||||
|
* ORC_REG_PREV_SP, also known as DWARF Call Frame Address (CFA), is the
|
||||||
|
* address of the previous frame: the caller's SP before it called the current
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* ORC_REG_UNDEFINED means the corresponding register's value didn't change in
|
||||||
|
* the current frame.
|
||||||
|
*
|
||||||
|
* The most commonly used base registers are SP and BP -- which the previous SP
|
||||||
|
* is usually based on -- and PREV_SP and UNDEFINED -- which the previous BP is
|
||||||
|
* usually based on.
|
||||||
|
*
|
||||||
|
* The rest of the base registers are needed for special cases like entry code
|
||||||
|
* and GCC realigned stacks.
|
||||||
|
*/
|
||||||
|
#define ORC_REG_UNDEFINED 0
|
||||||
|
#define ORC_REG_PREV_SP 1
|
||||||
|
#define ORC_REG_DX 2
|
||||||
|
#define ORC_REG_DI 3
|
||||||
|
#define ORC_REG_BP 4
|
||||||
|
#define ORC_REG_SP 5
|
||||||
|
#define ORC_REG_R10 6
|
||||||
|
#define ORC_REG_R13 7
|
||||||
|
#define ORC_REG_BP_INDIRECT 8
|
||||||
|
#define ORC_REG_SP_INDIRECT 9
|
||||||
|
#define ORC_REG_MAX 15
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ORC_TYPE_CALL: Indicates that sp_reg+sp_offset resolves to PREV_SP (the
|
||||||
|
* caller's SP right before it made the call). Used for all callable
|
||||||
|
* functions, i.e. all C code and all callable asm functions.
|
||||||
|
*
|
||||||
|
* ORC_TYPE_REGS: Used in entry code to indicate that sp_reg+sp_offset points
|
||||||
|
* to a fully populated pt_regs from a syscall, interrupt, or exception.
|
||||||
|
*
|
||||||
|
* ORC_TYPE_REGS_IRET: Used in entry code to indicate that sp_reg+sp_offset
|
||||||
|
* points to the iret return frame.
|
||||||
|
*
|
||||||
|
* The UNWIND_HINT macros are used only for the unwind_hint struct. They
|
||||||
|
* aren't used in struct orc_entry due to size and complexity constraints.
|
||||||
|
* Objtool converts them to real types when it converts the hints to orc
|
||||||
|
* entries.
|
||||||
|
*/
|
||||||
|
#define ORC_TYPE_CALL 0
|
||||||
|
#define ORC_TYPE_REGS 1
|
||||||
|
#define ORC_TYPE_REGS_IRET 2
|
||||||
|
#define UNWIND_HINT_TYPE_SAVE 3
|
||||||
|
#define UNWIND_HINT_TYPE_RESTORE 4
|
||||||
|
|
||||||
|
#ifndef __ASSEMBLY__
|
||||||
|
/*
|
||||||
|
* This struct is more or less a vastly simplified version of the DWARF Call
|
||||||
|
* Frame Information standard. It contains only the necessary parts of DWARF
|
||||||
|
* CFI, simplified for ease of access by the in-kernel unwinder. It tells the
|
||||||
|
* unwinder how to find the previous SP and BP (and sometimes entry regs) on
|
||||||
|
* the stack for a given code address. Each instance of the struct corresponds
|
||||||
|
* to one or more code locations.
|
||||||
|
*/
|
||||||
|
struct orc_entry {
|
||||||
|
s16 sp_offset;
|
||||||
|
s16 bp_offset;
|
||||||
|
unsigned sp_reg:4;
|
||||||
|
unsigned bp_reg:4;
|
||||||
|
unsigned type:2;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This struct is used by asm and inline asm code to manually annotate the
|
||||||
|
* location of registers on the stack for the ORC unwinder.
|
||||||
|
*
|
||||||
|
* Type can be either ORC_TYPE_* or UNWIND_HINT_TYPE_*.
|
||||||
|
*/
|
||||||
|
struct unwind_hint {
|
||||||
|
u32 ip;
|
||||||
|
s16 sp_offset;
|
||||||
|
u8 sp_reg;
|
||||||
|
u8 type;
|
||||||
|
};
|
||||||
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
||||||
|
#endif /* _ORC_TYPES_H */
|
||||||
Reference in New Issue
Block a user