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