123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- #! /usr/bin/python
- # mklibs.py: An automated way to create a minimal /lib/ directory.
- #
- # Copyright 2001 by Falk Hueffner <falk@debian.org>
- # & Goswin Brederlow <goswin.brederlow@student.uni-tuebingen.de>
- #
- # mklibs.sh by Marcus Brinkmann <Marcus.Brinkmann@ruhr-uni-bochum.de>
- # used as template
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- # HOW IT WORKS
- #
- # - Gather all unresolved symbols and libraries needed by the programs
- # and reduced libraries
- # - Gather all symbols provided by the already reduced libraries
- # (none on the first pass)
- # - If all symbols are provided we are done
- # - go through all libraries and remember what symbols they provide
- # - go through all unresolved/needed symbols and mark them as used
- # - for each library:
- # - find pic file (if not present copy and strip the so)
- # - compile in only used symbols
- # - strip
- # - back to the top
- # TODO
- # * complete argument parsing as given as comment in main
- import commands
- import string
- import re
- import sys
- import os
- import glob
- import getopt
- from stat import *
- DEBUG_NORMAL = 1
- DEBUG_VERBOSE = 2
- DEBUG_SPAM = 3
- debuglevel = DEBUG_NORMAL
- def debug(level, *msg):
- if debuglevel >= level:
- print string.join(msg)
- # A simple set class. It should be replaced with the standard sets.Set
- # type as soon as Python 2.3 is out.
- class Set:
- def __init__(self):
- self.__dict = {}
- def add(self, obj):
- self.__dict[obj] = 1
- def contains(self, obj):
- return self.__dict.has_key(obj)
- def merge(self, s):
- for e in s.elems():
- self.add(e)
- def elems(self):
- return self.__dict.keys()
- def size(self):
- return len(self.__dict)
- def __eq__(self, other):
- return self.__dict == other.__dict
- def __str__(self):
- return `self.__dict.keys()`
- def __repr__(self):
- return `self.__dict.keys()`
- # return a list of lines of output of the command
- def command(command, *args):
- debug(DEBUG_SPAM, "calling", command, string.join(args))
- (status, output) = commands.getstatusoutput(command + ' ' + string.join(args))
- if os.WEXITSTATUS(status) != 0:
- print "Command failed with status", os.WEXITSTATUS(status), ":", \
- command, string.join(args)
- print "With output:", output
- sys.exit(1)
- return string.split(output, '\n')
- # Filter a list according to a regexp containing a () group. Return
- # a Set.
- def regexpfilter(list, regexp, groupnr = 1):
- pattern = re.compile(regexp)
- result = Set()
- for x in list:
- match = pattern.match(x)
- if match:
- result.add(match.group(groupnr))
- return result
- # Return a Set of rpath strings for the passed object
- def rpath(obj):
- if not os.access(obj, os.F_OK):
- raise "Cannot find lib: " + obj
- output = command(target + "objdump", "--private-headers", obj)
- return map(lambda x: root + "/" + x, regexpfilter(output, ".*RPATH\s*(\S+)$").elems())
- # Return a Set of libraries the passed objects depend on.
- def library_depends(obj):
- if not os.access(obj, os.F_OK):
- raise "Cannot find lib: " + obj
- output = command(target + "objdump", "--private-headers", obj)
- return regexpfilter(output, ".*NEEDED\s*(\S+)$")
- # Return a list of libraries the passed objects depend on. The
- # libraries are in "-lfoo" format suitable for passing to gcc.
- def library_depends_gcc_libnames(obj):
- if not os.access(obj, os.F_OK):
- raise "Cannot find lib: " + obj
- output = command(target + "objdump", "--private-headers", obj)
- output = regexpfilter(output, ".*NEEDED\s*lib(\S+)\.so.*$")
- if not output.elems():
- return ""
- else:
- return "-l" + string.join(output.elems(), " -l")
- # Scan readelf output. Example:
- # Num: Value Size Type Bind Vis Ndx Name
- # 1: 000000012002ab48 168 FUNC GLOBAL DEFAULT UND strchr@GLIBC_2.0 (2)
- symline_regexp = \
- re.compile("\s*\d+: .+\s+\d+\s+\w+\s+(\w+)+\s+\w+\s+(\w+)\s+([^\s@]+)")
- # Return undefined symbols in an object as a Set of tuples (name, weakness)
- def undefined_symbols(obj):
- if not os.access(obj, os.F_OK):
- raise "Cannot find lib" + obj
- result = Set()
- output = command(target + "readelf", "-s", "-W", obj)
- for line in output:
- match = symline_regexp.match(line)
- if match:
- bind, ndx, name = match.groups()
- if ndx == "UND":
- result.add((name, bind == "WEAK"))
- return result
- # Return a Set of symbols provided by a library
- def provided_symbols(obj):
- if not os.access(obj, os.F_OK):
- raise "Cannot find lib" + obj
- result = Set()
- debug(DEBUG_SPAM, "provided_symbols result = ", `result`)
- output = command(target + "readelf", "-s", "-W", obj)
- for line in output:
- match = symline_regexp.match(line)
- if match:
- bind, ndx, name = match.groups()
- if bind != "LOCAL" and not ndx in ("UND", "ABS"):
- debug(DEBUG_SPAM, "provided_symbols adding ", `name`)
- result.add(name)
- return result
- # Return real target of a symlink
- def resolve_link(file):
- debug(DEBUG_SPAM, "resolving", file)
- while S_ISLNK(os.lstat(file)[ST_MODE]):
- new_file = os.readlink(file)
- if new_file[0] != "/":
- file = os.path.join(os.path.dirname(file), new_file)
- else:
- file = new_file
- debug(DEBUG_SPAM, "resolved to", file)
- return file
- # Find complete path of a library, by searching in lib_path
- def find_lib(lib):
- for path in lib_path:
- if os.access(path + "/" + lib, os.F_OK):
- return path + "/" + lib
- return ""
- # Find a PIC archive for the library
- def find_pic(lib):
- base_name = so_pattern.match(lib).group(1)
- for path in lib_path:
- for file in glob.glob(path + "/" + base_name + "_pic.a"):
- if os.access(file, os.F_OK):
- return resolve_link(file)
- return ""
- # Find a PIC .map file for the library
- def find_pic_map(lib):
- base_name = so_pattern.match(lib).group(1)
- for path in lib_path:
- for file in glob.glob(path + "/" + base_name + "_pic.map"):
- if os.access(file, os.F_OK):
- return resolve_link(file)
- return ""
- def extract_soname(so_file):
- soname_data = regexpfilter(command(target + "readelf", "--all", "-W", so_file),
- ".*SONAME.*\[(.*)\].*")
- if soname_data.elems():
- return soname_data.elems()[0]
- return ""
- def usage(was_err):
- if was_err:
- outfd = sys.stderr
- else:
- outfd = sys.stdout
- print >> outfd, "Usage: mklibs [OPTION]... -d DEST FILE ..."
- print >> outfd, "Make a set of minimal libraries for FILE(s) in DEST."
- print >> outfd, ""
- print >> outfd, " -d, --dest-dir DIRECTORY create libraries in DIRECTORY"
- print >> outfd, " -D, --no-default-lib omit default libpath (", string.join(default_lib_path, " : "), ")"
- print >> outfd, " -L DIRECTORY[:DIRECTORY]... add DIRECTORY(s) to the library search path"
- print >> outfd, " --ldlib LDLIB use LDLIB for the dynamic linker"
- print >> outfd, " --libc-extras-dir DIRECTORY look for libc extra files in DIRECTORY"
- # Ugh... Adding the trailing '-' breaks common practice.
- #print >> outfd, " --target TARGET prepend TARGET- to the gcc and binutils calls"
- print >> outfd, " --target TARGET prepend TARGET to the gcc and binutils calls"
- print >> outfd, " --root ROOT search in ROOT for library rpaths"
- print >> outfd, " -v, --verbose explain what is being done"
- print >> outfd, " -h, --help display this help and exit"
- sys.exit(was_err)
- def version(vers):
- print "mklibs: version ",vers
- print ""
- #################### main ####################
- ## Usage: ./mklibs.py [OPTION]... -d DEST FILE ...
- ## Make a set of minimal libraries for FILE ... in directory DEST.
- ##
- ## Options:
- ## -L DIRECTORY Add DIRECTORY to library search path.
- ## -D, --no-default-lib Do not use default lib directories of /lib:/usr/lib
- ## -n, --dry-run Don't actually run any commands; just print them.
- ## -v, --verbose Print additional progress information.
- ## -V, --version Print the version number and exit.
- ## -h, --help Print this help and exit.
- ## --ldlib Name of dynamic linker (overwrites environment variable ldlib)
- ## --libc-extras-dir Directory for libc extra files
- ## --target Use as prefix for gcc or binutils calls
- ##
- ## -d, --dest-dir DIRECTORY Create libraries in DIRECTORY.
- ##
- ## Required arguments for long options are also mandatory for the short options.
- # Clean the environment
- vers="0.12 with uClibc fixes"
- os.environ['LC_ALL'] = "C"
- # Argument parsing
- opts = "L:DnvVhd:r:"
- longopts = ["no-default-lib", "dry-run", "verbose", "version", "help",
- "dest-dir=", "ldlib=", "libc-extras-dir=", "target=", "root="]
- # some global variables
- lib_rpath = []
- lib_path = []
- dest_path = "DEST"
- ldlib = "LDLIB"
- include_default_lib_path = "yes"
- default_lib_path = ["/lib/", "/usr/lib/", "/usr/X11R6/lib/"]
- libc_extras_dir = "/usr/lib/libc_pic"
- target = ""
- root = ""
- so_pattern = re.compile("((lib|ld).*)\.so(\..+)*")
- script_pattern = re.compile("^#!\s*/")
- try:
- optlist, proglist = getopt.getopt(sys.argv[1:], opts, longopts)
- except getopt.GetoptError, msg:
- print >> sys.stderr, msg
- usage(1)
- for opt, arg in optlist:
- if opt in ("-v", "--verbose"):
- if debuglevel < DEBUG_SPAM:
- debuglevel = debuglevel + 1
- elif opt == "-L":
- lib_path.extend(string.split(arg, ":"))
- elif opt in ("-d", "--dest-dir"):
- dest_path = arg
- elif opt in ("-D", "--no-default-lib"):
- include_default_lib_path = "no"
- elif opt == "--ldlib":
- ldlib = arg
- elif opt == "--libc-extras-dir":
- libc_extras_dir = arg
- elif opt == "--target":
- #target = arg + "-"
- target = arg
- elif opt in ("-r", "--root"):
- root = arg
- elif opt in ("--help", "-h"):
- usage(0)
- sys.exit(0)
- elif opt in ("--version", "-V"):
- version(vers)
- sys.exit(0)
- else:
- print "WARNING: unknown option: " + opt + "\targ: " + arg
- if include_default_lib_path == "yes":
- lib_path.extend(default_lib_path)
- if ldlib == "LDLIB":
- ldlib = os.getenv("ldlib")
- objects = {} # map from inode to filename
- for prog in proglist:
- inode = os.stat(prog)[ST_INO]
- if objects.has_key(inode):
- debug(DEBUG_SPAM, prog, "is a hardlink to", objects[inode])
- elif so_pattern.match(prog):
- debug(DEBUG_SPAM, prog, "is a library")
- elif script_pattern.match(open(prog).read(256)):
- debug(DEBUG_SPAM, prog, "is a script")
- else:
- objects[inode] = prog
- if not ldlib:
- pattern = re.compile(".*Requesting program interpreter:.*/([^\]/]+).*")
- for obj in objects.values():
- output = command(target + "readelf", "--program-headers", obj)
- for x in output:
- match = pattern.match(x)
- if match:
- ldlib = match.group(1)
- break
- if ldlib:
- break
- if not ldlib:
- sys.exit("E: Dynamic linker not found, aborting.")
- debug(DEBUG_NORMAL, "I: Using", ldlib, "as dynamic linker.")
- pattern = re.compile(".*ld-uClibc.*");
- if pattern.match(ldlib):
- uclibc = 1
- else:
- uclibc = 0
- # Check for rpaths
- for obj in objects.values():
- rpath_val = rpath(obj)
- if rpath_val:
- if root:
- if debuglevel >= DEBUG_VERBOSE:
- print "Adding rpath " + string.join(rpath_val, ":") + " for " + obj
- lib_rpath.extend(rpath_val)
- else:
- print "warning: " + obj + " may need rpath, but --root not specified"
- lib_path.extend(lib_rpath)
- passnr = 1
- previous_pass_unresolved = Set()
- while 1:
- debug(DEBUG_NORMAL, "I: library reduction pass", `passnr`)
- if debuglevel >= DEBUG_VERBOSE:
- print "Objects:",
- for obj in objects.values():
- print obj[string.rfind(obj, '/') + 1:],
- print
- passnr = passnr + 1
- # Gather all already reduced libraries and treat them as objects as well
- small_libs = []
- for lib in regexpfilter(os.listdir(dest_path), "(.*-so-stripped)$").elems():
- obj = dest_path + "/" + lib
- small_libs.append(obj)
- inode = os.stat(obj)[ST_INO]
- if objects.has_key(inode):
- debug(DEBUG_SPAM, obj, "is hardlink to", objects[inode])
- else:
- objects[inode] = obj
- # DEBUG
- for obj in objects.values():
- small_libs.append(obj)
- debug(DEBUG_VERBOSE, "Object:", obj)
- # calculate what symbols and libraries are needed
- needed_symbols = Set() # Set of (name, weakness-flag)
- libraries = Set()
- for obj in objects.values():
- needed_symbols.merge(undefined_symbols(obj))
- libraries.merge(library_depends(obj))
- # FIXME: on i386 this is undefined but not marked UND
- # I don't know how to detect those symbols but this seems
- # to be the only one and including it on alpha as well
- # doesn't hurt. I guess all archs can live with this.
- needed_symbols.add(("sys_siglist", 1))
- # calculate what symbols are present in small_libs
- present_symbols = Set()
- for lib in small_libs:
- present_symbols.merge(provided_symbols(lib))
- # are we finished?
- using_ctor_dtor = 0
- num_unresolved = 0
- present_symbols_elems = present_symbols.elems()
- unresolved = Set()
- for (symbol, is_weak) in needed_symbols.elems():
- if not symbol in present_symbols_elems:
- debug(DEBUG_SPAM, "Still need:", symbol, `is_weak`)
- unresolved.add((symbol, is_weak))
- num_unresolved = num_unresolved + 1
- debug (DEBUG_NORMAL, `needed_symbols.size()`, "symbols,",
- `num_unresolved`, "unresolved")
- if num_unresolved == 0:
- break
- if unresolved == previous_pass_unresolved:
- # No progress in last pass. Verify all remaining symbols are weak.
- for (symbol, is_weak) in unresolved.elems():
- if not is_weak:
- raise "Unresolvable symbol " + symbol
- break
- previous_pass_unresolved = unresolved
- library_symbols = {}
- library_symbols_used = {}
- symbol_provider = {}
- # Calculate all symbols each library provides
- for library in libraries.elems():
- path = find_lib(library)
- if not path:
- sys.exit("Library not found: " + library + " in path: "
- + string.join(lib_path, " : "))
- symbols = provided_symbols(path)
- library_symbols[library] = Set()
- library_symbols_used[library] = Set()
- for symbol in symbols.elems():
- if symbol_provider.has_key(symbol):
- # in doubt, prefer symbols from libc
- if re.match("^libc[\.-]", library):
- library_symbols[library].add(symbol)
- symbol_provider[symbol] = library
- else:
- debug(DEBUG_SPAM, "duplicate symbol", symbol, "in",
- symbol_provider[symbol], "and", library)
- else:
- library_symbols[library].add(symbol)
- symbol_provider[symbol] = library
- # Fixup support for constructors and destructors
- if symbol_provider.has_key("_init"):
- debug(DEBUG_VERBOSE, library, ": Library has a constructor!");
- using_ctor_dtor = 1
- library_symbols[library].add("_init")
- symbol_provider["_init"] = library
- library_symbols_used[library].add("_init")
- if symbol_provider.has_key("_fini"):
- debug(DEBUG_VERBOSE, library, ": Library has a destructor!");
- using_ctor_dtor = 1
- library_symbols[library].add("_fini")
- symbol_provider["_fini"] = library
- library_symbols_used[library].add("_fini")
- # which symbols are actually used from each lib
- for (symbol, is_weak) in needed_symbols.elems():
- if not symbol_provider.has_key(symbol):
- if not is_weak:
- if not uclibc or (symbol != "main"):
- raise "No library provides non-weak " + symbol
- else:
- lib = symbol_provider[symbol]
- library_symbols_used[lib].add(symbol)
- # reduce libraries
- for library in libraries.elems():
- debug(DEBUG_VERBOSE, "reducing", library)
- debug(DEBUG_SPAM, "using: " + string.join(library_symbols_used[library].elems()))
- so_file = find_lib(library)
- if root and (re.compile("^" + root).search(so_file)):
- debug(DEBUG_VERBOSE, "no action required for " + so_file)
- continue
- so_file_name = os.path.basename(so_file)
- if not so_file:
- sys.exit("File not found:" + library)
- pic_file = find_pic(library)
- if not pic_file:
- # No pic file, so we have to use the .so file, no reduction
- debug(DEBUG_VERBOSE, "No pic file found for", so_file, "; copying")
- command(target + "objcopy", "--strip-unneeded -R .note -R .comment",
- so_file, dest_path + "/" + so_file_name + "-so-stripped")
- else:
- # we have a pic file, recompile
- debug(DEBUG_SPAM, "extracting from:", pic_file, "so_file:", so_file)
- soname = extract_soname(so_file)
- if soname == "":
- debug(DEBUG_VERBOSE, so_file, " has no soname, copying")
- continue
- debug(DEBUG_SPAM, "soname:", soname)
- base_name = so_pattern.match(library).group(1)
- # libc needs its soinit.o and sofini.o as well as the pic
- if (base_name == "libc") and not uclibc:
- # force dso_handle.os to be included, otherwise reduced libc
- # may segfault in ptmalloc_init due to undefined weak reference
- extra_flags = find_lib(ldlib) + " -u __dso_handle"
- extra_pre_obj = libc_extras_dir + "/soinit.o"
- extra_post_obj = libc_extras_dir + "/sofini.o"
- else:
- extra_flags = ""
- extra_pre_obj = ""
- extra_post_obj = ""
- map_file = find_pic_map(library)
- if map_file:
- extra_flags = extra_flags + " -Wl,--version-script=" + map_file
- if library_symbols_used[library].elems():
- joined_symbols = "-u" + string.join(library_symbols_used[library].elems(), " -u")
- else:
- joined_symbols = ""
- if using_ctor_dtor == 1:
- extra_flags = extra_flags + " -shared"
- # compile in only used symbols
- command(target + "gcc",
- "-nostdlib -nostartfiles -shared -Wl,-soname=" + soname,\
- joined_symbols, \
- "-o", dest_path + "/" + so_file_name + "-so", \
- extra_pre_obj, \
- pic_file, \
- extra_post_obj, \
- extra_flags, \
- "-lgcc -L", dest_path, \
- "-L" + string.join(lib_path, " -L"), \
- library_depends_gcc_libnames(so_file))
- # strip result
- command(target + "objcopy", "--strip-unneeded -R .note -R .comment",
- dest_path + "/" + so_file_name + "-so",
- dest_path + "/" + so_file_name + "-so-stripped")
- ## DEBUG
- debug(DEBUG_VERBOSE, so_file, "\t", `os.stat(so_file)[ST_SIZE]`)
- debug(DEBUG_VERBOSE, dest_path + "/" + so_file_name + "-so", "\t",
- `os.stat(dest_path + "/" + so_file_name + "-so")[ST_SIZE]`)
- debug(DEBUG_VERBOSE, dest_path + "/" + so_file_name + "-so-stripped",
- "\t", `os.stat(dest_path + "/" + so_file_name + "-so-stripped")[ST_SIZE]`)
- # Finalising libs and cleaning up
- for lib in regexpfilter(os.listdir(dest_path), "(.*)-so-stripped$").elems():
- os.rename(dest_path + "/" + lib + "-so-stripped", dest_path + "/" + lib)
- for lib in regexpfilter(os.listdir(dest_path), "(.*-so)$").elems():
- os.remove(dest_path + "/" + lib)
- # Canonicalize library names.
- for lib in regexpfilter(os.listdir(dest_path), "(.*so[.\d]*)$").elems():
- this_lib_path = dest_path + "/" + lib
- if os.path.islink(this_lib_path):
- debug(DEBUG_VERBOSE, "Unlinking %s." % lib)
- os.remove(this_lib_path)
- continue
- soname = extract_soname(this_lib_path)
- if soname:
- debug(DEBUG_VERBOSE, "Moving %s to %s." % (lib, soname))
- os.rename(dest_path + "/" + lib, dest_path + "/" + soname)
- # Make sure the dynamic linker is present and is executable
- ld_file = find_lib(ldlib)
- ld_file_name = os.path.basename(ld_file)
- if not os.access(dest_path + "/" + ld_file_name, os.F_OK):
- debug(DEBUG_NORMAL, "I: stripping and copying dynamic linker.")
- command(target + "objcopy", "--strip-unneeded -R .note -R .comment",
- ld_file, dest_path + "/" + ld_file_name)
- os.chmod(dest_path + "/" + ld_file_name, 0755)
|