937 lines · 22.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * kallsyms.c: in-kernel printing of symbolic oopses and stack traces. * * Rewritten and vastly simplified by Rusty Russell for in-kernel * module loader: *   Copyright 2002 Rusty Russell <rusty@rustcorp.com.au> IBM Corporation * * ChangeLog: * * (25/Aug/2004) Paulo Marques <pmarques@grupopie.com> *      Changed the compression method from stem compression to "table lookup" *      compression (see scripts/kallsyms.c for a more complete description) */#include <linux/kallsyms.h>#include <linux/init.h>#include <linux/seq_file.h>#include <linux/fs.h>#include <linux/kdb.h>#include <linux/err.h>#include <linux/proc_fs.h>#include <linux/sched.h>	/* for cond_resched */#include <linux/ctype.h>#include <linux/slab.h>#include <linux/filter.h>#include <linux/ftrace.h>#include <linux/kprobes.h>#include <linux/build_bug.h>#include <linux/compiler.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/bsearch.h>#include <linux/btf_ids.h> #include "kallsyms_internal.h" /* * Expand a compressed symbol data into the resulting uncompressed string, * if uncompressed string is too long (>= maxlen), it will be truncated, * given the offset to where the symbol is in the compressed stream. */static unsigned int kallsyms_expand_symbol(unsigned int off,					   char *result, size_t maxlen){	int len, skipped_first = 0;	const char *tptr;	const u8 *data; 	/* Get the compressed symbol length from the first symbol byte. */	data = &kallsyms_names[off];	len = *data;	data++;	off++; 	/* If MSB is 1, it is a "big" symbol, so needs an additional byte. */	if ((len & 0x80) != 0) {		len = (len & 0x7F) | (*data << 7);		data++;		off++;	} 	/*	 * Update the offset to return the offset for the next symbol on	 * the compressed stream.	 */	off += len; 	/*	 * For every byte on the compressed symbol data, copy the table	 * entry for that byte.	 */	while (len) {		tptr = &kallsyms_token_table[kallsyms_token_index[*data]];		data++;		len--; 		while (*tptr) {			if (skipped_first) {				if (maxlen <= 1)					goto tail;				*result = *tptr;				result++;				maxlen--;			} else				skipped_first = 1;			tptr++;		}	} tail:	if (maxlen)		*result = '\0'; 	/* Return to offset to the next symbol. */	return off;} /* * Get symbol type information. This is encoded as a single char at the * beginning of the symbol name. */static char kallsyms_get_symbol_type(unsigned int off){	/*	 * Get just the first code, look it up in the token table,	 * and return the first char from this token. If MSB of length	 * is 1, it is a "big" symbol, so needs an additional byte.	 */	if (kallsyms_names[off] & 0x80)		off++;	return kallsyms_token_table[kallsyms_token_index[kallsyms_names[off + 1]]];}  /* * Find the offset on the compressed stream given and index in the * kallsyms array. */static unsigned int get_symbol_offset(unsigned long pos){	const u8 *name;	int i, len; 	/*	 * Use the closest marker we have. We have markers every 256 positions,	 * so that should be close enough.	 */	name = &kallsyms_names[kallsyms_markers[pos >> 8]]; 	/*	 * Sequentially scan all the symbols up to the point we're searching	 * for. Every symbol is stored in a [<len>][<len> bytes of data] format,	 * so we just need to add the len to the current pointer for every	 * symbol we wish to skip.	 */	for (i = 0; i < (pos & 0xFF); i++) {		len = *name; 		/*		 * If MSB is 1, it is a "big" symbol, so we need to look into		 * the next byte (and skip it, too).		 */		if ((len & 0x80) != 0)			len = ((len & 0x7F) | (name[1] << 7)) + 1; 		name = name + len + 1;	} 	return name - kallsyms_names;} unsigned long kallsyms_sym_address(int idx){	/* non-relocatable 32-bit kernels just embed the value directly */	if (!IS_ENABLED(CONFIG_64BIT) && !IS_ENABLED(CONFIG_RELOCATABLE))		return (u32)kallsyms_offsets[idx];	return (unsigned long)offset_to_ptr(kallsyms_offsets + idx);} static unsigned int get_symbol_seq(int index){	unsigned int i, seq = 0; 	for (i = 0; i < 3; i++)		seq = (seq << 8) | kallsyms_seqs_of_names[3 * index + i]; 	return seq;} static int kallsyms_lookup_names(const char *name,				 unsigned int *start,				 unsigned int *end){	int ret;	int low, mid, high;	unsigned int seq, off;	char namebuf[KSYM_NAME_LEN]; 	low = 0;	high = kallsyms_num_syms - 1; 	while (low <= high) {		mid = low + (high - low) / 2;		seq = get_symbol_seq(mid);		off = get_symbol_offset(seq);		kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));		ret = strcmp(name, namebuf);		if (ret > 0)			low = mid + 1;		else if (ret < 0)			high = mid - 1;		else			break;	} 	if (low > high)		return -ESRCH; 	low = mid;	while (low) {		seq = get_symbol_seq(low - 1);		off = get_symbol_offset(seq);		kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));		if (strcmp(name, namebuf))			break;		low--;	}	*start = low; 	if (end) {		high = mid;		while (high < kallsyms_num_syms - 1) {			seq = get_symbol_seq(high + 1);			off = get_symbol_offset(seq);			kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));			if (strcmp(name, namebuf))				break;			high++;		}		*end = high;	} 	return 0;} /* Lookup the address for this symbol. Returns 0 if not found. */unsigned long kallsyms_lookup_name(const char *name){	int ret;	unsigned int i; 	/* Skip the search for empty string. */	if (!*name)		return 0; 	ret = kallsyms_lookup_names(name, &i, NULL);	if (!ret)		return kallsyms_sym_address(get_symbol_seq(i)); 	return module_kallsyms_lookup_name(name);} /* * Iterate over all symbols in vmlinux.  For symbols from modules use * module_kallsyms_on_each_symbol instead. */int kallsyms_on_each_symbol(int (*fn)(void *, const char *, unsigned long),			    void *data){	char namebuf[KSYM_NAME_LEN];	unsigned long i;	unsigned int off;	int ret; 	for (i = 0, off = 0; i < kallsyms_num_syms; i++) {		off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));		ret = fn(data, namebuf, kallsyms_sym_address(i));		if (ret != 0)			return ret;		cond_resched();	}	return 0;} int kallsyms_on_each_match_symbol(int (*fn)(void *, unsigned long),				  const char *name, void *data){	int ret;	unsigned int i, start, end; 	ret = kallsyms_lookup_names(name, &start, &end);	if (ret)		return 0; 	for (i = start; !ret && i <= end; i++) {		ret = fn(data, kallsyms_sym_address(get_symbol_seq(i)));		cond_resched();	} 	return ret;} static unsigned long get_symbol_pos(unsigned long addr,				    unsigned long *symbolsize,				    unsigned long *offset){	unsigned long symbol_start = 0, symbol_end = 0;	unsigned long i, low, high, mid; 	/* Do a binary search on the sorted kallsyms_offsets array. */	low = 0;	high = kallsyms_num_syms; 	while (high - low > 1) {		mid = low + (high - low) / 2;		if (kallsyms_sym_address(mid) <= addr)			low = mid;		else			high = mid;	} 	/*	 * Search for the first aliased symbol. Aliased	 * symbols are symbols with the same address.	 */	while (low && kallsyms_sym_address(low-1) == kallsyms_sym_address(low))		--low; 	symbol_start = kallsyms_sym_address(low); 	/* Search for next non-aliased symbol. */	for (i = low + 1; i < kallsyms_num_syms; i++) {		if (kallsyms_sym_address(i) > symbol_start) {			symbol_end = kallsyms_sym_address(i);			break;		}	} 	/* If we found no next symbol, we use the end of the section. */	if (!symbol_end) {		if (is_kernel_inittext(addr))			symbol_end = (unsigned long)_einittext;		else if (IS_ENABLED(CONFIG_KALLSYMS_ALL))			symbol_end = (unsigned long)_end;		else			symbol_end = (unsigned long)_etext;	} 	if (symbolsize)		*symbolsize = symbol_end - symbol_start;	if (offset)		*offset = addr - symbol_start; 	return low;} /* * Lookup an address but don't bother to find any names. */int kallsyms_lookup_size_offset(unsigned long addr, unsigned long *symbolsize,				unsigned long *offset){	char namebuf[KSYM_NAME_LEN]; 	if (is_ksym_addr(addr)) {		get_symbol_pos(addr, symbolsize, offset);		return 1;	}	return !!module_address_lookup(addr, symbolsize, offset, NULL, NULL, namebuf) ||	       !!bpf_address_lookup(addr, symbolsize, offset, namebuf);} static int kallsyms_lookup_buildid(unsigned long addr,			unsigned long *symbolsize,			unsigned long *offset, char **modname,			const unsigned char **modbuildid, char *namebuf){	int ret; 	/*	 * kallsyms_lookus() returns pointer to namebuf on success and	 * NULL on error. But some callers ignore the return value.	 * Instead they expect @namebuf filled either with valid	 * or empty string.	 */	namebuf[0] = 0;	/*	 * Initialize the module-related return values. They are not set	 * when the symbol is in vmlinux or it is a bpf address.	 */	if (modname)		*modname = NULL;	if (modbuildid)		*modbuildid = NULL; 	if (is_ksym_addr(addr)) {		unsigned long pos; 		pos = get_symbol_pos(addr, symbolsize, offset);		/* Grab name */		kallsyms_expand_symbol(get_symbol_offset(pos),				       namebuf, KSYM_NAME_LEN); 		return strlen(namebuf);	} 	/* See if it's in a module or a BPF JITed image. */	ret = module_address_lookup(addr, symbolsize, offset,				    modname, modbuildid, namebuf);	if (!ret)		ret = bpf_address_lookup(addr, symbolsize, offset, namebuf); 	if (!ret)		ret = ftrace_mod_address_lookup(addr, symbolsize, offset,						modname, modbuildid, namebuf); 	return ret;} /* * Lookup an address * - modname is set to NULL if it's in the kernel. * - We guarantee that the returned name is valid until we reschedule even if. *   It resides in a module. * - We also guarantee that modname will be valid until rescheduled. */const char *kallsyms_lookup(unsigned long addr,			    unsigned long *symbolsize,			    unsigned long *offset,			    char **modname, char *namebuf){	int ret = kallsyms_lookup_buildid(addr, symbolsize, offset, modname,					  NULL, namebuf); 	if (!ret)		return NULL; 	return namebuf;} int lookup_symbol_name(unsigned long addr, char *symname){	symname[0] = '\0';	symname[KSYM_NAME_LEN - 1] = '\0'; 	if (is_ksym_addr(addr)) {		unsigned long pos; 		pos = get_symbol_pos(addr, NULL, NULL);		/* Grab name */		kallsyms_expand_symbol(get_symbol_offset(pos),				       symname, KSYM_NAME_LEN);		return 0;	}	/* See if it's in a module. */	return lookup_module_symbol_name(addr, symname);} #ifdef CONFIG_STACKTRACE_BUILD_ID static int append_buildid(char *buffer,  const char *modname,			  const unsigned char *buildid){	if (!modname)		return 0; 	if (!buildid) {		pr_warn_once("Undefined buildid for the module %s\n", modname);		return 0;	} 	/* build ID should match length of sprintf */#ifdef CONFIG_MODULES	static_assert(sizeof(typeof_member(struct module, build_id)) == 20);#endif 	return sprintf(buffer, " %20phN", buildid);} #else /* CONFIG_STACKTRACE_BUILD_ID */ static int append_buildid(char *buffer,   const char *modname,			  const unsigned char *buildid){	return 0;} #endif /* CONFIG_STACKTRACE_BUILD_ID */ /* Look up a kernel symbol and return it in a text buffer. */static int __sprint_symbol(char *buffer, unsigned long address,			   int symbol_offset, int add_offset, int add_buildid){	char *modname;	const unsigned char *buildid;	unsigned long offset, size;	int len; 	/* Prevent module removal until modname and modbuildid are printed */	guard(rcu)(); 	address += symbol_offset;	len = kallsyms_lookup_buildid(address, &size, &offset, &modname, &buildid,				       buffer);	if (!len)		return sprintf(buffer, "0x%lx", address - symbol_offset); 	offset -= symbol_offset; 	if (add_offset)		len += sprintf(buffer + len, "+%#lx/%#lx", offset, size); 	if (modname) {		len += sprintf(buffer + len, " [%s", modname);		if (add_buildid)			len += append_buildid(buffer + len, modname, buildid);		len += sprintf(buffer + len, "]");	} 	return len;

Lines 1–500 of 937.