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 sys
10import tempfile
11import unittest
12
13from binman import elf
14from patman import command
15from patman import test_util
16from patman import tools
17from patman import tout
18
19binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
20
21
22class FakeEntry:
23    """A fake Entry object, usedfor testing
24
25    This supports an entry with a given size.
26    """
27    def __init__(self, contents_size):
28        self.contents_size = contents_size
29        self.data = tools.GetBytes(ord('a'), contents_size)
30
31    def GetPath(self):
32        return 'entry_path'
33
34
35class FakeSection:
36    """A fake Section object, used for testing
37
38    This has the minimum feature set needed to support testing elf functions.
39    A LookupSymbol() function is provided which returns a fake value for amu
40    symbol requested.
41    """
42    def __init__(self, sym_value=1):
43        self.sym_value = sym_value
44
45    def GetPath(self):
46        return 'section_path'
47
48    def LookupImageSymbol(self, name, weak, msg, base_addr):
49        """Fake implementation which returns the same value for all symbols"""
50        return self.sym_value
51
52    def GetImage(self):
53        return self
54
55def BuildElfTestFiles(target_dir):
56    """Build ELF files used for testing in binman
57
58    This compiles and links the test files into the specified directory. It the
59    Makefile and source files in the binman test/ directory.
60
61    Args:
62        target_dir: Directory to put the files into
63    """
64    if not os.path.exists(target_dir):
65        os.mkdir(target_dir)
66    testdir = os.path.join(binman_dir, 'test')
67
68    # If binman is involved from the main U-Boot Makefile the -r and -R
69    # flags are set in MAKEFLAGS. This prevents this Makefile from working
70    # correctly. So drop any make flags here.
71    if 'MAKEFLAGS' in os.environ:
72        del os.environ['MAKEFLAGS']
73    tools.Run('make', '-C', target_dir, '-f',
74              os.path.join(testdir, 'Makefile'), 'SRC=%s/' % testdir)
75
76
77class TestElf(unittest.TestCase):
78    @classmethod
79    def setUpClass(cls):
80        cls._indir = tempfile.mkdtemp(prefix='elf.')
81        tools.SetInputDirs(['.'])
82        BuildElfTestFiles(cls._indir)
83
84    @classmethod
85    def tearDownClass(cls):
86        if cls._indir:
87            shutil.rmtree(cls._indir)
88
89    @classmethod
90    def ElfTestFile(cls, fname):
91        return os.path.join(cls._indir, fname)
92
93    def testAllSymbols(self):
94        """Test that we can obtain a symbol from the ELF file"""
95        fname = self.ElfTestFile('u_boot_ucode_ptr')
96        syms = elf.GetSymbols(fname, [])
97        self.assertIn('.ucode', syms)
98
99    def testRegexSymbols(self):
100        """Test that we can obtain from the ELF file by regular expression"""
101        fname = self.ElfTestFile('u_boot_ucode_ptr')
102        syms = elf.GetSymbols(fname, ['ucode'])
103        self.assertIn('.ucode', syms)
104        syms = elf.GetSymbols(fname, ['missing'])
105        self.assertNotIn('.ucode', syms)
106        syms = elf.GetSymbols(fname, ['missing', 'ucode'])
107        self.assertIn('.ucode', syms)
108
109    def testMissingFile(self):
110        """Test that a missing file is detected"""
111        entry = FakeEntry(10)
112        section = FakeSection()
113        with self.assertRaises(ValueError) as e:
114            syms = elf.LookupAndWriteSymbols('missing-file', entry, section)
115        self.assertIn("Filename 'missing-file' not found in input path",
116                      str(e.exception))
117
118    def testOutsideFile(self):
119        """Test a symbol which extends outside the entry area is detected"""
120        entry = FakeEntry(10)
121        section = FakeSection()
122        elf_fname = self.ElfTestFile('u_boot_binman_syms')
123        with self.assertRaises(ValueError) as e:
124            syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
125        self.assertIn('entry_path has offset 4 (size 8) but the contents size '
126                      'is a', str(e.exception))
127
128    def testMissingImageStart(self):
129        """Test that we detect a missing __image_copy_start symbol
130
131        This is needed to mark the start of the image. Without it we cannot
132        locate the offset of a binman symbol within the image.
133        """
134        entry = FakeEntry(10)
135        section = FakeSection()
136        elf_fname = self.ElfTestFile('u_boot_binman_syms_bad')
137        self.assertEqual(elf.LookupAndWriteSymbols(elf_fname, entry, section),
138                         None)
139
140    def testBadSymbolSize(self):
141        """Test that an attempt to use an 8-bit symbol are detected
142
143        Only 32 and 64 bits are supported, since we need to store an offset
144        into the image.
145        """
146        entry = FakeEntry(10)
147        section = FakeSection()
148        elf_fname =self.ElfTestFile('u_boot_binman_syms_size')
149        with self.assertRaises(ValueError) as e:
150            syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
151        self.assertIn('has size 1: only 4 and 8 are supported',
152                      str(e.exception))
153
154    def testNoValue(self):
155        """Test the case where we have no value for the symbol
156
157        This should produce -1 values for all thress symbols, taking up the
158        first 16 bytes of the image.
159        """
160        entry = FakeEntry(24)
161        section = FakeSection(sym_value=None)
162        elf_fname = self.ElfTestFile('u_boot_binman_syms')
163        syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
164        self.assertEqual(tools.GetBytes(255, 20) + tools.GetBytes(ord('a'), 4),
165                                                                  entry.data)
166
167    def testDebug(self):
168        """Check that enabling debug in the elf module produced debug output"""
169        try:
170            tout.Init(tout.DEBUG)
171            entry = FakeEntry(20)
172            section = FakeSection()
173            elf_fname = self.ElfTestFile('u_boot_binman_syms')
174            with test_util.capture_sys_output() as (stdout, stderr):
175                syms = elf.LookupAndWriteSymbols(elf_fname, entry, section)
176            self.assertTrue(len(stdout.getvalue()) > 0)
177        finally:
178            tout.Init(tout.WARNING)
179
180    def testMakeElf(self):
181        """Test for the MakeElf function"""
182        outdir = tempfile.mkdtemp(prefix='elf.')
183        expected_text = b'1234'
184        expected_data = b'wxyz'
185        elf_fname = os.path.join(outdir, 'elf')
186        bin_fname = os.path.join(outdir, 'bin')
187
188        # Make an Elf file and then convert it to a fkat binary file. This
189        # should produce the original data.
190        elf.MakeElf(elf_fname, expected_text, expected_data)
191        objcopy, args = tools.GetTargetCompileTool('objcopy')
192        args += ['-O', 'binary', elf_fname, bin_fname]
193        stdout = command.Output(objcopy, *args)
194        with open(bin_fname, 'rb') as fd:
195            data = fd.read()
196        self.assertEqual(expected_text + expected_data, data)
197        shutil.rmtree(outdir)
198
199    def testDecodeElf(self):
200        """Test for the MakeElf function"""
201        if not elf.ELF_TOOLS:
202            self.skipTest('Python elftools not available')
203        outdir = tempfile.mkdtemp(prefix='elf.')
204        expected_text = b'1234'
205        expected_data = b'wxyz'
206        elf_fname = os.path.join(outdir, 'elf')
207        elf.MakeElf(elf_fname, expected_text, expected_data)
208        data = tools.ReadFile(elf_fname)
209
210        load = 0xfef20000
211        entry = load + 2
212        expected = expected_text + expected_data
213        self.assertEqual(elf.ElfInfo(expected, load, entry, len(expected)),
214                         elf.DecodeElf(data, 0))
215        self.assertEqual(elf.ElfInfo(b'\0\0' + expected[2:],
216                                     load, entry, len(expected)),
217                         elf.DecodeElf(data, load + 2))
218        shutil.rmtree(outdir)
219
220
221if __name__ == '__main__':
222    unittest.main()
223