1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2017 Google, Inc 3# Written by Simon Glass <sjg@chromium.org> 4# 5# Test for the elf module 6 7import os 8import shutil 9import struct 10import sys 11import tempfile 12import unittest 13 14from binman import elf 15from u_boot_pylib import command 16from u_boot_pylib import test_util 17from u_boot_pylib import tools 18from u_boot_pylib import tout 19 20binman_dir = os.path.dirname(os.path.realpath(sys.argv[0])) 21 22 23class FakeEntry: 24 """A fake Entry object, usedfor testing 25 26 This supports an entry with a given size. 27 """ 28 def __init__(self, contents_size): 29 self.contents_size = contents_size 30 self.data = tools.get_bytes(ord('a'), contents_size) 31 32 def GetPath(self): 33 return 'entry_path' 34 35 36class FakeSection: 37 """A fake Section object, used for testing 38 39 This has the minimum feature set needed to support testing elf functions. 40 A LookupSymbol() function is provided which returns a fake value for amu 41 symbol requested. 42 """ 43 def __init__(self, sym_value=1): 44 self.sym_value = sym_value 45 46 def GetPath(self): 47 return 'section_path' 48 49 def LookupImageSymbol(self, name, weak, msg, base_addr): 50 """Fake implementation which returns the same value for all symbols""" 51 return self.sym_value 52 53 def GetImage(self): 54 return self 55 56def BuildElfTestFiles(target_dir): 57 """Build ELF files used for testing in binman 58 59 This compiles and links the test files into the specified directory. It uses 60 the Makefile and source files in the binman test/ directory. 61 62 Args: 63 target_dir: Directory to put the files into 64 """ 65 if not os.path.exists(target_dir): 66 os.mkdir(target_dir) 67 testdir = os.path.join(binman_dir, 'test') 68 69 # If binman is involved from the main U-Boot Makefile the -r and -R 70 # flags are set in MAKEFLAGS. This prevents this Makefile from working 71 # correctly. So drop any make flags here. 72 if 'MAKEFLAGS' in os.environ: 73 del os.environ['MAKEFLAGS'] 74 try: 75 tools.run('make', '-C', target_dir, '-f', 76 os.path.join(testdir, 'Makefile'), 'SRC=%s/' % testdir) 77 except ValueError as e: 78 # The test system seems to suppress this in a strange way 79 print(e) 80 81 82class TestElf(unittest.TestCase): 83 @classmethod 84 def setUpClass(cls): 85 cls._indir = tempfile.mkdtemp(prefix='elf.') 86 tools.set_input_dirs(['.']) 87 BuildElfTestFiles(cls._indir) 88 89 @classmethod 90 def tearDownClass(cls): 91 if cls._indir: 92 shutil.rmtree(cls._indir) 93 94 @classmethod 95 def ElfTestFile(cls, fname): 96 return os.path.join(cls._indir, fname) 97 98 def testAllSymbols(self): 99 """Test that we can obtain a symbol from the ELF file""" 100 fname = self.ElfTestFile('u_boot_ucode_ptr') 101 syms = elf.GetSymbols(fname, []) 102 self.assertIn('_dt_ucode_base_size', syms) 103 104 def testRegexSymbols(self): 105 """Test that we can obtain from the ELF file by regular expression""" 106 fname = self.ElfTestFile('u_boot_ucode_ptr') 107 syms = elf.GetSymbols(fname, ['ucode']) 108 self.assertIn('_dt_ucode_base_size', syms) 109 syms = elf.GetSymbols(fname, ['missing']) 110 self.assertNotIn('_dt_ucode_base_size', syms) 111 syms = elf.GetSymbols(fname, ['missing', 'ucode']) 112 self.assertIn('_dt_ucode_base_size', syms) 113 114 def testMissingFile(self): 115 """Test that a missing file is detected""" 116 entry = FakeEntry(10) 117 section = FakeSection() 118 with self.assertRaises(ValueError) as e: 119 elf.LookupAndWriteSymbols('missing-file', entry, section) 120 self.assertIn("Filename 'missing-file' not found in input path", 121 str(e.exception)) 122 123 def testOutsideFile(self): 124 """Test a symbol which extends outside the entry area is detected""" 125 if not elf.ELF_TOOLS: 126 self.skipTest('Python elftools not available') 127 entry = FakeEntry(10) 128 section = FakeSection() 129 elf_fname = self.ElfTestFile('u_boot_binman_syms') 130 with self.assertRaises(ValueError) as e: 131 elf.LookupAndWriteSymbols(elf_fname, entry, section) 132 self.assertIn('entry_path has offset 8 (size 8) but the contents size ' 133 'is a', str(e.exception)) 134 135 def testMissingImageStart(self): 136 """Test that we detect a missing __image_copy_start symbol 137 138 This is needed to mark the start of the image. Without it we cannot 139 locate the offset of a binman symbol within the image. 140 """ 141 entry = FakeEntry(10) 142 section = FakeSection() 143 elf_fname = self.ElfTestFile('u_boot_binman_syms_bad') 144 elf.LookupAndWriteSymbols(elf_fname, entry, section) 145 146 def testBadSymbolSize(self): 147 """Test that an attempt to use an 8-bit symbol are detected 148 149 Only 32 and 64 bits are supported, since we need to store an offset 150 into the image. 151 """ 152 if not elf.ELF_TOOLS: 153 self.skipTest('Python elftools not available') 154 entry = FakeEntry(10) 155 section = FakeSection() 156 elf_fname =self.ElfTestFile('u_boot_binman_syms_size') 157 with self.assertRaises(ValueError) as e: 158 elf.LookupAndWriteSymbols(elf_fname, entry, section) 159 self.assertIn('has size 1: only 4 and 8 are supported', 160 str(e.exception)) 161 162 def testNoValue(self): 163 """Test the case where we have no value for the symbol 164 165 This should produce -1 values for all thress symbols, taking up the 166 first 16 bytes of the image. 167 """ 168 if not elf.ELF_TOOLS: 169 self.skipTest('Python elftools not available') 170 entry = FakeEntry(28) 171 section = FakeSection(sym_value=None) 172 elf_fname = self.ElfTestFile('u_boot_binman_syms') 173 elf.LookupAndWriteSymbols(elf_fname, entry, section) 174 expected = (struct.pack('<L', elf.BINMAN_SYM_MAGIC_VALUE) + 175 tools.get_bytes(255, 20) + 176 tools.get_bytes(ord('a'), 4)) 177 self.assertEqual(expected, entry.data) 178 179 def testDebug(self): 180 """Check that enabling debug in the elf module produced debug output""" 181 if not elf.ELF_TOOLS: 182 self.skipTest('Python elftools not available') 183 try: 184 tout.init(tout.DEBUG) 185 entry = FakeEntry(24) 186 section = FakeSection() 187 elf_fname = self.ElfTestFile('u_boot_binman_syms') 188 with test_util.capture_sys_output() as (stdout, stderr): 189 elf.LookupAndWriteSymbols(elf_fname, entry, section) 190 self.assertTrue(len(stdout.getvalue()) > 0) 191 finally: 192 tout.init(tout.WARNING) 193 194 def testMakeElf(self): 195 """Test for the MakeElf function""" 196 outdir = tempfile.mkdtemp(prefix='elf.') 197 expected_text = b'1234' 198 expected_data = b'wxyz' 199 elf_fname = os.path.join(outdir, 'elf') 200 bin_fname = os.path.join(outdir, 'bin') 201 202 # Make an Elf file and then convert it to a fkat binary file. This 203 # should produce the original data. 204 elf.MakeElf(elf_fname, expected_text, expected_data) 205 objcopy, args = tools.get_target_compile_tool('objcopy') 206 args += ['-O', 'binary', elf_fname, bin_fname] 207 stdout = command.output(objcopy, *args) 208 with open(bin_fname, 'rb') as fd: 209 data = fd.read() 210 self.assertEqual(expected_text + expected_data, data) 211 shutil.rmtree(outdir) 212 213 def testDecodeElf(self): 214 """Test for the MakeElf function""" 215 if not elf.ELF_TOOLS: 216 self.skipTest('Python elftools not available') 217 outdir = tempfile.mkdtemp(prefix='elf.') 218 expected_text = b'1234' 219 expected_data = b'wxyz' 220 elf_fname = os.path.join(outdir, 'elf') 221 elf.MakeElf(elf_fname, expected_text, expected_data) 222 data = tools.read_file(elf_fname) 223 224 load = 0xfef20000 225 entry = load + 2 226 expected = expected_text + expected_data 227 self.assertEqual(elf.ElfInfo(expected, load, entry, len(expected)), 228 elf.DecodeElf(data, 0)) 229 self.assertEqual(elf.ElfInfo(b'\0\0' + expected[2:], 230 load, entry, len(expected)), 231 elf.DecodeElf(data, load + 2)) 232 shutil.rmtree(outdir) 233 234 def testEmbedData(self): 235 """Test for the GetSymbolFileOffset() function""" 236 if not elf.ELF_TOOLS: 237 self.skipTest('Python elftools not available') 238 239 fname = self.ElfTestFile('embed_data') 240 offset = elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end']) 241 start = offset['embed_start'].offset 242 end = offset['embed_end'].offset 243 data = tools.read_file(fname) 244 embed_data = data[start:end] 245 expect = struct.pack('<IIIII', 2, 3, 0x1234, 0x5678, 0) 246 self.assertEqual(expect, embed_data) 247 248 def testEmbedFail(self): 249 """Test calling GetSymbolFileOffset() without elftools""" 250 try: 251 old_val = elf.ELF_TOOLS 252 elf.ELF_TOOLS = False 253 fname = self.ElfTestFile('embed_data') 254 with self.assertRaises(ValueError) as e: 255 elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end']) 256 self.assertIn("Python: No module named 'elftools'", 257 str(e.exception)) 258 finally: 259 elf.ELF_TOOLS = old_val 260 261 def testEmbedDataNoSym(self): 262 """Test for GetSymbolFileOffset() getting no symbols""" 263 if not elf.ELF_TOOLS: 264 self.skipTest('Python elftools not available') 265 266 fname = self.ElfTestFile('embed_data') 267 offset = elf.GetSymbolFileOffset(fname, ['missing_sym']) 268 self.assertEqual({}, offset) 269 270 def test_read_loadable_segments(self): 271 """Test for read_loadable_segments()""" 272 if not elf.ELF_TOOLS: 273 self.skipTest('Python elftools not available') 274 fname = self.ElfTestFile('embed_data') 275 segments, entry = elf.read_loadable_segments(tools.read_file(fname)) 276 277 def test_read_segments_fail(self): 278 """Test for read_loadable_segments() without elftools""" 279 try: 280 old_val = elf.ELF_TOOLS 281 elf.ELF_TOOLS = False 282 fname = self.ElfTestFile('embed_data') 283 with self.assertRaises(ValueError) as e: 284 elf.read_loadable_segments(tools.read_file(fname)) 285 self.assertIn("Python: No module named 'elftools'", 286 str(e.exception)) 287 finally: 288 elf.ELF_TOOLS = old_val 289 290 def test_read_segments_bad_data(self): 291 """Test for read_loadable_segments() with an invalid ELF file""" 292 if not elf.ELF_TOOLS: 293 self.skipTest('Python elftools not available') 294 fname = self.ElfTestFile('embed_data') 295 with self.assertRaises(ValueError) as e: 296 elf.read_loadable_segments(tools.get_bytes(100, 100)) 297 self.assertIn('Magic number does not match', str(e.exception)) 298 299 def test_get_file_offset(self): 300 """Test GetFileOffset() gives the correct file offset for a symbol""" 301 if not elf.ELF_TOOLS: 302 self.skipTest('Python elftools not available') 303 fname = self.ElfTestFile('embed_data') 304 syms = elf.GetSymbols(fname, ['embed']) 305 addr = syms['embed'].address 306 offset = elf.GetFileOffset(fname, addr) 307 data = tools.read_file(fname) 308 309 # Just use the first 4 bytes and assume it is little endian 310 embed_data = data[offset:offset + 4] 311 embed_value = struct.unpack('<I', embed_data)[0] 312 self.assertEqual(0x1234, embed_value) 313 314 def test_get_file_offset_fail(self): 315 """Test calling GetFileOffset() without elftools""" 316 try: 317 old_val = elf.ELF_TOOLS 318 elf.ELF_TOOLS = False 319 fname = self.ElfTestFile('embed_data') 320 with self.assertRaises(ValueError) as e: 321 elf.GetFileOffset(fname, 0) 322 self.assertIn("Python: No module named 'elftools'", 323 str(e.exception)) 324 finally: 325 elf.ELF_TOOLS = old_val 326 327 def test_get_symbol_from_address(self): 328 """Test GetSymbolFromAddress()""" 329 if not elf.ELF_TOOLS: 330 self.skipTest('Python elftools not available') 331 fname = self.ElfTestFile('elf_sections') 332 sym_name = 'calculate' 333 syms = elf.GetSymbols(fname, [sym_name]) 334 addr = syms[sym_name].address 335 sym = elf.GetSymbolFromAddress(fname, addr) 336 self.assertEqual(sym_name, sym) 337 338 def test_get_symbol_from_address_fail(self): 339 """Test calling GetSymbolFromAddress() without elftools""" 340 try: 341 old_val = elf.ELF_TOOLS 342 elf.ELF_TOOLS = False 343 fname = self.ElfTestFile('embed_data') 344 with self.assertRaises(ValueError) as e: 345 elf.GetSymbolFromAddress(fname, 0x1000) 346 self.assertIn("Python: No module named 'elftools'", 347 str(e.exception)) 348 finally: 349 elf.ELF_TOOLS = old_val 350 351 def test_is_valid(self): 352 """Test is_valid()""" 353 self.assertEqual(False, elf.is_valid(b'')) 354 self.assertEqual(False, elf.is_valid(b'1234')) 355 356 fname = self.ElfTestFile('elf_sections') 357 data = tools.read_file(fname) 358 self.assertEqual(True, elf.is_valid(data)) 359 self.assertEqual(False, elf.is_valid(data[4:])) 360 361 def test_get_symbol_offset(self): 362 fname = self.ElfTestFile('embed_data') 363 syms = elf.GetSymbols(fname, ['embed_start', 'embed']) 364 expected = syms['embed'].address - syms['embed_start'].address 365 val = elf.GetSymbolOffset(fname, 'embed', 'embed_start') 366 self.assertEqual(expected, val) 367 368 with self.assertRaises(KeyError) as e: 369 elf.GetSymbolOffset(fname, 'embed') 370 self.assertIn('__image_copy_start', str(e.exception)) 371 372 373if __name__ == '__main__': 374 unittest.main() 375