1664 lines · 39.7 KB · C
RawDownload
/* * Copyright (C) 2015-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 <string.h>#include <stdlib.h> #include "check.h"#include "elf.h"#include "special.h"#include "arch.h"#include "warn.h" #include <linux/hashtable.h>#include <linux/kernel.h> struct alternative {	struct list_head list;	struct instruction *insn;}; const char *objname;static bool nofp;struct cfi_state initial_func_cfi; static struct instruction *find_insn(struct objtool_file *file,				     struct section *sec, unsigned long offset){	struct instruction *insn; 	hash_for_each_possible(file->insn_hash, insn, hash, offset)		if (insn->sec == sec && insn->offset == offset)			return insn; 	return NULL;} static struct instruction *next_insn_same_sec(struct objtool_file *file,					      struct instruction *insn){	struct instruction *next = list_next_entry(insn, list); 	if (!next || &next->list == &file->insn_list || next->sec != insn->sec)		return NULL; 	return next;} static bool gcov_enabled(struct objtool_file *file){	struct section *sec;	struct symbol *sym; 	for_each_sec(file, sec)		list_for_each_entry(sym, &sec->symbol_list, list)			if (!strncmp(sym->name, "__gcov_.", 8))				return true; 	return false;} #define func_for_each_insn(file, func, insn)				\	for (insn = find_insn(file, func->sec, func->offset);		\	     insn && &insn->list != &file->insn_list &&			\		insn->sec == func->sec &&				\		insn->offset < func->offset + func->len;		\	     insn = list_next_entry(insn, list)) #define func_for_each_insn_continue_reverse(file, func, insn)		\	for (insn = list_prev_entry(insn, list);			\	     &insn->list != &file->insn_list &&				\		insn->sec == func->sec && insn->offset >= func->offset;	\	     insn = list_prev_entry(insn, list)) #define sec_for_each_insn_from(file, insn)				\	for (; insn; insn = next_insn_same_sec(file, insn)) #define sec_for_each_insn_continue(file, insn)				\	for (insn = next_insn_same_sec(file, insn); insn;		\	     insn = next_insn_same_sec(file, insn)) /* * Check if the function has been manually whitelisted with the * STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted * due to its use of a context switching instruction. */static bool ignore_func(struct objtool_file *file, struct symbol *func){	struct rela *rela;	struct instruction *insn; 	/* check for STACK_FRAME_NON_STANDARD */	if (file->whitelist && file->whitelist->rela)		list_for_each_entry(rela, &file->whitelist->rela->rela_list, list) {			if (rela->sym->type == STT_SECTION &&			    rela->sym->sec == func->sec &&			    rela->addend == func->offset)				return true;			if (rela->sym->type == STT_FUNC && rela->sym == func)				return true;		} 	/* check if it has a context switching instruction */	func_for_each_insn(file, func, insn)		if (insn->type == INSN_CONTEXT_SWITCH)			return true; 	return false;} /* * This checks to see if the given function is a "noreturn" function. * * For global functions which are outside the scope of this object file, we * have to keep a manual list of them. * * For local functions, we have to detect them manually by simply looking for * the lack of a return instruction. * * Returns: *  -1: error *   0: no dead end *   1: dead end */static int __dead_end_function(struct objtool_file *file, struct symbol *func,			       int recursion){	int i;	struct instruction *insn;	bool empty = true; 	/*	 * Unfortunately these have to be hard coded because the noreturn	 * attribute isn't provided in ELF data.	 */	static const char * const global_noreturns[] = {		"__stack_chk_fail",		"panic",		"do_exit",		"do_task_dead",		"__module_put_and_exit",		"complete_and_exit",		"kvm_spurious_fault",		"__reiserfs_panic",		"lbug_with_loc",		"fortify_panic",	}; 	if (func->bind == STB_WEAK)		return 0; 	if (func->bind == STB_GLOBAL)		for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)			if (!strcmp(func->name, global_noreturns[i]))				return 1; 	if (!func->sec)		return 0; 	func_for_each_insn(file, func, insn) {		empty = false; 		if (insn->type == INSN_RETURN)			return 0;	} 	if (empty)		return 0; 	/*	 * A function can have a sibling call instead of a return.  In that	 * case, the function's dead-end status depends on whether the target	 * of the sibling call returns.	 */	func_for_each_insn(file, func, insn) {		if (insn->sec != func->sec ||		    insn->offset >= func->offset + func->len)			break; 		if (insn->type == INSN_JUMP_UNCONDITIONAL) {			struct instruction *dest = insn->jump_dest;			struct symbol *dest_func; 			if (!dest)				/* sibling call to another file */				return 0; 			if (dest->sec != func->sec ||			    dest->offset < func->offset ||			    dest->offset >= func->offset + func->len) {				/* local sibling call */				dest_func = find_symbol_by_offset(dest->sec,								  dest->offset);				if (!dest_func)					continue; 				if (recursion == 5) {					WARN_FUNC("infinite recursion (objtool bug!)",						  dest->sec, dest->offset);					return -1;				} 				return __dead_end_function(file, dest_func,							   recursion + 1);			}		} 		if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts))			/* sibling call */			return 0;	} 	return 1;} static int dead_end_function(struct objtool_file *file, struct symbol *func){	return __dead_end_function(file, func, 0);} static void clear_insn_state(struct insn_state *state){	int i; 	memset(state, 0, sizeof(*state));	state->cfa.base = CFI_UNDEFINED;	for (i = 0; i < CFI_NUM_REGS; i++)		state->regs[i].base = CFI_UNDEFINED;	state->drap_reg = CFI_UNDEFINED;} /* * Call the arch-specific instruction decoder for all the instructions and add * them to the global instruction list. */static int decode_instructions(struct objtool_file *file){	struct section *sec;	struct symbol *func;	unsigned long offset;	struct instruction *insn;	int ret; 	for_each_sec(file, sec) { 		if (!(sec->sh.sh_flags & SHF_EXECINSTR))			continue; 		for (offset = 0; offset < sec->len; offset += insn->len) {			insn = malloc(sizeof(*insn));			if (!insn) {				WARN("malloc failed");				return -1;			}			memset(insn, 0, sizeof(*insn));			INIT_LIST_HEAD(&insn->alts);			clear_insn_state(&insn->state); 			insn->sec = sec;			insn->offset = offset; 			ret = arch_decode_instruction(file->elf, sec, offset,						      sec->len - offset,						      &insn->len, &insn->type,						      &insn->immediate,						      &insn->stack_op);			if (ret)				return ret; 			if (!insn->type || insn->type > INSN_LAST) {				WARN_FUNC("invalid instruction type %d",					  insn->sec, insn->offset, insn->type);				return -1;			} 			hash_add(file->insn_hash, &insn->hash, insn->offset);			list_add_tail(&insn->list, &file->insn_list);		} 		list_for_each_entry(func, &sec->symbol_list, list) {			if (func->type != STT_FUNC)				continue; 			if (!find_insn(file, sec, func->offset)) {				WARN("%s(): can't find starting instruction",				     func->name);				return -1;			} 			func_for_each_insn(file, func, insn)				if (!insn->func)					insn->func = func;		}	} 	return 0;} /* * Find all uses of the unreachable() macro, which are code path dead ends. */static int add_dead_ends(struct objtool_file *file){	struct section *sec;	struct rela *rela;	struct instruction *insn;	bool found; 	sec = find_section_by_name(file->elf, ".rela.discard.unreachable");	if (!sec)		return 0; 	list_for_each_entry(rela, &sec->rela_list, list) {		if (rela->sym->type != STT_SECTION) {			WARN("unexpected relocation symbol type in %s", sec->name);			return -1;		}		insn = find_insn(file, rela->sym->sec, rela->addend);		if (insn)			insn = list_prev_entry(insn, list);		else if (rela->addend == rela->sym->sec->len) {			found = false;			list_for_each_entry_reverse(insn, &file->insn_list, list) {				if (insn->sec == rela->sym->sec) {					found = true;					break;				}			} 			if (!found) {				WARN("can't find unreachable insn at %s+0x%x",				     rela->sym->sec->name, rela->addend);				return -1;			}		} else {			WARN("can't find unreachable insn at %s+0x%x",			     rela->sym->sec->name, rela->addend);			return -1;		} 		insn->dead_end = true;	} 	return 0;} /* * Warnings shouldn't be reported for ignored functions. */static void add_ignores(struct objtool_file *file){	struct instruction *insn;	struct section *sec;	struct symbol *func; 	for_each_sec(file, sec) {		list_for_each_entry(func, &sec->symbol_list, list) {			if (func->type != STT_FUNC)				continue; 			if (!ignore_func(file, func))				continue; 			func_for_each_insn(file, func, insn)				insn->ignore = true;		}	}} /* * Find the destination instructions for all jumps. */static int add_jump_destinations(struct objtool_file *file){	struct instruction *insn;	struct rela *rela;	struct section *dest_sec;	unsigned long dest_off; 	for_each_insn(file, insn) {		if (insn->type != INSN_JUMP_CONDITIONAL &&		    insn->type != INSN_JUMP_UNCONDITIONAL)			continue; 		if (insn->ignore)			continue; 		rela = find_rela_by_dest_range(insn->sec, insn->offset,					       insn->len);		if (!rela) {			dest_sec = insn->sec;			dest_off = insn->offset + insn->len + insn->immediate;		} else if (rela->sym->type == STT_SECTION) {			dest_sec = rela->sym->sec;			dest_off = rela->addend + 4;		} else if (rela->sym->sec->idx) {			dest_sec = rela->sym->sec;			dest_off = rela->sym->sym.st_value + rela->addend + 4;		} else {			/* sibling call */			insn->jump_dest = 0;			continue;		} 		insn->jump_dest = find_insn(file, dest_sec, dest_off);		if (!insn->jump_dest) { 			/*			 * This is a special case where an alt instruction			 * jumps past the end of the section.  These are			 * handled later in handle_group_alt().			 */			if (!strcmp(insn->sec->name, ".altinstr_replacement"))				continue; 			WARN_FUNC("can't find jump dest instruction at %s+0x%lx",				  insn->sec, insn->offset, dest_sec->name,				  dest_off);			return -1;		}	} 	return 0;} /* * Find the destination instructions for all calls. */static int add_call_destinations(struct objtool_file *file){	struct instruction *insn;	unsigned long dest_off;	struct rela *rela; 	for_each_insn(file, insn) {		if (insn->type != INSN_CALL)			continue; 		rela = find_rela_by_dest_range(insn->sec, insn->offset,					       insn->len);		if (!rela) {			dest_off = insn->offset + insn->len + insn->immediate;			insn->call_dest = find_symbol_by_offset(insn->sec,								dest_off);			if (!insn->call_dest) {				WARN_FUNC("can't find call dest symbol at offset 0x%lx",					  insn->sec, insn->offset, dest_off);				return -1;			}		} else if (rela->sym->type == STT_SECTION) {			insn->call_dest = find_symbol_by_offset(rela->sym->sec,								rela->addend+4);			if (!insn->call_dest ||			    insn->call_dest->type != STT_FUNC) {				WARN_FUNC("can't find call dest symbol at %s+0x%x",					  insn->sec, insn->offset,					  rela->sym->sec->name,					  rela->addend + 4);				return -1;			}		} else			insn->call_dest = rela->sym;	} 	return 0;} /* * The .alternatives section requires some extra special care, over and above * what other special sections require: * * 1. Because alternatives are patched in-place, we need to insert a fake jump *    instruction at the end so that validate_branch() skips all the original *    replaced instructions when validating the new instruction path. * * 2. An added wrinkle is that the new instruction length might be zero.  In *    that case the old instructions are replaced with noops.  We simulate that *    by creating a fake jump as the only new instruction. * * 3. In some cases, the alternative section includes an instruction which *    conditionally jumps to the _end_ of the entry.  We have to modify these *    jumps' destinations to point back to .text rather than the end of the *    entry in .altinstr_replacement. * * 4. It has been requested that we don't validate the !POPCNT feature path *    which is a "very very small percentage of machines". */

Lines 1–500 of 1664.