Skip to content
Snippets Groups Projects
modpost.c 56.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /* Postprocess module symbol versions
     *
     * Copyright 2003       Kai Germaschewski
     * Copyright 2002-2004  Rusty Russell, IBM Corporation
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
     * Copyright 2006-2008  Sam Ravnborg
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * Based in part on module-init-tools/depmod.c,file2alias
     *
     * This software may be used and distributed according to the terms
     * of the GNU General Public License, incorporated herein by reference.
     *
     * Usage: modpost vmlinux module1.o module2.o ...
     */
    
    
    #define _GNU_SOURCE
    #include <stdio.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <ctype.h>
    
    #include <string.h>
    
    Rusty Russell's avatar
    Rusty Russell committed
    #include <stdbool.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include "modpost.h"
    
    #include "../../include/generated/autoconf.h"
    
    #include "../../include/linux/license.h"
    
    #include "../../include/linux/export.h"
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* Are we using CONFIG_MODVERSIONS? */
    int modversions = 0;
    /* Warn about undefined symbols? (do so if we have vmlinux) */
    int have_vmlinux = 0;
    /* Is CONFIG_MODULE_SRCVERSION_ALL set? */
    static int all_versions = 0;
    
    /* If we are modposting external module set to 1 */
    static int external_module = 0;
    
    /* Warn about section mismatch in vmlinux if set to 1 */
    static int vmlinux_section_warnings = 1;
    
    /* Only warn about unresolved symbols */
    static int warn_unresolved = 0;
    
    /* How a symbol is exported */
    
    static int sec_mismatch_count = 0;
    static int sec_mismatch_verbose = 1;
    
    /* ignore missing files */
    static int ignore_missing_files;
    
    enum export {
    	export_plain,      export_unused,     export_gpl,
    	export_unused_gpl, export_gpl_future, export_unknown
    };
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #define PRINTF __attribute__ ((format (printf, 1, 2)))
    
    PRINTF void fatal(const char *fmt, ...)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	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, ...)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	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);
    }
    
    
    Rusty Russell's avatar
    Rusty Russell committed
    static inline bool strends(const char *str, const char *postfix)
    {
    	if (strlen(str) < strlen(postfix))
    		return false;
    
    	return strcmp(str + strlen(str) - strlen(postfix), postfix) == 0;
    }
    
    
    static int is_vmlinux(const char *modname)
    {
    	const char *myname;
    
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	myname = strrchr(modname, '/');
    	if (myname)
    
    	return (strcmp(myname, "vmlinux") == 0) ||
    	       (strcmp(myname, "vmlinux.o") == 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    void *do_nofail(void *ptr, const char *expr)
    {
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	if (!ptr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		fatal("modpost: Memory allocation failure: %s.\n", expr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return ptr;
    }
    
    /* A list of all modules we processed */
    static struct module *modules;
    
    
    static struct module *find_module(char *modname)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct module *mod;
    
    	for (mod = modules; mod; mod = mod->next)
    		if (strcmp(mod->name, modname) == 0)
    			break;
    	return mod;
    }
    
    
    Rusty Russell's avatar
    Rusty Russell committed
    static struct module *new_module(const char *modname)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct module *mod;
    
    Rusty Russell's avatar
    Rusty Russell committed
    	char *p;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	mod = NOFAIL(malloc(sizeof(*mod)));
    	memset(mod, 0, sizeof(*mod));
    	p = NOFAIL(strdup(modname));
    
    	/* strip trailing .o */
    
    Rusty Russell's avatar
    Rusty Russell committed
    	if (strends(p, ".o")) {
    		p[strlen(p) - 2] = '\0';
    		mod->is_dot_o = 1;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* add to list */
    	mod->name = p;
    
    	mod->gpl_compatible = -1;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	mod->next = modules;
    	modules = mod;
    
    	return mod;
    }
    
    /* A hash of all exported symbols,
     * struct symbol is also used for lists of unresolved symbols */
    
    #define SYMBOL_HASH_SIZE 1024
    
    struct symbol {
    	struct symbol *next;
    	struct module *module;
    	unsigned int crc;
    	int crc_valid;
    	unsigned int weak:1;
    
    	unsigned int vmlinux:1;    /* 1 if symbol is defined in vmlinux */
    	unsigned int kernel:1;     /* 1 if symbol is from kernel
    				    *  (only for external modules) **/
    
    	unsigned int preloaded:1;  /* 1 if symbol from Module.symvers, or crc */
    
    	enum export  export;       /* Type of export */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	char name[0];
    };
    
    static struct symbol *symbolhash[SYMBOL_HASH_SIZE];
    
    /* This is based on the hash agorithm from gdbm, via tdb */
    static inline unsigned int tdb_hash(const char *name)
    {
    	unsigned value;	/* Used to compute the hash value.  */
    	unsigned   i;	/* Used to cycle through random values. */
    
    	/* Set the initial value from the key size. */
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	for (value = 0x238F13AF * strlen(name), i = 0; name[i]; i++)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		value = (value + (((unsigned char *)name)[i] << (i*5 % 24)));
    
    	return (1103515243 * value + 12345);
    }
    
    
    /**
     * Allocate a new symbols for use in the hash of exported symbols or
     * the list of unresolved symbols per module
     **/
    static struct symbol *alloc_symbol(const char *name, unsigned int weak,
    				   struct symbol *next)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct symbol *s = NOFAIL(malloc(sizeof(*s) + strlen(name) + 1));
    
    	memset(s, 0, sizeof(*s));
    	strcpy(s->name, name);
    	s->weak = weak;
    	s->next = next;
    	return s;
    }
    
    /* For the hash of exported symbols */
    
    static struct symbol *new_symbol(const char *name, struct module *module,
    				 enum export export)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned int hash;
    	struct symbol *new;
    
    	hash = tdb_hash(name) % SYMBOL_HASH_SIZE;
    	new = symbolhash[hash] = alloc_symbol(name, 0, symbolhash[hash]);
    	new->module = module;
    
    	new->export = export;
    
    static struct symbol *find_symbol(const char *name)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct symbol *s;
    
    	/* For our purposes, .foo matches foo.  PPC64 needs this. */
    	if (name[0] == '.')
    		name++;
    
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	for (s = symbolhash[tdb_hash(name) % SYMBOL_HASH_SIZE]; s; s = s->next) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (strcmp(s->name, name) == 0)
    			return s;
    	}
    	return NULL;
    }
    
    
    static struct {
    	const char *str;
    	enum export export;
    } export_list[] = {
    	{ .str = "EXPORT_SYMBOL",            .export = export_plain },
    
    	{ .str = "EXPORT_UNUSED_SYMBOL",     .export = export_unused },
    
    	{ .str = "EXPORT_SYMBOL_GPL",        .export = export_gpl },
    
    	{ .str = "EXPORT_UNUSED_SYMBOL_GPL", .export = export_unused_gpl },
    
    	{ .str = "EXPORT_SYMBOL_GPL_FUTURE", .export = export_gpl_future },
    	{ .str = "(unknown)",                .export = export_unknown },
    };
    
    
    static const char *export_str(enum export ex)
    {
    	return export_list[ex].str;
    }
    
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    static enum export export_no(const char *s)
    
    	if (!s)
    		return export_unknown;
    
    	for (i = 0; export_list[i].export != export_unknown; i++) {
    		if (strcmp(export_list[i].str, s) == 0)
    			return export_list[i].export;
    	}
    	return export_unknown;
    }
    
    
    static const char *sec_name(struct elf_info *elf, int secindex);
    
    #define strstarts(str, prefix) (strncmp(str, prefix, strlen(prefix)) == 0)
    
    static enum export export_from_secname(struct elf_info *elf, unsigned int sec)
    {
    	const char *secname = sec_name(elf, sec);
    
    	if (strstarts(secname, "___ksymtab+"))
    		return export_plain;
    	else if (strstarts(secname, "___ksymtab_unused+"))
    		return export_unused;
    	else if (strstarts(secname, "___ksymtab_gpl+"))
    		return export_gpl;
    	else if (strstarts(secname, "___ksymtab_unused_gpl+"))
    		return export_unused_gpl;
    	else if (strstarts(secname, "___ksymtab_gpl_future+"))
    		return export_gpl_future;
    	else
    		return export_unknown;
    }
    
    
    static enum export export_from_sec(struct elf_info *elf, unsigned int sec)
    
    {
    	if (sec == elf->export_sec)
    		return export_plain;
    
    	else if (sec == elf->export_unused_sec)
    		return export_unused;
    
    	else if (sec == elf->export_gpl_sec)
    		return export_gpl;
    
    	else if (sec == elf->export_unused_gpl_sec)
    		return export_unused_gpl;
    
    	else if (sec == elf->export_gpl_future_sec)
    		return export_gpl_future;
    	else
    		return export_unknown;
    }
    
    
    /**
     * Add an exported symbol - it may have already been added without a
     * CRC, in this case just update the CRC
     **/
    
    static struct symbol *sym_add_exported(const char *name, struct module *mod,
    				       enum export export)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct symbol *s = find_symbol(name);
    
    	if (!s) {
    
    		s = new_symbol(name, mod, export);
    
    	} else {
    		if (!s->preloaded) {
    
    			warn("%s: '%s' exported twice. Previous export "
    
    			     "was in %s%s\n", mod->name, name,
    			     s->module->name,
    			     is_vmlinux(s->module->name) ?"":".ko");
    
    		} else {
    			/* In case Modules.symvers was out of date */
    			s->module = mod;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	s->preloaded = 0;
    
    	s->vmlinux   = is_vmlinux(mod->name);
    	s->kernel    = 0;
    
    	s->export    = export;
    
    	return s;
    }
    
    static void sym_update_crc(const char *name, struct module *mod,
    
    			   unsigned int crc, enum export export)
    
    {
    	struct symbol *s = find_symbol(name);
    
    
    		s = new_symbol(name, mod, export);
    
    		/* Don't complain when we find it later. */
    		s->preloaded = 1;
    	}
    
    	s->crc = crc;
    	s->crc_valid = 1;
    
    void *grab_file(const char *filename, unsigned long *size)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct stat st;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int fd;
    
    	fd = open(filename, O_RDONLY);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	*size = st.st_size;
    	map = mmap(NULL, *size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (map == MAP_FAILED)
    		return NULL;
    	return map;
    }
    
    
    /**
      * Return a copy of the next line in a mmap'ed file.
      * spaces in the beginning of the line is trimmed away.
      * Return a pointer to a static buffer.
      **/
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    char *get_next_line(unsigned long *pos, void *file, unsigned long size)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	static char line[4096];
    	int skip = 1;
    	size_t len = 0;
    	signed char *p = (signed char *)file + *pos;
    	char *s = line;
    
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	for (; *pos < size ; (*pos)++) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (skip && isspace(*p)) {
    			p++;
    			continue;
    		}
    		skip = 0;
    		if (*p != '\n' && (*pos < size)) {
    			len++;
    			*s++ = *p++;
    			if (len > 4095)
    				break; /* Too long, stop */
    		} else {
    			/* End of string */
    			*s = '\0';
    			return line;
    		}
    	}
    	/* End of buffer */
    	return NULL;
    }
    
    
    void release_file(void *file, unsigned long size)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	munmap(file, size);
    }
    
    
    static int parse_elf(struct elf_info *info, const char *filename)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned int i;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	Elf_Shdr *sechdrs;
    	Elf_Sym  *sym;
    
    	const char *secstrings;
    	unsigned int symtab_idx = ~0U, symtab_shndx_idx = ~0U;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	hdr = grab_file(filename, &info->size);
    	if (!hdr) {
    
    		if (ignore_missing_files) {
    			fprintf(stderr, "%s: %s (ignored)\n", filename,
    				strerror(errno));
    			return 0;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		perror(filename);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	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;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* 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);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	sechdrs = (void *)hdr + hdr->e_shoff;
    	info->sechdrs = sechdrs;
    
    
    	/* Check if file offset is correct */
    	if (hdr->e_shoff > info->size) {
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    		fatal("section header offset=%lu in file '%s' is bigger than "
    		      "filesize=%lu\n", (unsigned long)hdr->e_shoff,
    		      filename, info->size);
    
    	if (hdr->e_shnum == SHN_UNDEF) {
    
    		/*
    		 * There are more than 64k sections,
    		 * read count from .sh_size.
    		 */
    		info->num_sections = TO_NATIVE(sechdrs[0].sh_size);
    	}
    	else {
    		info->num_sections = hdr->e_shnum;
    	}
    	if (hdr->e_shstrndx == SHN_XINDEX) {
    
    		info->secindex_strings = TO_NATIVE(sechdrs[0].sh_link);
    
    	}
    	else {
    		info->secindex_strings = hdr->e_shstrndx;
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Fix endianness in section headers */
    
    	for (i = 0; i < info->num_sections; 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);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	/* Find symbol table. */
    
    	secstrings = (void *)hdr + sechdrs[info->secindex_strings].sh_offset;
    	for (i = 1; i < info->num_sections; i++) {
    
    		const char *secname;
    
    		int nobits = sechdrs[i].sh_type == SHT_NOBITS;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		if (!nobits && sechdrs[i].sh_offset > info->size) {
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    			fatal("%s is truncated. sechdrs[i].sh_offset=%lu > "
    			      "sizeof(*hrd)=%zu\n", filename,
    			      (unsigned long)sechdrs[i].sh_offset,
    			      sizeof(*hdr));
    
    		secname = secstrings + sechdrs[i].sh_name;
    		if (strcmp(secname, ".modinfo") == 0) {
    
    			if (nobits)
    				fatal("%s has NOBITS .modinfo\n", filename);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			info->modinfo = (void *)hdr + sechdrs[i].sh_offset;
    			info->modinfo_len = sechdrs[i].sh_size;
    
    		} else if (strcmp(secname, "__ksymtab") == 0)
    			info->export_sec = i;
    
    		else if (strcmp(secname, "__ksymtab_unused") == 0)
    			info->export_unused_sec = i;
    
    		else if (strcmp(secname, "__ksymtab_gpl") == 0)
    			info->export_gpl_sec = i;
    
    		else if (strcmp(secname, "__ksymtab_unused_gpl") == 0)
    			info->export_unused_gpl_sec = i;
    
    		else if (strcmp(secname, "__ksymtab_gpl_future") == 0)
    			info->export_gpl_future_sec = i;
    
    
    		if (sechdrs[i].sh_type == SHT_SYMTAB) {
    			unsigned int sh_link_idx;
    			symtab_idx = i;
    			info->symtab_start = (void *)hdr +
    			    sechdrs[i].sh_offset;
    			info->symtab_stop  = (void *)hdr +
    			    sechdrs[i].sh_offset + sechdrs[i].sh_size;
    
    			sh_link_idx = sechdrs[i].sh_link;
    
    			info->strtab       = (void *)hdr +
    			    sechdrs[sh_link_idx].sh_offset;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		/* 32bit section no. table? ("more than 64k sections") */
    		if (sechdrs[i].sh_type == SHT_SYMTAB_SHNDX) {
    			symtab_shndx_idx = i;
    			info->symtab_shndx_start = (void *)hdr +
    			    sechdrs[i].sh_offset;
    			info->symtab_shndx_stop  = (void *)hdr +
    			    sechdrs[i].sh_offset + sechdrs[i].sh_size;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	if (!info->symtab_start)
    
    		fatal("%s has no symtab?\n", filename);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* 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);
    	}
    
    
    	if (symtab_shndx_idx != ~0U) {
    		Elf32_Word *p;
    
    		if (symtab_idx != sechdrs[symtab_shndx_idx].sh_link)
    
    			fatal("%s: SYMTAB_SHNDX has bad sh_link: %u!=%u\n",
    
    			      filename, sechdrs[symtab_shndx_idx].sh_link,
    
    			      symtab_idx);
    		/* Fix endianness */
    		for (p = info->symtab_shndx_start; p < info->symtab_shndx_stop;
    		     p++)
    			*p = TO_NATIVE(*p);
    	}
    
    
    static void parse_elf_finish(struct elf_info *info)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	release_file(info->hdr, info->size);
    }
    
    
    static int ignore_undef_symbol(struct elf_info *info, const char *symname)
    {
    	/* ignore __this_module, it will be resolved shortly */
    
    	if (strcmp(symname, VMLINUX_SYMBOL_STR(__this_module)) == 0)
    
    		return 1;
    	/* ignore global offset table */
    	if (strcmp(symname, "_GLOBAL_OFFSET_TABLE_") == 0)
    		return 1;
    	if (info->hdr->e_machine == EM_PPC)
    		/* Special register function linked on all modules during final link of .ko */
    		if (strncmp(symname, "_restgpr_", sizeof("_restgpr_") - 1) == 0 ||
    		    strncmp(symname, "_savegpr_", sizeof("_savegpr_") - 1) == 0 ||
    		    strncmp(symname, "_rest32gpr_", sizeof("_rest32gpr_") - 1) == 0 ||
    
    		    strncmp(symname, "_save32gpr_", sizeof("_save32gpr_") - 1) == 0 ||
    		    strncmp(symname, "_restvr_", sizeof("_restvr_") - 1) == 0 ||
    		    strncmp(symname, "_savevr_", sizeof("_savevr_") - 1) == 0)
    
    	if (info->hdr->e_machine == EM_PPC64)
    		/* Special register function linked on all modules during final link of .ko */
    		if (strncmp(symname, "_restgpr0_", sizeof("_restgpr0_") - 1) == 0 ||
    
    		    strncmp(symname, "_savegpr0_", sizeof("_savegpr0_") - 1) == 0 ||
    		    strncmp(symname, "_restvr_", sizeof("_restvr_") - 1) == 0 ||
    		    strncmp(symname, "_savevr_", sizeof("_savevr_") - 1) == 0)
    
    	/* Do not ignore this symbol */
    	return 0;
    }
    
    
    #define CRC_PFX     VMLINUX_SYMBOL_STR(__crc_)
    #define KSYMTAB_PFX VMLINUX_SYMBOL_STR(__ksymtab_)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void handle_modversions(struct module *mod, struct elf_info *info,
    			       Elf_Sym *sym, const char *symname)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	unsigned int crc;
    
    	if ((!is_vmlinux(mod->name) || mod->is_dot_o) &&
    	    strncmp(symname, "__ksymtab", 9) == 0)
    
    		export = export_from_secname(info, get_secindex(info, sym));
    	else
    		export = export_from_sec(info, get_secindex(info, sym));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* CRC'd symbol */
    	if (strncmp(symname, CRC_PFX, strlen(CRC_PFX)) == 0) {
    		crc = (unsigned int) sym->st_value;
    		sym_update_crc(symname + strlen(CRC_PFX), mod, crc,
    				export);
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	switch (sym->st_shndx) {
    	case SHN_COMMON:
    
    		warn("\"%s\" [%s] is COMMON symbol\n", symname, mod->name);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		break;
    	case SHN_UNDEF:
    		/* undefined symbol */
    		if (ELF_ST_BIND(sym->st_info) != STB_GLOBAL &&
    		    ELF_ST_BIND(sym->st_info) != STB_WEAK)
    			break;
    
    		if (ignore_undef_symbol(info, symname))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			break;
    
    /* cope with newer glibc (2.3.4 or higher) STT_ definition in elf.h */
    #if defined(STT_REGISTER) || defined(STT_SPARC_REGISTER)
    /* add compatibility with older glibc */
    #ifndef STT_SPARC_REGISTER
    #define STT_SPARC_REGISTER STT_REGISTER
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (info->hdr->e_machine == EM_SPARC ||
    		    info->hdr->e_machine == EM_SPARCV9) {
    			/* Ignore register directives. */
    
    			if (ELF_ST_TYPE(sym->st_info) == STT_SPARC_REGISTER)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				break;
    
    			if (symname[0] == '.') {
    				char *munged = strdup(symname);
    				munged[0] = '_';
    				munged[1] = toupper(munged[1]);
    				symname = munged;
    			}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    #endif
    
    #ifdef CONFIG_HAVE_UNDERSCORE_SYMBOL_PREFIX
    		if (symname[0] != '_')
    			break;
    		else
    			symname++;
    #endif
    		mod->unres = alloc_symbol(symname,
    					  ELF_ST_BIND(sym->st_info) == STB_WEAK,
    					  mod->unres);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		break;
    	default:
    		/* All exported symbols */
    
    		if (strncmp(symname, KSYMTAB_PFX, strlen(KSYMTAB_PFX)) == 0) {
    
    			sym_add_exported(symname + strlen(KSYMTAB_PFX), mod,
    					export);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    
    		if (strcmp(symname, VMLINUX_SYMBOL_STR(init_module)) == 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			mod->has_init = 1;
    
    		if (strcmp(symname, VMLINUX_SYMBOL_STR(cleanup_module)) == 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			mod->has_cleanup = 1;
    		break;
    	}
    }
    
    
    /**
     * Parse tag=value strings from .modinfo section
     **/
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static char *next_string(char *string, unsigned long *secsize)
    {
    	/* Skip non-zero chars */
    	while (string[0]) {
    		string++;
    		if ((*secsize)-- <= 1)
    			return NULL;
    	}
    
    	/* Skip any zero padding. */
    	while (!string[0]) {
    		string++;
    		if ((*secsize)-- <= 1)
    			return NULL;
    	}
    	return string;
    }
    
    
    static char *get_next_modinfo(void *modinfo, unsigned long modinfo_len,
    			      const char *tag, char *info)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	char *p;
    	unsigned int taglen = strlen(tag);
    	unsigned long size = modinfo_len;
    
    
    	if (info) {
    		size -= info - (char *)modinfo;
    		modinfo = next_string(info, &size);
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	for (p = modinfo; p; p = next_string(p, &size)) {
    		if (strncmp(p, tag, taglen) == 0 && p[taglen] == '=')
    			return p + taglen + 1;
    	}
    	return NULL;
    }
    
    
    static char *get_modinfo(void *modinfo, unsigned long modinfo_len,
    			 const char *tag)
    
    {
    	return get_next_modinfo(modinfo, modinfo_len, tag, NULL);
    }
    
    
    /**
     * Test if string s ends in string sub
     * return 0 if match
     **/
    static int strrcmp(const char *s, const char *sub)
    {
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	int slen, sublen;
    
    	if (!s || !sub)
    		return 1;
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	sublen = strlen(sub);
    
    	if ((slen == 0) || (sublen == 0))
    		return 1;
    
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	if (sublen > slen)
    		return 1;
    
    Sam Ravnborg's avatar
    Sam Ravnborg committed
    	return memcmp(s + slen - sublen, sub, sublen);
    
    static const char *sym_name(struct elf_info *elf, Elf_Sym *sym)
    {
    
    	if (sym)
    		return elf->strtab + sym->st_name;
    	else
    
    static const char *sec_name(struct elf_info *elf, int secindex)
    
    {
    	Elf_Shdr *sechdrs = elf->sechdrs;
    	return (void *)elf->hdr +
    
    		elf->sechdrs[elf->secindex_strings].sh_offset +
    		sechdrs[secindex].sh_name;
    
    }
    
    static const char *sech_name(struct elf_info *elf, Elf_Shdr *sechdr)
    {
    	return (void *)elf->hdr +
    
    		elf->sechdrs[elf->secindex_strings].sh_offset +
    		sechdr->sh_name;
    
    /* if sym is empty or point to a string
     * like ".[0-9]+" then return 1.
     * This is the optional prefix added by ld to some sections
     */
    static int number_prefix(const char *sym)
    {
    	if (*sym++ == '\0')
    		return 1;
    	if (*sym != '.')
    		return 0;
    	do {
    		char c = *sym++;
    		if (c < '0' || c > '9')
    			return 0;
    	} while (*sym);
    	return 1;
    }
    
    /* The pattern is an array of simple patterns.
     * "foo" will match an exact string equal to "foo"
    
     * "*foo" will match a string that ends with "foo"
    
     * "foo*" will match a string that begins with "foo"
     * "foo$" will match a string equal to "foo" or "foo.1"
     *   where the '1' can be any number including several digits.
     *   The $ syntax is for sections where ld append a dot number
     *   to make section name unique.
     */
    
    static int match(const char *sym, const char * const pat[])
    
    {
    	const char *p;
    	while (*pat) {
    		p = *pat++;
    		const char *endp = p + strlen(p) - 1;
    
    
    		/* "*foo" */
    		if (*p == '*') {
    			if (strrcmp(sym, p + 1) == 0)
    				return 1;
    		}
    
    		else if (*endp == '*') {
    
    			if (strncmp(sym, p, strlen(p) - 1) == 0)
    				return 1;
    		}
    		/* "foo$" */
    		else if (*endp == '$') {
    			if (strncmp(sym, p, strlen(p) - 1) == 0) {
    				if (number_prefix(sym + strlen(p) - 1))
    					return 1;
    			}
    		}
    		/* no wildcards */
    		else {
    			if (strcmp(p, sym) == 0)
    				return 1;
    		}
    	}
    	/* no match */
    	return 0;
    }
    
    /* sections that we do not want to do full section mismatch check on */
    static const char *section_white_list[] =
    
    	".zdebug*",		/* Compressed debug sections. */
    
    	".GCC-command-line",	/* mn10300 */
    
    	".GCC.command.line",	/* record-gcc-switches, non mn10300 */
    
    	".mdebug*",        /* alpha, score, mips etc. */
    	".pdr",            /* alpha, score, mips etc. */
    	".stab*",
    	".note*",
    	".got*",
    	".toc*",
    
    	".xt.prop",				 /* xtensa */
    	".xt.lit",         /* xtensa */
    
    	".arcextmap*",			/* arc */
    	".gnu.linkonce.arcext*",	/* arc : modules */
    
     * This is used to find sections missing the SHF_ALLOC flag.
    
     * The cause of this is often a section specified in assembler
    
    static void check_section(const char *modname, struct elf_info *elf,
                              Elf_Shdr *sechdr)
    
    	const char *sec = sech_name(elf, sechdr);
    
    	if (sechdr->sh_type == SHT_PROGBITS &&
    	    !(sechdr->sh_flags & SHF_ALLOC) &&
    	    !match(sec, section_white_list)) {
    		warn("%s (%s): unexpected non-allocatable section.\n"
    		     "Did you forget to use \"ax\"/\"aw\" in a .S file?\n"
    		     "Note that for example <linux/init.h> contains\n"
    		     "section definitions for use in .S files.\n\n",
    		     modname, sec);
    
    #define ALL_INIT_DATA_SECTIONS \
    
    	".init.setup$", ".init.rodata$", ".meminit.rodata$", \
    	".init.data$", ".meminit.data$"
    
    #define ALL_EXIT_DATA_SECTIONS \
    
    	".exit.data$", ".memexit.data$"
    
    #define ALL_INIT_TEXT_SECTIONS \
    
    	".init.text$", ".meminit.text$"
    
    #define ALL_EXIT_TEXT_SECTIONS \
    
    	".exit.text$", ".memexit.text$"
    
    #define ALL_PCI_INIT_SECTIONS	\
    	".pci_fixup_early$", ".pci_fixup_header$", ".pci_fixup_final$", \
    	".pci_fixup_enable$", ".pci_fixup_resume$", \
    	".pci_fixup_resume_early$", ".pci_fixup_suspend$"
    
    
    #define ALL_XXXINIT_SECTIONS MEM_INIT_SECTIONS
    #define ALL_XXXEXIT_SECTIONS MEM_EXIT_SECTIONS
    
    
    #define ALL_INIT_SECTIONS INIT_SECTIONS, ALL_XXXINIT_SECTIONS
    #define ALL_EXIT_SECTIONS EXIT_SECTIONS, ALL_XXXEXIT_SECTIONS
    
    #define DATA_SECTIONS ".data$", ".data.rel$"
    
    #define TEXT_SECTIONS ".text$", ".text.unlikely$"
    
    Jan Beulich's avatar
    Jan Beulich committed
    #define INIT_SECTIONS      ".init.*"
    #define MEM_INIT_SECTIONS  ".meminit.*"
    
    Jan Beulich's avatar
    Jan Beulich committed
    #define EXIT_SECTIONS      ".exit.*"
    #define MEM_EXIT_SECTIONS  ".memexit.*"
    
    /* init data sections */
    
    static const char *init_data_sections[] = { ALL_INIT_DATA_SECTIONS, NULL };
    
    static const char *init_sections[] = { ALL_INIT_SECTIONS, NULL };
    
    
    /* All init and exit sections (code + data) */
    static const char *init_exit_sections[] =
    
    	{ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS, NULL };
    
    
    /* data section */
    static const char *data_sections[] = { DATA_SECTIONS, NULL };
    
    
    /* symbols in .data that may refer to init/exit sections */
    
    #define DEFAULT_SYMBOL_WHITE_LIST					\
    	"*driver",							\
    	"*_template", /* scsi uses *_template a lot */			\
    	"*_timer",    /* arm uses ops structures named _timer a lot */	\
    	"*_sht",      /* scsi also used *_sht to some extent */		\
    	"*_ops",							\
    	"*_probe",							\
    	"*_probe_one",							\
    	"*_console"
    
    
    static const char *head_sections[] = { ".head.text*", NULL };
    static const char *linker_symbols[] =
    	{ "__init_begin", "_sinittext", "_einittext", NULL };
    
    
    	TEXT_TO_ANY_INIT,
    	DATA_TO_ANY_INIT,
    	TEXT_TO_ANY_EXIT,
    	DATA_TO_ANY_EXIT,
    	XXXINIT_TO_SOME_INIT,
    	XXXEXIT_TO_SOME_EXIT,
    	ANY_INIT_TO_ANY_EXIT,
    	ANY_EXIT_TO_ANY_INIT,
    
    struct sectioncheck {
    	const char *fromsec[20];
    	const char *tosec[20];
    
    	const char *symbol_white_list[20];
    
    };
    
    const struct sectioncheck sectioncheck[] = {
    /* Do not reference init/exit code/data from
     * normal code and data
     */
    {
    
    	.fromsec = { TEXT_SECTIONS, NULL },
    	.tosec   = { ALL_INIT_SECTIONS, NULL },
    
    	.mismatch = TEXT_TO_ANY_INIT,
    
    	.symbol_white_list = { DEFAULT_SYMBOL_WHITE_LIST, NULL },
    
    },
    {
    	.fromsec = { DATA_SECTIONS, NULL },
    
    	.tosec   = { ALL_XXXINIT_SECTIONS, NULL },
    
    	.mismatch = DATA_TO_ANY_INIT,
    
    	.symbol_white_list = { DEFAULT_SYMBOL_WHITE_LIST, NULL },
    
    {
    	.fromsec = { DATA_SECTIONS, NULL },
    	.tosec   = { INIT_SECTIONS, NULL },
    	.mismatch = DATA_TO_ANY_INIT,
    	.symbol_white_list = {
    		"*_template", "*_timer", "*_sht", "*_ops",
    		"*_probe", "*_probe_one", "*_console", NULL
    	},
    },
    
    {
    	.fromsec = { TEXT_SECTIONS, NULL },
    	.tosec   = { ALL_EXIT_SECTIONS, NULL },
    
    	.mismatch = TEXT_TO_ANY_EXIT,
    
    	.symbol_white_list = { DEFAULT_SYMBOL_WHITE_LIST, NULL },
    
    },
    {
    	.fromsec = { DATA_SECTIONS, NULL },
    	.tosec   = { ALL_EXIT_SECTIONS, NULL },
    
    	.mismatch = DATA_TO_ANY_EXIT,
    
    	.symbol_white_list = { DEFAULT_SYMBOL_WHITE_LIST, NULL },
    
    /* Do not reference init code/data from meminit code/data */
    
    	.fromsec = { ALL_XXXINIT_SECTIONS, NULL },
    
    	.tosec   = { INIT_SECTIONS, NULL },
    
    	.mismatch = XXXINIT_TO_SOME_INIT,
    
    	.symbol_white_list = { DEFAULT_SYMBOL_WHITE_LIST, NULL },