268 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "elflib.h"
 | |
| 
 | |
| #define PRINTF __attribute__ ((format (printf, 1, 2)))
 | |
| 
 | |
| PRINTF void fatal(const char *fmt, ...)
 | |
| {
 | |
| 	va_list arglist;
 | |
| 
 | |
| 	fprintf(stderr, "FATAL: ");
 | |
| 
 | |
| 	va_start(arglist, fmt);
 | |
| 	vfprintf(stderr, fmt, arglist);
 | |
| 	va_end(arglist);
 | |
| 
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| PRINTF void warn(const char *fmt, ...)
 | |
| {
 | |
| 	va_list arglist;
 | |
| 
 | |
| 	fprintf(stderr, "WARNING: ");
 | |
| 
 | |
| 	va_start(arglist, fmt);
 | |
| 	vfprintf(stderr, fmt, arglist);
 | |
| 	va_end(arglist);
 | |
| }
 | |
| 
 | |
| PRINTF void merror(const char *fmt, ...)
 | |
| {
 | |
| 	va_list arglist;
 | |
| 
 | |
| 	fprintf(stderr, "ERROR: ");
 | |
| 
 | |
| 	va_start(arglist, fmt);
 | |
| 	vfprintf(stderr, fmt, arglist);
 | |
| 	va_end(arglist);
 | |
| }
 | |
| 
 | |
| static void *grab_file_rw(const char *filename, unsigned long *size,
 | |
| 			  unsigned char write)
 | |
| {
 | |
| 	struct stat st;
 | |
| 	void *map;
 | |
| 	int fd, open_flag, map_flag;
 | |
| 
 | |
| 	if (write) {
 | |
| 		open_flag = O_RDWR;
 | |
| 		map_flag = MAP_SHARED;
 | |
| 	} else {
 | |
| 		open_flag = O_RDONLY;
 | |
| 		map_flag = MAP_PRIVATE;
 | |
| 	}
 | |
| 
 | |
| 	fd = open(filename, open_flag);
 | |
| 	if (fd < 0 || fstat(fd, &st) != 0)
 | |
| 		return NULL;
 | |
| 
 | |
| 	*size = st.st_size;
 | |
| 	map = mmap(NULL, *size, PROT_READ|PROT_WRITE, map_flag, fd, 0);
 | |
| 	close(fd);
 | |
| 
 | |
| 	if (map == MAP_FAILED)
 | |
| 		return NULL;
 | |
| 	return map;
 | |
| }
 | |
| 
 | |
| static void set_ksymtable(struct elf_info *info, enum ksymtab_type type,
 | |
| 			  Elf_Ehdr *hdr, Elf_Shdr *sechdrs, unsigned int secidx,
 | |
| 			  const char *secname)
 | |
| {
 | |
| 
 | |
| 	info->ksym_tables[type].start = (struct kernel_symbol *) \
 | |
| 			((void *) hdr + sechdrs[secidx].sh_offset);
 | |
| 	info->ksym_tables[type].stop = (struct kernel_symbol *) \
 | |
| 	((void *) hdr + sechdrs[secidx].sh_offset + sechdrs[secidx].sh_size);
 | |
| 	info->ksym_tables[type].name = strdup(secname);
 | |
| 	info->ksym_tables[type].sec = secidx;
 | |
| }
 | |
| 
 | |
| static int parse_elf_rw(struct elf_info *info, const char *filename,
 | |
| 			unsigned char write)
 | |
| {
 | |
| 	unsigned int i;
 | |
| 	Elf_Ehdr *hdr;
 | |
| 	Elf_Shdr *sechdrs;
 | |
| 	Elf_Sym  *sym;
 | |
| 	char *lkm_suffix;
 | |
| 
 | |
| 	hdr = grab_file_rw(filename, &info->size, write);
 | |
| 	if (!hdr) {
 | |
| 		perror(filename);
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	info->hdr = hdr;
 | |
| 	if (info->size < sizeof(*hdr)) {
 | |
| 		/* file too small, assume this is an empty .o file */
 | |
| 		return 0;
 | |
| 	}
 | |
| 	/* Is this a valid ELF file? */
 | |
| 	if ((hdr->e_ident[EI_MAG0] != ELFMAG0) ||
 | |
| 	    (hdr->e_ident[EI_MAG1] != ELFMAG1) ||
 | |
| 	    (hdr->e_ident[EI_MAG2] != ELFMAG2) ||
 | |
| 	    (hdr->e_ident[EI_MAG3] != ELFMAG3)) {
 | |
| 		/* Not an ELF file - silently ignore it */
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/* Fix endianness in ELF header */
 | |
| 	hdr->e_type      = TO_NATIVE(hdr->e_type);
 | |
| 	hdr->e_machine   = TO_NATIVE(hdr->e_machine);
 | |
| 	hdr->e_version   = TO_NATIVE(hdr->e_version);
 | |
| 	hdr->e_entry     = TO_NATIVE(hdr->e_entry);
 | |
| 	hdr->e_phoff     = TO_NATIVE(hdr->e_phoff);
 | |
| 	hdr->e_shoff     = TO_NATIVE(hdr->e_shoff);
 | |
| 	hdr->e_flags     = TO_NATIVE(hdr->e_flags);
 | |
| 	hdr->e_ehsize    = TO_NATIVE(hdr->e_ehsize);
 | |
| 	hdr->e_phentsize = TO_NATIVE(hdr->e_phentsize);
 | |
| 	hdr->e_phnum     = TO_NATIVE(hdr->e_phnum);
 | |
| 	hdr->e_shentsize = TO_NATIVE(hdr->e_shentsize);
 | |
| 	hdr->e_shnum     = TO_NATIVE(hdr->e_shnum);
 | |
| 	hdr->e_shstrndx  = TO_NATIVE(hdr->e_shstrndx);
 | |
| 	sechdrs = (void *)hdr + hdr->e_shoff;
 | |
| 	info->sechdrs = sechdrs;
 | |
| 
 | |
| 	/* Check if file offset is correct */
 | |
| 	if (hdr->e_shoff > info->size) {
 | |
| 		fatal("section header offset=%lu in file '%s' is bigger than "
 | |
| 		      "filesize=%lu\n", (unsigned long)hdr->e_shoff,
 | |
| 		      filename, info->size);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/* Fix endianness in section headers */
 | |
| 	for (i = 0; i < hdr->e_shnum; i++) {
 | |
| 		sechdrs[i].sh_name      = TO_NATIVE(sechdrs[i].sh_name);
 | |
| 		sechdrs[i].sh_type      = TO_NATIVE(sechdrs[i].sh_type);
 | |
| 		sechdrs[i].sh_flags     = TO_NATIVE(sechdrs[i].sh_flags);
 | |
| 		sechdrs[i].sh_addr      = TO_NATIVE(sechdrs[i].sh_addr);
 | |
| 		sechdrs[i].sh_offset    = TO_NATIVE(sechdrs[i].sh_offset);
 | |
| 		sechdrs[i].sh_size      = TO_NATIVE(sechdrs[i].sh_size);
 | |
| 		sechdrs[i].sh_link      = TO_NATIVE(sechdrs[i].sh_link);
 | |
| 		sechdrs[i].sh_info      = TO_NATIVE(sechdrs[i].sh_info);
 | |
| 		sechdrs[i].sh_addralign = TO_NATIVE(sechdrs[i].sh_addralign);
 | |
| 		sechdrs[i].sh_entsize   = TO_NATIVE(sechdrs[i].sh_entsize);
 | |
| 	}
 | |
| 	/* Find symbol tables and text section. */
 | |
| 	for (i = 1; i < hdr->e_shnum; i++) {
 | |
| 		const char *secstrings
 | |
| 			= (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
 | |
| 		const char *secname;
 | |
| 		int nobits = sechdrs[i].sh_type == SHT_NOBITS;
 | |
| 
 | |
| 		if (!nobits && sechdrs[i].sh_offset > info->size) {
 | |
| 			fatal("%s is truncated. sechdrs[i].sh_offset=%lu > "
 | |
| 			      "sizeof(*hrd)=%zu\n", filename,
 | |
| 			      (unsigned long)sechdrs[i].sh_offset,
 | |
| 			      sizeof(*hdr));
 | |
| 			return 0;
 | |
| 		}
 | |
| 		secname = secstrings + sechdrs[i].sh_name;
 | |
| 
 | |
| 		if (strcmp(secname, ".text") == 0)
 | |
| 			info->base_addr = sechdrs[i].sh_addr -
 | |
| 					  sechdrs[i].sh_offset;
 | |
| 		else if (strcmp(secname, ".modinfo") == 0) {
 | |
| 			if (nobits)
 | |
| 				fatal("%s has NOBITS .modinfo\n", filename);
 | |
| 			info->modinfo = (void *)hdr + sechdrs[i].sh_offset;
 | |
| 			info->modinfo_len = sechdrs[i].sh_size;
 | |
| 		} else if (strcmp(secname, "__ksymtab") == 0)
 | |
| 			set_ksymtable(info, KSYMTAB, hdr, sechdrs, i, secname);
 | |
| 		else if (strcmp(secname, "__ksymtab_unused") == 0)
 | |
| 			set_ksymtable(info, KSYMTAB_UNUSED, hdr, sechdrs, i,
 | |
| 				      secname);
 | |
| 		else if (strcmp(secname, "__ksymtab_gpl") == 0)
 | |
| 			set_ksymtable(info, KSYMTAB_GPL, hdr, sechdrs, i,
 | |
| 				      secname);
 | |
| 		else if (strcmp(secname, "__ksymtab_unused_gpl") == 0)
 | |
| 			set_ksymtable(info, KSYMTAB_UNUSED_GPL, hdr, sechdrs, i,
 | |
| 				      secname);
 | |
| 		else if (strcmp(secname, "__ksymtab_gpl_future") == 0)
 | |
| 			set_ksymtable(info, KSYMTAB_GPL_FUTURE, hdr, sechdrs, i,
 | |
| 				      secname);
 | |
| 		else if (strcmp(secname, "__ksymtab_strings") == 0)
 | |
| 			info->kstrings = (void *)hdr + sechdrs[i].sh_offset;
 | |
| 		else if (strcmp(secname, "__markers_strings") == 0)
 | |
| 			info->markers_strings_sec = i;
 | |
| 		else if (strcmp(secname, ".undef.hash") == 0) {
 | |
| 			info->undef_hash.start = (void *)
 | |
| 			    hdr + sechdrs[i].sh_offset;
 | |
| 			info->undef_hash.stop = (void *)
 | |
| 			    hdr + sechdrs[i].sh_offset + sechdrs[i].sh_size;
 | |
| 		}
 | |
| 
 | |
| 		if (sechdrs[i].sh_type != SHT_SYMTAB)
 | |
| 			continue;
 | |
| 
 | |
| 		info->symtab_start = (void *)hdr + sechdrs[i].sh_offset;
 | |
| 		info->symtab_stop  = (void *)hdr + sechdrs[i].sh_offset
 | |
| 					+ sechdrs[i].sh_size;
 | |
| 		info->strtab       = (void *)hdr +
 | |
| 					sechdrs[sechdrs[i].sh_link].sh_offset;
 | |
| 	}
 | |
| 	if (!info->symtab_start)
 | |
| 		fatal("%s has no symtab?\n", filename);
 | |
| 
 | |
| 	/* Check if it is the vmlinux or lkm */
 | |
| 	lkm_suffix = strstr(filename, ".ko");
 | |
| 	if (lkm_suffix && (strlen(lkm_suffix) == 3))
 | |
| 		/* Likely this is a lkm */
 | |
| 		/*
 | |
| 		 * ksym->name is an offset with respect to the start of the
 | |
| 		 *  __ksymtab_strings
 | |
| 		 */
 | |
| 		info->kstr_offset = (long) info->kstrings;
 | |
| 	else
 | |
| 		/*
 | |
| 		 * In this case, ksym->name is the absolute value of the string
 | |
| 		 * into the __ksymtab_strings
 | |
| 		 */
 | |
| 		 info->kstr_offset = (long)info->hdr - (long)info->base_addr;
 | |
| 
 | |
| 	/* Fix endianness in symbols */
 | |
| 	for (sym = info->symtab_start; sym < info->symtab_stop; sym++) {
 | |
| 		sym->st_shndx = TO_NATIVE(sym->st_shndx);
 | |
| 		sym->st_name  = TO_NATIVE(sym->st_name);
 | |
| 		sym->st_value = TO_NATIVE(sym->st_value);
 | |
| 		sym->st_size  = TO_NATIVE(sym->st_size);
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| ksym_hash_t gnu_hash(const unsigned char *name)
 | |
| {
 | |
| 	ksym_hash_t h = 5381;
 | |
| 	unsigned char c;
 | |
| 	for (c = *name; c != '\0'; c = *++name)
 | |
| 		h = h * 33 + c;
 | |
| 	return h & 0xffffffff;
 | |
| }
 | |
| void *grab_file(const char *filename, unsigned long *size)
 | |
| {
 | |
| 	/* Read-only */
 | |
| 	return grab_file_rw(filename, size, 0);
 | |
| }
 | |
| 
 | |
| void release_file(void *file, unsigned long size)
 | |
| {
 | |
| 	munmap(file, size);
 | |
| }
 | |
| 
 | |
| int parse_elf(struct elf_info *info, const char *filename)
 | |
| {
 | |
| 	/* Read-only */
 | |
| 	return parse_elf_rw(info, filename, 0);
 | |
| }
 | |
| 
 | |
| int parse_writable_elf(struct elf_info *info, const char *filename)
 | |
| {
 | |
| 	return parse_elf_rw(info, filename, 1);
 | |
| }
 | |
| 
 | |
| void parse_elf_finish(struct elf_info *info)
 | |
| {
 | |
| 	release_file(info->hdr, info->size);
 | |
| }
 |