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