919 lines · 21.1 KB · C
RawDownload
/* Generate assembler source containing symbol information * * Copyright 2002       by Kai Germaschewski * * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * * Usage: kallsyms [--all-symbols] [--absolute-percpu] *                         [--base-relative] in.map > out.S * *      Table compression uses all the unused char codes on the symbols and *  maps these to the most used substrings (tokens). For instance, it might *  map char code 0xF7 to represent "write_" and then in every symbol where *  "write_" appears it can be replaced by 0xF7, saving 5 bytes. *      The used codes themselves are also placed in the table so that the *  decompresion can work without "special cases". *      Applied to kernel symbols, this usually produces a compression ratio *  of about 50%. * */ #include <getopt.h>#include <stdbool.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <limits.h> #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) #define _stringify_1(x)	#x#define _stringify(x)	_stringify_1(x) #define KSYM_NAME_LEN		512 /* * A substantially bigger size than the current maximum. * * It cannot be defined as an expression because it gets stringified * for the fscanf() format string. Therefore, a _Static_assert() is * used instead to maintain the relationship with KSYM_NAME_LEN. */#define KSYM_NAME_LEN_BUFFER	2048_Static_assert(	KSYM_NAME_LEN_BUFFER == KSYM_NAME_LEN * 4,	"Please keep KSYM_NAME_LEN_BUFFER in sync with KSYM_NAME_LEN"); struct sym_entry {	unsigned long long addr;	unsigned int len;	unsigned int seq;	unsigned int start_pos;	unsigned int percpu_absolute;	unsigned char sym[];}; struct addr_range {	const char *start_sym, *end_sym;	unsigned long long start, end;}; static unsigned long long _text;static unsigned long long relative_base;static struct addr_range text_ranges[] = {	{ "_stext",     "_etext"     },	{ "_sinittext", "_einittext" },};#define text_range_text     (&text_ranges[0])#define text_range_inittext (&text_ranges[1]) static struct addr_range percpu_range = {	"__per_cpu_start", "__per_cpu_end", -1ULL, 0}; static struct sym_entry **table;static unsigned int table_size, table_cnt;static int all_symbols;static int absolute_percpu;static int base_relative;static int lto_clang; static int token_profit[0x10000]; /* the table that holds the result of the compression */static unsigned char best_table[256][2];static unsigned char best_table_len[256];  static void usage(void){	fprintf(stderr, "Usage: kallsyms [--all-symbols] [--absolute-percpu] "			"[--base-relative] [--lto-clang] in.map > out.S\n");	exit(1);} static char *sym_name(const struct sym_entry *s){	return (char *)s->sym + 1;} static bool is_ignored_symbol(const char *name, char type){	/* Symbol names that exactly match to the following are ignored.*/	static const char * const ignored_symbols[] = {		/*		 * Symbols which vary between passes. Passes 1 and 2 must have		 * identical symbol lists. The kallsyms_* symbols below are		 * only added after pass 1, they would be included in pass 2		 * when --all-symbols is specified so exclude them to get a		 * stable symbol list.		 */		"kallsyms_addresses",		"kallsyms_offsets",		"kallsyms_relative_base",		"kallsyms_num_syms",		"kallsyms_names",		"kallsyms_markers",		"kallsyms_token_table",		"kallsyms_token_index",		/* Exclude linker generated symbols which vary between passes */		"_SDA_BASE_",		/* ppc */		"_SDA2_BASE_",		/* ppc */		NULL	}; 	/* Symbol names that begin with the following are ignored.*/	static const char * const ignored_prefixes[] = {		"__efistub_",		/* arm64 EFI stub namespace */		"__kvm_nvhe_$",		/* arm64 local symbols in non-VHE KVM namespace */		"__kvm_nvhe_.L",	/* arm64 local symbols in non-VHE KVM namespace */		"__AArch64ADRPThunk_",	/* arm64 lld */		"__ARMV5PILongThunk_",	/* arm lld */		"__ARMV7PILongThunk_",		"__ThumbV7PILongThunk_",		"__LA25Thunk_",		/* mips lld */		"__microLA25Thunk_",		"__kcfi_typeid_",	/* CFI type identifiers */		NULL	}; 	/* Symbol names that end with the following are ignored.*/	static const char * const ignored_suffixes[] = {		"_from_arm",		/* arm */		"_from_thumb",		/* arm */		"_veneer",		/* arm */		NULL	}; 	/* Symbol names that contain the following are ignored.*/	static const char * const ignored_matches[] = {		".long_branch.",	/* ppc stub */		".plt_branch.",		/* ppc stub */		NULL	}; 	const char * const *p; 	for (p = ignored_symbols; *p; p++)		if (!strcmp(name, *p))			return true; 	for (p = ignored_prefixes; *p; p++)		if (!strncmp(name, *p, strlen(*p)))			return true; 	for (p = ignored_suffixes; *p; p++) {		int l = strlen(name) - strlen(*p); 		if (l >= 0 && !strcmp(name + l, *p))			return true;	} 	for (p = ignored_matches; *p; p++) {		if (strstr(name, *p))			return true;	} 	if (type == 'U' || type == 'u')		return true;	/* exclude debugging symbols */	if (type == 'N' || type == 'n')		return true; 	if (toupper(type) == 'A') {		/* Keep these useful absolute symbols */		if (strcmp(name, "__kernel_syscall_via_break") &&		    strcmp(name, "__kernel_syscall_via_epc") &&		    strcmp(name, "__kernel_sigtramp") &&		    strcmp(name, "__gp"))			return true;	} 	return false;} static void check_symbol_range(const char *sym, unsigned long long addr,			       struct addr_range *ranges, int entries){	size_t i;	struct addr_range *ar; 	for (i = 0; i < entries; ++i) {		ar = &ranges[i]; 		if (strcmp(sym, ar->start_sym) == 0) {			ar->start = addr;			return;		} else if (strcmp(sym, ar->end_sym) == 0) {			ar->end = addr;			return;		}	}} static struct sym_entry *read_symbol(FILE *in){	char name[KSYM_NAME_LEN_BUFFER+1], type;	unsigned long long addr;	unsigned int len;	struct sym_entry *sym;	int rc; 	rc = fscanf(in, "%llx %c %" _stringify(KSYM_NAME_LEN_BUFFER) "s\n", &addr, &type, name);	if (rc != 3) {		if (rc != EOF && fgets(name, ARRAY_SIZE(name), in) == NULL)			fprintf(stderr, "Read error or end of file.\n");		return NULL;	}	if (strlen(name) >= KSYM_NAME_LEN) {		fprintf(stderr, "Symbol %s too long for kallsyms (%zu >= %d).\n"				"Please increase KSYM_NAME_LEN both in kernel and kallsyms.c\n",			name, strlen(name), KSYM_NAME_LEN);		return NULL;	} 	if (strcmp(name, "_text") == 0)		_text = addr; 	/* Ignore most absolute/undefined (?) symbols. */	if (is_ignored_symbol(name, type))		return NULL; 	check_symbol_range(name, addr, text_ranges, ARRAY_SIZE(text_ranges));	check_symbol_range(name, addr, &percpu_range, 1); 	/* include the type field in the symbol name, so that it gets	 * compressed together */ 	len = strlen(name) + 1; 	sym = malloc(sizeof(*sym) + len + 1);	if (!sym) {		fprintf(stderr, "kallsyms failure: "			"unable to allocate required amount of memory\n");		exit(EXIT_FAILURE);	}	sym->addr = addr;	sym->len = len;	sym->sym[0] = type;	strcpy(sym_name(sym), name);	sym->percpu_absolute = 0; 	return sym;} static int symbol_in_range(const struct sym_entry *s,			   const struct addr_range *ranges, int entries){	size_t i;	const struct addr_range *ar; 	for (i = 0; i < entries; ++i) {		ar = &ranges[i]; 		if (s->addr >= ar->start && s->addr <= ar->end)			return 1;	} 	return 0;} static int symbol_valid(const struct sym_entry *s){	const char *name = sym_name(s); 	/* if --all-symbols is not specified, then symbols outside the text	 * and inittext sections are discarded */	if (!all_symbols) {		if (symbol_in_range(s, text_ranges,				    ARRAY_SIZE(text_ranges)) == 0)			return 0;		/* Corner case.  Discard any symbols with the same value as		 * _etext _einittext; they can move between pass 1 and 2 when		 * the kallsyms data are added.  If these symbols move then		 * they may get dropped in pass 2, which breaks the kallsyms		 * rules.		 */		if ((s->addr == text_range_text->end &&		     strcmp(name, text_range_text->end_sym)) ||		    (s->addr == text_range_inittext->end &&		     strcmp(name, text_range_inittext->end_sym)))			return 0;	} 	return 1;} /* remove all the invalid symbols from the table */static void shrink_table(void){	unsigned int i, pos; 	pos = 0;	for (i = 0; i < table_cnt; i++) {		if (symbol_valid(table[i])) {			if (pos != i)				table[pos] = table[i];			pos++;		} else {			free(table[i]);		}	}	table_cnt = pos; 	/* When valid symbol is not registered, exit to error */	if (!table_cnt) {		fprintf(stderr, "No valid symbol.\n");		exit(1);	}} static void read_map(const char *in){	FILE *fp;	struct sym_entry *sym; 	fp = fopen(in, "r");	if (!fp) {		perror(in);		exit(1);	} 	while (!feof(fp)) {		sym = read_symbol(fp);		if (!sym)			continue; 		sym->start_pos = table_cnt; 		if (table_cnt >= table_size) {			table_size += 10000;			table = realloc(table, sizeof(*table) * table_size);			if (!table) {				fprintf(stderr, "out of memory\n");				fclose(fp);				exit (1);			}		} 		table[table_cnt++] = sym;	} 	fclose(fp);} static void output_label(const char *label){	printf(".globl %s\n", label);	printf("\tALGN\n");	printf("%s:\n", label);} /* Provide proper symbols relocatability by their '_text' relativeness. */static void output_address(unsigned long long addr){	if (_text <= addr)		printf("\tPTR\t_text + %#llx\n", addr - _text);	else		printf("\tPTR\t_text - %#llx\n", _text - addr);} /* uncompress a compressed symbol. When this function is called, the best table * might still be compressed itself, so the function needs to be recursive */static int expand_symbol(const unsigned char *data, int len, char *result){	int c, rlen, total=0; 	while (len) {		c = *data;		/* if the table holds a single char that is the same as the one		 * we are looking for, then end the search */		if (best_table[c][0]==c && best_table_len[c]==1) {			*result++ = c;			total++;		} else {			/* if not, recurse and expand */			rlen = expand_symbol(best_table[c], best_table_len[c], result);			total += rlen;			result += rlen;		}		data++;		len--;	}	*result=0; 	return total;} static int symbol_absolute(const struct sym_entry *s){	return s->percpu_absolute;} static char * s_name(char *buf){	/* Skip the symbol type */	return buf + 1;} static void cleanup_symbol_name(char *s){	char *p; 	if (!lto_clang)		return; 	/*	 * ASCII[.]   = 2e	 * ASCII[0-9] = 30,39	 * ASCII[A-Z] = 41,5a	 * ASCII[_]   = 5f	 * ASCII[a-z] = 61,7a	 *	 * As above, replacing '.' with '\0' does not affect the main sorting,	 * but it helps us with subsorting.	 */	p = strchr(s, '.');	if (p)		*p = '\0';} static int compare_names(const void *a, const void *b){	int ret;	char sa_namebuf[KSYM_NAME_LEN];	char sb_namebuf[KSYM_NAME_LEN];	const struct sym_entry *sa = *(const struct sym_entry **)a;	const struct sym_entry *sb = *(const struct sym_entry **)b; 	expand_symbol(sa->sym, sa->len, sa_namebuf);	expand_symbol(sb->sym, sb->len, sb_namebuf);	cleanup_symbol_name(s_name(sa_namebuf));	cleanup_symbol_name(s_name(sb_namebuf));	ret = strcmp(s_name(sa_namebuf), s_name(sb_namebuf));	if (!ret) {		if (sa->addr > sb->addr)			return 1;		else if (sa->addr < sb->addr)			return -1; 		/* keep old order */		return (int)(sa->seq - sb->seq);	} 	return ret;} static void sort_symbols_by_name(void){	qsort(table, table_cnt, sizeof(table[0]), compare_names);} static void write_src(void){	unsigned int i, k, off;	unsigned int best_idx[256];	unsigned int *markers;	char buf[KSYM_NAME_LEN]; 	printf("#include <asm/bitsperlong.h>\n");	printf("#if BITS_PER_LONG == 64\n");	printf("#define PTR .quad\n");	printf("#define ALGN .balign 8\n");	printf("#else\n");	printf("#define PTR .long\n");	printf("#define ALGN .balign 4\n");	printf("#endif\n"); 	printf("\t.section .rodata, \"a\"\n"); 	if (!base_relative)		output_label("kallsyms_addresses");	else		output_label("kallsyms_offsets"); 	for (i = 0; i < table_cnt; i++) {		if (base_relative) {			/*

Lines 1–500 of 919.