Prepare v2023.10
[platform/kernel/u-boot.git] / tools / binman / elf_test.py
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
7 import os
8 import shutil
9 import struct
10 import sys
11 import tempfile
12 import unittest
13
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
19
20 binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
21
22
23 class 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
36 class 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
56 def 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
82 class 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         count = elf.LookupAndWriteSymbols(elf_fname, entry, section)
145         self.assertEqual(0, count)
146
147     def testBadSymbolSize(self):
148         """Test that an attempt to use an 8-bit symbol are detected
149
150         Only 32 and 64 bits are supported, since we need to store an offset
151         into the image.
152         """
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',
161                       str(e.exception))
162
163     def testNoValue(self):
164         """Test the case where we have no value for the symbol
165
166         This should produce -1 values for all three symbols, taking up the
167         first 16 bytes of the image.
168         """
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)
180
181     def testDebug(self):
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')
185         try:
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)
193         finally:
194             tout.init(tout.WARNING)
195
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')
203
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:
211             data = fd.read()
212         self.assertEqual(expected_text + expected_data, data)
213         shutil.rmtree(outdir)
214
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)
225
226         load = 0xfef20000
227         entry = load + 2
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)
235
236     def testEmbedData(self):
237         """Test for the GetSymbolFileOffset() function"""
238         if not elf.ELF_TOOLS:
239             self.skipTest('Python elftools not available')
240
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)
249
250     def testEmbedFail(self):
251         """Test calling GetSymbolFileOffset() without elftools"""
252         try:
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)
268
269             self.assertIn(
270                 "Section 'section_path': entry 'entry_path': Cannot write symbols to an ELF file without Python elftools",
271                 str(e.exception))
272         finally:
273             elf.ELF_TOOLS = old_val
274
275     def testEmbedDataNoSym(self):
276         """Test for GetSymbolFileOffset() getting no symbols"""
277         if not elf.ELF_TOOLS:
278             self.skipTest('Python elftools not available')
279
280         fname = self.ElfTestFile('embed_data')
281         offset = elf.GetSymbolFileOffset(fname, ['missing_sym'])
282         self.assertEqual({}, offset)
283
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))
290
291     def test_read_segments_fail(self):
292         """Test for read_loadable_segments() without elftools"""
293         try:
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'",
300                           str(e.exception))
301         finally:
302             elf.ELF_TOOLS = old_val
303
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))
312
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)
322
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)
327
328     def test_get_file_offset_fail(self):
329         """Test calling GetFileOffset() without elftools"""
330         try:
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'",
337                       str(e.exception))
338         finally:
339             elf.ELF_TOOLS = old_val
340
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)
351
352     def test_get_symbol_from_address_fail(self):
353         """Test calling GetSymbolFromAddress() without elftools"""
354         try:
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'",
361                           str(e.exception))
362         finally:
363             elf.ELF_TOOLS = old_val
364
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'))
369
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:]))
374
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)
381
382         with self.assertRaises(KeyError) as e:
383             elf.GetSymbolOffset(fname, 'embed')
384         self.assertIn('__image_copy_start', str(e.exception))
385
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)
390
391
392 if __name__ == '__main__':
393     unittest.main()