xref: /openbmc/openbmc/poky/meta/lib/oe/qa.py (revision eb8dc403)
1import os, struct, mmap
2
3class NotELFFileError(Exception):
4    pass
5
6class ELFFile:
7    EI_NIDENT = 16
8
9    EI_CLASS      = 4
10    EI_DATA       = 5
11    EI_VERSION    = 6
12    EI_OSABI      = 7
13    EI_ABIVERSION = 8
14
15    E_MACHINE    = 0x12
16
17    # possible values for EI_CLASS
18    ELFCLASSNONE = 0
19    ELFCLASS32   = 1
20    ELFCLASS64   = 2
21
22    # possible value for EI_VERSION
23    EV_CURRENT   = 1
24
25    # possible values for EI_DATA
26    EI_DATA_NONE  = 0
27    EI_DATA_LSB  = 1
28    EI_DATA_MSB  = 2
29
30    PT_INTERP = 3
31
32    def my_assert(self, expectation, result):
33        if not expectation == result:
34            #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name)
35            raise NotELFFileError("%s is not an ELF" % self.name)
36
37    def __init__(self, name):
38        self.name = name
39        self.objdump_output = {}
40
41    # Context Manager functions to close the mmap explicitly
42    def __enter__(self):
43        return self
44
45    def __exit__(self, exc_type, exc_value, traceback):
46        self.data.close()
47
48    def open(self):
49        with open(self.name, "rb") as f:
50            try:
51                self.data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
52            except ValueError:
53                # This means the file is empty
54                raise NotELFFileError("%s is empty" % self.name)
55
56        # Check the file has the minimum number of ELF table entries
57        if len(self.data) < ELFFile.EI_NIDENT + 4:
58            raise NotELFFileError("%s is not an ELF" % self.name)
59
60        # ELF header
61        self.my_assert(self.data[0], 0x7f)
62        self.my_assert(self.data[1], ord('E'))
63        self.my_assert(self.data[2], ord('L'))
64        self.my_assert(self.data[3], ord('F'))
65        if self.data[ELFFile.EI_CLASS] == ELFFile.ELFCLASS32:
66            self.bits = 32
67        elif self.data[ELFFile.EI_CLASS] == ELFFile.ELFCLASS64:
68            self.bits = 64
69        else:
70            # Not 32-bit or 64.. lets assert
71            raise NotELFFileError("ELF but not 32 or 64 bit.")
72        self.my_assert(self.data[ELFFile.EI_VERSION], ELFFile.EV_CURRENT)
73
74        self.endian = self.data[ELFFile.EI_DATA]
75        if self.endian not in (ELFFile.EI_DATA_LSB, ELFFile.EI_DATA_MSB):
76            raise NotELFFileError("Unexpected EI_DATA %x" % self.endian)
77
78    def osAbi(self):
79        return self.data[ELFFile.EI_OSABI]
80
81    def abiVersion(self):
82        return self.data[ELFFile.EI_ABIVERSION]
83
84    def abiSize(self):
85        return self.bits
86
87    def isLittleEndian(self):
88        return self.endian == ELFFile.EI_DATA_LSB
89
90    def isBigEndian(self):
91        return self.endian == ELFFile.EI_DATA_MSB
92
93    def getStructEndian(self):
94        return {ELFFile.EI_DATA_LSB: "<",
95                ELFFile.EI_DATA_MSB: ">"}[self.endian]
96
97    def getShort(self, offset):
98        return struct.unpack_from(self.getStructEndian() + "H", self.data, offset)[0]
99
100    def getWord(self, offset):
101        return struct.unpack_from(self.getStructEndian() + "i", self.data, offset)[0]
102
103    def isDynamic(self):
104        """
105        Return True if there is a .interp segment (therefore dynamically
106        linked), otherwise False (statically linked).
107        """
108        offset = self.getWord(self.bits == 32 and 0x1C or 0x20)
109        size = self.getShort(self.bits == 32 and 0x2A or 0x36)
110        count = self.getShort(self.bits == 32 and 0x2C or 0x38)
111
112        for i in range(0, count):
113            p_type = self.getWord(offset + i * size)
114            if p_type == ELFFile.PT_INTERP:
115                return True
116        return False
117
118    def machine(self):
119        """
120        We know the endian stored in self.endian and we
121        know the position
122        """
123        return self.getShort(ELFFile.E_MACHINE)
124
125    def run_objdump(self, cmd, d):
126        import bb.process
127        import sys
128
129        if cmd in self.objdump_output:
130            return self.objdump_output[cmd]
131
132        objdump = d.getVar('OBJDUMP')
133
134        env = os.environ.copy()
135        env["LC_ALL"] = "C"
136        env["PATH"] = d.getVar('PATH')
137
138        try:
139            bb.note("%s %s %s" % (objdump, cmd, self.name))
140            self.objdump_output[cmd] = bb.process.run([objdump, cmd, self.name], env=env, shell=False)[0]
141            return self.objdump_output[cmd]
142        except Exception as e:
143            bb.note("%s %s %s failed: %s" % (objdump, cmd, self.name, e))
144            return ""
145
146def elf_machine_to_string(machine):
147    """
148    Return the name of a given ELF e_machine field or the hex value as a string
149    if it isn't recognised.
150    """
151    try:
152        return {
153            0x02: "SPARC",
154            0x03: "x86",
155            0x08: "MIPS",
156            0x14: "PowerPC",
157            0x28: "ARM",
158            0x2A: "SuperH",
159            0x32: "IA-64",
160            0x3E: "x86-64",
161            0xB7: "AArch64"
162        }[machine]
163    except:
164        return "Unknown (%s)" % repr(machine)
165
166if __name__ == "__main__":
167    import sys
168
169    with ELFFile(sys.argv[1]) as elf:
170        elf.open()
171        print(elf.isDynamic())
172