2294 lines · 57.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later#define _GNU_SOURCE /* memmem() */#include <subcmd/parse-options.h>#include <stdlib.h>#include <string.h>#include <libgen.h>#include <stdio.h>#include <ctype.h> #include <objtool/objtool.h>#include <objtool/warn.h>#include <objtool/arch.h>#include <objtool/klp.h>#include <objtool/util.h>#include <objtool/special.h> #include <linux/align.h>#include <linux/objtool_types.h>#include <linux/livepatch_external.h>#include <linux/stringify.h>#include <linux/string.h>#include <linux/jhash.h> #define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER)) struct elfs {	struct elf *orig, *patched, *out;	const char *modname;}; struct export {	struct hlist_node hash;	char *mod;	char *sym;	bool mod_ns;}; bool debug, debug_correlate, debug_clone;int indent; static const char * const klp_diff_usage[] = {	"objtool klp diff [<options>] <in1.o> <in2.o> <out.o>",	NULL,}; static const struct option klp_diff_options[] = {	OPT_GROUP("Options:"),	OPT_BOOLEAN('d', "debug", &debug, "enable all debug output"),	OPT_BOOLEAN(0, "debug-correlate", &debug_correlate, "enable correlation debug output"),	OPT_BOOLEAN(0, "debug-clone", &debug_clone, "enable cloning debug output"),	OPT_END(),}; static DEFINE_HASHTABLE(exports, 15); static char *escape_str(const char *orig){	size_t len = 0;	const char *a;	char *b, *new; 	for (a = orig; *a; a++) {		switch (*a) {		case '\001': len += 5; break;		case '\n':		case '\t':   len += 2; break;		default: len++;		}	} 	new = malloc(len + 1);	if (!new)		return NULL; 	for (a = orig, b = new; *a; a++) {		switch (*a) {		case '\001': memcpy(b, "<SOH>", 5); b += 5; break;		case '\n': *b++ = '\\'; *b++ = 'n'; break;		case '\t': *b++ = '\\'; *b++ = 't'; break;		default:   *b++ = *a;		}	} 	*b = '\0';	return new;} /* * Convert a build-tree object path to a runtime module name: strip * directory components, replace '-' with '_', and remove file * extensions.  Examples: * *   "arch/x86/kvm/kvm" -> "kvm" *   "arch/x86/kvm/kvm-intel" -> "kvm_intel". * * Used by read_exports() to normalize Module.symvers entries and by * __find_modname() as a fallback when .modinfo lacks a "name=" tag. */static char *normalize_modname(char *name){	char *slash = strrchr(name, '/'); 	if (slash)		name = slash + 1; 	for (char *c = name; *c; c++) {		if (*c == '-')			*c = '_';		else if (*c == '.') {			*c = '\0';			break;		}	}	return name;} static int read_exports(void){	const char *symvers = "Module.symvers";	char line[1024], *path = NULL;	unsigned int line_num = 0;	FILE *file; 	file = fopen(symvers, "r");	if (!file) {		path = top_level_dir(symvers);		if (!path) {			ERROR("can't open '%s', \"objtool diff\" should be run from the kernel tree", symvers);			return -1;		} 		file = fopen(path, "r");		if (!file) {			ERROR_GLIBC("fopen");			return -1;		}	} 	while (fgets(line, 1024, file)) {		char *sym, *mod, *type, *namespace;		struct export *export; 		line_num++; 		sym = strchr(line, '\t');		if (!sym) {			ERROR("malformed Module.symvers (sym) at line %d", line_num);			return -1;		} 		*sym++ = '\0'; 		mod = strchr(sym, '\t');		if (!mod) {			ERROR("malformed Module.symvers (mod) at line %d", line_num);			return -1;		} 		*mod++ = '\0'; 		type = strchr(mod, '\t');		if (!type) {			ERROR("malformed Module.symvers (type) at line %d", line_num);			return -1;		} 		*type++ = '\0'; 		namespace = strchr(type, '\t');		if (!namespace) {			ERROR("malformed Module.symvers (namespace) at line %d", line_num);			return -1;		} 		*namespace++ = '\0'; 		if (*sym == '\0' || *mod == '\0') {			ERROR("malformed Module.symvers at line %d", line_num);			return -1;		} 		export = calloc(1, sizeof(*export));		if (!export) {			ERROR_GLIBC("calloc");			return -1;		} 		export->mod = strdup(mod);		if (!export->mod) {			ERROR_GLIBC("strdup");			return -1;		} 		if (strcmp(export->mod, "vmlinux"))			export->mod = normalize_modname(export->mod); 		export->sym = strdup(sym);		if (!export->sym) {			ERROR_GLIBC("strdup");			return -1;		} 		/* EXPORT_SYMBOL_FOR_MODULES() */		export->mod_ns = strstarts(namespace, "module:"); 		hash_add(exports, &export->hash, str_hash(sym));	} 	free(path);	fclose(file); 	return 0;} static int read_sym_checksums(struct elf *elf){	struct section *sec; 	sec = find_section_by_name(elf, ".discard.sym_checksum");	if (!sec) {		ERROR("'%s' missing .discard.sym_checksum section, file not processed by 'objtool klp checksum'?",		      elf->name);		return -1;	} 	if (!sec->rsec) {		ERROR("missing reloc section for .discard.sym_checksum");		return -1;	} 	if (sec_size(sec) % sizeof(struct sym_checksum)) {		ERROR("struct sym_checksum size mismatch");		return -1;	} 	for (int i = 0; i < sec_size(sec) / sizeof(struct sym_checksum); i++) {		struct sym_checksum *sym_checksum;		struct reloc *reloc;		struct symbol *sym; 		sym_checksum = (struct sym_checksum *)sec->data->d_buf + i; 		reloc = find_reloc_by_dest(elf, sec, i * sizeof(*sym_checksum));		if (!reloc) {			ERROR("can't find reloc for sym_checksum[%d]", i);			return -1;		} 		sym = reloc->sym; 		if (is_sec_sym(sym)) {			ERROR("not sure how to handle section %s", sym->name);			return -1;		} 		if (is_func_sym(sym) || is_object_sym(sym))			sym->csum.checksum = sym_checksum->checksum;	} 	return 0;} static struct symbol *first_file_symbol(struct elf *elf){	struct symbol *sym; 	for_each_sym(elf, sym) {		if (is_file_sym(sym))			return sym;	} 	return NULL;} static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym){	for_each_sym_continue(elf, sym) {		if (is_file_sym(sym))			return sym;	} 	return NULL;} /* * Certain static local variables should never be correlated.  They will be * used in place rather than referencing the originals. */static bool is_uncorrelated_static_local(struct symbol *sym){	static const char * const vars[] = {		"__already_done",		"__func__",		"__key",		"__warned",		"_entry",		"_entry_ptr",		"_rs",		"descriptor",		"CSWTCH",	};	const char *dot; 	if (!is_object_sym(sym) || !is_local_sym(sym))		return false; 	/* WARN_ONCE, etc */	if (!strcmp(sym->sec->name, ".data..once"))		return true; 	dot = strchr(sym->name, '.');	if (!dot)		return false; 	for (int i = 0; i < ARRAY_SIZE(vars); i++) {		size_t len = strlen(vars[i]); 		/* GCC: <var>.<id> */		if (strstarts(sym->name, vars[i]) && (sym->name[len] == '.'))			return true; 		/* Clang: <func>.<var>[.<id>] */		if (strstarts(dot + 1, vars[i]) &&		    (dot[1 + len] == '.' || dot[1 + len] == '\0'))			return true;	} 	return false;} /* * .L symbols are assembler-local labels not present in kallsyms.  They must * never become KLP relocations; instead their data is cloned into the patch * module.  This covers .Ltmp* (Clang temp labels), .L__const.* (Clang local * constants), and any other assembler-local pattern. */static bool is_local_label(struct symbol *sym){	return strstarts(sym->name, ".L");} static bool is_special_section(struct section *sec){	static const char * const specials[] = {		".altinstructions",		".kcfi_traps",		"__bug_table",		"__ex_table",		"__jump_table",		"__mcount_loc", 		/*		 * Extract .static_call_sites here to inherit non-module		 * preferential treatment.  The later static call processing		 * during klp module build will be skipped when it sees this		 * section already exists.		 */		".static_call_sites",	}; 	static const char * const non_special_discards[] = {		".discard.addressable",		".discard.sym_checksum",	}; 	if (is_text_sec(sec))		return false; 	for (int i = 0; i < ARRAY_SIZE(specials); i++) {		if (!strcmp(sec->name, specials[i]))			return true;	} 	/* Most .discard data sections are special */	for (int i = 0; i < ARRAY_SIZE(non_special_discards); i++) {		if (!strcmp(sec->name, non_special_discards[i]))			return false;	} 	return strstarts(sec->name, ".discard.");} /* * These sections are referenced by special sections but aren't considered * special sections themselves. */static bool is_special_section_aux(struct section *sec){	static const char * const specials_aux[] = {		".altinstr_replacement",		".altinstr_aux",	}; 	for (int i = 0; i < ARRAY_SIZE(specials_aux); i++) {		if (!strcmp(sec->name, specials_aux[i]))			return true;	} 	return false;} /* * Symbols created by ___ADDRESSABLE() are only used to convince the toolchain * not to optimize out the referenced symbol. */static bool is_addressable_sym(struct symbol *sym){	return !strcmp(sym->sec->name, ".discard.addressable");} /* * ABS symbols are typically assembly .set/.equ constants which are never * referenced by relocations.  (Exclude FILE symbols which are also SHN_ABS.) */static bool is_abs_sym(struct symbol *sym){	return sym->sym.st_shndx == SHN_ABS && !is_file_sym(sym);} static bool is_initcall_sym(struct symbol *sym){	return strstarts(sym->name, "__initcall__") ||	       strstarts(sym->name, "__initstub__");} /* * Some .rodata is anonymous and can't be correlated due to there being no * symbol names. * * The .rodata.cst* sections aren't technically anonymous, they're SHF_MERGE * constant pool sections containing small fixed-size data (lookup tables, * bitmasks) which are only read by value, so pointer equivalence isn't needed. * They are typically referenced by UBSAN data sections. */static bool is_anonymous_rodata(struct symbol *sym){	return is_rodata_sec(sym->sec) &&	       (!is_object_sym(sym) || strstarts(sym->sec->name, ".rodata.cst"));} /* * These symbols should never be correlated, so their local patched versions * are used instead of linking to the originals. */static bool dont_correlate(struct symbol *sym){	return is_file_sym(sym) ||	       is_null_sym(sym) ||	       is_sec_sym(sym) ||	       is_abs_sym(sym) ||	       is_prefix_func(sym) ||	       is_uncorrelated_static_local(sym) ||	       is_local_label(sym) ||	       is_string_sec(sym->sec) ||	       is_anonymous_rodata(sym) ||	       is_initcall_sym(sym) ||	       is_addressable_sym(sym) ||	       is_special_section(sym->sec) ||	       is_special_section_aux(sym->sec);} static const char *llvm_suffix(const char *name){	return strstr(name, ".llvm.");} static bool is_llvm_sym(struct symbol *sym){	return llvm_suffix(sym->name);} /* * Determine if two symbols have compatible source file origins: * *   - If both symbols are local, only return true if they belong to the same *     ELF file symbol. * *   - If both symbols are global, always return true, as globals don't have *     file associations. * *   - If they have different scopes, also return true, as the patch might have *     changed the symbol's scope. * * Works for both same-ELF (direct pointer compare) and cross-ELF * (compare via file->twin) cases. */static bool maybe_same_file(struct symbol *sym1, struct symbol *sym2){	if (!sym1->file || !sym2->file)		return true;	if (sym1->file == sym2->file)		return true;	return sym1->file->twin == sym2->file;} /* * Similar to maybe_same_file(), but strict: no scope changes allowed. * * Works for both same-ELF (direct pointer compare) and cross-ELF * (compare via file->twin) cases.

Lines 1–500 of 2294.