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