1248 lines · 28.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> */ #define _GNU_SOURCE#include <fnmatch.h> #include <objtool/arch.h>#include <objtool/check.h>#include <objtool/disas.h>#include <objtool/special.h>#include <objtool/warn.h> #include <bfd.h>#include <linux/string.h>#include <tools/dis-asm-compat.h> /* * Size of the buffer for storing the result of disassembling * a single instruction. */#define DISAS_RESULT_SIZE	1024 struct disas_context {	struct objtool_file *file;	struct instruction *insn;	bool alt_applied;	char result[DISAS_RESULT_SIZE];	disassembler_ftype disassembler;	struct disassemble_info info;}; /* * Maximum number of alternatives */#define DISAS_ALT_MAX		5 /* * Maximum number of instructions per alternative */#define DISAS_ALT_INSN_MAX	50 /* * Information to disassemble an alternative */struct disas_alt {	struct instruction *orig_insn;		/* original instruction */	struct alternative *alt;		/* alternative or NULL if default code */	char *name;				/* name for this alternative */	int width;				/* formatting width */	struct {		char *str;			/* instruction string */		int offset;			/* instruction offset */		int nops;			/* number of nops */	} insn[DISAS_ALT_INSN_MAX];		/* alternative instructions */	int insn_idx;				/* index of the next instruction to print */}; #define DALT_DEFAULT(dalt)	(!(dalt)->alt)#define DALT_INSN(dalt)		(DALT_DEFAULT(dalt) ? (dalt)->orig_insn : (dalt)->alt->insn)#define DALT_GROUP(dalt)	(DALT_INSN(dalt)->alt_group)#define DALT_ALTID(dalt)	((dalt)->orig_insn->offset) #define ALT_FLAGS_SHIFT		16#define ALT_FLAG_NOT		(1 << 0)#define ALT_FLAG_DIRECT_CALL	(1 << 1)#define ALT_FEATURE_MASK	((1 << ALT_FLAGS_SHIFT) - 1) static int alt_feature(unsigned int ft_flags){	return (ft_flags & ALT_FEATURE_MASK);} static int alt_flags(unsigned int ft_flags){	return (ft_flags >> ALT_FLAGS_SHIFT);} /* * Wrapper around asprintf() to allocate and format a string. * Return the allocated string or NULL on error. */static char *strfmt(const char *fmt, ...){	va_list ap;	char *str;	int rv; 	va_start(ap, fmt);	rv = vasprintf(&str, fmt, ap);	va_end(ap); 	return rv == -1 ? NULL : str;} static int sprint_name(char *str, const char *name, unsigned long offset){	int len; 	if (offset)		len = sprintf(str, "%s+0x%lx", name, offset);	else		len = sprintf(str, "%s", name); 	return len;} #define DINFO_FPRINTF(dinfo, ...)	\	((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__))#define bfd_vma_fmt			\	__builtin_choose_expr(sizeof(bfd_vma) == sizeof(unsigned long), "%#lx <%s>", "%#llx <%s>") static int disas_result_fprintf(struct disas_context *dctx,				const char *fmt, va_list ap){	char *buf = dctx->result;	int avail, len; 	len = strlen(buf);	if (len >= DISAS_RESULT_SIZE - 1) {		WARN_FUNC(dctx->insn->sec, dctx->insn->offset,			  "disassembly buffer is full");		return -1;	}	avail = DISAS_RESULT_SIZE - len; 	len = vsnprintf(buf + len, avail, fmt, ap);	if (len < 0 || len >= avail) {		WARN_FUNC(dctx->insn->sec, dctx->insn->offset,			  "disassembly buffer is truncated");		return -1;	} 	return 0;} static int disas_fprintf(void *stream, const char *fmt, ...){	va_list arg;	int rv; 	va_start(arg, fmt);	rv = disas_result_fprintf(stream, fmt, arg);	va_end(arg); 	return rv;} /* * For init_disassemble_info_compat(). */static int disas_fprintf_styled(void *stream,				enum disassembler_style style,				const char *fmt, ...){	va_list arg;	int rv; 	va_start(arg, fmt);	rv = disas_result_fprintf(stream, fmt, arg);	va_end(arg); 	return rv;} static void disas_print_addr_sym(struct section *sec, struct symbol *sym,				 bfd_vma addr, struct disassemble_info *dinfo){	char symstr[1024];	char *str; 	if (sym) {		sprint_name(symstr, sym->name, addr - sym->offset);		DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, symstr);	} else {		str = offstr(sec, addr);		DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, str);		free(str);	}} static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo){	struct disas_context *dctx = dinfo->application_data;	struct instruction *orig_first_insn;	struct alt_group *alt_group;	unsigned long offset;	struct symbol *sym; 	/*	 * Check if we are processing an alternative at the original	 * instruction address (i.e. if alt_applied is true) and if	 * we are referencing an address inside the alternative.	 *	 * For example, this happens if there is a branch inside an	 * alternative. In that case, the address should be updated	 * to a reference inside the original instruction flow.	 */	if (!dctx->alt_applied)		return false; 	alt_group = dctx->insn->alt_group;	if (!alt_group || !alt_group->orig_group ||	    addr < alt_group->first_insn->offset ||	    addr > alt_group->last_insn->offset)		return false; 	orig_first_insn = alt_group->orig_group->first_insn;	offset = addr - alt_group->first_insn->offset; 	addr = orig_first_insn->offset + offset;	sym = insn_sym(orig_first_insn); 	disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo); 	return true;} static void disas_print_addr_noreloc(bfd_vma addr,				     struct disassemble_info *dinfo){	struct disas_context *dctx = dinfo->application_data;	struct instruction *insn = dctx->insn;	struct symbol *sym = insn_sym(insn); 	if (disas_print_addr_alt(addr, dinfo))		return; 	if (sym && (addr < sym->offset || addr >= sym->offset + sym->len))		sym = NULL; 	disas_print_addr_sym(insn->sec, sym, addr, dinfo);} static void disas_print_addr_reloc(bfd_vma addr, struct disassemble_info *dinfo){	struct disas_context *dctx = dinfo->application_data;	struct instruction *insn = dctx->insn;	unsigned long offset;	struct reloc *reloc;	char symstr[1024];	char *str; 	reloc = find_reloc_by_dest_range(dctx->file->elf, insn->sec,					 insn->offset, insn->len);	if (!reloc) {		/*		 * There is no relocation for this instruction although		 * the address to resolve points to the next instruction.		 * So this is an effective reference to the next IP, for		 * example: "lea 0x0(%rip),%rdi". The kernel can reference		 * the next IP with _THIS_IP_ macro.		 */		DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, "_THIS_IP_");		return;	} 	offset = arch_insn_adjusted_addend(insn, reloc); 	/*	 * If the relocation symbol is a section name (for example ".bss")	 * then we try to further resolve the name.	 */	if (is_sec_sym(reloc->sym)) {		str = offstr(reloc->sym->sec, reloc->sym->offset + offset);		DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, str);		free(str);	} else {		sprint_name(symstr, reloc->sym->name, offset);		DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, symstr);	}} /* * Resolve an address into a "<symbol>+<offset>" string. */static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo){	struct disas_context *dctx = dinfo->application_data;	struct instruction *insn = dctx->insn;	struct instruction *jump_dest;	struct symbol *sym;	bool is_reloc; 	/*	 * If the instruction is a call/jump and it references a	 * destination then this is likely the address we are looking	 * up. So check it first.	 */	jump_dest = insn->jump_dest;	if (jump_dest && insn_sym(jump_dest) && jump_dest->offset == addr) {		if (!disas_print_addr_alt(addr, dinfo))			disas_print_addr_sym(jump_dest->sec, insn_sym(jump_dest),					     addr, dinfo);		return;	} 	/*	 * If the address points to the next instruction then there is	 * probably a relocation. It can be a false positive when the	 * current instruction is referencing the address of the next	 * instruction. This particular case will be handled in	 * disas_print_addr_reloc().	 */	is_reloc = (addr == insn->offset + insn->len); 	/*	 * The call destination offset can be the address we are looking	 * up, or 0 if there is a relocation.	 */	sym = insn_call_dest(insn);	if (sym && (sym->offset == addr || (sym->offset == 0 && is_reloc))) {		DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, sym->name);		return;	} 	if (!is_reloc)		disas_print_addr_noreloc(addr, dinfo);	else		disas_print_addr_reloc(addr, dinfo);} /* * Initialize disassemble info arch, mach (32 or 64-bit) and options. */int disas_info_init(struct disassemble_info *dinfo,		    int arch, int mach32, int mach64,		    const char *options){	struct disas_context *dctx = dinfo->application_data;	struct objtool_file *file = dctx->file; 	dinfo->arch = arch; 	switch (file->elf->ehdr.e_ident[EI_CLASS]) {	case ELFCLASS32:		dinfo->mach = mach32;		break;	case ELFCLASS64:		dinfo->mach = mach64;		break;	default:		return -1;	} 	dinfo->disassembler_options = options; 	return 0;} struct disas_context *disas_context_create(struct objtool_file *file){	struct disas_context *dctx;	struct disassemble_info *dinfo;	int err; 	dctx = malloc(sizeof(*dctx));	if (!dctx) {		WARN("failed to allocate disassembly context");		return NULL;	} 	dctx->file = file;	dinfo = &dctx->info; 	init_disassemble_info_compat(dinfo, dctx,				     disas_fprintf, disas_fprintf_styled); 	dinfo->read_memory_func = buffer_read_memory;	dinfo->print_address_func = disas_print_address;	dinfo->application_data = dctx; 	/*	 * bfd_openr() is not used to avoid doing ELF data processing	 * and caching that has already being done. Here, we just need	 * to identify the target file so we call an arch specific	 * function to fill some disassemble info (arch, mach).	 */ 	dinfo->arch = bfd_arch_unknown;	dinfo->mach = 0; 	err = arch_disas_info_init(dinfo);	if (err || dinfo->arch == bfd_arch_unknown || dinfo->mach == 0) {		WARN("failed to init disassembly arch");		goto error;	} 	dinfo->endian = (file->elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) ?		BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE; 	disassemble_init_for_target(dinfo); 	dctx->disassembler = disassembler(dinfo->arch,					  dinfo->endian == BFD_ENDIAN_BIG,					  dinfo->mach, NULL);	if (!dctx->disassembler) {		WARN("failed to create disassembler function");		goto error;	} 	return dctx; error:	free(dctx);	return NULL;} void disas_context_destroy(struct disas_context *dctx){	free(dctx);} char *disas_result(struct disas_context *dctx){	return dctx->result;} #define DISAS_INSN_OFFSET_SPACE		10#define DISAS_INSN_SPACE		60 #define DISAS_PRINSN(dctx, insn, depth)			\	disas_print_insn(stdout, dctx, insn, depth, "\n") /* * Print a message in the instruction flow. If sec is not NULL then the * address at the section offset is printed in addition of the message, * otherwise only the message is printed. */static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset,			int depth, const char *format, va_list ap){	const char *addr_str;	int i, n;	int len; 	len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE;	if (depth < 0) {		len += depth;		depth = 0;	} 	n = 0; 	if (sec) {		addr_str = offstr(sec, offset);		n += fprintf(stream, "%6lx:  %-*s  ", offset, len, addr_str);		free((char *)addr_str);	} else {		len += DISAS_INSN_OFFSET_SPACE + 1;		n += fprintf(stream, "%-*s", len, "");	} 	/* print vertical bars to show the code flow */	for (i = 0; i < depth; i++)		n += fprintf(stream, "| "); 	if (format)		n += vfprintf(stream, format, ap); 	return n;} static int disas_print(FILE *stream, struct section *sec, unsigned long offset,			int depth, const char *format, ...){	va_list args;	int len; 	va_start(args, format);	len = disas_vprint(stream, sec, offset, depth, format, args);	va_end(args); 	return len;} /* * Print a message in the instruction flow. If insn is not NULL then * the instruction address is printed in addition of the message, * otherwise only the message is printed. In all cases, the instruction * itself is not printed. */void disas_print_info(FILE *stream, struct instruction *insn, int depth,		      const char *format, ...){	struct section *sec;	unsigned long off;	va_list args; 	if (insn) {		sec = insn->sec;		off = insn->offset;	} else {		sec = NULL;		off = 0;	} 	va_start(args, format);	disas_vprint(stream, sec, off, depth, format, args);

Lines 1–500 of 1248.