4834 lines · 110.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> */ #include <string.h>#include <stdlib.h>#include <inttypes.h>#include <sys/mman.h> #include <objtool/builtin.h>#include <objtool/cfi.h>#include <objtool/arch.h>#include <objtool/check.h>#include <objtool/special.h>#include <objtool/warn.h>#include <objtool/endianness.h> #include <linux/objtool_types.h>#include <linux/hashtable.h>#include <linux/kernel.h>#include <linux/static_call_types.h> struct alternative {	struct alternative *next;	struct instruction *insn;	bool skip_orig;}; static unsigned long nr_cfi, nr_cfi_reused, nr_cfi_cache; static struct cfi_init_state initial_func_cfi;static struct cfi_state init_cfi;static struct cfi_state func_cfi;static struct cfi_state force_undefined_cfi; 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, sec_offset_hash(sec, offset)) {		if (insn->sec == sec && insn->offset == offset)			return insn;	} 	return NULL;} struct instruction *next_insn_same_sec(struct objtool_file *file,				       struct instruction *insn){	if (insn->idx == INSN_CHUNK_MAX)		return find_insn(file, insn->sec, insn->offset + insn->len); 	insn++;	if (!insn->len)		return NULL; 	return insn;} static struct instruction *next_insn_same_func(struct objtool_file *file,					       struct instruction *insn){	struct instruction *next = next_insn_same_sec(file, insn);	struct symbol *func = insn_func(insn); 	if (!func)		return NULL; 	if (next && insn_func(next) == func)		return next; 	/* Check if we're already in the subfunction: */	if (func == func->cfunc)		return NULL; 	/* Move to the subfunction: */	return find_insn(file, func->cfunc->sec, func->cfunc->offset);} static struct instruction *prev_insn_same_sec(struct objtool_file *file,					      struct instruction *insn){	if (insn->idx == 0) {		if (insn->prev_len)			return find_insn(file, insn->sec, insn->offset - insn->prev_len);		return NULL;	} 	return insn - 1;} static struct instruction *prev_insn_same_sym(struct objtool_file *file,					      struct instruction *insn){	struct instruction *prev = prev_insn_same_sec(file, insn); 	if (prev && insn_func(prev) == insn_func(insn))		return prev; 	return NULL;} #define for_each_insn(file, insn)					\	for (struct section *__sec, *__fake = (struct section *)1;	\	     __fake; __fake = NULL)					\		for_each_sec(file, __sec)				\			sec_for_each_insn(file, __sec, insn) #define func_for_each_insn(file, func, insn)				\	for (insn = find_insn(file, func->sec, func->offset);		\	     insn;							\	     insn = next_insn_same_func(file, insn)) #define sym_for_each_insn(file, sym, insn)				\	for (insn = find_insn(file, sym->sec, sym->offset);		\	     insn && insn->offset < sym->offset + sym->len;		\	     insn = next_insn_same_sec(file, insn)) #define sym_for_each_insn_continue_reverse(file, sym, insn)		\	for (insn = prev_insn_same_sec(file, insn);			\	     insn && insn->offset >= sym->offset;			\	     insn = prev_insn_same_sec(file, insn)) #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)) static inline struct symbol *insn_call_dest(struct instruction *insn){	if (insn->type == INSN_JUMP_DYNAMIC ||	    insn->type == INSN_CALL_DYNAMIC)		return NULL; 	return insn->_call_dest;} static inline struct reloc *insn_jump_table(struct instruction *insn){	if (insn->type == INSN_JUMP_DYNAMIC ||	    insn->type == INSN_CALL_DYNAMIC)		return insn->_jump_table; 	return NULL;} static bool is_jump_table_jump(struct instruction *insn){	struct alt_group *alt_group = insn->alt_group; 	if (insn_jump_table(insn))		return true; 	/* Retpoline alternative for a jump table? */	return alt_group && alt_group->orig_group &&	       insn_jump_table(alt_group->orig_group->first_insn);} static bool is_sibling_call(struct instruction *insn){	/*	 * Assume only STT_FUNC calls have jump-tables.	 */	if (insn_func(insn)) {		/* An indirect jump is either a sibling call or a jump to a table. */		if (insn->type == INSN_JUMP_DYNAMIC)			return !is_jump_table_jump(insn);	} 	/* add_jump_destinations() sets insn_call_dest(insn) for sibling calls. */	return (is_static_jump(insn) && insn_call_dest(insn));} /* * 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. */static bool __dead_end_function(struct objtool_file *file, struct symbol *func,				int recursion){	int i;	struct instruction *insn;	bool empty = true; #define NORETURN(func) __stringify(func),	static const char * const global_noreturns[] = {#include "noreturns.h"	};#undef NORETURN 	if (!func)		return false; 	if (func->bind == STB_GLOBAL || func->bind == STB_WEAK)		for (i = 0; i < ARRAY_SIZE(global_noreturns); i++)			if (!strcmp(func->name, global_noreturns[i]))				return true; 	if (func->bind == STB_WEAK)		return false; 	if (!func->len)		return false; 	insn = find_insn(file, func->sec, func->offset);	if (!insn || !insn_func(insn))		return false; 	func_for_each_insn(file, func, insn) {		empty = false; 		if (insn->type == INSN_RETURN)			return false;	} 	if (empty)		return false; 	/*	 * 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 (is_sibling_call(insn)) {			struct instruction *dest = insn->jump_dest; 			if (!dest)				/* sibling call to another file */				return false; 			/* local sibling call */			if (recursion == 5) {				/*				 * Infinite recursion: two functions have				 * sibling calls to each other.  This is a very				 * rare case.  It means they aren't dead ends.				 */				return false;			} 			return __dead_end_function(file, insn_func(dest), recursion+1);		}	} 	return true;} static bool dead_end_function(struct objtool_file *file, struct symbol *func){	return __dead_end_function(file, func, 0);} static void init_cfi_state(struct cfi_state *cfi){	int i; 	for (i = 0; i < CFI_NUM_REGS; i++) {		cfi->regs[i].base = CFI_UNDEFINED;		cfi->vals[i].base = CFI_UNDEFINED;	}	cfi->cfa.base = CFI_UNDEFINED;	cfi->drap_reg = CFI_UNDEFINED;	cfi->drap_offset = -1;} static void init_insn_state(struct objtool_file *file, struct insn_state *state,			    struct section *sec){	memset(state, 0, sizeof(*state));	init_cfi_state(&state->cfi); 	/*	 * We need the full vmlinux for noinstr validation, otherwise we can	 * not correctly determine insn_call_dest(insn)->sec (external symbols	 * do not have a section).	 */	if (opts.link && opts.noinstr && sec)		state->noinstr = sec->noinstr;} static struct cfi_state *cfi_alloc(void){	struct cfi_state *cfi = calloc(1, sizeof(struct cfi_state));	if (!cfi) {		WARN("calloc failed");		exit(1);	}	nr_cfi++;	return cfi;} static int cfi_bits;static struct hlist_head *cfi_hash; static inline bool cficmp(struct cfi_state *cfi1, struct cfi_state *cfi2){	return memcmp((void *)cfi1 + sizeof(cfi1->hash),		      (void *)cfi2 + sizeof(cfi2->hash),		      sizeof(struct cfi_state) - sizeof(struct hlist_node));} static inline u32 cfi_key(struct cfi_state *cfi){	return jhash((void *)cfi + sizeof(cfi->hash),		     sizeof(*cfi) - sizeof(cfi->hash), 0);} static struct cfi_state *cfi_hash_find_or_add(struct cfi_state *cfi){	struct hlist_head *head = &cfi_hash[hash_min(cfi_key(cfi), cfi_bits)];	struct cfi_state *obj; 	hlist_for_each_entry(obj, head, hash) {		if (!cficmp(cfi, obj)) {			nr_cfi_cache++;			return obj;		}	} 	obj = cfi_alloc();	*obj = *cfi;	hlist_add_head(&obj->hash, head); 	return obj;} static void cfi_hash_add(struct cfi_state *cfi){	struct hlist_head *head = &cfi_hash[hash_min(cfi_key(cfi), cfi_bits)]; 	hlist_add_head(&cfi->hash, head);} static void *cfi_hash_alloc(unsigned long size){	cfi_bits = max(10, ilog2(size));	cfi_hash = mmap(NULL, sizeof(struct hlist_head) << cfi_bits,			PROT_READ|PROT_WRITE,			MAP_PRIVATE|MAP_ANON, -1, 0);	if (cfi_hash == (void *)-1L) {		WARN("mmap fail cfi_hash");		cfi_hash = NULL;	}  else if (opts.stats) {		printf("cfi_bits: %d\n", cfi_bits);	} 	return cfi_hash;} static unsigned long nr_insns;static unsigned long nr_insns_visited; /* * 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) {		struct instruction *insns = NULL;		u8 prev_len = 0;		u8 idx = 0; 		if (!(sec->sh.sh_flags & SHF_EXECINSTR))			continue; 		if (strcmp(sec->name, ".altinstr_replacement") &&		    strcmp(sec->name, ".altinstr_aux") &&		    strncmp(sec->name, ".discard.", 9))			sec->text = true; 		if (!strcmp(sec->name, ".noinstr.text") ||		    !strcmp(sec->name, ".entry.text") ||		    !strcmp(sec->name, ".cpuidle.text") ||		    !strncmp(sec->name, ".text..__x86.", 13))			sec->noinstr = true; 		/*		 * .init.text code is ran before userspace and thus doesn't		 * strictly need retpolines, except for modules which are		 * loaded late, they very much do need retpoline in their		 * .init.text		 */		if (!strcmp(sec->name, ".init.text") && !opts.module)			sec->init = true; 		for (offset = 0; offset < sec->sh.sh_size; offset += insn->len) {			if (!insns || idx == INSN_CHUNK_MAX) {				insns = calloc(sizeof(*insn), INSN_CHUNK_SIZE);				if (!insns) {					WARN("malloc failed");					return -1;				}				idx = 0;			} else {				idx++;			}			insn = &insns[idx];			insn->idx = idx; 			INIT_LIST_HEAD(&insn->call_node);			insn->sec = sec;			insn->offset = offset;			insn->prev_len = prev_len; 			ret = arch_decode_instruction(file, sec, offset,						      sec->sh.sh_size - offset,						      insn);			if (ret)				return ret; 			prev_len = insn->len; 			/*			 * By default, "ud2" is a dead end unless otherwise			 * annotated, because GCC 7 inserts it for certain			 * divide-by-zero cases.			 */			if (insn->type == INSN_BUG)				insn->dead_end = true; 			hash_add(file->insn_hash, &insn->hash, sec_offset_hash(sec, insn->offset));			nr_insns++;		} //		printf("%s: last chunk used: %d\n", sec->name, (int)idx); 		sec_for_each_sym(sec, func) {			if (func->type != STT_NOTYPE && func->type != STT_FUNC)				continue; 			if (func->offset == sec->sh.sh_size) {				/* Heuristic: likely an "end" symbol */				if (func->type == STT_NOTYPE)					continue;				WARN("%s(): STT_FUNC at end of section",				     func->name);				return -1;			} 			if (func->embedded_insn || func->alias != func)				continue; 			if (!find_insn(file, sec, func->offset)) {				WARN("%s(): can't find starting instruction",				     func->name);				return -1;			} 			sym_for_each_insn(file, func, insn) {				insn->sym = func;				if (func->type == STT_FUNC &&				    insn->type == INSN_ENDBR &&				    list_empty(&insn->call_node)) {					if (insn->offset == func->offset) {						list_add_tail(&insn->call_node, &file->endbr_list);						file->nr_endbr++;					} else {						file->nr_endbr_int++;					}				}			}		}	} 	if (opts.stats)		printf("nr_insns: %lu\n", nr_insns); 	return 0;} /* * Read the pv_ops[] .data table to find the static initialized values. */static int add_pv_ops(struct objtool_file *file, const char *symname){	struct symbol *sym, *func;	unsigned long off, end;	struct reloc *reloc;	int idx; 	sym = find_symbol_by_name(file->elf, symname);	if (!sym)

Lines 1–500 of 4834.