Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751942AbdF1POI (ORCPT ); Wed, 28 Jun 2017 11:14:08 -0400 Received: from mx1.redhat.com ([209.132.183.28]:59468 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752484AbdF1PMA (ORCPT ); Wed, 28 Jun 2017 11:12:00 -0400 DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com EBAC5F5CE Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx02.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=jpoimboe@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com EBAC5F5CE From: Josh Poimboeuf To: x86@kernel.org Cc: linux-kernel@vger.kernel.org, live-patching@vger.kernel.org, Linus Torvalds , Andy Lutomirski , Jiri Slaby , Ingo Molnar , "H. Peter Anvin" , Peter Zijlstra Subject: [PATCH v2 4/8] objtool: add undwarf debuginfo generation Date: Wed, 28 Jun 2017 10:11:08 -0500 Message-Id: In-Reply-To: References: X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.26]); Wed, 28 Jun 2017 15:12:00 +0000 (UTC) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 34348 Lines: 1261 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 undwarf, which is more suitable for an in-kernel unwinder. See tools/objtool/Documentation/undwarf.txt for a more detailed description of this new debuginfo format and why it's preferable to DWARF. Signed-off-by: Josh Poimboeuf --- tools/objtool/Build | 3 + tools/objtool/Documentation/stack-validation.txt | 46 ++--- tools/objtool/builtin-check.c | 2 +- tools/objtool/builtin-undwarf.c | 70 ++++++++ tools/objtool/builtin.h | 1 + tools/objtool/check.c | 59 ++++++- tools/objtool/check.h | 15 +- tools/objtool/elf.c | 212 ++++++++++++++++++++-- tools/objtool/elf.h | 15 +- tools/objtool/objtool.c | 3 +- tools/objtool/undwarf-types.h | 81 +++++++++ tools/objtool/{builtin.h => undwarf.h} | 18 +- tools/objtool/undwarf_dump.c | 212 ++++++++++++++++++++++ tools/objtool/undwarf_gen.c | 215 +++++++++++++++++++++++ 14 files changed, 892 insertions(+), 60 deletions(-) create mode 100644 tools/objtool/builtin-undwarf.c create mode 100644 tools/objtool/undwarf-types.h copy tools/objtool/{builtin.h => undwarf.h} (67%) create mode 100644 tools/objtool/undwarf_dump.c create mode 100644 tools/objtool/undwarf_gen.c diff --git a/tools/objtool/Build b/tools/objtool/Build index 6f2e198..9fb3f2f 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-undwarf.o objtool-y += check.o +objtool-y += undwarf_gen.o +objtool-y += undwarf_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 17c1195..14c0ded 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 'undwarf generate' subcommand which generates +debuginfo for the undwarf unwinder. See Documentation/x86/undwarf.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. - - We might also add a runtime stack validation debug option where we - periodically walk the stack from schedule() and/or an NMI to ensure - that the stack metadata is sane and that we reach the bottom of the - stack. - - So the benefit of objtool here will be that external tooling should - always show perfect stack traces. And the same will be true for - kernel warning/oops traces if the architecture has a runtime DWARF - unwinder. +b) Out-of-band debuginfo generation (undwarf) + + As an alternative to frame pointers, undwarf metadata can be used to + walk the stack. Unlike frame pointers, undwarf is out of band. So + it doesn't affect runtime performance and it can be reliable even + when interrupts or exceptions are involved. + + For more details, see undwarf.txt. c) Higher live patching compatibility rate diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 365c34e..eedf089 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-undwarf.c b/tools/objtool/builtin-undwarf.c new file mode 100644 index 0000000..900b1e5 --- /dev/null +++ b/tools/objtool/builtin-undwarf.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 undwarf: + * + * This command analyzes a .o file and adds an .undwarf section to it, which is + * used by the in-kernel "undwarf" unwinder. + * + * This command is a superset of "objtool check". + */ + +#include +#include +#include "builtin.h" +#include "check.h" + + +static const char *undwarf_usage[] = { + "objtool undwarf generate [] file.o", + "objtool undwarf dump file.o", + NULL, +}; + +extern const struct option check_options[]; +extern bool nofp; + +int cmd_undwarf(int argc, const char **argv) +{ + const char *objname; + + argc--; argv++; + if (!strncmp(argv[0], "gen", 3)) { + argc = parse_options(argc, argv, check_options, undwarf_usage, 0); + if (argc != 1) + usage_with_options(undwarf_usage, check_options); + + objname = argv[0]; + + return check(objname, nofp, true); + + } + + if (!strcmp(argv[0], "dump")) { + if (argc != 2) + usage_with_options(undwarf_usage, check_options); + + objname = argv[1]; + + return undwarf_dump(objname); + } + + usage_with_options(undwarf_usage, check_options); + + return 0; +} diff --git a/tools/objtool/builtin.h b/tools/objtool/builtin.h index 34d2ba7..0b9722f 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_undwarf(int argc, const char **argv); #endif /* _BUILTIN_H */ diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 2f80aa51..f76ac4c 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; @@ -253,6 +253,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) { @@ -941,6 +946,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) { @@ -1026,6 +1055,10 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) return 0; } + if (state->type == UNDWARF_TYPE_REGS || + state->type == UNDWARF_TYPE_REGS_IRET) + return update_insn_state_regs(insn, state); + switch (op->dest.type) { case OP_DEST_REG: @@ -1317,6 +1350,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)", @@ -1606,7 +1643,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 undwarf) { struct objtool_file file; int ret, warnings = 0; @@ -1614,7 +1651,7 @@ int check(const char *_objname, bool _nofp) objname = _objname; nofp = _nofp; - file.elf = elf_open(objname); + file.elf = elf_open(objname, undwarf ? O_RDWR : O_RDONLY); if (!file.elf) return 1; @@ -1647,6 +1684,20 @@ int check(const char *_objname, bool _nofp) warnings += ret; } + if (undwarf) { + ret = create_undwarf(&file); + if (ret < 0) + goto out; + + ret = create_undwarf_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 da85f5b..2fe0810 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 "undwarf.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 undwarf undwarf; }; 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 undwarf); + +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 1a7e8aa..6e9f980 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -30,16 +30,6 @@ #include "elf.h" #include "warn.h" -/* - * Fallback for systems without this "read, mmaping if possible" cmd. - */ -#ifndef ELF_C_READ_MMAP -#define ELF_C_READ_MMAP ELF_C_READ -#endif - -#define WARN_ELF(format, ...) \ - WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1)) - struct section *find_section_by_name(struct elf *elf, const char *name) { struct section *sec; @@ -349,9 +339,10 @@ static int read_relas(struct elf *elf) return 0; } -struct elf *elf_open(const char *name) +struct elf *elf_open(const char *name, int flags) { struct elf *elf; + Elf_Cmd cmd; elf_version(EV_CURRENT); @@ -364,13 +355,20 @@ struct elf *elf_open(const char *name) INIT_LIST_HEAD(&elf->sections); - elf->fd = open(name, O_RDONLY); + elf->fd = open(name, flags); if (elf->fd == -1) { perror("open"); goto err; } - elf->elf = elf_begin(elf->fd, ELF_C_READ_MMAP, NULL); + if ((flags & O_ACCMODE) == O_RDONLY) + cmd = ELF_C_READ_MMAP; + else if ((flags & O_ACCMODE) == O_RDWR) + cmd = ELF_C_RDWR; + else /* O_WRONLY */ + cmd = ELF_C_WRITE; + + elf->elf = elf_begin(elf->fd, cmd, NULL); if (!elf->elf) { WARN_ELF("elf_begin"); goto err; @@ -397,6 +395,194 @@ struct elf *elf_open(const char *name) return NULL; } +struct section *elf_create_section(struct elf *elf, const char *name, + size_t entsize, int nr) +{ + struct section *sec, *shstrtab; + size_t size = entsize * nr; + struct Elf_Scn *s; + Elf_Data *data; + + sec = malloc(sizeof(*sec)); + if (!sec) { + perror("malloc"); + return NULL; + } + memset(sec, 0, sizeof(*sec)); + + INIT_LIST_HEAD(&sec->symbol_list); + INIT_LIST_HEAD(&sec->rela_list); + hash_init(sec->rela_hash); + hash_init(sec->symbol_hash); + + list_add_tail(&sec->list, &elf->sections); + + s = elf_newscn(elf->elf); + if (!s) { + WARN_ELF("elf_newscn"); + return NULL; + } + + sec->name = strdup(name); + if (!sec->name) { + perror("strdup"); + return NULL; + } + + sec->idx = elf_ndxscn(s); + sec->len = size; + sec->changed = true; + + sec->data = elf_newdata(s); + if (!sec->data) { + WARN_ELF("elf_newdata"); + return NULL; + } + + sec->data->d_size = size; + sec->data->d_align = 1; + + if (size) { + sec->data->d_buf = malloc(size); + if (!sec->data->d_buf) { + perror("malloc"); + return NULL; + } + memset(sec->data->d_buf, 0, size); + } + + if (!gelf_getshdr(s, &sec->sh)) { + WARN_ELF("gelf_getshdr"); + return NULL; + } + + sec->sh.sh_size = size; + sec->sh.sh_entsize = entsize; + sec->sh.sh_type = SHT_PROGBITS; + sec->sh.sh_addralign = 1; + sec->sh.sh_flags = SHF_ALLOC; + + + /* Add section name to .shstrtab */ + shstrtab = find_section_by_name(elf, ".shstrtab"); + if (!shstrtab) { + WARN("can't find .shstrtab section"); + return NULL; + } + + s = elf_getscn(elf->elf, shstrtab->idx); + if (!s) { + WARN_ELF("elf_getscn"); + return NULL; + } + + data = elf_newdata(s); + if (!data) { + WARN_ELF("elf_newdata"); + return NULL; + } + + data->d_buf = sec->name; + data->d_size = strlen(name) + 1; + data->d_align = 1; + + sec->sh.sh_name = shstrtab->len; + + shstrtab->len += strlen(name) + 1; + shstrtab->changed = true; + + return sec; +} + +struct section *elf_create_rela_section(struct elf *elf, struct section *base) +{ + char *relaname; + struct section *sec; + + relaname = malloc(strlen(base->name) + strlen(".rela") + 1); + if (!relaname) { + perror("malloc"); + return NULL; + } + strcpy(relaname, ".rela"); + strcat(relaname, base->name); + + sec = elf_create_section(elf, relaname, sizeof(GElf_Rela), 0); + if (!sec) + return NULL; + + base->rela = sec; + sec->base = base; + + sec->sh.sh_type = SHT_RELA; + sec->sh.sh_addralign = 8; + sec->sh.sh_link = find_section_by_name(elf, ".symtab")->idx; + sec->sh.sh_info = base->idx; + sec->sh.sh_flags = SHF_INFO_LINK; + + return sec; +} + +int elf_rebuild_rela_section(struct section *sec) +{ + struct rela *rela; + int nr, idx = 0, size; + GElf_Rela *relas; + + nr = 0; + list_for_each_entry(rela, &sec->rela_list, list) + nr++; + + size = nr * sizeof(*relas); + relas = malloc(size); + if (!relas) { + perror("malloc"); + return -1; + } + + sec->data->d_buf = relas; + sec->data->d_size = size; + + sec->sh.sh_size = size; + + idx = 0; + list_for_each_entry(rela, &sec->rela_list, list) { + relas[idx].r_offset = rela->offset; + relas[idx].r_addend = rela->addend; + relas[idx].r_info = GELF_R_INFO(rela->sym->idx, rela->type); + idx++; + } + + return 0; +} + +int elf_write(struct elf *elf) +{ + struct section *sec; + Elf_Scn *s; + + list_for_each_entry(sec, &elf->sections, list) { + if (sec->changed) { + s = elf_getscn(elf->elf, sec->idx); + if (!s) { + WARN_ELF("elf_getscn"); + return -1; + } + if (!gelf_update_shdr (s, &sec->sh)) { + WARN_ELF("gelf_update_shdr"); + return -1; + } + } + } + + if (elf_update(elf->elf, ELF_C_WRITE) < 0) { + WARN_ELF("elf_update"); + return -1; + } + + return 0; +} + void elf_close(struct elf *elf) { struct section *sec, *tmpsec; diff --git a/tools/objtool/elf.h b/tools/objtool/elf.h index 343968b..d86e2ff1 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 ecc5b1b..b2051d1 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" }, + {"undwarf", cmd_undwarf, "Generate in-place undwarf metadata for an object file" }, }; bool help; diff --git a/tools/objtool/undwarf-types.h b/tools/objtool/undwarf-types.h new file mode 100644 index 0000000..ef92a1d --- /dev/null +++ b/tools/objtool/undwarf-types.h @@ -0,0 +1,81 @@ +/* + * 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 _UNDWARF_TYPES_H +#define _UNDWARF_TYPES_H + +/* + * The UNDWARF_REG_* registers are base registers which are used to find other + * registers on the stack. + * + * The CFA (call frame address) is the value of the stack pointer on the + * previous frame, i.e. the caller's SP before it called the callee. + * + * The CFA is usually based on SP, unless a frame pointer has been saved, in + * which case it's based on BP. + * + * BP is usually either based on CFA or is undefined (meaning its value didn't + * change for the current frame). + * + * So the CFA base is usually either SP or BP, and the FP base is usually either + * CFA or undefined. The rest of the base registers are needed for special + * cases like entry code and gcc aligned stacks. + */ +#define UNDWARF_REG_UNDEFINED 0 +#define UNDWARF_REG_CFA 1 +#define UNDWARF_REG_DX 2 +#define UNDWARF_REG_DI 3 +#define UNDWARF_REG_BP 4 +#define UNDWARF_REG_SP 5 +#define UNDWARF_REG_R10 6 +#define UNDWARF_REG_R13 7 +#define UNDWARF_REG_BP_INDIRECT 8 +#define UNDWARF_REG_SP_INDIRECT 9 +#define UNDWARF_REG_MAX 15 + +/* + * UNDWARF_TYPE_CFA: Indicates that cfa_reg+cfa_offset points to the caller's + * stack pointer (aka the CFA in DWARF terms). Used for all callable + * functions, i.e. all C code and all callable asm functions. + * + * UNDWARF_TYPE_REGS: Used in entry code to indicate that cfa_reg+cfa_offset + * points to a fully populated pt_regs from a syscall, interrupt, or exception. + * + * UNDWARF_TYPE_REGS_IRET: Used in entry code to indicate that + * cfa_reg+cfa_offset points to the iret return frame. + */ +#define UNDWARF_TYPE_CFA 0 +#define UNDWARF_TYPE_REGS 1 +#define UNDWARF_TYPE_REGS_IRET 2 + +/* + * This struct contains a simplified version of the DWARF Call Frame + * Information standard. It contains only the necessary parts of the real + * DWARF, 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 (IP). Each instance of the struct + * corresponds to one or more code locations. + */ +struct undwarf { + short cfa_offset; + short bp_offset; + unsigned cfa_reg:4; + unsigned bp_reg:4; + unsigned type:2; +}; + +#endif /* _UNDWARF_TYPES_H */ diff --git a/tools/objtool/builtin.h b/tools/objtool/undwarf.h similarity index 67% copy from tools/objtool/builtin.h copy to tools/objtool/undwarf.h index 34d2ba7..c9f5116 100644 --- a/tools/objtool/builtin.h +++ b/tools/objtool/undwarf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Josh Poimboeuf + * 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 @@ -14,9 +14,17 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ -#ifndef _BUILTIN_H -#define _BUILTIN_H -extern int cmd_check(int argc, const char **argv); +#ifndef _UNDWARF_H +#define _UNDWARF_H -#endif /* _BUILTIN_H */ +#include "undwarf-types.h" + +struct objtool_file; + +int create_undwarf(struct objtool_file *file); +int create_undwarf_sections(struct objtool_file *file); + +int undwarf_dump(const char *objname); + +#endif /* _UNDWARF_H */ diff --git a/tools/objtool/undwarf_dump.c b/tools/objtool/undwarf_dump.c new file mode 100644 index 0000000..7bab393 --- /dev/null +++ b/tools/objtool/undwarf_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 "undwarf.h" +#include "warn.h" + +static const char *reg_name(unsigned int reg) +{ + switch (reg) { + case UNDWARF_REG_CFA: + return "cfa"; + case UNDWARF_REG_DX: + return "dx"; + case UNDWARF_REG_DI: + return "di"; + case UNDWARF_REG_BP: + return "bp"; + case UNDWARF_REG_SP: + return "sp"; + case UNDWARF_REG_R10: + return "r10"; + case UNDWARF_REG_R13: + return "r13"; + case UNDWARF_REG_BP_INDIRECT: + return "bp(ind)"; + case UNDWARF_REG_SP_INDIRECT: + return "sp(ind)"; + default: + return "?"; + } +} + +static const char *undwarf_type_name(unsigned int type) +{ + switch (type) { + case UNDWARF_TYPE_CFA: + return "cfa"; + case UNDWARF_TYPE_REGS: + return "regs"; + case UNDWARF_TYPE_REGS_IRET: + return "iret"; + default: + return "?"; + } +} + +static void print_reg(unsigned int reg, int offset) +{ + if (reg == UNDWARF_REG_BP_INDIRECT) + printf("(bp%+d)", offset); + else if (reg == UNDWARF_REG_SP_INDIRECT) + printf("(sp%+d)", offset); + else if (reg == UNDWARF_REG_UNDEFINED) + printf("(und)"); + else + printf("%s%+d", reg_name(reg), offset); +} + +int undwarf_dump(const char *_objname) +{ + int fd, nr_entries, i, *undwarf_ip = NULL, undwarf_size = 0; + struct undwarf *undwarf = NULL; + char *name; + unsigned long nr_sections, undwarf_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_undwarf_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, ".undwarf")) { + undwarf = data->d_buf; + undwarf_size = sh.sh_size; + } else if (!strcmp(name, ".undwarf_ip")) { + undwarf_ip = data->d_buf; + undwarf_ip_addr = sh.sh_addr; + } else if (!strcmp(name, ".rela.undwarf_ip")) { + rela_undwarf_ip = data; + } + } + + if (!symtab || !undwarf || !undwarf_ip) + return 0; + + if (undwarf_size % sizeof(*undwarf) != 0) { + WARN("bad .undwarf section size"); + return -1; + } + + nr_entries = undwarf_size / sizeof(*undwarf); + for (i = 0; i < nr_entries; i++) { + if (rela_undwarf_ip) { + if (!gelf_getrela(rela_undwarf_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:", undwarf_ip_addr + (i * sizeof(int)) + undwarf_ip[i]); + } + + + printf(" cfa:"); + + print_reg(undwarf[i].cfa_reg, undwarf[i].cfa_offset); + + printf(" bp:"); + + print_reg(undwarf[i].bp_reg, undwarf[i].bp_offset); + + printf(" type:%s\n", undwarf_type_name(undwarf[i].type)); + } + + elf_end(elf); + close(fd); + + return 0; +} diff --git a/tools/objtool/undwarf_gen.c b/tools/objtool/undwarf_gen.c new file mode 100644 index 0000000..03021d8 --- /dev/null +++ b/tools/objtool/undwarf_gen.c @@ -0,0 +1,215 @@ +/* + * 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 "undwarf.h" +#include "check.h" +#include "warn.h" + +int create_undwarf(struct objtool_file *file) +{ + struct instruction *insn; + + for_each_insn(file, insn) { + struct undwarf *undwarf = &insn->undwarf; + struct cfi_reg *cfa = &insn->state.cfa; + struct cfi_reg *bp = &insn->state.regs[CFI_BP]; + + if (cfa->base == CFI_UNDEFINED) { + undwarf->cfa_reg = UNDWARF_REG_UNDEFINED; + continue; + } + + switch (cfa->base) { + case CFI_SP: + undwarf->cfa_reg = UNDWARF_REG_SP; + break; + case CFI_SP_INDIRECT: + undwarf->cfa_reg = UNDWARF_REG_SP_INDIRECT; + break; + case CFI_BP: + undwarf->cfa_reg = UNDWARF_REG_BP; + break; + case CFI_BP_INDIRECT: + undwarf->cfa_reg = UNDWARF_REG_BP_INDIRECT; + break; + case CFI_R10: + undwarf->cfa_reg = UNDWARF_REG_R10; + break; + case CFI_R13: + undwarf->cfa_reg = UNDWARF_REG_R13; + break; + case CFI_DI: + undwarf->cfa_reg = UNDWARF_REG_DI; + break; + case CFI_DX: + undwarf->cfa_reg = UNDWARF_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: + undwarf->bp_reg = UNDWARF_REG_UNDEFINED; + break; + case CFI_CFA: + undwarf->bp_reg = UNDWARF_REG_CFA; + break; + case CFI_BP: + undwarf->bp_reg = UNDWARF_REG_BP; + break; + default: + WARN_FUNC("unknown BP base reg %d", + insn->sec, insn->offset, bp->base); + return -1; + } + + undwarf->cfa_offset = cfa->offset; + undwarf->bp_offset = bp->offset; + undwarf->type = insn->state.type; + } + + return 0; +} + +static int create_undwarf_entry(struct section *u_sec, struct section *ip_relasec, + unsigned int idx, struct section *insn_sec, + unsigned long insn_off, struct undwarf *u) +{ + struct undwarf *undwarf; + struct rela *rela; + + /* populate undwarf */ + undwarf = (struct undwarf *)u_sec->data->d_buf + idx; + memcpy(undwarf, u, sizeof(*undwarf)); + + /* 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_undwarf_sections(struct objtool_file *file) +{ + struct instruction *insn, *prev_insn; + struct section *sec, *u_sec, *ip_relasec; + unsigned int idx; + + struct undwarf empty = { + .cfa_reg = UNDWARF_REG_UNDEFINED, + .bp_reg = UNDWARF_REG_UNDEFINED, + .type = UNDWARF_TYPE_CFA, + }; + + sec = find_section_by_name(file->elf, ".undwarf"); + if (sec) { + WARN("file already has .undwarf section, skipping"); + return -1; + } + + /* count the number of needed undwarves */ + 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->undwarf, &prev_insn->undwarf, + sizeof(struct undwarf))) { + idx++; + } + prev_insn = insn; + } + + /* section terminator */ + if (prev_insn) + idx++; + } + if (!idx) + return -1; + + + /* create .undwarf_ip and .rela.undwarf_ip sections */ + sec = elf_create_section(file->elf, ".undwarf_ip", sizeof(int), idx); + + ip_relasec = elf_create_rela_section(file->elf, sec); + if (!ip_relasec) + return -1; + + /* create .undwarf section */ + u_sec = elf_create_section(file->elf, ".undwarf", + sizeof(struct undwarf), 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->undwarf, + &prev_insn->undwarf, + sizeof(struct undwarf))) { + + if (create_undwarf_entry(u_sec, ip_relasec, idx, + insn->sec, insn->offset, + &insn->undwarf)) + return -1; + + idx++; + } + prev_insn = insn; + } + + /* section terminator */ + if (prev_insn) { + if (create_undwarf_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; +} -- 2.7.5