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