blob: c98083961b535168cd0bfd76a1e766370ba0d0ad [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass24ad3652017-11-13 18:54:54 -07002# Copyright (c) 2017 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glass24ad3652017-11-13 18:54:54 -07005# Test for the elf module
6
7import os
Simon Glass4f379ea2019-07-08 13:18:34 -06008import shutil
Simon Glassa4e259e2021-11-03 21:09:16 -06009import struct
Simon Glass24ad3652017-11-13 18:54:54 -070010import sys
Simon Glass4f379ea2019-07-08 13:18:34 -060011import tempfile
Simon Glass24ad3652017-11-13 18:54:54 -070012import unittest
13
Simon Glassc585dd42020-04-17 18:09:03 -060014from binman import elf
Simon Glass131444f2023-02-23 18:18:04 -070015from u_boot_pylib import command
16from u_boot_pylib import test_util
17from u_boot_pylib import tools
18from u_boot_pylib import tout
Simon Glass24ad3652017-11-13 18:54:54 -070019
20binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
Simon Glass4ca8e042017-11-13 18:55:01 -070021
Simon Glass4ca8e042017-11-13 18:55:01 -070022
23class FakeEntry:
Simon Glass4114f972018-07-17 13:25:26 -060024 """A fake Entry object, usedfor testing
25
26 This supports an entry with a given size.
27 """
Simon Glass4ca8e042017-11-13 18:55:01 -070028 def __init__(self, contents_size):
29 self.contents_size = contents_size
Simon Glass80025522022-01-29 14:14:04 -070030 self.data = tools.get_bytes(ord('a'), contents_size)
Simon Glass4ca8e042017-11-13 18:55:01 -070031
32 def GetPath(self):
33 return 'entry_path'
34
Simon Glass4114f972018-07-17 13:25:26 -060035
Simon Glass8a6f56e2018-06-01 09:38:13 -060036class FakeSection:
Simon Glass4114f972018-07-17 13:25:26 -060037 """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 """
Simon Glass4ca8e042017-11-13 18:55:01 -070043 def __init__(self, sym_value=1):
44 self.sym_value = sym_value
45
46 def GetPath(self):
Simon Glass8a6f56e2018-06-01 09:38:13 -060047 return 'section_path'
Simon Glass4ca8e042017-11-13 18:55:01 -070048
Simon Glassecbe4732021-01-06 21:35:15 -070049 def LookupImageSymbol(self, name, weak, msg, base_addr):
Simon Glass4114f972018-07-17 13:25:26 -060050 """Fake implementation which returns the same value for all symbols"""
Simon Glass4ca8e042017-11-13 18:55:01 -070051 return self.sym_value
Simon Glass24ad3652017-11-13 18:54:54 -070052
Simon Glassecbe4732021-01-06 21:35:15 -070053 def GetImage(self):
54 return self
Simon Glass4114f972018-07-17 13:25:26 -060055
Simon Glassf6290892019-08-24 07:22:53 -060056def BuildElfTestFiles(target_dir):
57 """Build ELF files used for testing in binman
58
Simon Glass571adc82022-02-08 11:49:55 -070059 This compiles and links the test files into the specified directory. It uses
60 the Makefile and source files in the binman test/ directory.
Simon Glassf6290892019-08-24 07:22:53 -060061
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']
Simon Glass271fd8f2021-11-03 21:09:15 -060074 try:
Simon Glass80025522022-01-29 14:14:04 -070075 tools.run('make', '-C', target_dir, '-f',
Simon Glass271fd8f2021-11-03 21:09:15 -060076 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)
Simon Glassf6290892019-08-24 07:22:53 -060080
81
Simon Glass24ad3652017-11-13 18:54:54 -070082class TestElf(unittest.TestCase):
Simon Glass752e7552018-10-01 21:12:41 -060083 @classmethod
Simon Glass4affd4b2019-08-24 07:22:54 -060084 def setUpClass(cls):
85 cls._indir = tempfile.mkdtemp(prefix='elf.')
Simon Glass80025522022-01-29 14:14:04 -070086 tools.set_input_dirs(['.'])
Simon Glass4affd4b2019-08-24 07:22:54 -060087 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)
Simon Glass752e7552018-10-01 21:12:41 -060097
Simon Glass24ad3652017-11-13 18:54:54 -070098 def testAllSymbols(self):
Simon Glass4114f972018-07-17 13:25:26 -060099 """Test that we can obtain a symbol from the ELF file"""
Simon Glass4affd4b2019-08-24 07:22:54 -0600100 fname = self.ElfTestFile('u_boot_ucode_ptr')
Simon Glass24ad3652017-11-13 18:54:54 -0700101 syms = elf.GetSymbols(fname, [])
Simon Glass315400e2022-01-09 20:13:37 -0700102 self.assertIn('_dt_ucode_base_size', syms)
Simon Glass24ad3652017-11-13 18:54:54 -0700103
104 def testRegexSymbols(self):
Simon Glass4114f972018-07-17 13:25:26 -0600105 """Test that we can obtain from the ELF file by regular expression"""
Simon Glass4affd4b2019-08-24 07:22:54 -0600106 fname = self.ElfTestFile('u_boot_ucode_ptr')
Simon Glass24ad3652017-11-13 18:54:54 -0700107 syms = elf.GetSymbols(fname, ['ucode'])
Simon Glass315400e2022-01-09 20:13:37 -0700108 self.assertIn('_dt_ucode_base_size', syms)
Simon Glass24ad3652017-11-13 18:54:54 -0700109 syms = elf.GetSymbols(fname, ['missing'])
Simon Glass315400e2022-01-09 20:13:37 -0700110 self.assertNotIn('_dt_ucode_base_size', syms)
Simon Glass24ad3652017-11-13 18:54:54 -0700111 syms = elf.GetSymbols(fname, ['missing', 'ucode'])
Simon Glass315400e2022-01-09 20:13:37 -0700112 self.assertIn('_dt_ucode_base_size', syms)
Simon Glass24ad3652017-11-13 18:54:54 -0700113
Simon Glass4ca8e042017-11-13 18:55:01 -0700114 def testMissingFile(self):
Simon Glass4114f972018-07-17 13:25:26 -0600115 """Test that a missing file is detected"""
Simon Glass4ca8e042017-11-13 18:55:01 -0700116 entry = FakeEntry(10)
Simon Glass8a6f56e2018-06-01 09:38:13 -0600117 section = FakeSection()
Simon Glass4ca8e042017-11-13 18:55:01 -0700118 with self.assertRaises(ValueError) as e:
Simon Glass2a0fa982022-02-11 13:23:21 -0700119 elf.LookupAndWriteSymbols('missing-file', entry, section)
Simon Glass4ca8e042017-11-13 18:55:01 -0700120 self.assertIn("Filename 'missing-file' not found in input path",
121 str(e.exception))
122
123 def testOutsideFile(self):
Simon Glass4114f972018-07-17 13:25:26 -0600124 """Test a symbol which extends outside the entry area is detected"""
Stefan Herbrechtsmeier732742e2022-08-19 16:25:18 +0200125 if not elf.ELF_TOOLS:
126 self.skipTest('Python elftools not available')
Simon Glass4ca8e042017-11-13 18:55:01 -0700127 entry = FakeEntry(10)
Simon Glass8a6f56e2018-06-01 09:38:13 -0600128 section = FakeSection()
Simon Glass5d0c0262019-08-24 07:22:56 -0600129 elf_fname = self.ElfTestFile('u_boot_binman_syms')
Simon Glass4ca8e042017-11-13 18:55:01 -0700130 with self.assertRaises(ValueError) as e:
Simon Glass2a0fa982022-02-11 13:23:21 -0700131 elf.LookupAndWriteSymbols(elf_fname, entry, section)
Alper Nebi Yasak9634dc92022-06-18 15:13:11 +0300132 self.assertIn('entry_path has offset 8 (size 8) but the contents size '
Simon Glass4ca8e042017-11-13 18:55:01 -0700133 'is a', str(e.exception))
134
135 def testMissingImageStart(self):
Simon Glass4114f972018-07-17 13:25:26 -0600136 """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 """
Simon Glass4ca8e042017-11-13 18:55:01 -0700141 entry = FakeEntry(10)
Simon Glass8a6f56e2018-06-01 09:38:13 -0600142 section = FakeSection()
Simon Glass46ea6912019-08-24 07:22:58 -0600143 elf_fname = self.ElfTestFile('u_boot_binman_syms_bad')
Simon Glass2a0fa982022-02-11 13:23:21 -0700144 elf.LookupAndWriteSymbols(elf_fname, entry, section)
Simon Glass4ca8e042017-11-13 18:55:01 -0700145
146 def testBadSymbolSize(self):
Simon Glass4114f972018-07-17 13:25:26 -0600147 """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 """
Stefan Herbrechtsmeier732742e2022-08-19 16:25:18 +0200152 if not elf.ELF_TOOLS:
153 self.skipTest('Python elftools not available')
Simon Glass4ca8e042017-11-13 18:55:01 -0700154 entry = FakeEntry(10)
Simon Glass8a6f56e2018-06-01 09:38:13 -0600155 section = FakeSection()
Simon Glassb8deb122019-08-24 07:22:57 -0600156 elf_fname =self.ElfTestFile('u_boot_binman_syms_size')
Simon Glass4ca8e042017-11-13 18:55:01 -0700157 with self.assertRaises(ValueError) as e:
Simon Glass2a0fa982022-02-11 13:23:21 -0700158 elf.LookupAndWriteSymbols(elf_fname, entry, section)
Simon Glass4ca8e042017-11-13 18:55:01 -0700159 self.assertIn('has size 1: only 4 and 8 are supported',
160 str(e.exception))
161
162 def testNoValue(self):
Simon Glass4114f972018-07-17 13:25:26 -0600163 """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 """
Stefan Herbrechtsmeier732742e2022-08-19 16:25:18 +0200168 if not elf.ELF_TOOLS:
169 self.skipTest('Python elftools not available')
Alper Nebi Yasak9634dc92022-06-18 15:13:11 +0300170 entry = FakeEntry(28)
Simon Glass8a6f56e2018-06-01 09:38:13 -0600171 section = FakeSection(sym_value=None)
Simon Glass5d0c0262019-08-24 07:22:56 -0600172 elf_fname = self.ElfTestFile('u_boot_binman_syms')
Simon Glass2a0fa982022-02-11 13:23:21 -0700173 elf.LookupAndWriteSymbols(elf_fname, entry, section)
Alper Nebi Yasak9634dc92022-06-18 15:13:11 +0300174 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)
Simon Glass4ca8e042017-11-13 18:55:01 -0700178
179 def testDebug(self):
Simon Glass4114f972018-07-17 13:25:26 -0600180 """Check that enabling debug in the elf module produced debug output"""
Stefan Herbrechtsmeier732742e2022-08-19 16:25:18 +0200181 if not elf.ELF_TOOLS:
182 self.skipTest('Python elftools not available')
Simon Glassb6dff4c2019-07-20 12:23:36 -0600183 try:
Simon Glass011f1b32022-01-29 14:14:15 -0700184 tout.init(tout.DEBUG)
Alper Nebi Yasak9634dc92022-06-18 15:13:11 +0300185 entry = FakeEntry(24)
Simon Glassb6dff4c2019-07-20 12:23:36 -0600186 section = FakeSection()
Simon Glass5d0c0262019-08-24 07:22:56 -0600187 elf_fname = self.ElfTestFile('u_boot_binman_syms')
Simon Glassb6dff4c2019-07-20 12:23:36 -0600188 with test_util.capture_sys_output() as (stdout, stderr):
Simon Glass2a0fa982022-02-11 13:23:21 -0700189 elf.LookupAndWriteSymbols(elf_fname, entry, section)
Simon Glassb6dff4c2019-07-20 12:23:36 -0600190 self.assertTrue(len(stdout.getvalue()) > 0)
191 finally:
Simon Glass011f1b32022-01-29 14:14:15 -0700192 tout.init(tout.WARNING)
Simon Glass4ca8e042017-11-13 18:55:01 -0700193
Simon Glass4f379ea2019-07-08 13:18:34 -0600194 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')
Simon Glassd349ada2019-08-24 07:22:45 -0600200 bin_fname = os.path.join(outdir, 'bin')
Simon Glass4f379ea2019-07-08 13:18:34 -0600201
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)
Simon Glass80025522022-01-29 14:14:04 -0700205 objcopy, args = tools.get_target_compile_tool('objcopy')
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300206 args += ['-O', 'binary', elf_fname, bin_fname]
Simon Glass840be732022-01-29 14:14:05 -0700207 stdout = command.output(objcopy, *args)
Simon Glass4f379ea2019-07-08 13:18:34 -0600208 with open(bin_fname, 'rb') as fd:
209 data = fd.read()
210 self.assertEqual(expected_text + expected_data, data)
211 shutil.rmtree(outdir)
212
Simon Glass567b6822019-07-08 13:18:35 -0600213 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)
Simon Glass80025522022-01-29 14:14:04 -0700222 data = tools.read_file(elf_fname)
Simon Glass567b6822019-07-08 13:18:35 -0600223
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))
Simon Glass4affd4b2019-08-24 07:22:54 -0600232 shutil.rmtree(outdir)
Simon Glass567b6822019-07-08 13:18:35 -0600233
Simon Glassa4e259e2021-11-03 21:09:16 -0600234 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
Simon Glass80025522022-01-29 14:14:04 -0700243 data = tools.read_file(fname)
Simon Glassa4e259e2021-11-03 21:09:16 -0600244 embed_data = data[start:end]
Simon Glass6490d4b2023-01-23 11:29:41 -0700245 expect = struct.pack('<IIIII', 2, 3, 0x1234, 0x5678, 0)
Simon Glassa4e259e2021-11-03 21:09:16 -0600246 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'])
Simon Glassacc03752022-03-05 20:18:57 -0700256 self.assertIn("Python: No module named 'elftools'",
Simon Glassa4e259e2021-11-03 21:09:16 -0600257 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
Simon Glassacc03752022-03-05 20:18:57 -0700270 def test_read_loadable_segments(self):
271 """Test for read_loadable_segments()"""
Simon Glass571adc82022-02-08 11:49:55 -0700272 if not elf.ELF_TOOLS:
273 self.skipTest('Python elftools not available')
274 fname = self.ElfTestFile('embed_data')
Simon Glassacc03752022-03-05 20:18:57 -0700275 segments, entry = elf.read_loadable_segments(tools.read_file(fname))
Simon Glass571adc82022-02-08 11:49:55 -0700276
277 def test_read_segments_fail(self):
Simon Glassacc03752022-03-05 20:18:57 -0700278 """Test for read_loadable_segments() without elftools"""
Simon Glass571adc82022-02-08 11:49:55 -0700279 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:
Simon Glassacc03752022-03-05 20:18:57 -0700284 elf.read_loadable_segments(tools.read_file(fname))
285 self.assertIn("Python: No module named 'elftools'",
Simon Glass571adc82022-02-08 11:49:55 -0700286 str(e.exception))
287 finally:
288 elf.ELF_TOOLS = old_val
289
290 def test_read_segments_bad_data(self):
Simon Glassacc03752022-03-05 20:18:57 -0700291 """Test for read_loadable_segments() with an invalid ELF file"""
Stefan Herbrechtsmeier732742e2022-08-19 16:25:18 +0200292 if not elf.ELF_TOOLS:
293 self.skipTest('Python elftools not available')
Simon Glass571adc82022-02-08 11:49:55 -0700294 fname = self.ElfTestFile('embed_data')
295 with self.assertRaises(ValueError) as e:
Simon Glassacc03752022-03-05 20:18:57 -0700296 elf.read_loadable_segments(tools.get_bytes(100, 100))
Simon Glass571adc82022-02-08 11:49:55 -0700297 self.assertIn('Magic number does not match', str(e.exception))
298
Simon Glassea64c022022-03-18 19:19:49 -0600299 def test_get_file_offset(self):
300 """Test GetFileOffset() gives the correct file offset for a symbol"""
Stefan Herbrechtsmeier732742e2022-08-19 16:25:18 +0200301 if not elf.ELF_TOOLS:
302 self.skipTest('Python elftools not available')
Simon Glassea64c022022-03-18 19:19:49 -0600303 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()"""
Stefan Herbrechtsmeier732742e2022-08-19 16:25:18 +0200329 if not elf.ELF_TOOLS:
330 self.skipTest('Python elftools not available')
Simon Glassea64c022022-03-18 19:19:49 -0600331 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
Simon Glass6e657f62023-01-07 14:07:13 -0700351 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
Simon Glass6490d4b2023-01-23 11:29:41 -0700361 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
Simon Glass4ca8e042017-11-13 18:55:01 -0700372
Simon Glass24ad3652017-11-13 18:54:54 -0700373if __name__ == '__main__':
374 unittest.main()