forked from Minki/linux
0decf1f8de
Objtool currently only compiles for x86 architectures. This is fine as it presently does not support tooling for other architectures. However, we would like to be able to convert other kernel tools to run as objtool sub commands because they too process ELF object files. This will allow us to convert tools such as recordmcount to use objtool's ELF code. Since much of recordmcount's ELF code is copy-paste code to/from a variety of other kernel tools (look at modpost for example) this means that if we can convert recordmcount we can convert more. We define weak definitions for subcommand entry functions and other weak definitions for shared functions critical to building existing subcommands. These return 127 when the command is missing which signify tools that do not exist on all architectures. In this case the "check" and "orc" tools do not exist on all architectures so we only add them for x86. Future changes adding support for "check", to arm64 for example, can then modify the SUBCMD_CHECK variable when building for arm64. Objtool is not currently wired in to KConfig to be built for other architectures because it's not needed for those architectures and there are no commands it supports other than those for x86. As more command support is enabled on various architectures the necessary KConfig changes can be made (e.g. adding "STACK_VALIDATION") to trigger building objtool. [ jpoimboe: remove aliases, add __weak macro, add error messages ] Cc: Julien Thierry <jthierry@redhat.com> Signed-off-by: Matt Helsley <mhelsley@vmware.com> Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
230 lines
4.7 KiB
C
230 lines
4.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.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->cfi.cfa;
|
|
struct cfi_reg *bp = &insn->cfi.regs[CFI_BP];
|
|
|
|
orc->end = insn->cfi.end;
|
|
|
|
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->cfi.type;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int create_orc_entry(struct elf *elf, 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));
|
|
|
|
if (insn_sec->sym) {
|
|
rela->sym = insn_sec->sym;
|
|
rela->addend = insn_off;
|
|
} else {
|
|
/*
|
|
* The Clang assembler doesn't produce section symbols, so we
|
|
* have to reference the function symbol instead:
|
|
*/
|
|
rela->sym = find_symbol_containing(insn_sec, insn_off);
|
|
if (!rela->sym) {
|
|
/*
|
|
* Hack alert. This happens when we need to reference
|
|
* the NOP pad insn immediately after the function.
|
|
*/
|
|
rela->sym = find_symbol_containing(insn_sec,
|
|
insn_off - 1);
|
|
}
|
|
if (!rela->sym) {
|
|
WARN("missing symbol for insn at offset 0x%lx\n",
|
|
insn_off);
|
|
return -1;
|
|
}
|
|
|
|
rela->addend = insn_off - rela->sym->offset;
|
|
}
|
|
|
|
rela->type = R_X86_64_PC32;
|
|
rela->offset = idx * sizeof(int);
|
|
rela->sec = ip_relasec;
|
|
|
|
elf_add_rela(elf, rela);
|
|
|
|
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);
|
|
if (!sec)
|
|
return -1;
|
|
|
|
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(file->elf, 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(file->elf, 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;
|
|
}
|