Browse Source

Read dependency libraries from the ELF file itself rather than using ldd. Also, remove any RPATH/RUNPATH entry if the .dynamic section contains one

rdb 15 years ago
parent
commit
f5834b67c5
1 changed files with 133 additions and 13 deletions
  1. 133 13
      direct/src/p3d/Packager.py

+ 133 - 13
direct/src/p3d/Packager.py

@@ -12,6 +12,7 @@ import string
 import types
 import types
 import getpass
 import getpass
 import platform
 import platform
+import struct
 from direct.p3d.FileSpec import FileSpec
 from direct.p3d.FileSpec import FileSpec
 from direct.p3d.SeqValue import SeqValue
 from direct.p3d.SeqValue import SeqValue
 from direct.showbase import Loader
 from direct.showbase import Loader
@@ -1062,7 +1063,7 @@ class Packager:
 
 
                 # Copy it every time, because the source file might
                 # Copy it every time, because the source file might
                 # have changed since last time we ran.
                 # have changed since last time we ran.
-                assert file.filename.exists(), "File doesn't exist: %s" % ffilename
+                assert file.filename.exists(), "File doesn't exist: %s" % file.filename
                 tmpfile = Filename.temporary('', "p3d_" + file.filename.getBasename())
                 tmpfile = Filename.temporary('', "p3d_" + file.filename.getBasename())
                 file.filename.copyTo(tmpfile)
                 file.filename.copyTo(tmpfile)
                 file.filename = tmpfile
                 file.filename = tmpfile
@@ -1178,6 +1179,120 @@ class Packager:
 
 
             return filenames
             return filenames
 
 
+        def __readAndStripELF(self, file):
+            """ Reads the indicated ELF binary, and returns a list with
+            dependencies.  If it contains data that should be stripped,
+            it writes the stripped library to a temporary file.  Returns
+            None if the file failed to read (e.g. not an ELF file). """
+
+            # Read the first 16 bytes, which identify the ELF file.
+            elf = open(file.filename.toOsSpecific(), 'rb')
+            try:
+                ident = elf.read(16)
+            except IOError:
+                elf.close()
+                return None
+
+            if not ident.startswith("\177ELF"):
+                # Not an elf.  Beware of orcs.
+                return None
+
+            # Make sure we read in the correct endianness and integer size
+            byteOrder = "<>"[ord(ident[5]) - 1]
+            elfClass = ord(ident[4]) - 1 # 32-bits, 64-bits
+            headerStruct = byteOrder + ("HHIIIIIHHHHHH", "HHIQQQIHHHHHH")[elfClass]
+            sectionStruct = byteOrder + ("4xI8xIII8xI", "4xI16xQQI12xQ")[elfClass]
+            dynamicStruct = byteOrder + ("iI", "qQ")[elfClass]
+
+            type, machine, version, entry, phoff, shoff, flags, ehsize, phentsize, phnum, shentsize, shnum, shstrndx \
+              = struct.unpack(headerStruct, elf.read(struct.calcsize(headerStruct)))
+            dynamicSections = []
+            stringTables = {}
+
+            # Seek to the section header table and find the .dynamic section.
+            elf.seek(shoff)
+            for i in range(shnum):
+                type, offset, size, link, entsize = struct.unpack_from(sectionStruct, elf.read(shentsize))
+                if type == 6 and link != 0: # DYNAMIC type, links to string table
+                    dynamicSections.append((offset, size, link, entsize))
+                    stringTables[link] = None
+
+            # Read the relevant string tables.
+            for idx in stringTables.keys():
+                elf.seek(shoff + idx * shentsize)
+                type, offset, size, link, entsize = struct.unpack_from(sectionStruct, elf.read(shentsize))
+                if type != 3: continue
+                elf.seek(offset)
+                stringTables[idx] = elf.read(size)
+
+            # Loop through the dynamic sections and rewrite it if it has an rpath/runpath.
+            rewriteSections = []
+            filenames = []
+            rpath = []
+            for offset, size, link, entsize in dynamicSections:
+                elf.seek(offset)
+                data = elf.read(entsize)
+                tag, val = struct.unpack_from(dynamicStruct, data)
+                newSectionData = ""
+                startReplace = None
+                pad = 0
+
+                # Read tags until we find a NULL tag.
+                while tag != 0:
+                    if tag == 1: # A NEEDED entry.  Read it from the string table.
+                        filenames.append(stringTables[link][val : stringTables[link].find('\0', val)])
+
+                    elif tag == 15 or tag == 29:
+                        rpath += stringTables[link][val : stringTables[link].find('\0', val)].split(':')
+                        # An RPATH or RUNPATH entry.
+                        if not startReplace:
+                            startReplace = elf.tell() - entsize
+                        if startReplace:
+                            pad += entsize
+
+                    elif startReplace is not None:
+                        newSectionData += data
+
+                    data = elf.read(entsize)
+                    tag, val = struct.unpack_from(dynamicStruct, data)
+
+                if startReplace is not None:
+                    newSectionData += data + ("\0" * pad)
+                    rewriteSections.append((startReplace, newSectionData))
+            elf.close()
+
+            # No rpaths/runpaths found, so nothing to do any more.
+            if len(rewriteSections) == 0:
+                return filenames
+
+            # Attempt to resolve any of the directly
+            # dependent filenames along the RPATH.
+            for f in range(len(filenames)):
+                filename = filenames[f]
+                for rdir in rpath:
+                    if os.path.isfile(os.path.join(rdir, filename)):
+                        filenames[f] = os.path.join(rdir, filename)
+                        break
+
+            if not file.deleteTemp:
+                # Copy the file to a temporary location because we
+                # don't want to modify the original (there's a big
+                # chance that we break it).
+
+                tmpfile = Filename.temporary('', "p3d_" + file.filename.getBasename())
+                file.filename.copyTo(tmpfile)
+                file.filename = tmpfile
+                file.deleteTemp = True
+
+            # Open the temporary file and rewrite the dynamic sections.
+            elf = open(file.filename.toOsSpecific(), 'r+b')
+            for offset, data in rewriteSections:
+                elf.seek(offset)
+                elf.write(data)
+            elf.write("\0" * pad)
+            elf.close()
+            return filenames
+
         def __addImplicitDependenciesPosix(self):
         def __addImplicitDependenciesPosix(self):
             """ Walks through the list of files, looking for so's
             """ Walks through the list of files, looking for so's
             and executables that might include implicit dependencies
             and executables that might include implicit dependencies
@@ -1195,19 +1310,24 @@ class Packager:
                     # Skip this file.
                     # Skip this file.
                     continue
                     continue
 
 
-                tempFile = Filename.temporary('', 'p3d_', '.txt')
-                command = 'ldd "%s" >"%s"' % (
-                    file.filename.toOsSpecific(),
-                    tempFile.toOsSpecific())
-                try:
-                    os.system(command)
-                except:
-                    pass
-                filenames = None
+                # Check if this is an ELF binary.
+                filenames = self.__readAndStripELF(file)
+
+                # If that failed, perhaps ldd will help us.
+                if filenames is None:
+                    tempFile = Filename.temporary('', 'p3d_', '.txt')
+                    command = 'ldd "%s" >"%s"' % (
+                        file.filename.toOsSpecific(),
+                        tempFile.toOsSpecific())
+                    try:
+                        os.system(command)
+                    except:
+                        pass
+
+                    if tempFile.exists():
+                        filenames = self.__parseDependenciesPosix(tempFile)
+                        tempFile.unlink()
 
 
-                if tempFile.exists():
-                    filenames = self.__parseDependenciesPosix(tempFile)
-                    tempFile.unlink()
                 if filenames is None:
                 if filenames is None:
                     self.notify.warning("Unable to determine dependencies from %s" % (file.filename))
                     self.notify.warning("Unable to determine dependencies from %s" % (file.filename))
                     continue
                     continue