59d5af6732
drop numbers and commit hashes from patch metadata to reduce future patch churn
642 lines
19 KiB
Diff
642 lines
19 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Josh Poimboeuf <jpoimboe@redhat.com>
|
|
Date: Tue, 11 Jul 2017 10:33:43 -0500
|
|
Subject: [PATCH] objtool, x86: Add facility for asm code to provide unwind
|
|
hints
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
CVE-2017-5754
|
|
|
|
Some asm (and inline asm) code does special things to the stack which
|
|
objtool can't understand. (Nor can GCC or GNU assembler, for that
|
|
matter.) In such cases we need a facility for the code to provide
|
|
annotations, so the unwinder can unwind through it.
|
|
|
|
This provides such a facility, in the form of unwind hints. They're
|
|
similar to the GNU assembler .cfi* directives, but they give more
|
|
information, and are needed in far fewer places, because objtool can
|
|
fill in the blanks by following branches and adjusting the stack pointer
|
|
for pushes and pops.
|
|
|
|
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
|
|
Cc: Andy Lutomirski <luto@kernel.org>
|
|
Cc: Borislav Petkov <bp@alien8.de>
|
|
Cc: Brian Gerst <brgerst@gmail.com>
|
|
Cc: Denys Vlasenko <dvlasenk@redhat.com>
|
|
Cc: H. Peter Anvin <hpa@zytor.com>
|
|
Cc: Jiri Slaby <jslaby@suse.cz>
|
|
Cc: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Cc: Mike Galbraith <efault@gmx.de>
|
|
Cc: Peter Zijlstra <peterz@infradead.org>
|
|
Cc: Thomas Gleixner <tglx@linutronix.de>
|
|
Cc: live-patching@vger.kernel.org
|
|
Link: http://lkml.kernel.org/r/0f5f3c9104fca559ff4088bece1d14ae3bca52d5.1499786555.git.jpoimboe@redhat.com
|
|
Signed-off-by: Ingo Molnar <mingo@kernel.org>
|
|
(cherry picked from commit 39358a033b2e4432052265c1fa0f36f572d8cfb5)
|
|
Signed-off-by: Andy Whitcroft <apw@canonical.com>
|
|
Signed-off-by: Kleber Sacilotto de Souza <kleber.souza@canonical.com>
|
|
(cherry picked from commit a1fed2e10e84d48643a09861c2d127968621813e)
|
|
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
|
|
---
|
|
tools/objtool/Makefile | 3 +
|
|
arch/x86/include/asm/orc_types.h | 107 ++++++++++++++++++++
|
|
arch/x86/include/asm/unwind_hints.h | 103 +++++++++++++++++++
|
|
tools/objtool/check.h | 4 +-
|
|
tools/objtool/orc_types.h | 22 +++++
|
|
tools/objtool/check.c | 191 +++++++++++++++++++++++++++++++++---
|
|
6 files changed, 417 insertions(+), 13 deletions(-)
|
|
create mode 100644 arch/x86/include/asm/orc_types.h
|
|
create mode 100644 arch/x86/include/asm/unwind_hints.h
|
|
|
|
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
|
|
index 0e2765e243c0..3a6425fefc43 100644
|
|
--- a/tools/objtool/Makefile
|
|
+++ b/tools/objtool/Makefile
|
|
@@ -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_types.h ../../arch/x86/include/asm/inat_types.h >/dev/null) \
|
|
|| 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 $@
|
|
|
|
|
|
diff --git a/arch/x86/include/asm/orc_types.h b/arch/x86/include/asm/orc_types.h
|
|
new file mode 100644
|
|
index 000000000000..7dc777a6cb40
|
|
--- /dev/null
|
|
+++ b/arch/x86/include/asm/orc_types.h
|
|
@@ -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 */
|
|
diff --git a/arch/x86/include/asm/unwind_hints.h b/arch/x86/include/asm/unwind_hints.h
|
|
new file mode 100644
|
|
index 000000000000..5e02b11c9b86
|
|
--- /dev/null
|
|
+++ b/arch/x86/include/asm/unwind_hints.h
|
|
@@ -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 */
|
|
diff --git a/tools/objtool/check.h b/tools/objtool/check.h
|
|
index 046874bbe226..ac3d4b13f17b 100644
|
|
--- a/tools/objtool/check.h
|
|
+++ b/tools/objtool/check.h
|
|
@@ -43,7 +43,7 @@ struct instruction {
|
|
unsigned int len;
|
|
unsigned char type;
|
|
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 instruction *jump_dest;
|
|
struct list_head alts;
|
|
@@ -58,7 +58,7 @@ struct objtool_file {
|
|
struct list_head insn_list;
|
|
DECLARE_HASHTABLE(insn_hash, 16);
|
|
struct section *rodata, *whitelist;
|
|
- bool ignore_unreachables, c_file;
|
|
+ bool ignore_unreachables, c_file, hints;
|
|
};
|
|
|
|
int check(const char *objname, bool nofp, bool orc);
|
|
diff --git a/tools/objtool/orc_types.h b/tools/objtool/orc_types.h
|
|
index fc5cf6cffd9a..9c9dc579bd7d 100644
|
|
--- a/tools/objtool/orc_types.h
|
|
+++ b/tools/objtool/orc_types.h
|
|
@@ -61,11 +61,19 @@
|
|
*
|
|
* 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
|
|
@@ -82,4 +90,18 @@ struct orc_entry {
|
|
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 */
|
|
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
|
|
index cb57c526ba17..368275de5f23 100644
|
|
--- a/tools/objtool/check.c
|
|
+++ b/tools/objtool/check.c
|
|
@@ -100,7 +100,6 @@ static bool gcov_enabled(struct objtool_file *file)
|
|
static bool ignore_func(struct objtool_file *file, struct symbol *func)
|
|
{
|
|
struct rela *rela;
|
|
- struct instruction *insn;
|
|
|
|
/* check for STACK_FRAME_NON_STANDARD */
|
|
if (file->whitelist && file->whitelist->rela)
|
|
@@ -113,11 +112,6 @@ static bool ignore_func(struct objtool_file *file, struct symbol *func)
|
|
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;
|
|
}
|
|
|
|
@@ -879,6 +873,99 @@ static int add_switch_table_alts(struct objtool_file *file)
|
|
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)
|
|
{
|
|
int ret;
|
|
@@ -909,6 +996,10 @@ static int decode_sections(struct objtool_file *file)
|
|
if (ret)
|
|
return ret;
|
|
|
|
+ ret = read_unwind_hints(file);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -1382,7 +1473,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
struct insn_state state)
|
|
{
|
|
struct alternative *alt;
|
|
- struct instruction *insn;
|
|
+ struct instruction *insn, *next_insn;
|
|
struct section *sec;
|
|
struct symbol *func = NULL;
|
|
int ret;
|
|
@@ -1397,6 +1488,8 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
}
|
|
|
|
while (1) {
|
|
+ next_insn = next_insn_same_sec(file, insn);
|
|
+
|
|
if (file->c_file && insn->func) {
|
|
if (func && func != insn->func) {
|
|
WARN("%s() falls through to next function %s()",
|
|
@@ -1414,13 +1507,54 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
}
|
|
|
|
if (insn->visited) {
|
|
- if (!!insn_state_match(insn, &state))
|
|
+ if (!insn->hint && !insn_state_match(insn, &state))
|
|
return 1;
|
|
|
|
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;
|
|
|
|
@@ -1497,6 +1631,14 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
|
|
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:
|
|
if (update_insn_state(insn, &state))
|
|
return -1;
|
|
@@ -1510,7 +1652,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
if (insn->dead_end)
|
|
return 0;
|
|
|
|
- insn = next_insn_same_sec(file, insn);
|
|
+ insn = next_insn;
|
|
if (!insn) {
|
|
WARN("%s: unexpected end of section", sec->name);
|
|
return 1;
|
|
@@ -1520,6 +1662,27 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|
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)
|
|
{
|
|
return (insn->type == INSN_CALL &&
|
|
@@ -1665,8 +1828,9 @@ int check(const char *_objname, bool _nofp, bool orc)
|
|
hash_init(file.insn_hash);
|
|
file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard");
|
|
file.rodata = find_section_by_name(file.elf, ".rodata");
|
|
- file.ignore_unreachables = false;
|
|
file.c_file = find_section_by_name(file.elf, ".comment");
|
|
+ file.ignore_unreachables = false;
|
|
+ file.hints = false;
|
|
|
|
arch_initial_func_cfi_state(&initial_func_cfi);
|
|
|
|
@@ -1683,6 +1847,11 @@ int check(const char *_objname, bool _nofp, bool orc)
|
|
goto out;
|
|
warnings += ret;
|
|
|
|
+ ret = validate_unwind_hints(&file);
|
|
+ if (ret < 0)
|
|
+ goto out;
|
|
+ warnings += ret;
|
|
+
|
|
if (!warnings) {
|
|
ret = validate_reachable_instructions(&file);
|
|
if (ret < 0)
|
|
--
|
|
2.14.2
|
|
|