Now that objtool knows the states of all registers on the stack for each instruction, it's straightforward to generate debuginfo for an unwinder to use. Instead of generating DWARF, generate a new format called ORC, which is more suitable for an in-kernel unwinder. See Documentation/x86/orc-unwinder.txt for a more detailed description of this new debuginfo format and why it's preferable to DWARF. Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Jiri Slaby <jslaby@suse.cz> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Mike Galbraith <efault@gmx.de> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: live-patching@vger.kernel.org Link: http://lkml.kernel.org/r/c9b9f01ba6c5ed2bdc9bb0957b78167fdbf9632e.1499786555.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar <mingo@kernel.org>tirimbino
parent
5a3cf86978
commit
627fce1480
@ -0,0 +1,70 @@ |
||||
/*
|
||||
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
/*
|
||||
* objtool orc: |
||||
* |
||||
* This command analyzes a .o file and adds .orc_unwind and .orc_unwind_ip |
||||
* sections to it, which is used by the in-kernel ORC unwinder. |
||||
* |
||||
* This command is a superset of "objtool check". |
||||
*/ |
||||
|
||||
#include <string.h> |
||||
#include <subcmd/parse-options.h> |
||||
#include "builtin.h" |
||||
#include "check.h" |
||||
|
||||
|
||||
static const char *orc_usage[] = { |
||||
"objtool orc generate [<options>] file.o", |
||||
"objtool orc dump file.o", |
||||
NULL, |
||||
}; |
||||
|
||||
extern const struct option check_options[]; |
||||
extern bool nofp; |
||||
|
||||
int cmd_orc(int argc, const char **argv) |
||||
{ |
||||
const char *objname; |
||||
|
||||
argc--; argv++; |
||||
if (!strncmp(argv[0], "gen", 3)) { |
||||
argc = parse_options(argc, argv, check_options, orc_usage, 0); |
||||
if (argc != 1) |
||||
usage_with_options(orc_usage, check_options); |
||||
|
||||
objname = argv[0]; |
||||
|
||||
return check(objname, nofp, true); |
||||
|
||||
} |
||||
|
||||
if (!strcmp(argv[0], "dump")) { |
||||
if (argc != 2) |
||||
usage_with_options(orc_usage, check_options); |
||||
|
||||
objname = argv[1]; |
||||
|
||||
return orc_dump(objname); |
||||
} |
||||
|
||||
usage_with_options(orc_usage, check_options); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,30 @@ |
||||
/*
|
||||
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#ifndef _ORC_H |
||||
#define _ORC_H |
||||
|
||||
#include "orc_types.h" |
||||
|
||||
struct objtool_file; |
||||
|
||||
int create_orc(struct objtool_file *file); |
||||
int create_orc_sections(struct objtool_file *file); |
||||
|
||||
int orc_dump(const char *objname); |
||||
|
||||
#endif /* _ORC_H */ |
@ -0,0 +1,212 @@ |
||||
/*
|
||||
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include <unistd.h> |
||||
#include "orc.h" |
||||
#include "warn.h" |
||||
|
||||
static const char *reg_name(unsigned int reg) |
||||
{ |
||||
switch (reg) { |
||||
case ORC_REG_PREV_SP: |
||||
return "prevsp"; |
||||
case ORC_REG_DX: |
||||
return "dx"; |
||||
case ORC_REG_DI: |
||||
return "di"; |
||||
case ORC_REG_BP: |
||||
return "bp"; |
||||
case ORC_REG_SP: |
||||
return "sp"; |
||||
case ORC_REG_R10: |
||||
return "r10"; |
||||
case ORC_REG_R13: |
||||
return "r13"; |
||||
case ORC_REG_BP_INDIRECT: |
||||
return "bp(ind)"; |
||||
case ORC_REG_SP_INDIRECT: |
||||
return "sp(ind)"; |
||||
default: |
||||
return "?"; |
||||
} |
||||
} |
||||
|
||||
static const char *orc_type_name(unsigned int type) |
||||
{ |
||||
switch (type) { |
||||
case ORC_TYPE_CALL: |
||||
return "call"; |
||||
case ORC_TYPE_REGS: |
||||
return "regs"; |
||||
case ORC_TYPE_REGS_IRET: |
||||
return "iret"; |
||||
default: |
||||
return "?"; |
||||
} |
||||
} |
||||
|
||||
static void print_reg(unsigned int reg, int offset) |
||||
{ |
||||
if (reg == ORC_REG_BP_INDIRECT) |
||||
printf("(bp%+d)", offset); |
||||
else if (reg == ORC_REG_SP_INDIRECT) |
||||
printf("(sp%+d)", offset); |
||||
else if (reg == ORC_REG_UNDEFINED) |
||||
printf("(und)"); |
||||
else |
||||
printf("%s%+d", reg_name(reg), offset); |
||||
} |
||||
|
||||
int orc_dump(const char *_objname) |
||||
{ |
||||
int fd, nr_entries, i, *orc_ip = NULL, orc_size = 0; |
||||
struct orc_entry *orc = NULL; |
||||
char *name; |
||||
unsigned long nr_sections, orc_ip_addr = 0; |
||||
size_t shstrtab_idx; |
||||
Elf *elf; |
||||
Elf_Scn *scn; |
||||
GElf_Shdr sh; |
||||
GElf_Rela rela; |
||||
GElf_Sym sym; |
||||
Elf_Data *data, *symtab = NULL, *rela_orc_ip = NULL; |
||||
|
||||
|
||||
objname = _objname; |
||||
|
||||
elf_version(EV_CURRENT); |
||||
|
||||
fd = open(objname, O_RDONLY); |
||||
if (fd == -1) { |
||||
perror("open"); |
||||
return -1; |
||||
} |
||||
|
||||
elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); |
||||
if (!elf) { |
||||
WARN_ELF("elf_begin"); |
||||
return -1; |
||||
} |
||||
|
||||
if (elf_getshdrnum(elf, &nr_sections)) { |
||||
WARN_ELF("elf_getshdrnum"); |
||||
return -1; |
||||
} |
||||
|
||||
if (elf_getshdrstrndx(elf, &shstrtab_idx)) { |
||||
WARN_ELF("elf_getshdrstrndx"); |
||||
return -1; |
||||
} |
||||
|
||||
for (i = 0; i < nr_sections; i++) { |
||||
scn = elf_getscn(elf, i); |
||||
if (!scn) { |
||||
WARN_ELF("elf_getscn"); |
||||
return -1; |
||||
} |
||||
|
||||
if (!gelf_getshdr(scn, &sh)) { |
||||
WARN_ELF("gelf_getshdr"); |
||||
return -1; |
||||
} |
||||
|
||||
name = elf_strptr(elf, shstrtab_idx, sh.sh_name); |
||||
if (!name) { |
||||
WARN_ELF("elf_strptr"); |
||||
return -1; |
||||
} |
||||
|
||||
data = elf_getdata(scn, NULL); |
||||
if (!data) { |
||||
WARN_ELF("elf_getdata"); |
||||
return -1; |
||||
} |
||||
|
||||
if (!strcmp(name, ".symtab")) { |
||||
symtab = data; |
||||
} else if (!strcmp(name, ".orc_unwind")) { |
||||
orc = data->d_buf; |
||||
orc_size = sh.sh_size; |
||||
} else if (!strcmp(name, ".orc_unwind_ip")) { |
||||
orc_ip = data->d_buf; |
||||
orc_ip_addr = sh.sh_addr; |
||||
} else if (!strcmp(name, ".rela.orc_unwind_ip")) { |
||||
rela_orc_ip = data; |
||||
} |
||||
} |
||||
|
||||
if (!symtab || !orc || !orc_ip) |
||||
return 0; |
||||
|
||||
if (orc_size % sizeof(*orc) != 0) { |
||||
WARN("bad .orc_unwind section size"); |
||||
return -1; |
||||
} |
||||
|
||||
nr_entries = orc_size / sizeof(*orc); |
||||
for (i = 0; i < nr_entries; i++) { |
||||
if (rela_orc_ip) { |
||||
if (!gelf_getrela(rela_orc_ip, i, &rela)) { |
||||
WARN_ELF("gelf_getrela"); |
||||
return -1; |
||||
} |
||||
|
||||
if (!gelf_getsym(symtab, GELF_R_SYM(rela.r_info), &sym)) { |
||||
WARN_ELF("gelf_getsym"); |
||||
return -1; |
||||
} |
||||
|
||||
scn = elf_getscn(elf, sym.st_shndx); |
||||
if (!scn) { |
||||
WARN_ELF("elf_getscn"); |
||||
return -1; |
||||
} |
||||
|
||||
if (!gelf_getshdr(scn, &sh)) { |
||||
WARN_ELF("gelf_getshdr"); |
||||
return -1; |
||||
} |
||||
|
||||
name = elf_strptr(elf, shstrtab_idx, sh.sh_name); |
||||
if (!name || !*name) { |
||||
WARN_ELF("elf_strptr"); |
||||
return -1; |
||||
} |
||||
|
||||
printf("%s+%lx:", name, rela.r_addend); |
||||
|
||||
} else { |
||||
printf("%lx:", orc_ip_addr + (i * sizeof(int)) + orc_ip[i]); |
||||
} |
||||
|
||||
|
||||
printf(" sp:"); |
||||
|
||||
print_reg(orc[i].sp_reg, orc[i].sp_offset); |
||||
|
||||
printf(" bp:"); |
||||
|
||||
print_reg(orc[i].bp_reg, orc[i].bp_offset); |
||||
|
||||
printf(" type:%s\n", orc_type_name(orc[i].type)); |
||||
} |
||||
|
||||
elf_end(elf); |
||||
close(fd); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,214 @@ |
||||
/*
|
||||
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include "orc.h" |
||||
#include "check.h" |
||||
#include "warn.h" |
||||
|
||||
int create_orc(struct objtool_file *file) |
||||
{ |
||||
struct instruction *insn; |
||||
|
||||
for_each_insn(file, insn) { |
||||
struct orc_entry *orc = &insn->orc; |
||||
struct cfi_reg *cfa = &insn->state.cfa; |
||||
struct cfi_reg *bp = &insn->state.regs[CFI_BP]; |
||||
|
||||
if (cfa->base == CFI_UNDEFINED) { |
||||
orc->sp_reg = ORC_REG_UNDEFINED; |
||||
continue; |
||||
} |
||||
|
||||
switch (cfa->base) { |
||||
case CFI_SP: |
||||
orc->sp_reg = ORC_REG_SP; |
||||
break; |
||||
case CFI_SP_INDIRECT: |
||||
orc->sp_reg = ORC_REG_SP_INDIRECT; |
||||
break; |
||||
case CFI_BP: |
||||
orc->sp_reg = ORC_REG_BP; |
||||
break; |
||||
case CFI_BP_INDIRECT: |
||||
orc->sp_reg = ORC_REG_BP_INDIRECT; |
||||
break; |
||||
case CFI_R10: |
||||
orc->sp_reg = ORC_REG_R10; |
||||
break; |
||||
case CFI_R13: |
||||
orc->sp_reg = ORC_REG_R13; |
||||
break; |
||||
case CFI_DI: |
||||
orc->sp_reg = ORC_REG_DI; |
||||
break; |
||||
case CFI_DX: |
||||
orc->sp_reg = ORC_REG_DX; |
||||
break; |
||||
default: |
||||
WARN_FUNC("unknown CFA base reg %d", |
||||
insn->sec, insn->offset, cfa->base); |
||||
return -1; |
||||
} |
||||
|
||||
switch(bp->base) { |
||||
case CFI_UNDEFINED: |
||||
orc->bp_reg = ORC_REG_UNDEFINED; |
||||
break; |
||||
case CFI_CFA: |
||||
orc->bp_reg = ORC_REG_PREV_SP; |
||||
break; |
||||
case CFI_BP: |
||||
orc->bp_reg = ORC_REG_BP; |
||||
break; |
||||
default: |
||||
WARN_FUNC("unknown BP base reg %d", |
||||
insn->sec, insn->offset, bp->base); |
||||
return -1; |
||||
} |
||||
|
||||
orc->sp_offset = cfa->offset; |
||||
orc->bp_offset = bp->offset; |
||||
orc->type = insn->state.type; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int create_orc_entry(struct section *u_sec, struct section *ip_relasec, |
||||
unsigned int idx, struct section *insn_sec, |
||||
unsigned long insn_off, struct orc_entry *o) |
||||
{ |
||||
struct orc_entry *orc; |
||||
struct rela *rela; |
||||
|
||||
/* populate ORC data */ |
||||
orc = (struct orc_entry *)u_sec->data->d_buf + idx; |
||||
memcpy(orc, o, sizeof(*orc)); |
||||
|
||||
/* populate rela for ip */ |
||||
rela = malloc(sizeof(*rela)); |
||||
if (!rela) { |
||||
perror("malloc"); |
||||
return -1; |
||||
} |
||||
memset(rela, 0, sizeof(*rela)); |
||||
|
||||
rela->sym = insn_sec->sym; |
||||
rela->addend = insn_off; |
||||
rela->type = R_X86_64_PC32; |
||||
rela->offset = idx * sizeof(int); |
||||
|
||||
list_add_tail(&rela->list, &ip_relasec->rela_list); |
||||
hash_add(ip_relasec->rela_hash, &rela->hash, rela->offset); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int create_orc_sections(struct objtool_file *file) |
||||
{ |
||||
struct instruction *insn, *prev_insn; |
||||
struct section *sec, *u_sec, *ip_relasec; |
||||
unsigned int idx; |
||||
|
||||
struct orc_entry empty = { |
||||
.sp_reg = ORC_REG_UNDEFINED, |
||||
.bp_reg = ORC_REG_UNDEFINED, |
||||
.type = ORC_TYPE_CALL, |
||||
}; |
||||
|
||||
sec = find_section_by_name(file->elf, ".orc_unwind"); |
||||
if (sec) { |
||||
WARN("file already has .orc_unwind section, skipping"); |
||||
return -1; |
||||
} |
||||
|
||||
/* count the number of needed orcs */ |
||||
idx = 0; |
||||
for_each_sec(file, sec) { |
||||
if (!sec->text) |
||||
continue; |
||||
|
||||
prev_insn = NULL; |
||||
sec_for_each_insn(file, sec, insn) { |
||||
if (!prev_insn || |
||||
memcmp(&insn->orc, &prev_insn->orc, |
||||
sizeof(struct orc_entry))) { |
||||
idx++; |
||||
} |
||||
prev_insn = insn; |
||||
} |
||||
|
||||
/* section terminator */ |
||||
if (prev_insn) |
||||
idx++; |
||||
} |
||||
if (!idx) |
||||
return -1; |
||||
|
||||
|
||||
/* create .orc_unwind_ip and .rela.orc_unwind_ip sections */ |
||||
sec = elf_create_section(file->elf, ".orc_unwind_ip", sizeof(int), idx); |
||||
|
||||
ip_relasec = elf_create_rela_section(file->elf, sec); |
||||
if (!ip_relasec) |
||||
return -1; |
||||
|
||||
/* create .orc_unwind section */ |
||||
u_sec = elf_create_section(file->elf, ".orc_unwind", |
||||
sizeof(struct orc_entry), idx); |
||||
|
||||
/* populate sections */ |
||||
idx = 0; |
||||
for_each_sec(file, sec) { |
||||
if (!sec->text) |
||||
continue; |
||||
|
||||
prev_insn = NULL; |
||||
sec_for_each_insn(file, sec, insn) { |
||||
if (!prev_insn || memcmp(&insn->orc, &prev_insn->orc, |
||||
sizeof(struct orc_entry))) { |
||||
|
||||
if (create_orc_entry(u_sec, ip_relasec, idx, |
||||
insn->sec, insn->offset, |
||||
&insn->orc)) |
||||
return -1; |
||||
|
||||
idx++; |
||||
} |
||||
prev_insn = insn; |
||||
} |
||||
|
||||
/* section terminator */ |
||||
if (prev_insn) { |
||||
if (create_orc_entry(u_sec, ip_relasec, idx, |
||||
prev_insn->sec, |
||||
prev_insn->offset + prev_insn->len, |
||||
&empty)) |
||||
return -1; |
||||
|
||||
idx++; |
||||
} |
||||
} |
||||
|
||||
if (elf_rebuild_rela_section(ip_relasec)) |
||||
return -1; |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,85 @@ |
||||
/*
|
||||
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#ifndef _ORC_TYPES_H |
||||
#define _ORC_TYPES_H |
||||
|
||||
#include <linux/types.h> |
||||
#include <linux/compiler.h> |
||||
|
||||
/*
|
||||
* The ORC_REG_* registers are base registers which are used to find other |
||||
* registers on the stack. |
||||
* |
||||
* ORC_REG_PREV_SP, also known as DWARF Call Frame Address (CFA), is the |
||||
* address of the previous frame: the caller's SP before it called the current |
||||
* function. |
||||
* |
||||
* ORC_REG_UNDEFINED means the corresponding register's value didn't change in |
||||
* the current frame. |
||||
* |
||||
* The most commonly used base registers are SP and BP -- which the previous SP |
||||
* is usually based on -- and PREV_SP and UNDEFINED -- which the previous BP is |
||||
* usually based on. |
||||
* |
||||
* The rest of the base registers are needed for special cases like entry code |
||||
* and GCC realigned stacks. |
||||
*/ |
||||
#define ORC_REG_UNDEFINED 0 |
||||
#define ORC_REG_PREV_SP 1 |
||||
#define ORC_REG_DX 2 |
||||
#define ORC_REG_DI 3 |
||||
#define ORC_REG_BP 4 |
||||
#define ORC_REG_SP 5 |
||||
#define ORC_REG_R10 6 |
||||
#define ORC_REG_R13 7 |
||||
#define ORC_REG_BP_INDIRECT 8 |
||||
#define ORC_REG_SP_INDIRECT 9 |
||||
#define ORC_REG_MAX 15 |
||||
|
||||
/*
|
||||
* ORC_TYPE_CALL: Indicates that sp_reg+sp_offset resolves to PREV_SP (the |
||||
* caller's SP right before it made the call). Used for all callable |
||||
* functions, i.e. all C code and all callable asm functions. |
||||
* |
||||
* ORC_TYPE_REGS: Used in entry code to indicate that sp_reg+sp_offset points |
||||
* to a fully populated pt_regs from a syscall, interrupt, or exception. |
||||
* |
||||
* ORC_TYPE_REGS_IRET: Used in entry code to indicate that sp_reg+sp_offset |
||||
* points to the iret return frame. |
||||
*/ |
||||
#define ORC_TYPE_CALL 0 |
||||
#define ORC_TYPE_REGS 1 |
||||
#define ORC_TYPE_REGS_IRET 2 |
||||
|
||||
/*
|
||||
* This struct is more or less a vastly simplified version of the DWARF Call |
||||
* Frame Information standard. It contains only the necessary parts of DWARF |
||||
* CFI, simplified for ease of access by the in-kernel unwinder. It tells the |
||||
* unwinder how to find the previous SP and BP (and sometimes entry regs) on |
||||
* the stack for a given code address. Each instance of the struct corresponds |
||||
* to one or more code locations. |
||||
*/ |
||||
struct orc_entry { |
||||
s16 sp_offset; |
||||
s16 bp_offset; |
||||
unsigned sp_reg:4; |
||||
unsigned bp_reg:4; |
||||
unsigned type:2; |
||||
} __packed; |
||||
|
||||
#endif /* _ORC_TYPES_H */ |
Loading…
Reference in new issue