Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_tokenizer / py / elf_reader_test.py
1 #!/usr/bin/env python3
2 # Copyright 2020 The Pigweed Authors
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 # use this file except in compliance with the License. You may obtain a copy of
6 # the License at
7 #
8 #     https://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations under
14 # the License.
15 """Tests the ELF reader Python module."""
16
17 import io
18 import os
19 import re
20 import unittest
21
22 from pw_tokenizer import elf_reader
23
24 # Output from the following command:
25 #
26 #   readelf -WS elf_reader_test_binary.elf
27 #
28 TEST_READELF_OUTPUT = ("""
29 There are 33 section headers, starting at offset 0x1758:
30
31 Section Headers:
32   [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
33   [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
34   [ 1] .interp           PROGBITS        0000000000000238 000238 00001c 00   A  0   0  1
35   [ 2] .note.ABI-tag     NOTE            0000000000000254 000254 000020 00   A  0   0  4
36   [ 3] .note.gnu.build-id NOTE            0000000000000274 000274 000024 00   A  0   0  4
37   [ 4] .dynsym           DYNSYM          0000000000000298 000298 0000a8 18   A  5   1  8
38   [ 5] .dynstr           STRTAB          0000000000000340 000340 00009b 00   A  0   0  1
39   [ 6] .gnu.hash         GNU_HASH        00000000000003e0 0003e0 00001c 00   A  4   0  8
40   [ 7] .gnu.version      VERSYM          00000000000003fc 0003fc 00000e 02   A  4   0  2
41   [ 8] .gnu.version_r    VERNEED         000000000000040c 00040c 000020 00   A  5   1  4
42   [ 9] .rela.dyn         RELA            0000000000000430 000430 0000d8 18   A  4   0  8
43   [10] .rela.plt         RELA            0000000000000508 000508 000018 18  AI  4  12  8
44   [11] .init             PROGBITS        0000000000000520 000520 000017 00  AX  0   0  4
45   [12] .plt              PROGBITS        0000000000000540 000540 000020 10  AX  0   0 16
46   [13] .text             PROGBITS        0000000000000560 000560 000151 00  AX  0   0 16
47   [14] .fini             PROGBITS        00000000000006b4 0006b4 000009 00  AX  0   0  4
48   [15] .rodata           PROGBITS        00000000000006c0 0006c0 000004 04  AM  0   0  4
49   [16] .test_section_1   PROGBITS        00000000000006d0 0006d0 000010 00   A  0   0 16
50   [17] .test_section_2   PROGBITS        00000000000006e0 0006e0 000004 00   A  0   0  4
51   [18] .eh_frame         X86_64_UNWIND   00000000000006e8 0006e8 0000d4 00   A  0   0  8
52   [19] .eh_frame_hdr     X86_64_UNWIND   00000000000007bc 0007bc 00002c 00   A  0   0  4
53   [20] .fini_array       FINI_ARRAY      0000000000001d80 000d80 000008 08  WA  0   0  8
54   [21] .init_array       INIT_ARRAY      0000000000001d88 000d88 000008 08  WA  0   0  8
55   [22] .dynamic          DYNAMIC         0000000000001d90 000d90 000220 10  WA  5   0  8
56   [23] .got              PROGBITS        0000000000001fb0 000fb0 000030 00  WA  0   0  8
57   [24] .got.plt          PROGBITS        0000000000001fe0 000fe0 000020 00  WA  0   0  8
58   [25] .data             PROGBITS        0000000000002000 001000 000010 00  WA  0   0  8
59   [26] .tm_clone_table   PROGBITS        0000000000002010 001010 000000 00  WA  0   0  8
60   [27] .bss              NOBITS          0000000000002010 001010 000001 00  WA  0   0  1
61   [28] .comment          PROGBITS        0000000000000000 001010 00001d 01  MS  0   0  1
62   [29] .note.gnu.gold-version NOTE            0000000000000000 001030 00001c 00      0   0  4
63   [30] .symtab           SYMTAB          0000000000000000 001050 000390 18     31  21  8
64   [31] .strtab           STRTAB          0000000000000000 0013e0 000227 00      0   0  1
65   [32] .shstrtab         STRTAB          0000000000000000 001607 00014a 00      0   0  1
66 Key to Flags:
67   W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
68   L (link order), O (extra OS processing required), G (group), T (TLS),
69   C (compressed), x (unknown), o (OS specific), E (exclude),
70   l (large), p (processor specific)
71 """)
72
73 TEST_ELF_PATH = os.path.join(os.path.dirname(__file__),
74                              'elf_reader_test_binary.elf')
75
76
77 class ElfReaderTest(unittest.TestCase):
78     """Tests the elf_reader.Elf class."""
79     def setUp(self):
80         super().setUp()
81         self._elf_file = open(TEST_ELF_PATH, 'rb')
82         self._elf = elf_reader.Elf(self._elf_file)
83
84     def tearDown(self):
85         super().tearDown()
86         self._elf_file.close()
87
88     def _section(self, name):
89         return next(self._elf.sections_with_name(name))
90
91     def test_readelf_comparison_using_the_readelf_binary(self):
92         """Compares elf_reader to readelf's output."""
93
94         parse_readelf_output = re.compile(r'\s+'
95                                           r'\[\s*(?P<number>\d+)\]\s+'
96                                           r'(?P<name>\.\S*)?\s+'
97                                           r'(?P<type>\S+)\s+'
98                                           r'(?P<addr>[0-9a-fA-F]+)\s+'
99                                           r'(?P<offset>[0-9a-fA-F]+)\s+'
100                                           r'(?P<size>[0-9a-fA-F]+)\s+')
101
102         readelf_sections = []
103         for number, name, _, addr, offset, size in parse_readelf_output.findall(
104                 TEST_READELF_OUTPUT):
105             readelf_sections.append((
106                 int(number),
107                 name or '',
108                 int(addr, 16),
109                 int(offset, 16),
110                 int(size, 16),
111             ))
112
113         self.assertEqual(len(readelf_sections), 33)
114         self.assertEqual(len(readelf_sections), len(self._elf.sections))
115
116         for (index,
117              section), readelf_section in zip(enumerate(self._elf.sections),
118                                               readelf_sections):
119             readelf_index, name, address, offset, size = readelf_section
120
121             self.assertEqual(index, readelf_index)
122             self.assertEqual(section.name, name)
123             self.assertEqual(section.address, address)
124             self.assertEqual(section.offset, offset)
125             self.assertEqual(section.size, size)
126
127     def test_dump_single_section(self):
128         self.assertEqual(self._elf.dump_section_contents(r'\.test_section_1'),
129                          b'You cannot pass\0')
130         self.assertEqual(self._elf.dump_section_contents(r'\.test_section_2'),
131                          b'\xef\xbe\xed\xfe')
132
133     def test_dump_multiple_sections(self):
134         if (self._section('.test_section_1').address <
135                 self._section('.test_section_2').address):
136             contents = b'You cannot pass\0\xef\xbe\xed\xfe'
137         else:
138             contents = b'\xef\xbe\xed\xfeYou cannot pass\0'
139
140         self.assertIn(self._elf.dump_section_contents(r'.test_section_\d'),
141                       contents)
142
143     def test_read_values(self):
144         address = self._section('.test_section_1').address
145         self.assertEqual(self._elf.read_value(address), b'You cannot pass')
146
147         int32_address = self._section('.test_section_2').address
148         self.assertEqual(self._elf.read_value(int32_address, 4),
149                          b'\xef\xbe\xed\xfe')
150
151     def test_read_string(self):
152         bytes_io = io.BytesIO(
153             b'This is a null-terminated string\0No terminator!')
154         self.assertEqual(elf_reader.read_c_string(bytes_io),
155                          b'This is a null-terminated string')
156         self.assertEqual(elf_reader.read_c_string(bytes_io), b'No terminator!')
157         self.assertEqual(elf_reader.read_c_string(bytes_io), b'')
158
159     def test_compatible_file_for_elf(self):
160         self.assertTrue(elf_reader.compatible_file(self._elf_file))
161         self.assertTrue(elf_reader.compatible_file(io.BytesIO(b'\x7fELF')))
162
163     def test_compatible_file_for_elf_start_at_offset(self):
164         self._elf_file.seek(13)  # Seek ahead to get out of sync
165         self.assertTrue(elf_reader.compatible_file(self._elf_file))
166         self.assertEqual(13, self._elf_file.tell())
167
168     def test_compatible_file_for_invalid_elf(self):
169         self.assertFalse(elf_reader.compatible_file(io.BytesIO(b'\x7fELVESF')))
170
171
172 def _archive_file(data: bytes) -> bytes:
173     return ('FILE ID 90123456'
174             'MODIFIED 012'
175             'OWNER '
176             'GROUP '
177             'MODE 678'
178             f'{len(data):10}'  # File size -- the only part that's needed.
179             '`\n'.encode() + data)
180
181
182 class ArchiveTest(unittest.TestCase):
183     """Tests reading from archive files."""
184     def setUp(self):
185         super().setUp()
186
187         with open(TEST_ELF_PATH, 'rb') as fd:
188             self._elf_data = fd.read()
189
190         self._archive_entries = b'blah', b'hello', self._elf_data
191
192         self._archive_data = elf_reader.ARCHIVE_MAGIC + b''.join(
193             _archive_file(f) for f in self._archive_entries)
194         self._archive = io.BytesIO(self._archive_data)
195
196     def test_compatible_file_for_archive(self):
197         self.assertTrue(elf_reader.compatible_file(io.BytesIO(b'!<arch>\n')))
198         self.assertTrue(elf_reader.compatible_file(self._archive))
199
200     def test_compatible_file_for_invalid_archive(self):
201         self.assertFalse(elf_reader.compatible_file(io.BytesIO(b'!<arch>')))
202
203     def test_iterate_over_files(self):
204         for expected, size in zip(self._archive_entries,
205                                   elf_reader.files_in_archive(self._archive)):
206             self.assertEqual(expected, self._archive.read(size))
207
208     def test_iterate_over_empty_archive(self):
209         with self.assertRaises(StopIteration):
210             next(iter(elf_reader.files_in_archive(io.BytesIO(b'!<arch>\n'))))
211
212     def test_iterate_over_invalid_archive(self):
213         with self.assertRaises(elf_reader.FileDecodeError):
214             for _ in elf_reader.files_in_archive(
215                     io.BytesIO(b'!<arch>blah blahblah')):
216                 pass
217
218     def test_extra_newline_after_entry_is_ignored(self):
219         archive = io.BytesIO(elf_reader.ARCHIVE_MAGIC +
220                              _archive_file(self._elf_data) + b'\n' +
221                              _archive_file(self._elf_data))
222
223         for size in elf_reader.files_in_archive(archive):
224             self.assertEqual(self._elf_data, archive.read(size))
225
226     def test_two_extra_newlines_parsing_fails(self):
227         archive = io.BytesIO(elf_reader.ARCHIVE_MAGIC +
228                              _archive_file(self._elf_data) + b'\n\n' +
229                              _archive_file(self._elf_data))
230
231         with self.assertRaises(elf_reader.FileDecodeError):
232             for size in elf_reader.files_in_archive(archive):
233                 self.assertEqual(self._elf_data, archive.read(size))
234
235     def test_iterate_over_archive_with_invalid_size(self):
236         data = elf_reader.ARCHIVE_MAGIC + _archive_file(b'$' * 3210)
237         file = io.BytesIO(data)
238
239         # Iterate over the file normally.
240         for size in elf_reader.files_in_archive(file):
241             self.assertEqual(b'$' * 3210, file.read(size))
242
243         # Replace the size with a hex number, which is not valid.
244         with self.assertRaises(elf_reader.FileDecodeError):
245             for _ in elf_reader.files_in_archive(
246                     io.BytesIO(data.replace(b'3210', b'0x99'))):
247                 pass
248
249     def test_elf_reader_dump_single_section(self):
250         elf = elf_reader.Elf(self._archive)
251         self.assertEqual(elf.dump_section_contents(r'\.test_section_1'),
252                          b'You cannot pass\0')
253         self.assertEqual(elf.dump_section_contents(r'\.test_section_2'),
254                          b'\xef\xbe\xed\xfe')
255
256     def test_elf_reader_read_values(self):
257         elf = elf_reader.Elf(self._archive)
258         address = next(elf.sections_with_name('.test_section_1')).address
259         self.assertEqual(elf.read_value(address), b'You cannot pass')
260
261         int32_address = next(elf.sections_with_name('.test_section_2')).address
262         self.assertEqual(elf.read_value(int32_address, 4), b'\xef\xbe\xed\xfe')
263
264
265 if __name__ == '__main__':
266     unittest.main()