1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2017 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Test for the elf module
14 from binman import elf
15 from u_boot_pylib import command
16 from u_boot_pylib import test_util
17 from u_boot_pylib import tools
18 from u_boot_pylib import tout
20 binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
24 """A fake Entry object, usedfor testing
26 This supports an entry with a given size.
28 def __init__(self, contents_size):
29 self.contents_size = contents_size
30 self.data = tools.get_bytes(ord('a'), contents_size)
37 """A fake Section object, used for testing
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
43 def __init__(self, sym_value=1):
44 self.sym_value = sym_value
49 def LookupImageSymbol(self, name, weak, msg, base_addr):
50 """Fake implementation which returns the same value for all symbols"""
56 def BuildElfTestFiles(target_dir):
57 """Build ELF files used for testing in binman
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.
63 target_dir: Directory to put the files into
65 if not os.path.exists(target_dir):
67 testdir = os.path.join(binman_dir, 'test')
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']
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
82 class TestElf(unittest.TestCase):
85 cls._indir = tempfile.mkdtemp(prefix='elf.')
86 tools.set_input_dirs(['.'])
87 BuildElfTestFiles(cls._indir)
90 def tearDownClass(cls):
92 shutil.rmtree(cls._indir)
95 def ElfTestFile(cls, fname):
96 return os.path.join(cls._indir, fname)
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)
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)
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",
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))
135 def testMissingImageStart(self):
136 """Test that we detect a missing __image_copy_start symbol
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.
141 entry = FakeEntry(10)
142 section = FakeSection()
143 elf_fname = self.ElfTestFile('u_boot_binman_syms_bad')
144 count = elf.LookupAndWriteSymbols(elf_fname, entry, section)
145 self.assertEqual(0, count)
147 def testBadSymbolSize(self):
148 """Test that an attempt to use an 8-bit symbol are detected
150 Only 32 and 64 bits are supported, since we need to store an offset
153 if not elf.ELF_TOOLS:
154 self.skipTest('Python elftools not available')
155 entry = FakeEntry(10)
156 section = FakeSection()
157 elf_fname =self.ElfTestFile('u_boot_binman_syms_size')
158 with self.assertRaises(ValueError) as e:
159 elf.LookupAndWriteSymbols(elf_fname, entry, section)
160 self.assertIn('has size 1: only 4 and 8 are supported',
163 def testNoValue(self):
164 """Test the case where we have no value for the symbol
166 This should produce -1 values for all three symbols, taking up the
167 first 16 bytes of the image.
169 if not elf.ELF_TOOLS:
170 self.skipTest('Python elftools not available')
171 entry = FakeEntry(28)
172 section = FakeSection(sym_value=None)
173 elf_fname = self.ElfTestFile('u_boot_binman_syms')
174 count = elf.LookupAndWriteSymbols(elf_fname, entry, section)
175 self.assertEqual(5, count)
176 expected = (struct.pack('<L', elf.BINMAN_SYM_MAGIC_VALUE) +
177 tools.get_bytes(255, 20) +
178 tools.get_bytes(ord('a'), 4))
179 self.assertEqual(expected, entry.data)
182 """Check that enabling debug in the elf module produced debug output"""
183 if not elf.ELF_TOOLS:
184 self.skipTest('Python elftools not available')
186 tout.init(tout.DEBUG)
187 entry = FakeEntry(24)
188 section = FakeSection()
189 elf_fname = self.ElfTestFile('u_boot_binman_syms')
190 with test_util.capture_sys_output() as (stdout, stderr):
191 elf.LookupAndWriteSymbols(elf_fname, entry, section)
192 self.assertTrue(len(stdout.getvalue()) > 0)
194 tout.init(tout.WARNING)
196 def testMakeElf(self):
197 """Test for the MakeElf function"""
198 outdir = tempfile.mkdtemp(prefix='elf.')
199 expected_text = b'1234'
200 expected_data = b'wxyz'
201 elf_fname = os.path.join(outdir, 'elf')
202 bin_fname = os.path.join(outdir, 'bin')
204 # Make an Elf file and then convert it to a fkat binary file. This
205 # should produce the original data.
206 elf.MakeElf(elf_fname, expected_text, expected_data)
207 objcopy, args = tools.get_target_compile_tool('objcopy')
208 args += ['-O', 'binary', elf_fname, bin_fname]
209 stdout = command.output(objcopy, *args)
210 with open(bin_fname, 'rb') as fd:
212 self.assertEqual(expected_text + expected_data, data)
213 shutil.rmtree(outdir)
215 def testDecodeElf(self):
216 """Test for the MakeElf function"""
217 if not elf.ELF_TOOLS:
218 self.skipTest('Python elftools not available')
219 outdir = tempfile.mkdtemp(prefix='elf.')
220 expected_text = b'1234'
221 expected_data = b'wxyz'
222 elf_fname = os.path.join(outdir, 'elf')
223 elf.MakeElf(elf_fname, expected_text, expected_data)
224 data = tools.read_file(elf_fname)
228 expected = expected_text + expected_data
229 self.assertEqual(elf.ElfInfo(expected, load, entry, len(expected)),
230 elf.DecodeElf(data, 0))
231 self.assertEqual(elf.ElfInfo(b'\0\0' + expected[2:],
232 load, entry, len(expected)),
233 elf.DecodeElf(data, load + 2))
234 shutil.rmtree(outdir)
236 def testEmbedData(self):
237 """Test for the GetSymbolFileOffset() function"""
238 if not elf.ELF_TOOLS:
239 self.skipTest('Python elftools not available')
241 fname = self.ElfTestFile('embed_data')
242 offset = elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end'])
243 start = offset['embed_start'].offset
244 end = offset['embed_end'].offset
245 data = tools.read_file(fname)
246 embed_data = data[start:end]
247 expect = struct.pack('<IIIII', 2, 3, 0x1234, 0x5678, 0)
248 self.assertEqual(expect, embed_data)
250 def testEmbedFail(self):
251 """Test calling GetSymbolFileOffset() without elftools"""
253 old_val = elf.ELF_TOOLS
254 elf.ELF_TOOLS = False
255 fname = self.ElfTestFile('embed_data')
256 with self.assertRaises(ValueError) as e:
257 elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end'])
258 with self.assertRaises(ValueError) as e:
259 elf.DecodeElf(tools.read_file(fname), 0xdeadbeef)
260 with self.assertRaises(ValueError) as e:
261 elf.GetFileOffset(fname, 0xdeadbeef)
262 with self.assertRaises(ValueError) as e:
263 elf.GetSymbolFromAddress(fname, 0xdeadbeef)
264 with self.assertRaises(ValueError) as e:
265 entry = FakeEntry(10)
266 section = FakeSection()
267 elf.LookupAndWriteSymbols(fname, entry, section, True)
270 "Section 'section_path': entry 'entry_path': Cannot write symbols to an ELF file without Python elftools",
273 elf.ELF_TOOLS = old_val
275 def testEmbedDataNoSym(self):
276 """Test for GetSymbolFileOffset() getting no symbols"""
277 if not elf.ELF_TOOLS:
278 self.skipTest('Python elftools not available')
280 fname = self.ElfTestFile('embed_data')
281 offset = elf.GetSymbolFileOffset(fname, ['missing_sym'])
282 self.assertEqual({}, offset)
284 def test_read_loadable_segments(self):
285 """Test for read_loadable_segments()"""
286 if not elf.ELF_TOOLS:
287 self.skipTest('Python elftools not available')
288 fname = self.ElfTestFile('embed_data')
289 segments, entry = elf.read_loadable_segments(tools.read_file(fname))
291 def test_read_segments_fail(self):
292 """Test for read_loadable_segments() without elftools"""
294 old_val = elf.ELF_TOOLS
295 elf.ELF_TOOLS = False
296 fname = self.ElfTestFile('embed_data')
297 with self.assertRaises(ValueError) as e:
298 elf.read_loadable_segments(tools.read_file(fname))
299 self.assertIn("Python: No module named 'elftools'",
302 elf.ELF_TOOLS = old_val
304 def test_read_segments_bad_data(self):
305 """Test for read_loadable_segments() with an invalid ELF file"""
306 if not elf.ELF_TOOLS:
307 self.skipTest('Python elftools not available')
308 fname = self.ElfTestFile('embed_data')
309 with self.assertRaises(ValueError) as e:
310 elf.read_loadable_segments(tools.get_bytes(100, 100))
311 self.assertIn('Magic number does not match', str(e.exception))
313 def test_get_file_offset(self):
314 """Test GetFileOffset() gives the correct file offset for a symbol"""
315 if not elf.ELF_TOOLS:
316 self.skipTest('Python elftools not available')
317 fname = self.ElfTestFile('embed_data')
318 syms = elf.GetSymbols(fname, ['embed'])
319 addr = syms['embed'].address
320 offset = elf.GetFileOffset(fname, addr)
321 data = tools.read_file(fname)
323 # Just use the first 4 bytes and assume it is little endian
324 embed_data = data[offset:offset + 4]
325 embed_value = struct.unpack('<I', embed_data)[0]
326 self.assertEqual(0x1234, embed_value)
328 def test_get_file_offset_fail(self):
329 """Test calling GetFileOffset() without elftools"""
331 old_val = elf.ELF_TOOLS
332 elf.ELF_TOOLS = False
333 fname = self.ElfTestFile('embed_data')
334 with self.assertRaises(ValueError) as e:
335 elf.GetFileOffset(fname, 0)
336 self.assertIn("Python: No module named 'elftools'",
339 elf.ELF_TOOLS = old_val
341 def test_get_symbol_from_address(self):
342 """Test GetSymbolFromAddress()"""
343 if not elf.ELF_TOOLS:
344 self.skipTest('Python elftools not available')
345 fname = self.ElfTestFile('elf_sections')
346 sym_name = 'calculate'
347 syms = elf.GetSymbols(fname, [sym_name])
348 addr = syms[sym_name].address
349 sym = elf.GetSymbolFromAddress(fname, addr)
350 self.assertEqual(sym_name, sym)
352 def test_get_symbol_from_address_fail(self):
353 """Test calling GetSymbolFromAddress() without elftools"""
355 old_val = elf.ELF_TOOLS
356 elf.ELF_TOOLS = False
357 fname = self.ElfTestFile('embed_data')
358 with self.assertRaises(ValueError) as e:
359 elf.GetSymbolFromAddress(fname, 0x1000)
360 self.assertIn("Python: No module named 'elftools'",
363 elf.ELF_TOOLS = old_val
365 def test_is_valid(self):
366 """Test is_valid()"""
367 self.assertEqual(False, elf.is_valid(b''))
368 self.assertEqual(False, elf.is_valid(b'1234'))
370 fname = self.ElfTestFile('elf_sections')
371 data = tools.read_file(fname)
372 self.assertEqual(True, elf.is_valid(data))
373 self.assertEqual(False, elf.is_valid(data[4:]))
375 def test_get_symbol_offset(self):
376 fname = self.ElfTestFile('embed_data')
377 syms = elf.GetSymbols(fname, ['embed_start', 'embed'])
378 expected = syms['embed'].address - syms['embed_start'].address
379 val = elf.GetSymbolOffset(fname, 'embed', 'embed_start')
380 self.assertEqual(expected, val)
382 with self.assertRaises(KeyError) as e:
383 elf.GetSymbolOffset(fname, 'embed')
384 self.assertIn('__image_copy_start', str(e.exception))
386 def test_get_symbol_address(self):
387 fname = self.ElfTestFile('embed_data')
388 addr = elf.GetSymbolAddress(fname, 'region_size')
389 self.assertEqual(0, addr)
392 if __name__ == '__main__':