qa.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #
  2. # SPDX-License-Identifier: GPL-2.0-only
  3. #
  4. import os, struct, mmap
  5. class NotELFFileError(Exception):
  6. pass
  7. class ELFFile:
  8. EI_NIDENT = 16
  9. EI_CLASS = 4
  10. EI_DATA = 5
  11. EI_VERSION = 6
  12. EI_OSABI = 7
  13. EI_ABIVERSION = 8
  14. E_MACHINE = 0x12
  15. # possible values for EI_CLASS
  16. ELFCLASSNONE = 0
  17. ELFCLASS32 = 1
  18. ELFCLASS64 = 2
  19. # possible value for EI_VERSION
  20. EV_CURRENT = 1
  21. # possible values for EI_DATA
  22. EI_DATA_NONE = 0
  23. EI_DATA_LSB = 1
  24. EI_DATA_MSB = 2
  25. PT_INTERP = 3
  26. def my_assert(self, expectation, result):
  27. if not expectation == result:
  28. #print "'%x','%x' %s" % (ord(expectation), ord(result), self.name)
  29. raise NotELFFileError("%s is not an ELF" % self.name)
  30. def __init__(self, name):
  31. self.name = name
  32. self.objdump_output = {}
  33. self.data = None
  34. # Context Manager functions to close the mmap explicitly
  35. def __enter__(self):
  36. return self
  37. def __exit__(self, exc_type, exc_value, traceback):
  38. if self.data:
  39. self.data.close()
  40. def open(self):
  41. with open(self.name, "rb") as f:
  42. try:
  43. self.data = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
  44. except ValueError:
  45. # This means the file is empty
  46. raise NotELFFileError("%s is empty" % self.name)
  47. # Check the file has the minimum number of ELF table entries
  48. if len(self.data) < ELFFile.EI_NIDENT + 4:
  49. raise NotELFFileError("%s is not an ELF" % self.name)
  50. # ELF header
  51. self.my_assert(self.data[0], 0x7f)
  52. self.my_assert(self.data[1], ord('E'))
  53. self.my_assert(self.data[2], ord('L'))
  54. self.my_assert(self.data[3], ord('F'))
  55. if self.data[ELFFile.EI_CLASS] == ELFFile.ELFCLASS32:
  56. self.bits = 32
  57. elif self.data[ELFFile.EI_CLASS] == ELFFile.ELFCLASS64:
  58. self.bits = 64
  59. else:
  60. # Not 32-bit or 64.. lets assert
  61. raise NotELFFileError("ELF but not 32 or 64 bit.")
  62. self.my_assert(self.data[ELFFile.EI_VERSION], ELFFile.EV_CURRENT)
  63. self.endian = self.data[ELFFile.EI_DATA]
  64. if self.endian not in (ELFFile.EI_DATA_LSB, ELFFile.EI_DATA_MSB):
  65. raise NotELFFileError("Unexpected EI_DATA %x" % self.endian)
  66. def osAbi(self):
  67. return self.data[ELFFile.EI_OSABI]
  68. def abiVersion(self):
  69. return self.data[ELFFile.EI_ABIVERSION]
  70. def abiSize(self):
  71. return self.bits
  72. def isLittleEndian(self):
  73. return self.endian == ELFFile.EI_DATA_LSB
  74. def isBigEndian(self):
  75. return self.endian == ELFFile.EI_DATA_MSB
  76. def getStructEndian(self):
  77. return {ELFFile.EI_DATA_LSB: "<",
  78. ELFFile.EI_DATA_MSB: ">"}[self.endian]
  79. def getShort(self, offset):
  80. return struct.unpack_from(self.getStructEndian() + "H", self.data, offset)[0]
  81. def getWord(self, offset):
  82. return struct.unpack_from(self.getStructEndian() + "i", self.data, offset)[0]
  83. def isDynamic(self):
  84. """
  85. Return True if there is a .interp segment (therefore dynamically
  86. linked), otherwise False (statically linked).
  87. """
  88. offset = self.getWord(self.bits == 32 and 0x1C or 0x20)
  89. size = self.getShort(self.bits == 32 and 0x2A or 0x36)
  90. count = self.getShort(self.bits == 32 and 0x2C or 0x38)
  91. for i in range(0, count):
  92. p_type = self.getWord(offset + i * size)
  93. if p_type == ELFFile.PT_INTERP:
  94. return True
  95. return False
  96. def machine(self):
  97. """
  98. We know the endian stored in self.endian and we
  99. know the position
  100. """
  101. return self.getShort(ELFFile.E_MACHINE)
  102. def run_objdump(self, cmd, d):
  103. import bb.process
  104. import sys
  105. if cmd in self.objdump_output:
  106. return self.objdump_output[cmd]
  107. objdump = d.getVar('OBJDUMP')
  108. env = os.environ.copy()
  109. env["LC_ALL"] = "C"
  110. env["PATH"] = d.getVar('PATH')
  111. try:
  112. bb.note("%s %s %s" % (objdump, cmd, self.name))
  113. self.objdump_output[cmd] = bb.process.run([objdump, cmd, self.name], env=env, shell=False)[0]
  114. return self.objdump_output[cmd]
  115. except Exception as e:
  116. bb.note("%s %s %s failed: %s" % (objdump, cmd, self.name, e))
  117. return ""
  118. def elf_machine_to_string(machine):
  119. """
  120. Return the name of a given ELF e_machine field or the hex value as a string
  121. if it isn't recognised.
  122. """
  123. try:
  124. return {
  125. 0x02: "SPARC",
  126. 0x03: "x86",
  127. 0x08: "MIPS",
  128. 0x14: "PowerPC",
  129. 0x28: "ARM",
  130. 0x2A: "SuperH",
  131. 0x32: "IA-64",
  132. 0x3E: "x86-64",
  133. 0xB7: "AArch64",
  134. 0xF7: "BPF"
  135. }[machine]
  136. except:
  137. return "Unknown (%s)" % repr(machine)
  138. if __name__ == "__main__":
  139. import sys
  140. with ELFFile(sys.argv[1]) as elf:
  141. elf.open()
  142. print(elf.isDynamic())