From 627fce14809ba5610b0cb476cd0186d3fcedecfc Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 11 Jul 2017 10:33:42 -0500 Subject: objtool: Add ORC unwind table generation 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 Cc: Andy Lutomirski Cc: Borislav Petkov Cc: Brian Gerst Cc: Denys Vlasenko Cc: H. Peter Anvin Cc: Jiri Slaby Cc: Linus Torvalds Cc: Mike Galbraith Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: live-patching@vger.kernel.org Link: http://lkml.kernel.org/r/c9b9f01ba6c5ed2bdc9bb0957b78167fdbf9632e.1499786555.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/Build | 3 + tools/objtool/Documentation/stack-validation.txt | 56 ++---- tools/objtool/builtin-check.c | 2 +- tools/objtool/builtin-orc.c | 70 ++++++++ tools/objtool/builtin.h | 1 + tools/objtool/check.c | 58 +++++- tools/objtool/check.h | 15 +- tools/objtool/elf.c | 212 ++++++++++++++++++++-- tools/objtool/elf.h | 15 +- tools/objtool/objtool.c | 3 +- tools/objtool/orc.h | 30 ++++ tools/objtool/orc_dump.c | 212 ++++++++++++++++++++++ tools/objtool/orc_gen.c | 214 +++++++++++++++++++++++ tools/objtool/orc_types.h | 85 +++++++++ 14 files changed, 916 insertions(+), 60 deletions(-) create mode 100644 tools/objtool/builtin-orc.c create mode 100644 tools/objtool/orc.h create mode 100644 tools/objtool/orc_dump.c create mode 100644 tools/objtool/orc_gen.c create mode 100644 tools/objtool/orc_types.h (limited to 'tools/objtool') 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 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-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 + * + * 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 . + */ + +/* + * 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 +#include +#include "builtin.h" +#include "check.h" + + +static const char *orc_usage[] = { + "objtool orc generate [] 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/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.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/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 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.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 @@ err: 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/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/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.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 + * + * 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 . + */ + +#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_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 + * + * 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 . + */ + +#include +#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 + * + * 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 . + */ + +#include +#include + +#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/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 + * + * 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 . + */ + +#ifndef _ORC_TYPES_H +#define _ORC_TYPES_H + +#include +#include + +/* + * 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 */ -- cgit v1.3-6-gb490 From 39358a033b2e4432052265c1fa0f36f572d8cfb5 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 11 Jul 2017 10:33:43 -0500 Subject: objtool, x86: Add facility for asm code to provide unwind hints 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 Cc: Andy Lutomirski Cc: Borislav Petkov Cc: Brian Gerst Cc: Denys Vlasenko Cc: H. Peter Anvin Cc: Jiri Slaby Cc: Linus Torvalds Cc: Mike Galbraith Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: live-patching@vger.kernel.org Link: http://lkml.kernel.org/r/0f5f3c9104fca559ff4088bece1d14ae3bca52d5.1499786555.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- arch/x86/include/asm/orc_types.h | 107 ++++++++++++++++++++ arch/x86/include/asm/unwind_hints.h | 103 +++++++++++++++++++ tools/objtool/Makefile | 3 + tools/objtool/check.c | 191 +++++++++++++++++++++++++++++++++--- tools/objtool/check.h | 4 +- tools/objtool/orc_types.h | 22 +++++ 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 (limited to 'tools/objtool') 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 + * + * 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 . + */ + +#ifndef _ORC_TYPES_H +#define _ORC_TYPES_H + +#include +#include + +/* + * 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/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/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) 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 */ -- cgit v1.3-6-gb490 From 867ac9d737094e46a6c33213f16dd1ec9e8bd5d5 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 24 Jul 2017 18:34:14 -0500 Subject: objtool: Fix gcov check for older versions of GCC Objtool tries to silence 'unreachable instruction' warnings when it detects gcov is enabled, because gcov produces a lot of unreachable instructions and they don't really matter. However, the 0-day bot is still reporting some unreachable instruction warnings with CONFIG_GCOV_KERNEL=y on GCC 4.6.4. As it turns out, objtool's gcov detection doesn't work with older versions of GCC because they don't create a bunch of symbols with the 'gcov.' prefix like newer versions of GCC do. Move the gcov check out of objtool and instead just create a new '--no-unreachable' flag which can be passed in by the kernel Makefile when CONFIG_GCOV_KERNEL is defined. Also rename the 'nofp' variable to 'no_fp' for consistency with the new 'no_unreachable' variable. Reported-by: kbuild test robot Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: 9cfffb116887 ("objtool: Skip all "unreachable instruction" warnings for gcov kernels") Link: http://lkml.kernel.org/r/c243dc78eb2ffdabb6e927844dea39b6033cd395.1500939244.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- scripts/Makefile.build | 3 +++ tools/objtool/builtin-check.c | 7 ++++--- tools/objtool/builtin-orc.c | 4 ++-- tools/objtool/check.c | 36 +++++++----------------------------- tools/objtool/check.h | 2 +- 5 files changed, 17 insertions(+), 35 deletions(-) (limited to 'tools/objtool') diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 4a9a2cec0a1b..854608d42e85 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -262,6 +262,9 @@ objtool_args = check ifndef CONFIG_FRAME_POINTER objtool_args += --no-fp 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_foo.o := 'y': skip objtool checking for a file diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index eedf089b1495..57254f5b2779 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -29,7 +29,7 @@ #include "builtin.h" #include "check.h" -bool nofp; +bool no_fp, no_unreachable; static const char * const check_usage[] = { "objtool check [] file.o", @@ -37,7 +37,8 @@ static const char * const check_usage[] = { }; 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(), }; @@ -52,5 +53,5 @@ int cmd_check(int argc, const char **argv) objname = argv[0]; - return check(objname, nofp, false); + return check(objname, no_fp, no_unreachable, false); } diff --git a/tools/objtool/builtin-orc.c b/tools/objtool/builtin-orc.c index 5ca41ab0df48..4c6b5c9ef073 100644 --- a/tools/objtool/builtin-orc.c +++ b/tools/objtool/builtin-orc.c @@ -37,7 +37,7 @@ static const char *orc_usage[] = { }; extern const struct option check_options[]; -extern bool nofp; +extern bool no_fp, no_unreachable; int cmd_orc(int argc, const char **argv) { @@ -51,7 +51,7 @@ int cmd_orc(int argc, const char **argv) objname = argv[0]; - return check(objname, nofp, true); + return check(objname, no_fp, no_unreachable, true); } diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 368275de5f23..3436a942b606 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -33,7 +33,7 @@ struct alternative { }; const char *objname; -static bool nofp; +static bool no_fp; struct cfi_state initial_func_cfi; struct instruction *find_insn(struct objtool_file *file, @@ -59,19 +59,6 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file, 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) \ for (insn = find_insn(file, func->sec, func->offset); \ insn && &insn->list != &file->insn_list && \ @@ -1174,7 +1161,7 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) regs[CFI_BP].base = CFI_BP; regs[CFI_BP].offset = -state->stack_size; state->bp_scratch = false; - } else if (!nofp) { + } else if (!no_fp) { WARN_FUNC("unknown stack-related register move", insn->sec, insn->offset); @@ -1345,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 */ - if (!nofp && insn->func && op->src.reg == CFI_BP && + if (!no_fp && insn->func && op->src.reg == CFI_BP && cfa->base != CFI_BP) state->bp_scratch = true; break; @@ -1593,7 +1580,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first, /* fallthrough */ 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", sec, insn->offset); return 1; @@ -1779,15 +1766,6 @@ static int validate_reachable_instructions(struct objtool_file *file) if (insn->visited || ignore_unreachable_insn(insn)) 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); return 1; } @@ -1812,13 +1790,13 @@ static void cleanup(struct objtool_file *file) elf_close(file->elf); } -int check(const char *_objname, bool _nofp, bool orc) +int check(const char *_objname, bool _no_fp, bool no_unreachable, bool orc) { struct objtool_file file; int ret, warnings = 0; objname = _objname; - nofp = _nofp; + no_fp = _no_fp; file.elf = elf_open(objname, orc ? O_RDWR : O_RDONLY); if (!file.elf) @@ -1829,7 +1807,7 @@ int check(const char *_objname, bool _nofp, bool orc) file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard"); file.rodata = find_section_by_name(file.elf, ".rodata"); file.c_file = find_section_by_name(file.elf, ".comment"); - file.ignore_unreachables = false; + file.ignore_unreachables = no_unreachable; file.hints = false; arch_initial_func_cfi_state(&initial_func_cfi); diff --git a/tools/objtool/check.h b/tools/objtool/check.h index ac3d4b13f17b..c9af11f0c8af 100644 --- a/tools/objtool/check.h +++ b/tools/objtool/check.h @@ -61,7 +61,7 @@ struct objtool_file { bool ignore_unreachables, c_file, hints; }; -int check(const char *objname, bool nofp, bool orc); +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); -- cgit v1.3-6-gb490 From 649ea4d5a624f061a111b1f1cb0e47cfdc3ac21b Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 27 Jul 2017 15:56:53 -0500 Subject: objtool: Assume unannotated UD2 instructions are dead ends Arnd reported some false positive warnings with GCC 7: drivers/hid/wacom_wac.o: warning: objtool: wacom_bpt3_touch()+0x2a5: stack state mismatch: cfa1=7+8 cfa2=6+16 drivers/iio/adc/vf610_adc.o: warning: objtool: vf610_adc_calculate_rates() falls through to next function vf610_adc_sample_set() drivers/pwm/pwm-hibvt.o: warning: objtool: hibvt_pwm_get_state() falls through to next function hibvt_pwm_remove() drivers/pwm/pwm-mediatek.o: warning: objtool: mtk_pwm_config() falls through to next function mtk_pwm_enable() drivers/spi/spi-bcm2835.o: warning: objtool: .text: unexpected end of section drivers/spi/spi-bcm2835aux.o: warning: objtool: .text: unexpected end of section drivers/watchdog/digicolor_wdt.o: warning: objtool: dc_wdt_get_timeleft() falls through to next function dc_wdt_restart() When GCC 7 detects a potential divide-by-zero condition, it sometimes inserts a UD2 instruction for the case where the divisor is zero, instead of letting the hardware trap on the divide instruction. Objtool doesn't consider UD2 to be fatal unless it's annotated with unreachable(). So it considers the GCC-generated UD2 to be non-fatal, and it tries to follow the control flow past the UD2 and gets confused. Previously, objtool *did* assume UD2 was always a dead end. That changed with the following commit: d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends") The motivation behind that change was that Peter was planning on using UD2 for __WARN(), which is *not* a dead end. However, it turns out that some emulators rely on UD2 being fatal, so he ended up using 'ud0' instead: 9a93848fe787 ("x86/debug: Implement __WARN() using UD0") For GCC 4.5+, it should be safe to go back to the previous assumption that UD2 is fatal, even when it's not annotated with unreachable(). But for pre-4.5 versions of GCC, the unreachable() macro isn't supported, so such cases of UD2 need to be explicitly annotated as reachable. Reported-by: Arnd Bergmann Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: d1091c7fa3d5 ("objtool: Improve detection of BUG() and other dead ends") Link: http://lkml.kernel.org/r/e57fa9dfede25f79487da8126ee9cdf7b856db65.1501188854.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- include/linux/compiler-gcc.h | 16 ------------ include/linux/compiler.h | 25 +++++++++++++++++- tools/objtool/arch.h | 5 ++-- tools/objtool/arch/x86/decode.c | 17 ++++++++---- tools/objtool/check.c | 57 +++++++++++++++++++++++++++++++++++++++-- 5 files changed, 94 insertions(+), 26 deletions(-) (limited to 'tools/objtool') diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h index 17cbe3cffc0e..dd4b4c59c2f0 100644 --- a/include/linux/compiler-gcc.h +++ b/include/linux/compiler-gcc.h @@ -128,22 +128,6 @@ #define __always_unused __attribute__((unused)) #define __mode(x) __attribute__((mode(x))) -#ifdef CONFIG_STACK_VALIDATION -#define annotate_unreachable() ({ \ - asm("%c0:\n\t" \ - ".pushsection .discard.unreachable\n\t" \ - ".long %c0b - .\n\t" \ - ".popsection\n\t" : : "i" (__LINE__)); \ -}) -#define ASM_UNREACHABLE \ - "999:\n\t" \ - ".pushsection .discard.unreachable\n\t" \ - ".long 999b - .\n\t" \ - ".popsection\n\t" -#else -#define annotate_unreachable() -#endif - /* gcc version specific checks */ #if GCC_VERSION < 30200 diff --git a/include/linux/compiler.h b/include/linux/compiler.h index 641f5912d75f..70f070ddd7c7 100644 --- a/include/linux/compiler.h +++ b/include/linux/compiler.h @@ -185,11 +185,34 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val, #endif /* Unreachable code */ +#ifdef CONFIG_STACK_VALIDATION +#define annotate_reachable() ({ \ + asm("%c0:\n\t" \ + ".pushsection .discard.reachable\n\t" \ + ".long %c0b - .\n\t" \ + ".popsection\n\t" : : "i" (__LINE__)); \ +}) +#define annotate_unreachable() ({ \ + asm("%c0:\n\t" \ + ".pushsection .discard.unreachable\n\t" \ + ".long %c0b - .\n\t" \ + ".popsection\n\t" : : "i" (__LINE__)); \ +}) +#define ASM_UNREACHABLE \ + "999:\n\t" \ + ".pushsection .discard.unreachable\n\t" \ + ".long 999b - .\n\t" \ + ".popsection\n\t" +#else +#define annotate_reachable() +#define annotate_unreachable() +#endif + #ifndef ASM_UNREACHABLE # define ASM_UNREACHABLE #endif #ifndef unreachable -# define unreachable() do { } while (1) +# define unreachable() do { annotate_reachable(); do { } while (1); } while (0) #endif /* diff --git a/tools/objtool/arch.h b/tools/objtool/arch.h index 21aeca874edb..b0d7dc3d71b5 100644 --- a/tools/objtool/arch.h +++ b/tools/objtool/arch.h @@ -31,8 +31,9 @@ #define INSN_RETURN 6 #define INSN_CONTEXT_SWITCH 7 #define INSN_STACK 8 -#define INSN_NOP 9 -#define INSN_OTHER 10 +#define INSN_BUG 9 +#define INSN_NOP 10 +#define INSN_OTHER 11 #define INSN_LAST INSN_OTHER enum op_dest_type { diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index a36c2eba64e7..e4b400b67e41 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -382,20 +382,27 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, case 0x0f: - if (op2 >= 0x80 && op2 <= 0x8f) + if (op2 >= 0x80 && op2 <= 0x8f) { + *type = INSN_JUMP_CONDITIONAL; - else if (op2 == 0x05 || op2 == 0x07 || op2 == 0x34 || - op2 == 0x35) + + } else if (op2 == 0x05 || op2 == 0x07 || op2 == 0x34 || + op2 == 0x35) { /* sysenter, sysret */ *type = INSN_CONTEXT_SWITCH; - else if (op2 == 0x0d || op2 == 0x1f) + } else if (op2 == 0x0b || op2 == 0xb9) { + + /* ud2 */ + *type = INSN_BUG; + + } else if (op2 == 0x0d || op2 == 0x1f) { /* nopl/nopw */ *type = INSN_NOP; - else if (op2 == 0xa0 || op2 == 0xa8) { + } else if (op2 == 0xa0 || op2 == 0xa8) { /* push fs/gs */ *type = INSN_STACK; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 3436a942b606..d07bf4a62b45 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -296,7 +296,7 @@ static int decode_instructions(struct objtool_file *file) } /* - * Find all uses of the unreachable() macro, which are code path dead ends. + * Mark "ud2" instructions and manually annotated dead ends. */ static int add_dead_ends(struct objtool_file *file) { @@ -305,9 +305,20 @@ static int add_dead_ends(struct objtool_file *file) struct instruction *insn; bool found; + /* + * By default, "ud2" is a dead end unless otherwise annotated, because + * GCC 7 inserts it for certain divide-by-zero cases. + */ + for_each_insn(file, insn) + if (insn->type == INSN_BUG) + insn->dead_end = true; + + /* + * Check for manually annotated dead ends. + */ sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); if (!sec) - return 0; + goto reachable; list_for_each_entry(rela, &sec->rela_list, list) { if (rela->sym->type != STT_SECTION) { @@ -340,6 +351,48 @@ static int add_dead_ends(struct objtool_file *file) insn->dead_end = true; } +reachable: + /* + * These manually annotated reachable checks are needed for GCC 4.4, + * where the Linux unreachable() macro isn't supported. In that case + * GCC doesn't know the "ud2" is fatal, so it generates code as if it's + * not a dead end. + */ + sec = find_section_by_name(file->elf, ".rela.discard.reachable"); + if (!sec) + return 0; + + list_for_each_entry(rela, &sec->rela_list, list) { + if (rela->sym->type != STT_SECTION) { + WARN("unexpected relocation symbol type in %s", sec->name); + return -1; + } + insn = find_insn(file, rela->sym->sec, rela->addend); + if (insn) + insn = list_prev_entry(insn, list); + else if (rela->addend == rela->sym->sec->len) { + found = false; + list_for_each_entry_reverse(insn, &file->insn_list, list) { + if (insn->sec == rela->sym->sec) { + found = true; + break; + } + } + + if (!found) { + WARN("can't find reachable insn at %s+0x%x", + rela->sym->sec->name, rela->addend); + return -1; + } + } else { + WARN("can't find reachable insn at %s+0x%x", + rela->sym->sec->name, rela->addend); + return -1; + } + + insn->dead_end = false; + } + return 0; } -- cgit v1.3-6-gb490 From 0e2bb2bc14b3df754b0a86e87cd8923df0701a1b Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 27 Jul 2017 15:56:54 -0500 Subject: objtool: Skip unreachable warnings for 'alt' instructions When a whitelisted function uses one of the ALTERNATIVE macros, it produces false positive warnings like: arch/x86/kvm/vmx.o: warning: objtool: .altinstr_replacement+0x0: unreachable instruction arch/x86/kvm/svm.o: warning: objtool: .altinstr_replacement+0x6e: unreachable instruction There's no easy way to whitelist alternative instructions, so instead just skip any 'unreachable' warnings associated with them. Reported-by: Arnd Bergmann Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Link: http://lkml.kernel.org/r/a5d0a8c60155f03b36a31fac871e12cf75f35fd0.1501188854.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/check.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index d07bf4a62b45..4f0c4aea8f6f 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1746,8 +1746,13 @@ static bool ignore_unreachable_insn(struct instruction *insn) /* * Ignore any unused exceptions. This can happen when a whitelisted * function has an exception table entry. + * + * Also ignore alternative replacement instructions. This can happen + * when a whitelisted function uses one of the ALTERNATIVE macros. */ - if (!strcmp(insn->sec->name, ".fixup")) + if (!strcmp(insn->sec->name, ".fixup") || + !strcmp(insn->sec->name, ".altinstr_replacement") || + !strcmp(insn->sec->name, ".altinstr_aux")) return true; /* -- cgit v1.3-6-gb490 From 5b8de48e82ba322483c925ce33f193e28e59a5fd Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 27 Jul 2017 15:56:55 -0500 Subject: objtool: Fix '-mtune=atom' decoding support in objtool 2.0 With '-mtune=atom', which is enabled with CONFIG_MATOM=y, GCC uses some unusual instructions for setting up the stack. Instead of: mov %rsp, %rbp it does: lea (%rsp), %rbp And instead of: add imm, %rsp it does: lea disp(%rsp), %rsp Add support for these instructions to the objtool decoder. Reported-by: Arnd Bergmann Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: baa41469a7b9 ("objtool: Implement stack validation 2.0") Link: http://lkml.kernel.org/r/4ea1db896e821226efe1f8e09f270771bde47e65.1501188854.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/arch/x86/decode.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index e4b400b67e41..7841e5d31973 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -271,7 +271,7 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, case 0x8d: if (rex == 0x48 && modrm == 0x65) { - /* lea -disp(%rbp), %rsp */ + /* lea disp(%rbp), %rsp */ *type = INSN_STACK; op->src.type = OP_SRC_ADD; op->src.reg = CFI_BP; @@ -281,6 +281,30 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, break; } + if (rex == 0x48 && (modrm == 0xa4 || modrm == 0x64) && + sib == 0x24) { + + /* lea disp(%rsp), %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = insn.displacement.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + if (rex == 0x48 && modrm == 0x2c && sib == 0x24) { + + /* lea (%rsp), %rbp */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = CFI_SP; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_BP; + break; + } + if (rex == 0x4c && modrm == 0x54 && sib == 0x24 && insn.displacement.value == 8) { -- cgit v1.3-6-gb490 From 21ec3bf6aeb4033210747837421e63286ba32646 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 27 Jul 2017 15:56:56 -0500 Subject: objtool: Disable GCC '-Wpacked' warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Objtool is failing to build with GCC 4.4.7 due to the following warnings: cc1: warnings being treated as errors In file included from orc.h:21, from orc_gen.c:21: orc_types.h:86: error: packed attribute is unnecessary for ‘sp_offset’ orc_types.h:87: error: packed attribute is unnecessary for ‘bp_offset’ orc_types.h:88: error: packed attribute is unnecessary for ‘sp_reg’ I suspect those warnings are a GCC bug. But -Wpacked isn't very useful anyway, so just disable it. Signed-off-by: Josh Poimboeuf Cc: Arnd Bergmann Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: 627fce14809b ("objtool: Add ORC unwind table generation") Link: http://lkml.kernel.org/r/76d85d7b5a87566465095c500bce222ff5d7b146.1501188854.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 3a6425fefc43..6976c73e60c4 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -25,7 +25,8 @@ OBJTOOL_IN := $(OBJTOOL)-in.o all: $(OBJTOOL) INCLUDES := -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(HOSTARCH)/include/uapi -CFLAGS += -Wall -Werror $(EXTRA_WARNINGS) -Wno-switch-default -Wno-switch-enum -fomit-frame-pointer -O2 -g $(INCLUDES) +WARNINGS := $(EXTRA_WARNINGS) -Wno-switch-default -Wno-switch-enum -Wno-packed +CFLAGS += -Wall -Werror $(WARNINGS) -fomit-frame-pointer -O2 -g $(INCLUDES) LDFLAGS += -lelf $(LIBSUBCMD) # Allow old libelf to be used: -- cgit v1.3-6-gb490 From 12b25729a194198e3c4289adaddc4115b10b094e Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 10 Aug 2017 16:37:25 -0500 Subject: objtool: Fix validate_branch() return codes The validate_branch() function should never return a negative value. Errors are treated as warnings so that even if something goes wrong, objtool does its best to generate ORC data for the rest of the file. Signed-off-by: Josh Poimboeuf Cc: Andy Lutomirski Cc: Arnd Bergmann Cc: Borislav Petkov Cc: Brian Gerst Cc: Denys Vlasenko Cc: H. Peter Anvin Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: baa41469a7b9 ("objtool: Implement stack validation 2.0") Link: http://lkml.kernel.org/r/d86671cfde823b50477cd2f6f548dfe54871e24d.1502401017.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/check.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 4f0c4aea8f6f..5814e907f8c2 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1524,7 +1524,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first, if (insn->alt_group && list_empty(&insn->alts)) { WARN_FUNC("don't know how to handle branch to middle of alternative instruction group", sec, insn->offset); - return -1; + return 1; } while (1) { @@ -1543,7 +1543,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first, if (func && insn->ignore) { WARN_FUNC("BUG: why am I validating an ignored function?", sec, insn->offset); - return -1; + return 1; } if (insn->visited) { @@ -1681,7 +1681,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first, case INSN_STACK: if (update_insn_state(insn, &state)) - return -1; + return 1; break; -- cgit v1.3-6-gb490 From bf4d1a83758368c842c94cab9661a75ca98bc848 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 10 Aug 2017 16:37:26 -0500 Subject: objtool: Track DRAP separately from callee-saved registers When GCC realigns a function's stack, it sometimes uses %r13 as the DRAP register, like: push %r13 lea 0x10(%rsp), %r13 and $0xfffffffffffffff0, %rsp pushq -0x8(%r13) push %rbp mov %rsp, %rbp push %r13 ... mov -0x8(%rbp),%r13 leaveq lea -0x10(%r13), %rsp pop %r13 retq Since %r13 was pushed onto the stack twice, its two stack locations need to be stored separately. The first push of %r13 is its original value, and the second push of %r13 is the caller's stack frame address. Since %r13 is a callee-saved register, we need to track the stack location of its original value separately from the DRAP register. This fixes the following false positive warning: lib/ubsan.o: warning: objtool: val_to_string.constprop.7()+0x97: leave instruction with modified stack frame Reported-by: Arnd Bergmann Signed-off-by: Josh Poimboeuf Cc: Andy Lutomirski Cc: Borislav Petkov Cc: Brian Gerst Cc: Denys Vlasenko Cc: H. Peter Anvin Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: baa41469a7b9 ("objtool: Implement stack validation 2.0") Link: http://lkml.kernel.org/r/3da23a6d4c5b3c1e21fc2ccc21a73941b97ff20a.1502401017.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/check.c | 54 ++++++++++++++++++++++++++++----------------------- tools/objtool/check.h | 2 +- 2 files changed, 31 insertions(+), 25 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 5814e907f8c2..17375925e7aa 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -221,6 +221,7 @@ static void clear_insn_state(struct insn_state *state) for (i = 0; i < CFI_NUM_REGS; i++) state->regs[i].base = CFI_UNDEFINED; state->drap_reg = CFI_UNDEFINED; + state->drap_offset = -1; } /* @@ -1110,8 +1111,7 @@ static int update_insn_state_regs(struct instruction *insn, struct insn_state *s static void save_reg(struct insn_state *state, unsigned char reg, int base, int offset) { - if ((arch_callee_saved_reg(reg) || - (state->drap && reg == state->drap_reg)) && + if (arch_callee_saved_reg(reg) && state->regs[reg].base == CFI_UNDEFINED) { state->regs[reg].base = base; state->regs[reg].offset = offset; @@ -1281,7 +1281,6 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) cfa->base = state->drap_reg; cfa->offset = state->stack_size = 0; state->drap = true; - } /* @@ -1299,17 +1298,19 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) cfa->base = CFI_SP; } - if (regs[op->dest.reg].offset == -state->stack_size) { + if (state->drap && cfa->base == CFI_BP_INDIRECT && + op->dest.type == OP_DEST_REG && + op->dest.reg == state->drap_reg && + state->drap_offset == -state->stack_size) { - if (state->drap && cfa->base == CFI_BP_INDIRECT && - op->dest.type == OP_DEST_REG && - op->dest.reg == state->drap_reg) { + /* drap: pop %drap */ + cfa->base = state->drap_reg; + cfa->offset = 0; + state->drap_offset = -1; - /* drap: pop %drap */ - cfa->base = state->drap_reg; - cfa->offset = 0; - } + } else if (regs[op->dest.reg].offset == -state->stack_size) { + /* pop %reg */ restore_reg(state, op->dest.reg); } @@ -1320,15 +1321,19 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) break; case OP_SRC_REG_INDIRECT: + if (state->drap && op->src.reg == CFI_BP && + op->src.offset == state->drap_offset) { + + /* drap: mov disp(%rbp), %drap */ + cfa->base = state->drap_reg; + cfa->offset = 0; + state->drap_offset = -1; + } + if (state->drap && op->src.reg == CFI_BP && op->src.offset == regs[op->dest.reg].offset) { /* drap: mov disp(%rbp), %reg */ - if (op->dest.reg == state->drap_reg) { - cfa->base = state->drap_reg; - cfa->offset = 0; - } - restore_reg(state, op->dest.reg); } else if (op->src.reg == cfa->base && @@ -1364,8 +1369,8 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) cfa->base = CFI_BP_INDIRECT; cfa->offset = -state->stack_size; - /* save drap so we know when to undefine it */ - save_reg(state, op->src.reg, CFI_CFA, -state->stack_size); + /* save drap so we know when to restore it */ + state->drap_offset = -state->stack_size; } else if (op->src.reg == CFI_BP && cfa->base == state->drap_reg) { @@ -1399,8 +1404,8 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) cfa->base = CFI_BP_INDIRECT; cfa->offset = op->dest.offset; - /* save drap so we know when to undefine it */ - save_reg(state, op->src.reg, CFI_CFA, op->dest.offset); + /* save drap offset so we know when to restore it */ + state->drap_offset = op->dest.offset; } else if (regs[op->src.reg].base == CFI_UNDEFINED) { @@ -1491,11 +1496,12 @@ static bool insn_state_match(struct instruction *insn, struct insn_state *state) 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)", + (state1->drap && state1->drap_reg != state2->drap_reg) || + (state1->drap && state1->drap_offset != state2->drap_offset)) { + WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", insn->sec, insn->offset, - state1->drap, state1->drap_reg, - state2->drap, state2->drap_reg); + state1->drap, state1->drap_reg, state1->drap_offset, + state2->drap, state2->drap_reg, state2->drap_offset); } else return true; diff --git a/tools/objtool/check.h b/tools/objtool/check.h index c9af11f0c8af..9f113016bf8c 100644 --- a/tools/objtool/check.h +++ b/tools/objtool/check.h @@ -32,7 +32,7 @@ struct insn_state { unsigned char type; bool bp_scratch; bool drap; - int drap_reg; + int drap_reg, drap_offset; }; struct instruction { -- cgit v1.3-6-gb490 From ee97638b5737cc0dba2f12a3bdcda761656b7c01 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 11 Aug 2017 12:24:15 -0500 Subject: objtool: Fix objtool fallthrough detection with function padding When GCC adds NOP padding between functions, those NOPs aren't associated with a function symbol, which breaks objtool's detection of a function falling through to another function. Instead it shows confusing errors like: drivers/mtd/chips/cfi_util.o: warning: objtool: cfi_qry_mode_on()+0x8b: return with modified stack frame drivers/mtd/chips/cfi_util.o: warning: objtool: cfi_qry_mode_on()+0x0: stack state mismatch: cfa1=-4-32 cfa2=7+8 drivers/mtd/chips/cfi_cmdset_0002.o: warning: objtool: fixup_use_fwh_lock()+0x8: unknown stack-related register move drivers/mtd/chips/cfi_cmdset_0002.o: warning: objtool: fixup_use_fwh_lock()+0x0: stack state mismatch: cfa1=6+16 cfa2=7+8 drivers/mtd/chips/cfi_cmdset_0002.o: warning: objtool: do_otp_write()+0xa: unsupported stack pointer realignment drivers/mtd/chips/cfi_cmdset_0002.o: warning: objtool: do_otp_write()+0x0: stack state mismatch: cfa1=-4-40 cfa2=7+8 Reported-by: kbuild test robot Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Link: http://lkml.kernel.org/r/43e7aae9a7a7710cd6df597fa9dc501da4ba0602.1502472193.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/check.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 17375925e7aa..3dffeb944523 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1536,15 +1536,15 @@ 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()", - func->name, insn->func->name); - return 1; - } + + if (file->c_file && func && insn->func && func != insn->func) { + WARN("%s() falls through to next function %s()", + func->name, insn->func->name); + return 1; } - func = insn->func; + if (insn->func) + func = insn->func; if (func && insn->ignore) { WARN_FUNC("BUG: why am I validating an ignored function?", -- cgit v1.3-6-gb490 From dd88a0a0c8615417fe6b4285769b5b772de87279 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 29 Aug 2017 12:51:03 -0500 Subject: objtool: Handle GCC stack pointer adjustment bug Arnd Bergmann reported the following warning with GCC 7.1.1: fs/fs_pin.o: warning: objtool: pin_kill()+0x139: stack state mismatch: cfa1=7+88 cfa2=7+96 And the kbuild robot reported the following warnings with GCC 5.4.1: fs/fs_pin.o: warning: objtool: pin_kill()+0x182: return with modified stack frame fs/quota/dquot.o: warning: objtool: dquot_alloc_inode()+0x140: stack state mismatch: cfa1=7+120 cfa2=7+128 fs/quota/dquot.o: warning: objtool: dquot_free_inode()+0x11a: stack state mismatch: cfa1=7+112 cfa2=7+120 Those warnings are caused by an unusual GCC non-optimization where it uses an intermediate register to adjust the stack pointer. It does: lea 0x8(%rsp), %rcx ... mov %rcx, %rsp Instead of the obvious: add $0x8, %rsp It makes no sense to use an intermediate register, so I opened a GCC bug to track it: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81813 But it's not exactly a high-priority bug and it looks like we'll be stuck with this issue for a while. So for now we have to track register values when they're loaded with stack pointer offsets. This is kind of a big workaround for a tiny problem, but c'est la vie. I hope to eventually create a GCC plugin to implement a big chunk of objtool's functionality. Hopefully at that point we'll be able to remove of a lot of these GCC-isms from the objtool code. Reported-by: Arnd Bergmann Reported-by: kbuild test robot Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Link: http://lkml.kernel.org/r/6a41a96884c725e7f05413bb7df40cfe824b2444.1504028945.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/arch/x86/decode.c | 94 ++++++++++++----------------------------- tools/objtool/cfi.h | 2 +- tools/objtool/check.c | 81 ++++++++++++++++++++++++++--------- tools/objtool/check.h | 1 + 4 files changed, 88 insertions(+), 90 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 7841e5d31973..0e8c8ec4fd4e 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -86,8 +86,8 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, struct insn insn; int x86_64, sign; unsigned char op1, op2, rex = 0, rex_b = 0, rex_r = 0, rex_w = 0, - modrm = 0, modrm_mod = 0, modrm_rm = 0, modrm_reg = 0, - sib = 0; + rex_x = 0, modrm = 0, modrm_mod = 0, modrm_rm = 0, + modrm_reg = 0, sib = 0; x86_64 = is_x86_64(elf); if (x86_64 == -1) @@ -114,6 +114,7 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, rex = insn.rex_prefix.bytes[0]; rex_w = X86_REX_W(rex) >> 3; rex_r = X86_REX_R(rex) >> 2; + rex_x = X86_REX_X(rex) >> 1; rex_b = X86_REX_B(rex); } @@ -217,6 +218,18 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, op->dest.reg = CFI_BP; break; } + + if (rex_w && !rex_b && modrm_mod == 3 && modrm_rm == 4) { + + /* mov reg, %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + /* fallthrough */ case 0x88: if (!rex_b && @@ -269,80 +282,28 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, break; case 0x8d: - if (rex == 0x48 && modrm == 0x65) { + if (sib == 0x24 && rex_w && !rex_b && !rex_x) { - /* lea disp(%rbp), %rsp */ + /* lea disp(%rsp), reg */ *type = INSN_STACK; op->src.type = OP_SRC_ADD; - op->src.reg = CFI_BP; + op->src.reg = CFI_SP; op->src.offset = insn.displacement.value; op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_SP; - break; - } + op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; - if (rex == 0x48 && (modrm == 0xa4 || modrm == 0x64) && - sib == 0x24) { + } else if (rex == 0x48 && modrm == 0x65) { - /* lea disp(%rsp), %rsp */ + /* lea disp(%rbp), %rsp */ *type = INSN_STACK; op->src.type = OP_SRC_ADD; - op->src.reg = CFI_SP; + op->src.reg = CFI_BP; op->src.offset = insn.displacement.value; op->dest.type = OP_DEST_REG; op->dest.reg = CFI_SP; - break; - } - if (rex == 0x48 && modrm == 0x2c && sib == 0x24) { - - /* lea (%rsp), %rbp */ - *type = INSN_STACK; - op->src.type = OP_SRC_REG; - op->src.reg = CFI_SP; - op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_BP; - break; - } - - if (rex == 0x4c && modrm == 0x54 && sib == 0x24 && - insn.displacement.value == 8) { - - /* - * lea 0x8(%rsp), %r10 - * - * Here r10 is the "drap" pointer, used as a stack - * pointer helper when the stack gets realigned. - */ - *type = INSN_STACK; - op->src.type = OP_SRC_ADD; - op->src.reg = CFI_SP; - op->src.offset = 8; - op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_R10; - break; - } - - if (rex == 0x4c && modrm == 0x6c && sib == 0x24 && - insn.displacement.value == 16) { - - /* - * lea 0x10(%rsp), %r13 - * - * Here r13 is the "drap" pointer, used as a stack - * pointer helper when the stack gets realigned. - */ - *type = INSN_STACK; - op->src.type = OP_SRC_ADD; - op->src.reg = CFI_SP; - op->src.offset = 16; - op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_R13; - break; - } - - if (rex == 0x49 && modrm == 0x62 && - insn.displacement.value == -8) { + } else if (rex == 0x49 && modrm == 0x62 && + insn.displacement.value == -8) { /* * lea -0x8(%r10), %rsp @@ -356,11 +317,9 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, op->src.offset = -8; op->dest.type = OP_DEST_REG; op->dest.reg = CFI_SP; - break; - } - if (rex == 0x49 && modrm == 0x65 && - insn.displacement.value == -16) { + } else if (rex == 0x49 && modrm == 0x65 && + insn.displacement.value == -16) { /* * lea -0x10(%r13), %rsp @@ -374,7 +333,6 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, op->src.offset = -16; op->dest.type = OP_DEST_REG; op->dest.reg = CFI_SP; - break; } break; diff --git a/tools/objtool/cfi.h b/tools/objtool/cfi.h index 443ab2c69992..2fe883c665c7 100644 --- a/tools/objtool/cfi.h +++ b/tools/objtool/cfi.h @@ -40,7 +40,7 @@ #define CFI_R14 14 #define CFI_R15 15 #define CFI_RA 16 -#define CFI_NUM_REGS 17 +#define CFI_NUM_REGS 17 struct cfi_reg { int base; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 3dffeb944523..f744617c9946 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -218,8 +218,10 @@ static void clear_insn_state(struct insn_state *state) memset(state, 0, sizeof(*state)); state->cfa.base = CFI_UNDEFINED; - for (i = 0; i < CFI_NUM_REGS; i++) + for (i = 0; i < CFI_NUM_REGS; i++) { state->regs[i].base = CFI_UNDEFINED; + state->vals[i].base = CFI_UNDEFINED; + } state->drap_reg = CFI_UNDEFINED; state->drap_offset = -1; } @@ -1201,24 +1203,47 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) switch (op->src.type) { case OP_SRC_REG: - if (cfa->base == op->src.reg && cfa->base == CFI_SP && - op->dest.reg == CFI_BP && regs[CFI_BP].base == CFI_CFA && - regs[CFI_BP].offset == -cfa->offset) { - - /* mov %rsp, %rbp */ - cfa->base = op->dest.reg; - state->bp_scratch = false; - } else if (state->drap) { - - /* drap: mov %rsp, %rbp */ - regs[CFI_BP].base = CFI_BP; - regs[CFI_BP].offset = -state->stack_size; - state->bp_scratch = false; - } else if (!no_fp) { - - WARN_FUNC("unknown stack-related register move", - insn->sec, insn->offset); - return -1; + if (op->src.reg == CFI_SP && op->dest.reg == CFI_BP) { + + if (cfa->base == CFI_SP && + regs[CFI_BP].base == CFI_CFA && + regs[CFI_BP].offset == -cfa->offset) { + + /* mov %rsp, %rbp */ + cfa->base = op->dest.reg; + state->bp_scratch = false; + } + + else if (state->drap) { + + /* drap: mov %rsp, %rbp */ + regs[CFI_BP].base = CFI_BP; + regs[CFI_BP].offset = -state->stack_size; + state->bp_scratch = false; + } + } + + else if (op->dest.reg == cfa->base) { + + /* mov %reg, %rsp */ + if (cfa->base == CFI_SP && + state->vals[op->src.reg].base == CFI_CFA) { + + /* + * This is needed for the rare case + * where GCC does something dumb like: + * + * lea 0x8(%rsp), %rcx + * ... + * mov %rcx, %rsp + */ + cfa->offset = -state->vals[op->src.reg].offset; + state->stack_size = cfa->offset; + + } else { + cfa->base = CFI_UNDEFINED; + cfa->offset = 0; + } } break; @@ -1240,11 +1265,25 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) break; } - if (op->dest.reg != CFI_BP && op->src.reg == CFI_SP && - cfa->base == CFI_SP) { + if (op->src.reg == CFI_SP && cfa->base == CFI_SP) { /* drap: lea disp(%rsp), %drap */ state->drap_reg = op->dest.reg; + + /* + * lea disp(%rsp), %reg + * + * This is needed for the rare case where GCC + * does something dumb like: + * + * lea 0x8(%rsp), %rcx + * ... + * mov %rcx, %rsp + */ + state->vals[op->dest.reg].base = CFI_CFA; + state->vals[op->dest.reg].offset = \ + -state->stack_size + op->src.offset; + break; } diff --git a/tools/objtool/check.h b/tools/objtool/check.h index 9f113016bf8c..47d9ea70a83d 100644 --- a/tools/objtool/check.h +++ b/tools/objtool/check.h @@ -33,6 +33,7 @@ struct insn_state { bool bp_scratch; bool drap; int drap_reg, drap_offset; + struct cfi_reg vals[CFI_NUM_REGS]; }; struct instruction { -- cgit v1.3-6-gb490 From 0998b7a0befdf6e734032895ee639a5e6f88cc3f Mon Sep 17 00:00:00 2001 From: Martin Kepplinger Date: Thu, 14 Sep 2017 08:01:38 +0200 Subject: objtool: Fix memory leak in elf_create_rela_section() Let's free the allocated char array 'relaname' before returning, in order to avoid leaking memory. Signed-off-by: Martin Kepplinger Acked-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: mingo.kernel.org@gmail.com Link: http://lkml.kernel.org/r/20170914060138.26472-1-martink@posteo.de Signed-off-by: Ingo Molnar --- tools/objtool/elf.c | 1 + 1 file changed, 1 insertion(+) (limited to 'tools/objtool') diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 6e9f980a7d26..1e89a5f8bfc9 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -508,6 +508,7 @@ struct section *elf_create_rela_section(struct elf *elf, struct section *base) strcat(relaname, base->name); sec = elf_create_section(elf, relaname, sizeof(GElf_Rela), 0); + free(relaname); if (!sec) return NULL; -- cgit v1.3-6-gb490 From df968c9329f6e5cf3596a0a54adb6f749747a746 Mon Sep 17 00:00:00 2001 From: Petr Vandrovec Date: Fri, 15 Sep 2017 02:15:05 -0500 Subject: objtool: Do not retrieve data from empty sections Binutils 2.29-9 in Debian return an error when elf_getdata is invoked on empty section (.note.GNU-stack in all kernel files), causing immediate failure of kernel build with: elf_getdata: can't manipulate null section As nothing is done with sections that have zero size, just do not retrieve their data at all. Signed-off-by: Petr Vandrovec Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Link: http://lkml.kernel.org/r/2ce30a44349065b70d0f00e71e286dc0cbe745e6.1505459652.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/elf.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 1e89a5f8bfc9..b4cd8bc62521 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -175,19 +175,20 @@ static int read_sections(struct elf *elf) return -1; } - sec->data = elf_getdata(s, NULL); - if (!sec->data) { - WARN_ELF("elf_getdata"); - return -1; - } - - if (sec->data->d_off != 0 || - sec->data->d_size != sec->sh.sh_size) { - WARN("unexpected data attributes for %s", sec->name); - return -1; + if (sec->sh.sh_size != 0) { + sec->data = elf_getdata(s, NULL); + if (!sec->data) { + WARN_ELF("elf_getdata"); + return -1; + } + if (sec->data->d_off != 0 || + sec->data->d_size != sec->sh.sh_size) { + WARN("unexpected data attributes for %s", + sec->name); + return -1; + } } - - sec->len = sec->data->d_size; + sec->len = sec->sh.sh_size; } /* sanity check, one more call to elf_nextscn() should return NULL */ -- cgit v1.3-6-gb490 From 97dab2ae7e8473a821f72a039ead0f36b12ba22d Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 15 Sep 2017 02:17:11 -0500 Subject: objtool: Fix object file corruption Arnd Bergmann reported that a randconfig build was failing with the following link error: built-in.o: member arch/x86/kernel/time.o in archive is not an object It turns out the link failed because the time.o file had been corrupted by objtool: nm: arch/x86/kernel/time.o: File format not recognized In certain rare cases, when a .o file's ORC table is very small, the .data section size doesn't change because it's page aligned. Because all the existing sections haven't changed size, libelf doesn't detect any section header changes, and so it doesn't update the section header table properly. Instead it writes junk in the section header entries for the new ORC sections. Make sure libelf properly updates the section header table by setting the ELF_F_DIRTY flag in the top level elf struct. Reported-by: Arnd Bergmann Signed-off-by: Josh Poimboeuf Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: 627fce14809b ("objtool: Add ORC unwind table generation") Link: http://lkml.kernel.org/r/e650fd0f2d8a209d1409a9785deb101fdaed55fb.1505459813.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/elf.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'tools/objtool') diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index b4cd8bc62521..24460155c82c 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -563,6 +563,7 @@ int elf_write(struct elf *elf) struct section *sec; Elf_Scn *s; + /* Update section headers for changed sections: */ list_for_each_entry(sec, &elf->sections, list) { if (sec->changed) { s = elf_getscn(elf->elf, sec->idx); @@ -570,13 +571,17 @@ int elf_write(struct elf *elf) WARN_ELF("elf_getscn"); return -1; } - if (!gelf_update_shdr (s, &sec->sh)) { + if (!gelf_update_shdr(s, &sec->sh)) { WARN_ELF("gelf_update_shdr"); return -1; } } } + /* Make sure the new section header entries get updated properly. */ + elf_flagelf(elf->elf, ELF_C_SET, ELF_F_DIRTY); + + /* Write all changes to the file. */ if (elf_update(elf->elf, ELF_C_WRITE) < 0) { WARN_ELF("elf_update"); return -1; -- cgit v1.3-6-gb490 From 0d0970eef3b03ef08b19da5bc3044410731cf38f Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 20 Sep 2017 16:24:32 -0500 Subject: objtool: Handle another GCC stack pointer adjustment bug The kbuild bot reported the following warning with GCC 4.4 and a randconfig: net/socket.o: warning: objtool: compat_sock_ioctl()+0x1083: stack state mismatch: cfa1=7+160 cfa2=-1+0 This is caused by another GCC non-optimization, where it backs up and restores the stack pointer for no apparent reason: 2f91: 48 89 e0 mov %rsp,%rax 2f94: 4c 89 e7 mov %r12,%rdi 2f97: 4c 89 f6 mov %r14,%rsi 2f9a: ba 20 00 00 00 mov $0x20,%edx 2f9f: 48 89 c4 mov %rax,%rsp This issue would have been happily ignored before the following commit: dd88a0a0c861 ("objtool: Handle GCC stack pointer adjustment bug") But now that objtool is paying attention to such stack pointer writes to/from a register, it needs to understand them properly. In this case that means recognizing that the "mov %rsp, %rax" instruction is potentially a backup of the stack pointer. Reported-by: kbuild test robot Signed-off-by: Josh Poimboeuf Cc: Alexander Potapenko Cc: Andrey Ryabinin Cc: Andy Lutomirski Cc: Arnd Bergmann Cc: Dmitriy Vyukov Cc: Linus Torvalds Cc: Matthias Kaehlcke Cc: Miguel Bernal Marin Cc: Peter Zijlstra Cc: Thomas Gleixner Fixes: dd88a0a0c861 ("objtool: Handle GCC stack pointer adjustment bug") Link: http://lkml.kernel.org/r/8c7aa8e9a36fbbb6655d9d8e7cea58958c912da8.1505942196.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- tools/objtool/arch/x86/decode.c | 6 +++--- tools/objtool/check.c | 43 +++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 17 deletions(-) (limited to 'tools/objtool') diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 0e8c8ec4fd4e..0f22768c0d4d 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -208,14 +208,14 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, break; case 0x89: - if (rex == 0x48 && modrm == 0xe5) { + if (rex_w && !rex_r && modrm_mod == 3 && modrm_reg == 4) { - /* mov %rsp, %rbp */ + /* mov %rsp, reg */ *type = INSN_STACK; op->src.type = OP_SRC_REG; op->src.reg = CFI_SP; op->dest.type = OP_DEST_REG; - op->dest.reg = CFI_BP; + op->dest.reg = op_to_cfi_reg[modrm_rm][rex_b]; break; } diff --git a/tools/objtool/check.c b/tools/objtool/check.c index f744617c9946..a0c518ecf085 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1203,24 +1203,39 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) switch (op->src.type) { case OP_SRC_REG: - if (op->src.reg == CFI_SP && op->dest.reg == CFI_BP) { + if (op->src.reg == CFI_SP && op->dest.reg == CFI_BP && + cfa->base == CFI_SP && + regs[CFI_BP].base == CFI_CFA && + regs[CFI_BP].offset == -cfa->offset) { + + /* mov %rsp, %rbp */ + cfa->base = op->dest.reg; + state->bp_scratch = false; + } - if (cfa->base == CFI_SP && - regs[CFI_BP].base == CFI_CFA && - regs[CFI_BP].offset == -cfa->offset) { + else if (op->src.reg == CFI_SP && + op->dest.reg == CFI_BP && state->drap) { - /* mov %rsp, %rbp */ - cfa->base = op->dest.reg; - state->bp_scratch = false; - } + /* drap: mov %rsp, %rbp */ + regs[CFI_BP].base = CFI_BP; + regs[CFI_BP].offset = -state->stack_size; + state->bp_scratch = false; + } - else if (state->drap) { + else if (op->src.reg == CFI_SP && cfa->base == CFI_SP) { - /* drap: mov %rsp, %rbp */ - regs[CFI_BP].base = CFI_BP; - regs[CFI_BP].offset = -state->stack_size; - state->bp_scratch = false; - } + /* + * mov %rsp, %reg + * + * This is needed for the rare case where GCC + * does: + * + * mov %rsp, %rax + * ... + * mov %rax, %rsp + */ + state->vals[op->dest.reg].base = CFI_CFA; + state->vals[op->dest.reg].offset = -state->stack_size; } else if (op->dest.reg == cfa->base) { -- cgit v1.3-6-gb490 From f5caf621ee357279e759c0911daf6d55c7d36f03 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 20 Sep 2017 16:24:33 -0500 Subject: x86/asm: Fix inline asm call constraints for Clang For inline asm statements which have a CALL instruction, we list the stack pointer as a constraint to convince GCC to ensure the frame pointer is set up first: static inline void foo() { register void *__sp asm(_ASM_SP); asm("call bar" : "+r" (__sp)) } Unfortunately, that pattern causes Clang to corrupt the stack pointer. The fix is easy: convert the stack pointer register variable to a global variable. It should be noted that the end result is different based on the GCC version. With GCC 6.4, this patch has exactly the same result as before: defconfig defconfig-nofp distro distro-nofp before 9820389 9491555 8816046 8516940 after 9820389 9491555 8816046 8516940 With GCC 7.2, however, GCC's behavior has changed. It now changes its behavior based on the conversion of the register variable to a global. That somehow convinces it to *always* set up the frame pointer before inserting *any* inline asm. (Therefore, listing the variable as an output constraint is a no-op and is no longer necessary.) It's a bit overkill, but the performance impact should be negligible. And in fact, there's a nice improvement with frame pointers disabled: defconfig defconfig-nofp distro distro-nofp before 9796316 9468236 9076191 8790305 after 9796957 9464267 9076381 8785949 So in summary, while listing the stack pointer as an output constraint is no longer necessary for newer versions of GCC, it's still needed for older versions. Suggested-by: Andrey Ryabinin Reported-by: Matthias Kaehlcke Signed-off-by: Josh Poimboeuf Cc: Alexander Potapenko Cc: Andy Lutomirski Cc: Arnd Bergmann Cc: Dmitriy Vyukov Cc: Linus Torvalds Cc: Miguel Bernal Marin Cc: Peter Zijlstra Cc: Thomas Gleixner Link: http://lkml.kernel.org/r/3db862e970c432ae823cf515c52b54fec8270e0e.1505942196.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar --- arch/x86/include/asm/alternative.h | 3 +-- arch/x86/include/asm/asm.h | 11 +++++++++++ arch/x86/include/asm/mshyperv.h | 10 ++++------ arch/x86/include/asm/paravirt_types.h | 14 +++++++------- arch/x86/include/asm/preempt.h | 15 +++++---------- arch/x86/include/asm/processor.h | 6 ++---- arch/x86/include/asm/rwsem.h | 4 ++-- arch/x86/include/asm/uaccess.h | 4 ++-- arch/x86/include/asm/xen/hypercall.h | 5 ++--- arch/x86/kvm/emulate.c | 3 +-- arch/x86/kvm/vmx.c | 3 +-- arch/x86/mm/fault.c | 3 +-- tools/objtool/Documentation/stack-validation.txt | 6 +++--- 13 files changed, 42 insertions(+), 45 deletions(-) (limited to 'tools/objtool') diff --git a/arch/x86/include/asm/alternative.h b/arch/x86/include/asm/alternative.h index 1b020381ab38..c096624137ae 100644 --- a/arch/x86/include/asm/alternative.h +++ b/arch/x86/include/asm/alternative.h @@ -218,10 +218,9 @@ static inline int alternatives_text_reserved(void *start, void *end) #define alternative_call_2(oldfunc, newfunc1, feature1, newfunc2, feature2, \ output, input...) \ { \ - register void *__sp asm(_ASM_SP); \ asm volatile (ALTERNATIVE_2("call %P[old]", "call %P[new1]", feature1,\ "call %P[new2]", feature2) \ - : output, "+r" (__sp) \ + : output, ASM_CALL_CONSTRAINT \ : [old] "i" (oldfunc), [new1] "i" (newfunc1), \ [new2] "i" (newfunc2), ## input); \ } diff --git a/arch/x86/include/asm/asm.h b/arch/x86/include/asm/asm.h index 676ee5807d86..c1eadbaf1115 100644 --- a/arch/x86/include/asm/asm.h +++ b/arch/x86/include/asm/asm.h @@ -132,4 +132,15 @@ /* For C file, we already have NOKPROBE_SYMBOL macro */ #endif +#ifndef __ASSEMBLY__ +/* + * This output constraint should be used for any inline asm which has a "call" + * instruction. Otherwise the asm may be inserted before the frame pointer + * gets set up by the containing function. If you forget to do this, objtool + * may print a "call without frame pointer save/setup" warning. + */ +register unsigned int __asm_call_sp asm("esp"); +#define ASM_CALL_CONSTRAINT "+r" (__asm_call_sp) +#endif + #endif /* _ASM_X86_ASM_H */ diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h index 63cc96f064dc..738503e1f80c 100644 --- a/arch/x86/include/asm/mshyperv.h +++ b/arch/x86/include/asm/mshyperv.h @@ -179,7 +179,6 @@ static inline u64 hv_do_hypercall(u64 control, void *input, void *output) u64 input_address = input ? virt_to_phys(input) : 0; u64 output_address = output ? virt_to_phys(output) : 0; u64 hv_status; - register void *__sp asm(_ASM_SP); #ifdef CONFIG_X86_64 if (!hv_hypercall_pg) @@ -187,7 +186,7 @@ static inline u64 hv_do_hypercall(u64 control, void *input, void *output) __asm__ __volatile__("mov %4, %%r8\n" "call *%5" - : "=a" (hv_status), "+r" (__sp), + : "=a" (hv_status), ASM_CALL_CONSTRAINT, "+c" (control), "+d" (input_address) : "r" (output_address), "m" (hv_hypercall_pg) : "cc", "memory", "r8", "r9", "r10", "r11"); @@ -202,7 +201,7 @@ static inline u64 hv_do_hypercall(u64 control, void *input, void *output) __asm__ __volatile__("call *%7" : "=A" (hv_status), - "+c" (input_address_lo), "+r" (__sp) + "+c" (input_address_lo), ASM_CALL_CONSTRAINT : "A" (control), "b" (input_address_hi), "D"(output_address_hi), "S"(output_address_lo), @@ -224,12 +223,11 @@ static inline u64 hv_do_hypercall(u64 control, void *input, void *output) static inline u64 hv_do_fast_hypercall8(u16 code, u64 input1) { u64 hv_status, control = (u64)code | HV_HYPERCALL_FAST_BIT; - register void *__sp asm(_ASM_SP); #ifdef CONFIG_X86_64 { __asm__ __volatile__("call *%4" - : "=a" (hv_status), "+r" (__sp), + : "=a" (hv_status), ASM_CALL_CONSTRAINT, "+c" (control), "+d" (input1) : "m" (hv_hypercall_pg) : "cc", "r8", "r9", "r10", "r11"); @@ -242,7 +240,7 @@ static inline u64 hv_do_fast_hypercall8(u16 code, u64 input1) __asm__ __volatile__ ("call *%5" : "=A"(hv_status), "+c"(input1_lo), - "+r"(__sp) + ASM_CALL_CONSTRAINT : "A" (control), "b" (input1_hi), "m" (hv_hypercall_pg) diff --git a/arch/x86/include/asm/paravirt_types.h b/arch/x86/include/asm/paravirt_types.h index 42873edd9f9d..280d94c36dad 100644 --- a/arch/x86/include/asm/paravirt_types.h +++ b/arch/x86/include/asm/paravirt_types.h @@ -459,8 +459,8 @@ int paravirt_disable_iospace(void); */ #ifdef CONFIG_X86_32 #define PVOP_VCALL_ARGS \ - unsigned long __eax = __eax, __edx = __edx, __ecx = __ecx; \ - register void *__sp asm("esp") + unsigned long __eax = __eax, __edx = __edx, __ecx = __ecx; + #define PVOP_CALL_ARGS PVOP_VCALL_ARGS #define PVOP_CALL_ARG1(x) "a" ((unsigned long)(x)) @@ -480,8 +480,8 @@ int paravirt_disable_iospace(void); /* [re]ax isn't an arg, but the return val */ #define PVOP_VCALL_ARGS \ unsigned long __edi = __edi, __esi = __esi, \ - __edx = __edx, __ecx = __ecx, __eax = __eax; \ - register void *__sp asm("rsp") + __edx = __edx, __ecx = __ecx, __eax = __eax; + #define PVOP_CALL_ARGS PVOP_VCALL_ARGS #define PVOP_CALL_ARG1(x) "D" ((unsigned long)(x)) @@ -532,7 +532,7 @@ int paravirt_disable_iospace(void); asm volatile(pre \ paravirt_alt(PARAVIRT_CALL) \ post \ - : call_clbr, "+r" (__sp) \ + : call_clbr, ASM_CALL_CONSTRAINT \ : paravirt_type(op), \ paravirt_clobber(clbr), \ ##__VA_ARGS__ \ @@ -542,7 +542,7 @@ int paravirt_disable_iospace(void); asm volatile(pre \ paravirt_alt(PARAVIRT_CALL) \ post \ - : call_clbr, "+r" (__sp) \ + : call_clbr, ASM_CALL_CONSTRAINT \ : paravirt_type(op), \ paravirt_clobber(clbr), \ ##__VA_ARGS__ \ @@ -569,7 +569,7 @@ int paravirt_disable_iospace(void); asm volatile(pre \ paravirt_alt(PARAVIRT_CALL) \ post \ - : call_clbr, "+r" (__sp) \ + : call_clbr, ASM_CALL_CONSTRAINT \ : paravirt_type(op), \ paravirt_clobber(clbr), \ ##__VA_ARGS__ \ diff --git a/arch/x86/include/asm/preempt.h b/arch/x86/include/asm/preempt.h index ec1f3c651150..4f44505dbf87 100644 --- a/arch/x86/include/asm/preempt.h +++ b/arch/x86/include/asm/preempt.h @@ -100,19 +100,14 @@ static __always_inline bool should_resched(int preempt_offset) #ifdef CONFIG_PREEMPT extern asmlinkage void ___preempt_schedule(void); -# define __preempt_schedule() \ -({ \ - register void *__sp asm(_ASM_SP); \ - asm volatile ("call ___preempt_schedule" : "+r"(__sp)); \ -}) +# define __preempt_schedule() \ + asm volatile ("call ___preempt_schedule" : ASM_CALL_CONSTRAINT) extern asmlinkage void preempt_schedule(void); extern asmlinkage void ___preempt_schedule_notrace(void); -# define __preempt_schedule_notrace() \ -({ \ - register void *__sp asm(_ASM_SP); \ - asm volatile ("call ___preempt_schedule_notrace" : "+r"(__sp)); \ -}) +# define __preempt_schedule_notrace() \ + asm volatile ("call ___preempt_schedule_notrace" : ASM_CALL_CONSTRAINT) + extern asmlinkage void preempt_schedule_notrace(void); #endif diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h index 3fa26a61eabc..b390ff76e58f 100644 --- a/arch/x86/include/asm/processor.h +++ b/arch/x86/include/asm/processor.h @@ -677,8 +677,6 @@ static inline void sync_core(void) * Like all of Linux's memory ordering operations, this is a * compiler barrier as well. */ - register void *__sp asm(_ASM_SP); - #ifdef CONFIG_X86_32 asm volatile ( "pushfl\n\t" @@ -686,7 +684,7 @@ static inline void sync_core(void) "pushl $1f\n\t" "iret\n\t" "1:" - : "+r" (__sp) : : "memory"); + : ASM_CALL_CONSTRAINT : : "memory"); #else unsigned int tmp; @@ -703,7 +701,7 @@ static inline void sync_core(void) "iretq\n\t" UNWIND_HINT_RESTORE "1:" - : "=&r" (tmp), "+r" (__sp) : : "cc", "memory"); + : "=&r" (tmp), ASM_CALL_CONSTRAINT : : "cc", "memory"); #endif } diff --git a/arch/x86/include/asm/rwsem.h b/arch/x86/include/asm/rwsem.h index a34e0d4b957d..7116b7931c7b 100644 --- a/arch/x86/include/asm/rwsem.h +++ b/arch/x86/include/asm/rwsem.h @@ -103,7 +103,6 @@ static inline bool __down_read_trylock(struct rw_semaphore *sem) ({ \ long tmp; \ struct rw_semaphore* ret; \ - register void *__sp asm(_ASM_SP); \ \ asm volatile("# beginning down_write\n\t" \ LOCK_PREFIX " xadd %1,(%4)\n\t" \ @@ -114,7 +113,8 @@ static inline bool __down_read_trylock(struct rw_semaphore *sem) " call " slow_path "\n" \ "1:\n" \ "# ending down_write" \ - : "+m" (sem->count), "=d" (tmp), "=a" (ret), "+r" (__sp) \ + : "+m" (sem->count), "=d" (tmp), \ + "=a" (ret), ASM_CALL_CONSTRAINT \ : "a" (sem), "1" (RWSEM_ACTIVE_WRITE_BIAS) \ : "memory", "cc"); \ ret; \ diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h index 184eb9894dae..78e8fcc87d4c 100644 --- a/arch/x86/include/asm/uaccess.h +++ b/arch/x86/include/asm/uaccess.h @@ -166,11 +166,11 @@ __typeof__(__builtin_choose_expr(sizeof(x) > sizeof(0UL), 0ULL, 0UL)) ({ \ int __ret_gu; \ register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX); \ - register void *__sp asm(_ASM_SP); \ __chk_user_ptr(ptr); \ might_fault(); \ asm volatile("call __get_user_%P4" \ - : "=a" (__ret_gu), "=r" (__val_gu), "+r" (__sp) \ + : "=a" (__ret_gu), "=r" (__val_gu), \ + ASM_CALL_CONSTRAINT \ : "0" (ptr), "i" (sizeof(*(ptr)))); \ (x) = (__force __typeof__(*(ptr))) __val_gu; \ __builtin_expect(__ret_gu, 0); \ diff --git a/arch/x86/include/asm/xen/hypercall.h b/arch/x86/include/asm/xen/hypercall.h index 9606688caa4b..128a1a0b1450 100644 --- a/arch/x86/include/asm/xen/hypercall.h +++ b/arch/x86/include/asm/xen/hypercall.h @@ -113,10 +113,9 @@ extern struct { char _entry[32]; } hypercall_page[]; register unsigned long __arg2 asm(__HYPERCALL_ARG2REG) = __arg2; \ register unsigned long __arg3 asm(__HYPERCALL_ARG3REG) = __arg3; \ register unsigned long __arg4 asm(__HYPERCALL_ARG4REG) = __arg4; \ - register unsigned long __arg5 asm(__HYPERCALL_ARG5REG) = __arg5; \ - register void *__sp asm(_ASM_SP); + register unsigned long __arg5 asm(__HYPERCALL_ARG5REG) = __arg5; -#define __HYPERCALL_0PARAM "=r" (__res), "+r" (__sp) +#define __HYPERCALL_0PARAM "=r" (__res), ASM_CALL_CONSTRAINT #define __HYPERCALL_1PARAM __HYPERCALL_0PARAM, "+r" (__arg1) #define __HYPERCALL_2PARAM __HYPERCALL_1PARAM, "+r" (__arg2) #define __HYPERCALL_3PARAM __HYPERCALL_2PARAM, "+r" (__arg3) diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c index 16bf6655aa85..f23f13403f33 100644 --- a/arch/x86/kvm/emulate.c +++ b/arch/x86/kvm/emulate.c @@ -5296,7 +5296,6 @@ static void fetch_possible_mmx_operand(struct x86_emulate_ctxt *ctxt, static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *)) { - register void *__sp asm(_ASM_SP); ulong flags = (ctxt->eflags & EFLAGS_MASK) | X86_EFLAGS_IF; if (!(ctxt->d & ByteOp)) @@ -5304,7 +5303,7 @@ static int fastop(struct x86_emulate_ctxt *ctxt, void (*fop)(struct fastop *)) asm("push %[flags]; popf; call *%[fastop]; pushf; pop %[flags]\n" : "+a"(ctxt->dst.val), "+d"(ctxt->src.val), [flags]"+D"(flags), - [fastop]"+S"(fop), "+r"(__sp) + [fastop]"+S"(fop), ASM_CALL_CONSTRAINT : "c"(ctxt->src2.val)); ctxt->eflags = (ctxt->eflags & ~EFLAGS_MASK) | (flags & EFLAGS_MASK); diff --git a/arch/x86/kvm/vmx.c b/arch/x86/kvm/vmx.c index 06c0c6d0541e..6ee237f509dc 100644 --- a/arch/x86/kvm/vmx.c +++ b/arch/x86/kvm/vmx.c @@ -9036,7 +9036,6 @@ static void vmx_complete_atomic_exit(struct vcpu_vmx *vmx) static void vmx_handle_external_intr(struct kvm_vcpu *vcpu) { u32 exit_intr_info = vmcs_read32(VM_EXIT_INTR_INFO); - register void *__sp asm(_ASM_SP); if ((exit_intr_info & (INTR_INFO_VALID_MASK | INTR_INFO_INTR_TYPE_MASK)) == (INTR_INFO_VALID_MASK | INTR_TYPE_EXT_INTR)) { @@ -9065,7 +9064,7 @@ static void vmx_handle_external_intr(struct kvm_vcpu *vcpu) #ifdef CONFIG_X86_64 [sp]"=&r"(tmp), #endif - "+r"(__sp) + ASM_CALL_CONSTRAINT : [entry]"r"(entry), [ss]"i"(__KERNEL_DS), diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c index b836a7274e12..39567b5c33da 100644 --- a/arch/x86/mm/fault.c +++ b/arch/x86/mm/fault.c @@ -806,7 +806,6 @@ no_context(struct pt_regs *regs, unsigned long error_code, if (is_vmalloc_addr((void *)address) && (((unsigned long)tsk->stack - 1 - address < PAGE_SIZE) || address - ((unsigned long)tsk->stack + THREAD_SIZE) < PAGE_SIZE)) { - register void *__sp asm("rsp"); unsigned long stack = this_cpu_read(orig_ist.ist[DOUBLEFAULT_STACK]) - sizeof(void *); /* * We're likely to be running with very little stack space @@ -821,7 +820,7 @@ no_context(struct pt_regs *regs, unsigned long error_code, asm volatile ("movq %[stack], %%rsp\n\t" "call handle_stack_overflow\n\t" "1: jmp 1b" - : "+r" (__sp) + : ASM_CALL_CONSTRAINT : "D" ("kernel stack overflow (page fault)"), "S" (regs), "d" (address), [stack] "rm" (stack)); diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt index 6a1af43862df..3995735a878f 100644 --- a/tools/objtool/Documentation/stack-validation.txt +++ b/tools/objtool/Documentation/stack-validation.txt @@ -194,10 +194,10 @@ they mean, and suggestions for how to fix them. 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 asm() statement with a call instruction must declare the use of the - stack pointer in its output operand. For example, on x86_64: + stack pointer in its output operand. On x86_64, this means adding + the ASM_CALL_CONSTRAINT as an output constraint: - register void *__sp asm("rsp"); - asm volatile("call func" : "+r" (__sp)); + asm volatile("call func" : ASM_CALL_CONSTRAINT); Otherwise the stack frame may not get created before the call. -- cgit v1.3-6-gb490