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