mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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>
		
			
				
	
	
		
			214 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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;
 | 
						|
}
 |