mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 18:30:06 +00:00
![Josh Poimboeuf](/assets/img/avatar_default.png)
Add a 'signal' field which allows unwind hints to specify whether the instruction pointer should be taken literally (like for most interrupts and exceptions) rather than decremented (like for call stack return addresses) when used to find the next ORC entry. Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org> Signed-off-by: Ingo Molnar <mingo@kernel.org> Link: https://lore.kernel.org/r/d2c5ec4d83a45b513d8fd72fab59f1a8cfa46871.1676068346.git.jpoimboe@kernel.org
222 lines
4.4 KiB
C
222 lines
4.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <linux/objtool.h>
|
|
#include <asm/orc_types.h>
|
|
#include <objtool/objtool.h>
|
|
#include <objtool/warn.h>
|
|
#include <objtool/endianness.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 UNWIND_HINT_TYPE_CALL:
|
|
return "call";
|
|
case UNWIND_HINT_TYPE_REGS:
|
|
return "regs";
|
|
case UNWIND_HINT_TYPE_REGS_PARTIAL:
|
|
return "regs (partial)";
|
|
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;
|
|
size_t nr_sections;
|
|
Elf64_Addr orc_ip_addr = 0;
|
|
size_t shstrtab_idx, strtab_idx = 0;
|
|
Elf *elf;
|
|
Elf_Scn *scn;
|
|
GElf_Shdr sh;
|
|
GElf_Rela rela;
|
|
GElf_Sym sym;
|
|
Elf_Data *data, *symtab = NULL, *rela_orc_ip = NULL;
|
|
struct elf dummy_elf = {};
|
|
|
|
|
|
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 (!elf64_getehdr(elf)) {
|
|
WARN_ELF("elf64_getehdr");
|
|
return -1;
|
|
}
|
|
memcpy(&dummy_elf.ehdr, elf64_getehdr(elf), sizeof(dummy_elf.ehdr));
|
|
|
|
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, ".strtab")) {
|
|
strtab_idx = i;
|
|
} 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 || !strtab_idx || !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;
|
|
}
|
|
|
|
if (GELF_ST_TYPE(sym.st_info) == STT_SECTION) {
|
|
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) {
|
|
WARN_ELF("elf_strptr");
|
|
return -1;
|
|
}
|
|
} else {
|
|
name = elf_strptr(elf, strtab_idx, sym.st_name);
|
|
if (!name) {
|
|
WARN_ELF("elf_strptr");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
printf("%s+%llx:", name, (unsigned long long)rela.r_addend);
|
|
|
|
} else {
|
|
printf("%llx:", (unsigned long long)(orc_ip_addr + (i * sizeof(int)) + orc_ip[i]));
|
|
}
|
|
|
|
|
|
printf(" sp:");
|
|
|
|
print_reg(orc[i].sp_reg, bswap_if_needed(&dummy_elf, orc[i].sp_offset));
|
|
|
|
printf(" bp:");
|
|
|
|
print_reg(orc[i].bp_reg, bswap_if_needed(&dummy_elf, orc[i].bp_offset));
|
|
|
|
printf(" type:%s signal:%d end:%d\n",
|
|
orc_type_name(orc[i].type), orc[i].signal, orc[i].end);
|
|
}
|
|
|
|
elf_end(elf);
|
|
close(fd);
|
|
|
|
return 0;
|
|
}
|