blob: ac69a95b6547c13c761c35a5782b435744dae2b1 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassb50e5612017-11-13 18:54:54 -07002# Copyright (c) 2017 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glassb50e5612017-11-13 18:54:54 -07005# Test for the elf module
6
7import os
Simon Glassf58558a2019-07-08 13:18:34 -06008import shutil
Simon Glass056f0ef2021-11-03 21:09:16 -06009import struct
Simon Glassb50e5612017-11-13 18:54:54 -070010import sys
Simon Glassf58558a2019-07-08 13:18:34 -060011import tempfile
Simon Glassb50e5612017-11-13 18:54:54 -070012import unittest
13
Simon Glass16287932020-04-17 18:09:03 -060014from binman import elf
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import command
16from patman import test_util
17from patman import tools
18from patman import tout
Simon Glassb50e5612017-11-13 18:54:54 -070019
20binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
Simon Glass19790632017-11-13 18:55:01 -070021
Simon Glass19790632017-11-13 18:55:01 -070022
23class FakeEntry:
Simon Glassb2b0df82018-07-17 13:25:26 -060024 """A fake Entry object, usedfor testing
25
26 This supports an entry with a given size.
27 """
Simon Glass19790632017-11-13 18:55:01 -070028 def __init__(self, contents_size):
29 self.contents_size = contents_size
Simon Glassc6c10e72019-05-17 22:00:46 -060030 self.data = tools.GetBytes(ord('a'), contents_size)
Simon Glass19790632017-11-13 18:55:01 -070031
32 def GetPath(self):
33 return 'entry_path'
34
Simon Glassb2b0df82018-07-17 13:25:26 -060035
Simon Glassf55382b2018-06-01 09:38:13 -060036class FakeSection:
Simon Glassb2b0df82018-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 Glass19790632017-11-13 18:55:01 -070043 def __init__(self, sym_value=1):
44 self.sym_value = sym_value
45
46 def GetPath(self):
Simon Glassf55382b2018-06-01 09:38:13 -060047 return 'section_path'
Simon Glass19790632017-11-13 18:55:01 -070048
Simon Glass870a9ea2021-01-06 21:35:15 -070049 def LookupImageSymbol(self, name, weak, msg, base_addr):
Simon Glassb2b0df82018-07-17 13:25:26 -060050 """Fake implementation which returns the same value for all symbols"""
Simon Glass19790632017-11-13 18:55:01 -070051 return self.sym_value
Simon Glassb50e5612017-11-13 18:54:54 -070052
Simon Glass870a9ea2021-01-06 21:35:15 -070053 def GetImage(self):
54 return self
Simon Glassb2b0df82018-07-17 13:25:26 -060055
Simon Glass53e22bf2019-08-24 07:22:53 -060056def 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 the
60 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']
Simon Glass2fb2cd72021-11-03 21:09:15 -060074 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)
Simon Glass53e22bf2019-08-24 07:22:53 -060080
81
Simon Glassb50e5612017-11-13 18:54:54 -070082class TestElf(unittest.TestCase):
Simon Glasse0e62752018-10-01 21:12:41 -060083 @classmethod
Simon Glassf514d8f2019-08-24 07:22:54 -060084 def setUpClass(cls):
85 cls._indir = tempfile.mkdtemp(prefix='elf.')
Simon Glasse0e62752018-10-01 21:12:41 -060086 tools.SetInputDirs(['.'])
Simon Glassf514d8f2019-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 Glasse0e62752018-10-01 21:12:41 -060097
Simon Glassb50e5612017-11-13 18:54:54 -070098 def testAllSymbols(self):
Simon Glassb2b0df82018-07-17 13:25:26 -060099 """Test that we can obtain a symbol from the ELF file"""
Simon Glassf514d8f2019-08-24 07:22:54 -0600100 fname = self.ElfTestFile('u_boot_ucode_ptr')
Simon Glassb50e5612017-11-13 18:54:54 -0700101 syms = elf.GetSymbols(fname, [])
102 self.assertIn('.ucode', syms)
103
104 def testRegexSymbols(self):
Simon Glassb2b0df82018-07-17 13:25:26 -0600105 """Test that we can obtain from the ELF file by regular expression"""
Simon Glassf514d8f2019-08-24 07:22:54 -0600106 fname = self.ElfTestFile('u_boot_ucode_ptr')
Simon Glassb50e5612017-11-13 18:54:54 -0700107 syms = elf.GetSymbols(fname, ['ucode'])
108 self.assertIn('.ucode', syms)
109 syms = elf.GetSymbols(fname, ['missing'])
110 self.assertNotIn('.ucode', syms)
111 syms = elf.GetSymbols(fname, ['missing', 'ucode'])
112 self.assertIn('.ucode', syms)
113
Simon Glass19790632017-11-13 18:55:01 -0700114 def testMissingFile(self):
Simon Glassb2b0df82018-07-17 13:25:26 -0600115 """Test that a missing file is detected"""
Simon Glass19790632017-11-13 18:55:01 -0700116 entry = FakeEntry(10)
Simon Glassf55382b2018-06-01 09:38:13 -0600117 section = FakeSection()
Simon Glass19790632017-11-13 18:55:01 -0700118 with self.assertRaises(ValueError) as e:
Simon Glassf55382b2018-06-01 09:38:13 -0600119 syms = elf.LookupAndWriteSymbols('missing-file', entry, section)
Simon Glass19790632017-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 Glassb2b0df82018-07-17 13:25:26 -0600124 """Test a symbol which extends outside the entry area is detected"""
Simon Glass19790632017-11-13 18:55:01 -0700125 entry = FakeEntry(10)
Simon Glassf55382b2018-06-01 09:38:13 -0600126 section = FakeSection()
Simon Glass1542c8b2019-08-24 07:22:56 -0600127 elf_fname = self.ElfTestFile('u_boot_binman_syms')
Simon Glass19790632017-11-13 18:55:01 -0700128 with self.assertRaises(ValueError) as e:
Simon Glassf55382b2018-06-01 09:38:13 -0600129 syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
Simon Glass19790632017-11-13 18:55:01 -0700130 self.assertIn('entry_path has offset 4 (size 8) but the contents size '
131 'is a', str(e.exception))
132
133 def testMissingImageStart(self):
Simon Glassb2b0df82018-07-17 13:25:26 -0600134 """Test that we detect a missing __image_copy_start symbol
135
136 This is needed to mark the start of the image. Without it we cannot
137 locate the offset of a binman symbol within the image.
138 """
Simon Glass19790632017-11-13 18:55:01 -0700139 entry = FakeEntry(10)
Simon Glassf55382b2018-06-01 09:38:13 -0600140 section = FakeSection()
Simon Glass8dc60f92019-08-24 07:22:58 -0600141 elf_fname = self.ElfTestFile('u_boot_binman_syms_bad')
Simon Glassf55382b2018-06-01 09:38:13 -0600142 self.assertEqual(elf.LookupAndWriteSymbols(elf_fname, entry, section),
Simon Glass19790632017-11-13 18:55:01 -0700143 None)
144
145 def testBadSymbolSize(self):
Simon Glassb2b0df82018-07-17 13:25:26 -0600146 """Test that an attempt to use an 8-bit symbol are detected
147
148 Only 32 and 64 bits are supported, since we need to store an offset
149 into the image.
150 """
Simon Glass19790632017-11-13 18:55:01 -0700151 entry = FakeEntry(10)
Simon Glassf55382b2018-06-01 09:38:13 -0600152 section = FakeSection()
Simon Glasse9d2ee32019-08-24 07:22:57 -0600153 elf_fname =self.ElfTestFile('u_boot_binman_syms_size')
Simon Glass19790632017-11-13 18:55:01 -0700154 with self.assertRaises(ValueError) as e:
Simon Glassf55382b2018-06-01 09:38:13 -0600155 syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
Simon Glass19790632017-11-13 18:55:01 -0700156 self.assertIn('has size 1: only 4 and 8 are supported',
157 str(e.exception))
158
159 def testNoValue(self):
Simon Glassb2b0df82018-07-17 13:25:26 -0600160 """Test the case where we have no value for the symbol
161
162 This should produce -1 values for all thress symbols, taking up the
163 first 16 bytes of the image.
164 """
Simon Glassb87064c2019-08-24 07:23:05 -0600165 entry = FakeEntry(24)
Simon Glassf55382b2018-06-01 09:38:13 -0600166 section = FakeSection(sym_value=None)
Simon Glass1542c8b2019-08-24 07:22:56 -0600167 elf_fname = self.ElfTestFile('u_boot_binman_syms')
Simon Glassf55382b2018-06-01 09:38:13 -0600168 syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
Simon Glassb87064c2019-08-24 07:23:05 -0600169 self.assertEqual(tools.GetBytes(255, 20) + tools.GetBytes(ord('a'), 4),
Simon Glassc6c10e72019-05-17 22:00:46 -0600170 entry.data)
Simon Glass19790632017-11-13 18:55:01 -0700171
172 def testDebug(self):
Simon Glassb2b0df82018-07-17 13:25:26 -0600173 """Check that enabling debug in the elf module produced debug output"""
Simon Glass9f297b02019-07-20 12:23:36 -0600174 try:
175 tout.Init(tout.DEBUG)
176 entry = FakeEntry(20)
177 section = FakeSection()
Simon Glass1542c8b2019-08-24 07:22:56 -0600178 elf_fname = self.ElfTestFile('u_boot_binman_syms')
Simon Glass9f297b02019-07-20 12:23:36 -0600179 with test_util.capture_sys_output() as (stdout, stderr):
180 syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
181 self.assertTrue(len(stdout.getvalue()) > 0)
182 finally:
183 tout.Init(tout.WARNING)
Simon Glass19790632017-11-13 18:55:01 -0700184
Simon Glassf58558a2019-07-08 13:18:34 -0600185 def testMakeElf(self):
186 """Test for the MakeElf function"""
187 outdir = tempfile.mkdtemp(prefix='elf.')
188 expected_text = b'1234'
189 expected_data = b'wxyz'
190 elf_fname = os.path.join(outdir, 'elf')
Simon Glass9d44a7e2019-08-24 07:22:45 -0600191 bin_fname = os.path.join(outdir, 'bin')
Simon Glassf58558a2019-07-08 13:18:34 -0600192
193 # Make an Elf file and then convert it to a fkat binary file. This
194 # should produce the original data.
195 elf.MakeElf(elf_fname, expected_text, expected_data)
Alper Nebi Yasak1e4687a2020-09-06 14:46:05 +0300196 objcopy, args = tools.GetTargetCompileTool('objcopy')
197 args += ['-O', 'binary', elf_fname, bin_fname]
198 stdout = command.Output(objcopy, *args)
Simon Glassf58558a2019-07-08 13:18:34 -0600199 with open(bin_fname, 'rb') as fd:
200 data = fd.read()
201 self.assertEqual(expected_text + expected_data, data)
202 shutil.rmtree(outdir)
203
Simon Glassd8d40742019-07-08 13:18:35 -0600204 def testDecodeElf(self):
205 """Test for the MakeElf function"""
206 if not elf.ELF_TOOLS:
207 self.skipTest('Python elftools not available')
208 outdir = tempfile.mkdtemp(prefix='elf.')
209 expected_text = b'1234'
210 expected_data = b'wxyz'
211 elf_fname = os.path.join(outdir, 'elf')
212 elf.MakeElf(elf_fname, expected_text, expected_data)
213 data = tools.ReadFile(elf_fname)
214
215 load = 0xfef20000
216 entry = load + 2
217 expected = expected_text + expected_data
218 self.assertEqual(elf.ElfInfo(expected, load, entry, len(expected)),
219 elf.DecodeElf(data, 0))
220 self.assertEqual(elf.ElfInfo(b'\0\0' + expected[2:],
221 load, entry, len(expected)),
222 elf.DecodeElf(data, load + 2))
Simon Glassf514d8f2019-08-24 07:22:54 -0600223 shutil.rmtree(outdir)
Simon Glassd8d40742019-07-08 13:18:35 -0600224
Simon Glass056f0ef2021-11-03 21:09:16 -0600225 def testEmbedData(self):
226 """Test for the GetSymbolFileOffset() function"""
227 if not elf.ELF_TOOLS:
228 self.skipTest('Python elftools not available')
229
230 fname = self.ElfTestFile('embed_data')
231 offset = elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end'])
232 start = offset['embed_start'].offset
233 end = offset['embed_end'].offset
234 data = tools.ReadFile(fname)
235 embed_data = data[start:end]
236 expect = struct.pack('<III', 0x1234, 0x5678, 0)
237 self.assertEqual(expect, embed_data)
238
239 def testEmbedFail(self):
240 """Test calling GetSymbolFileOffset() without elftools"""
241 try:
242 old_val = elf.ELF_TOOLS
243 elf.ELF_TOOLS = False
244 fname = self.ElfTestFile('embed_data')
245 with self.assertRaises(ValueError) as e:
246 elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end'])
247 self.assertIn('Python elftools package is not available',
248 str(e.exception))
249 finally:
250 elf.ELF_TOOLS = old_val
251
252 def testEmbedDataNoSym(self):
253 """Test for GetSymbolFileOffset() getting no symbols"""
254 if not elf.ELF_TOOLS:
255 self.skipTest('Python elftools not available')
256
257 fname = self.ElfTestFile('embed_data')
258 offset = elf.GetSymbolFileOffset(fname, ['missing_sym'])
259 self.assertEqual({}, offset)
260
Simon Glass19790632017-11-13 18:55:01 -0700261
Simon Glassb50e5612017-11-13 18:54:54 -0700262if __name__ == '__main__':
263 unittest.main()