1340 lines
37 KiB
Diff
1340 lines
37 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Josh Poimboeuf <jpoimboe@redhat.com>
|
|
Date: Tue, 11 Jul 2017 10:33:42 -0500
|
|
Subject: [PATCH] objtool: Add ORC unwind table generation
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
CVE-2017-5754
|
|
|
|
Now that objtool knows the states of all registers on the stack for each
|
|
instruction, it's straightforward to generate debuginfo for an unwinder
|
|
to use.
|
|
|
|
Instead of generating DWARF, generate a new format called ORC, which is
|
|
more suitable for an in-kernel unwinder. See
|
|
Documentation/x86/orc-unwinder.txt for a more detailed description of
|
|
this new debuginfo format and why it's preferable to DWARF.
|
|
|
|
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/c9b9f01ba6c5ed2bdc9bb0957b78167fdbf9632e.1499786555.git.jpoimboe@redhat.com
|
|
Signed-off-by: Ingo Molnar <mingo@kernel.org>
|
|
(cherry picked from commit 627fce14809ba5610b0cb476cd0186d3fcedecfc)
|
|
Signed-off-by: Andy Whitcroft <apw@canonical.com>
|
|
Signed-off-by: Kleber Sacilotto de Souza <kleber.souza@canonical.com>
|
|
(cherry picked from commit 9460f7766786ad0f8330f78f22b81842632a5398)
|
|
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
|
|
---
|
|
tools/objtool/Documentation/stack-validation.txt | 56 ++----
|
|
tools/objtool/builtin.h | 1 +
|
|
tools/objtool/check.h | 15 +-
|
|
tools/objtool/elf.h | 15 +-
|
|
tools/objtool/orc.h | 30 ++++
|
|
tools/objtool/orc_types.h | 85 +++++++++
|
|
tools/objtool/builtin-check.c | 2 +-
|
|
tools/objtool/builtin-orc.c | 70 ++++++++
|
|
tools/objtool/check.c | 58 +++++-
|
|
tools/objtool/elf.c | 212 ++++++++++++++++++++--
|
|
tools/objtool/objtool.c | 3 +-
|
|
tools/objtool/orc_dump.c | 212 ++++++++++++++++++++++
|
|
tools/objtool/orc_gen.c | 214 +++++++++++++++++++++++
|
|
tools/objtool/Build | 3 +
|
|
14 files changed, 916 insertions(+), 60 deletions(-)
|
|
create mode 100644 tools/objtool/orc.h
|
|
create mode 100644 tools/objtool/orc_types.h
|
|
create mode 100644 tools/objtool/builtin-orc.c
|
|
create mode 100644 tools/objtool/orc_dump.c
|
|
create mode 100644 tools/objtool/orc_gen.c
|
|
|
|
diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt
|
|
index 17c1195f11f4..6a1af43862df 100644
|
|
--- a/tools/objtool/Documentation/stack-validation.txt
|
|
+++ b/tools/objtool/Documentation/stack-validation.txt
|
|
@@ -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
|
|
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
|
|
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
|
|
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?
|
|
-----------------------------------------
|
|
@@ -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
|
|
destroyed. This is an inherent limitation of frame pointers.
|
|
|
|
-b) 100% reliable stack traces for DWARF enabled kernels
|
|
-
|
|
- (NOTE: This is not yet implemented)
|
|
-
|
|
- As an alternative to frame pointers, DWARF Call Frame Information
|
|
- (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.
|
|
+b) ORC (Oops Rewind Capability) unwind table generation
|
|
|
|
- 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.
|
|
+ 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.
|
|
|
|
- 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.
|
|
+ For more details, see Documentation/x86/orc-unwinder.txt.
|
|
|
|
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
|
|
FRAME_END macros. Otherwise, if it's not a callable function, remove
|
|
its ELF function annotation by changing ENDPROC to END, and instead
|
|
- use the manual 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
|
|
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
|
|
reachable from) a callable function, the function should be annotated
|
|
with the ENTRY/ENDPROC macros (ENDPROC is the important one).
|
|
- Otherwise, the code should probably be annotated with the CFI hint
|
|
- macros in asm/undwarf.h so objtool and the unwinder can know the
|
|
+ Otherwise, the code should probably be annotated with the unwind hint
|
|
+ macros in asm/unwind_hints.h so objtool and the unwinder can know the
|
|
stack state associated with the code.
|
|
|
|
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
|
|
likely part of the kernel entry code. They should usually not have
|
|
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
|
|
@@ -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.
|
|
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
|
|
@@ -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
|
|
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
|
|
- asm/undwarf.h.
|
|
+ cases it's probably appropriate to use the unwind hint macros in
|
|
+ asm/unwind_hints.h.
|
|
|
|
|
|
8. file.o: warning: objtool: funcA() falls through to next function funcB()
|
|
diff --git a/tools/objtool/builtin.h b/tools/objtool/builtin.h
|
|
index 34d2ba78a616..dd526067fed5 100644
|
|
--- a/tools/objtool/builtin.h
|
|
+++ b/tools/objtool/builtin.h
|
|
@@ -18,5 +18,6 @@
|
|
#define _BUILTIN_H
|
|
|
|
extern int cmd_check(int argc, const char **argv);
|
|
+extern int cmd_orc(int argc, const char **argv);
|
|
|
|
#endif /* _BUILTIN_H */
|
|
diff --git a/tools/objtool/check.h b/tools/objtool/check.h
|
|
index da85f5b00ec6..046874bbe226 100644
|
|
--- a/tools/objtool/check.h
|
|
+++ b/tools/objtool/check.h
|
|
@@ -22,12 +22,14 @@
|
|
#include "elf.h"
|
|
#include "cfi.h"
|
|
#include "arch.h"
|
|
+#include "orc.h"
|
|
#include <linux/hashtable.h>
|
|
|
|
struct insn_state {
|
|
struct cfi_reg cfa;
|
|
struct cfi_reg regs[CFI_NUM_REGS];
|
|
int stack_size;
|
|
+ unsigned char type;
|
|
bool bp_scratch;
|
|
bool drap;
|
|
int drap_reg;
|
|
@@ -48,6 +50,7 @@ struct instruction {
|
|
struct symbol *func;
|
|
struct stack_op stack_op;
|
|
struct insn_state state;
|
|
+ struct orc_entry orc;
|
|
};
|
|
|
|
struct objtool_file {
|
|
@@ -58,9 +61,19 @@ struct objtool_file {
|
|
bool ignore_unreachables, c_file;
|
|
};
|
|
|
|
-int check(const char *objname, bool nofp);
|
|
+int check(const char *objname, bool nofp, bool orc);
|
|
+
|
|
+struct instruction *find_insn(struct objtool_file *file,
|
|
+ struct section *sec, unsigned long offset);
|
|
|
|
#define for_each_insn(file, insn) \
|
|
list_for_each_entry(insn, &file->insn_list, list)
|
|
|
|
+#define sec_for_each_insn(file, sec, insn) \
|
|
+ for (insn = find_insn(file, sec, 0); \
|
|
+ insn && &insn->list != &file->insn_list && \
|
|
+ insn->sec == sec; \
|
|
+ insn = list_next_entry(insn, list))
|
|
+
|
|
+
|
|
#endif /* _CHECK_H */
|
|
diff --git a/tools/objtool/elf.h b/tools/objtool/elf.h
|
|
index 343968b778cb..d86e2ff14466 100644
|
|
--- a/tools/objtool/elf.h
|
|
+++ b/tools/objtool/elf.h
|
|
@@ -28,6 +28,13 @@
|
|
# define elf_getshdrstrndx elf_getshstrndx
|
|
#endif
|
|
|
|
+/*
|
|
+ * Fallback for systems without this "read, mmaping if possible" cmd.
|
|
+ */
|
|
+#ifndef ELF_C_READ_MMAP
|
|
+#define ELF_C_READ_MMAP ELF_C_READ
|
|
+#endif
|
|
+
|
|
struct section {
|
|
struct list_head list;
|
|
GElf_Shdr sh;
|
|
@@ -41,6 +48,7 @@ struct section {
|
|
char *name;
|
|
int idx;
|
|
unsigned int len;
|
|
+ bool changed, text;
|
|
};
|
|
|
|
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 symbol *find_symbol_by_offset(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,
|
|
unsigned int len);
|
|
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);
|
|
|
|
#define for_each_sec(file, sec) \
|
|
diff --git a/tools/objtool/orc.h b/tools/objtool/orc.h
|
|
new file mode 100644
|
|
index 000000000000..a4139e386ef3
|
|
--- /dev/null
|
|
+++ b/tools/objtool/orc.h
|
|
@@ -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 */
|
|
diff --git a/tools/objtool/orc_types.h b/tools/objtool/orc_types.h
|
|
new file mode 100644
|
|
index 000000000000..fc5cf6cffd9a
|
|
--- /dev/null
|
|
+++ b/tools/objtool/orc_types.h
|
|
@@ -0,0 +1,85 @@
|
|
+/*
|
|
+ * 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.
|
|
+ */
|
|
+#define ORC_TYPE_CALL 0
|
|
+#define ORC_TYPE_REGS 1
|
|
+#define ORC_TYPE_REGS_IRET 2
|
|
+
|
|
+/*
|
|
+ * 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;
|
|
+
|
|
+#endif /* _ORC_TYPES_H */
|
|
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
|
|
index 365c34ecab26..eedf089b1495 100644
|
|
--- a/tools/objtool/builtin-check.c
|
|
+++ b/tools/objtool/builtin-check.c
|
|
@@ -52,5 +52,5 @@ int cmd_check(int argc, const char **argv)
|
|
|
|
objname = argv[0];
|
|
|
|
- return check(objname, nofp);
|
|
+ return check(objname, nofp, false);
|
|
}
|
|
diff --git a/tools/objtool/builtin-orc.c b/tools/objtool/builtin-orc.c
|
|
new file mode 100644
|
|
index 000000000000..5ca41ab0df48
|
|
--- /dev/null
|
|
+++ b/tools/objtool/builtin-orc.c
|
|
@@ -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 nofp;
|
|
+
|
|
+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, nofp, true);
|
|
+
|
|
+ }
|
|
+
|
|
+ if (!strcmp(argv[0], "dump")) {
|
|
+ if (argc != 2)
|
|
+ usage_with_options(orc_usage, check_options);
|
|
+
|
|
+ objname = argv[1];
|
|
+
|
|
+ return orc_dump(objname);
|
|
+ }
|
|
+
|
|
+ usage_with_options(orc_usage, check_options);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
|
|
index 2c6d74880403..cb57c526ba17 100644
|
|
--- a/tools/objtool/check.c
|
|
+++ b/tools/objtool/check.c
|
|
@@ -36,8 +36,8 @@ const char *objname;
|
|
static bool nofp;
|
|
struct cfi_state initial_func_cfi;
|
|
|
|
-static struct instruction *find_insn(struct objtool_file *file,
|
|
- struct section *sec, unsigned long offset)
|
|
+struct instruction *find_insn(struct objtool_file *file,
|
|
+ struct section *sec, unsigned long offset)
|
|
{
|
|
struct instruction *insn;
|
|
|
|
@@ -259,6 +259,11 @@ static int decode_instructions(struct objtool_file *file)
|
|
if (!(sec->sh.sh_flags & SHF_EXECINSTR))
|
|
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) {
|
|
insn = malloc(sizeof(*insn));
|
|
if (!insn) {
|
|
@@ -947,6 +952,30 @@ static bool has_valid_stack_frame(struct insn_state *state)
|
|
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,
|
|
int offset)
|
|
{
|
|
@@ -1032,6 +1061,9 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|
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) {
|
|
|
|
case OP_DEST_REG:
|
|
@@ -1323,6 +1355,10 @@ static bool insn_state_match(struct instruction *insn, struct insn_state *state)
|
|
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 ||
|
|
(state1->drap && state1->drap_reg != state2->drap_reg)) {
|
|
WARN_FUNC("stack state mismatch: drap1=%d(%d) drap2=%d(%d)",
|
|
@@ -1613,7 +1649,7 @@ static void cleanup(struct objtool_file *file)
|
|
elf_close(file->elf);
|
|
}
|
|
|
|
-int check(const char *_objname, bool _nofp)
|
|
+int check(const char *_objname, bool _nofp, bool orc)
|
|
{
|
|
struct objtool_file file;
|
|
int ret, warnings = 0;
|
|
@@ -1621,7 +1657,7 @@ int check(const char *_objname, bool _nofp)
|
|
objname = _objname;
|
|
nofp = _nofp;
|
|
|
|
- file.elf = elf_open(objname);
|
|
+ file.elf = elf_open(objname, orc ? O_RDWR : O_RDONLY);
|
|
if (!file.elf)
|
|
return 1;
|
|
|
|
@@ -1654,6 +1690,20 @@ int check(const char *_objname, bool _nofp)
|
|
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:
|
|
cleanup(&file);
|
|
|
|
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
|
|
index 1a7e8aa2af58..6e9f980a7d26 100644
|
|
--- a/tools/objtool/elf.c
|
|
+++ b/tools/objtool/elf.c
|
|
@@ -30,16 +30,6 @@
|
|
#include "elf.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 *sec;
|
|
@@ -349,9 +339,10 @@ static int read_relas(struct elf *elf)
|
|
return 0;
|
|
}
|
|
|
|
-struct elf *elf_open(const char *name)
|
|
+struct elf *elf_open(const char *name, int flags)
|
|
{
|
|
struct elf *elf;
|
|
+ Elf_Cmd cmd;
|
|
|
|
elf_version(EV_CURRENT);
|
|
|
|
@@ -364,13 +355,20 @@ struct elf *elf_open(const char *name)
|
|
|
|
INIT_LIST_HEAD(&elf->sections);
|
|
|
|
- elf->fd = open(name, O_RDONLY);
|
|
+ elf->fd = open(name, flags);
|
|
if (elf->fd == -1) {
|
|
perror("open");
|
|
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) {
|
|
WARN_ELF("elf_begin");
|
|
goto err;
|
|
@@ -397,6 +395,194 @@ struct elf *elf_open(const char *name)
|
|
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)
|
|
{
|
|
struct section *sec, *tmpsec;
|
|
diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c
|
|
index ecc5b1b5d15d..31e0f9143840 100644
|
|
--- a/tools/objtool/objtool.c
|
|
+++ b/tools/objtool/objtool.c
|
|
@@ -42,10 +42,11 @@ struct cmd_struct {
|
|
};
|
|
|
|
static const char objtool_usage_string[] =
|
|
- "objtool [OPTIONS] COMMAND [ARGS]";
|
|
+ "objtool COMMAND [ARGS]";
|
|
|
|
static struct cmd_struct objtool_cmds[] = {
|
|
{"check", cmd_check, "Perform stack metadata validation on an object file" },
|
|
+ {"orc", cmd_orc, "Generate in-place ORC unwind tables for an object file" },
|
|
};
|
|
|
|
bool help;
|
|
diff --git a/tools/objtool/orc_dump.c b/tools/objtool/orc_dump.c
|
|
new file mode 100644
|
|
index 000000000000..36c5bf6a2675
|
|
--- /dev/null
|
|
+++ b/tools/objtool/orc_dump.c
|
|
@@ -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;
|
|
+}
|
|
diff --git a/tools/objtool/orc_gen.c b/tools/objtool/orc_gen.c
|
|
new file mode 100644
|
|
index 000000000000..e5ca31429c9b
|
|
--- /dev/null
|
|
+++ b/tools/objtool/orc_gen.c
|
|
@@ -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;
|
|
+}
|
|
diff --git a/tools/objtool/Build b/tools/objtool/Build
|
|
index 6f2e1987c4d9..749becdf5b90 100644
|
|
--- a/tools/objtool/Build
|
|
+++ b/tools/objtool/Build
|
|
@@ -1,6 +1,9 @@
|
|
objtool-y += arch/$(SRCARCH)/
|
|
objtool-y += builtin-check.o
|
|
+objtool-y += builtin-orc.o
|
|
objtool-y += check.o
|
|
+objtool-y += orc_gen.o
|
|
+objtool-y += orc_dump.o
|
|
objtool-y += elf.o
|
|
objtool-y += special.o
|
|
objtool-y += objtool.o
|
|
--
|
|
2.14.2
|
|
|