xref: /OK3568_Linux_fs/yocto/poky/meta/lib/oe/qa.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
5import os, struct, mmap
6
7class NotELFFileError(Exception):
8    pass
9
10class ELFFile:
11    EI_NIDENT = 16
12
13    EI_CLASS      = 4
14    EI_DATA       = 5
15    EI_VERSION    = 6
16    EI_OSABI      = 7
17    EI_ABIVERSION = 8
18
19    E_MACHINE    = 0x12
20
21    # possible values for EI_CLASS
22    ELFCLASSNONE = 0
23    ELFCLASS32   = 1
24    ELFCLASS64   = 2
25
26    # possible value for EI_VERSION
27    EV_CURRENT   = 1
28
29    # possible values for EI_DATA
30    EI_DATA_NONE  = 0
31    EI_DATA_LSB  = 1
32    EI_DATA_MSB  = 2
33
34    PT_INTERP = 3
35
36    def my_assert(self, expectation, result):
37        if not expectation == result:
38            #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name)
39            raise NotELFFileError("%s is not an ELF" % self.name)
40
41    def __init__(self, name):
42        self.name = name
43        self.objdump_output = {}
44        self.data = None
45
46    # Context Manager functions to close the mmap explicitly
47    def __enter__(self):
48        return self
49
50    def __exit__(self, exc_type, exc_value, traceback):
51        self.close()
52
53    def close(self):
54        if self.data:
55            self.data.close()
56
57    def open(self):
58        with open(self.name, "rb") as f:
59            try:
60                self.data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
61            except ValueError:
62                # This means the file is empty
63                raise NotELFFileError("%s is empty" % self.name)
64
65        # Check the file has the minimum number of ELF table entries
66        if len(self.data) < ELFFile.EI_NIDENT + 4:
67            raise NotELFFileError("%s is not an ELF" % self.name)
68
69        # ELF header
70        self.my_assert(self.data[0], 0x7f)
71        self.my_assert(self.data[1], ord('E'))
72        self.my_assert(self.data[2], ord('L'))
73        self.my_assert(self.data[3], ord('F'))
74        if self.data[ELFFile.EI_CLASS] == ELFFile.ELFCLASS32:
75            self.bits = 32
76        elif self.data[ELFFile.EI_CLASS] == ELFFile.ELFCLASS64:
77            self.bits = 64
78        else:
79            # Not 32-bit or 64.. lets assert
80            raise NotELFFileError("ELF but not 32 or 64 bit.")
81        self.my_assert(self.data[ELFFile.EI_VERSION], ELFFile.EV_CURRENT)
82
83        self.endian = self.data[ELFFile.EI_DATA]
84        if self.endian not in (ELFFile.EI_DATA_LSB, ELFFile.EI_DATA_MSB):
85            raise NotELFFileError("Unexpected EI_DATA %x" % self.endian)
86
87    def osAbi(self):
88        return self.data[ELFFile.EI_OSABI]
89
90    def abiVersion(self):
91        return self.data[ELFFile.EI_ABIVERSION]
92
93    def abiSize(self):
94        return self.bits
95
96    def isLittleEndian(self):
97        return self.endian == ELFFile.EI_DATA_LSB
98
99    def isBigEndian(self):
100        return self.endian == ELFFile.EI_DATA_MSB
101
102    def getStructEndian(self):
103        return {ELFFile.EI_DATA_LSB: "<",
104                ELFFile.EI_DATA_MSB: ">"}[self.endian]
105
106    def getShort(self, offset):
107        return struct.unpack_from(self.getStructEndian() + "H", self.data, offset)[0]
108
109    def getWord(self, offset):
110        return struct.unpack_from(self.getStructEndian() + "i", self.data, offset)[0]
111
112    def isDynamic(self):
113        """
114        Return True if there is a .interp segment (therefore dynamically
115        linked), otherwise False (statically linked).
116        """
117        offset = self.getWord(self.bits == 32 and 0x1C or 0x20)
118        size = self.getShort(self.bits == 32 and 0x2A or 0x36)
119        count = self.getShort(self.bits == 32 and 0x2C or 0x38)
120
121        for i in range(0, count):
122            p_type = self.getWord(offset + i * size)
123            if p_type == ELFFile.PT_INTERP:
124                return True
125        return False
126
127    def machine(self):
128        """
129        We know the endian stored in self.endian and we
130        know the position
131        """
132        return self.getShort(ELFFile.E_MACHINE)
133
134    def set_objdump(self, cmd, output):
135        self.objdump_output[cmd] = output
136
137    def run_objdump(self, cmd, d):
138        import bb.process
139        import sys
140
141        if cmd in self.objdump_output:
142            return self.objdump_output[cmd]
143
144        objdump = d.getVar('OBJDUMP')
145
146        env = os.environ.copy()
147        env["LC_ALL"] = "C"
148        env["PATH"] = d.getVar('PATH')
149
150        try:
151            bb.note("%s %s %s" % (objdump, cmd, self.name))
152            self.objdump_output[cmd] = bb.process.run([objdump, cmd, self.name], env=env, shell=False)[0]
153            return self.objdump_output[cmd]
154        except Exception as e:
155            bb.note("%s %s %s failed: %s" % (objdump, cmd, self.name, e))
156            return ""
157
158def elf_machine_to_string(machine):
159    """
160    Return the name of a given ELF e_machine field or the hex value as a string
161    if it isn't recognised.
162    """
163    try:
164        return {
165            0x00: "Unset",
166            0x02: "SPARC",
167            0x03: "x86",
168            0x08: "MIPS",
169            0x14: "PowerPC",
170            0x28: "ARM",
171            0x2A: "SuperH",
172            0x32: "IA-64",
173            0x3E: "x86-64",
174            0xB7: "AArch64",
175            0xF7: "BPF"
176        }[machine]
177    except:
178        return "Unknown (%s)" % repr(machine)
179
180def write_error(type, error, d):
181    logfile = d.getVar('QA_LOGFILE')
182    if logfile:
183        p = d.getVar('P')
184        with open(logfile, "a+") as f:
185            f.write("%s: %s [%s]\n" % (p, error, type))
186
187def handle_error(error_class, error_msg, d):
188    if error_class in (d.getVar("ERROR_QA") or "").split():
189        write_error(error_class, error_msg, d)
190        bb.error("QA Issue: %s [%s]" % (error_msg, error_class))
191        d.setVar("QA_ERRORS_FOUND", "True")
192        return False
193    elif error_class in (d.getVar("WARN_QA") or "").split():
194        write_error(error_class, error_msg, d)
195        bb.warn("QA Issue: %s [%s]" % (error_msg, error_class))
196    else:
197        bb.note("QA Issue: %s [%s]" % (error_msg, error_class))
198    return True
199
200def add_message(messages, section, new_msg):
201    if section not in messages:
202        messages[section] = new_msg
203    else:
204        messages[section] = messages[section] + "\n" + new_msg
205
206def exit_with_message_if_errors(message, d):
207    qa_fatal_errors = bb.utils.to_boolean(d.getVar("QA_ERRORS_FOUND"), False)
208    if qa_fatal_errors:
209        bb.fatal(message)
210
211def exit_if_errors(d):
212    exit_with_message_if_errors("Fatal QA errors were found, failing task.", d)
213
214if __name__ == "__main__":
215    import sys
216
217    with ELFFile(sys.argv[1]) as elf:
218        elf.open()
219        print(elf.isDynamic())
220