blob: 3e2560de7b6d5bb445552fda7d9014b6398f53bb [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2013 The ChromiumOS Authors
Mike Frysinger69cb41d2013-08-11 20:08:19 -04002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Don Garrett25f309a2014-03-19 14:02:12 -07005"""Test cros_generate_breakpad_symbols."""
6
Mike Frysinger69cb41d2013-08-11 20:08:19 -04007import ctypes
Mike Frysinger374ba4f2019-11-14 23:45:15 -05008import io
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -08009import logging
10import multiprocessing
Mike Frysinger69cb41d2013-08-11 20:08:19 -040011import os
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -080012import pathlib
Mike Frysinger166fea02021-02-12 05:30:33 -050013from unittest import mock
Mike Frysinger69cb41d2013-08-11 20:08:19 -040014
Mike Frysinger69cb41d2013-08-11 20:08:19 -040015from chromite.lib import cros_test_lib
16from chromite.lib import osutils
17from chromite.lib import parallel
18from chromite.lib import parallel_unittest
19from chromite.lib import partial_mock
20from chromite.scripts import cros_generate_breakpad_symbols
21
Mike Frysinger69cb41d2013-08-11 20:08:19 -040022
23class FindDebugDirMock(partial_mock.PartialMock):
Alex Klein1699fab2022-09-08 08:46:06 -060024 """Mock out the DebugDir helper so we can point it to a tempdir."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040025
Alex Klein1699fab2022-09-08 08:46:06 -060026 TARGET = "chromite.scripts.cros_generate_breakpad_symbols"
27 ATTRS = ("FindDebugDir",)
28 DEFAULT_ATTR = "FindDebugDir"
Mike Frysinger69cb41d2013-08-11 20:08:19 -040029
Alex Klein1699fab2022-09-08 08:46:06 -060030 def __init__(self, path, *args, **kwargs):
31 self.path = path
32 super().__init__(*args, **kwargs)
Mike Frysinger69cb41d2013-08-11 20:08:19 -040033
Alex Klein1699fab2022-09-08 08:46:06 -060034 # pylint: disable=unused-argument
35 def FindDebugDir(self, _board, sysroot=None):
36 return self.path
Mike Frysinger69cb41d2013-08-11 20:08:19 -040037
38
Mike Frysinger3ef6d972019-08-24 20:07:42 -040039# This long decorator triggers a false positive in the docstring test.
40# https://github.com/PyCQA/pylint/issues/3077
41# pylint: disable=bad-docstring-quotes
Alex Klein1699fab2022-09-08 08:46:06 -060042@mock.patch(
43 "chromite.scripts.cros_generate_breakpad_symbols." "GenerateBreakpadSymbol"
44)
Mike Frysinger69cb41d2013-08-11 20:08:19 -040045class GenerateSymbolsTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -060046 """Test GenerateBreakpadSymbols."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -040047
Alex Klein1699fab2022-09-08 08:46:06 -060048 def setUp(self):
49 self.board = "monkey-board"
50 self.board_dir = os.path.join(self.tempdir, "build", self.board)
51 self.debug_dir = os.path.join(self.board_dir, "usr", "lib", "debug")
52 self.breakpad_dir = os.path.join(self.debug_dir, "breakpad")
Mike Frysinger69cb41d2013-08-11 20:08:19 -040053
Alex Klein1699fab2022-09-08 08:46:06 -060054 # Generate a tree of files which we'll scan through.
55 elf_files = [
56 "bin/elf",
57 "iii/large-elf",
58 # Need some kernel modules (with & without matching .debug).
59 "lib/modules/3.10/module.ko",
60 "lib/modules/3.10/module-no-debug.ko",
61 # Need a file which has an ELF only, but not a .debug.
62 "usr/bin/elf-only",
63 "usr/sbin/elf",
64 ]
65 debug_files = [
66 "bin/bad-file",
67 "bin/elf.debug",
68 "iii/large-elf.debug",
69 "boot/vmlinux.debug",
70 "lib/modules/3.10/module.ko.debug",
71 # Need a file which has a .debug only, but not an ELF.
72 "sbin/debug-only.debug",
73 "usr/sbin/elf.debug",
74 ]
75 for f in [os.path.join(self.board_dir, x) for x in elf_files] + [
76 os.path.join(self.debug_dir, x) for x in debug_files
77 ]:
78 osutils.Touch(f, makedirs=True)
Mike Frysinger69cb41d2013-08-11 20:08:19 -040079
Alex Klein1699fab2022-09-08 08:46:06 -060080 # Set up random build dirs and symlinks.
81 buildid = os.path.join(self.debug_dir, ".build-id", "00")
82 osutils.SafeMakedirs(buildid)
83 os.symlink("/asdf", os.path.join(buildid, "foo"))
84 os.symlink("/bin/sh", os.path.join(buildid, "foo.debug"))
85 os.symlink("/bin/sh", os.path.join(self.debug_dir, "file.debug"))
86 osutils.WriteFile(
87 os.path.join(self.debug_dir, "iii", "large-elf.debug"),
88 "just some content",
89 )
Mike Frysinger69cb41d2013-08-11 20:08:19 -040090
Alex Klein1699fab2022-09-08 08:46:06 -060091 self.StartPatcher(FindDebugDirMock(self.debug_dir))
Mike Frysinger69cb41d2013-08-11 20:08:19 -040092
Alex Klein1699fab2022-09-08 08:46:06 -060093 def testNormal(self, gen_mock):
94 """Verify all the files we expect to get generated do"""
95 with parallel_unittest.ParallelMock():
96 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
97 self.board, sysroot=self.board_dir
98 )
99 self.assertEqual(ret, 0)
100 self.assertEqual(gen_mock.call_count, 5)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400101
Alex Klein1699fab2022-09-08 08:46:06 -0600102 # The largest ELF should be processed first.
103 call1 = (
104 os.path.join(self.board_dir, "iii/large-elf"),
105 os.path.join(self.debug_dir, "iii/large-elf.debug"),
106 )
107 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400108
Alex Klein1699fab2022-09-08 08:46:06 -0600109 # The other ELFs can be called in any order.
110 call2 = (
111 os.path.join(self.board_dir, "bin/elf"),
112 os.path.join(self.debug_dir, "bin/elf.debug"),
113 )
114 call3 = (
115 os.path.join(self.board_dir, "usr/sbin/elf"),
116 os.path.join(self.debug_dir, "usr/sbin/elf.debug"),
117 )
118 call4 = (
119 os.path.join(self.board_dir, "lib/modules/3.10/module.ko"),
120 os.path.join(
121 self.debug_dir, "lib/modules/3.10/module.ko.debug"
122 ),
123 )
124 call5 = (
125 os.path.join(self.board_dir, "boot/vmlinux"),
126 os.path.join(self.debug_dir, "boot/vmlinux.debug"),
127 )
128 exp_calls = set((call2, call3, call4, call5))
129 actual_calls = set(
130 (
131 gen_mock.call_args_list[1][0],
132 gen_mock.call_args_list[2][0],
133 gen_mock.call_args_list[3][0],
134 gen_mock.call_args_list[4][0],
135 )
136 )
137 self.assertEqual(exp_calls, actual_calls)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 def testFileList(self, gen_mock):
140 """Verify that file_list restricts the symbols generated"""
141 with parallel_unittest.ParallelMock():
142 call1 = (
143 os.path.join(self.board_dir, "usr/sbin/elf"),
144 os.path.join(self.debug_dir, "usr/sbin/elf.debug"),
145 )
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700146
Alex Klein1699fab2022-09-08 08:46:06 -0600147 # Filter with elf path.
148 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
149 self.board,
150 sysroot=self.board_dir,
151 breakpad_dir=self.breakpad_dir,
152 file_list=[os.path.join(self.board_dir, "usr", "sbin", "elf")],
153 )
154 self.assertEqual(ret, 0)
155 self.assertEqual(gen_mock.call_count, 1)
156 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 # Filter with debug symbols file path.
159 gen_mock.reset_mock()
160 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
161 self.board,
162 sysroot=self.board_dir,
163 breakpad_dir=self.breakpad_dir,
164 file_list=[
165 os.path.join(self.debug_dir, "usr", "sbin", "elf.debug")
166 ],
167 )
168 self.assertEqual(ret, 0)
169 self.assertEqual(gen_mock.call_count, 1)
170 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 def testGenLimit(self, gen_mock):
173 """Verify generate_count arg works"""
174 with parallel_unittest.ParallelMock():
175 # Generate nothing!
176 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
177 self.board,
178 sysroot=self.board_dir,
179 breakpad_dir=self.breakpad_dir,
180 generate_count=0,
181 )
182 self.assertEqual(ret, 0)
183 self.assertEqual(gen_mock.call_count, 0)
Prathmesh Prabhu9995e9b2013-10-31 16:43:55 -0700184
Alex Klein1699fab2022-09-08 08:46:06 -0600185 # Generate just one.
186 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
187 self.board,
188 sysroot=self.board_dir,
189 breakpad_dir=self.breakpad_dir,
190 generate_count=1,
191 )
192 self.assertEqual(ret, 0)
193 self.assertEqual(gen_mock.call_count, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400194
Alex Klein1699fab2022-09-08 08:46:06 -0600195 # The largest ELF should be processed first.
196 call1 = (
197 os.path.join(self.board_dir, "iii/large-elf"),
198 os.path.join(self.debug_dir, "iii/large-elf.debug"),
199 )
200 self.assertEqual(gen_mock.call_args_list[0][0], call1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400201
Alex Klein1699fab2022-09-08 08:46:06 -0600202 def testGenErrors(self, gen_mock):
203 """Verify we handle errors from generation correctly"""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400204
Alex Klein1699fab2022-09-08 08:46:06 -0600205 def _SetError(*_args, **kwargs):
206 kwargs["num_errors"].value += 1
207 return 1
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400208
Alex Klein1699fab2022-09-08 08:46:06 -0600209 gen_mock.side_effect = _SetError
210 with parallel_unittest.ParallelMock():
211 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
212 self.board, sysroot=self.board_dir
213 )
214 self.assertEqual(ret, 5)
215 self.assertEqual(gen_mock.call_count, 5)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400216
Alex Klein1699fab2022-09-08 08:46:06 -0600217 def testCleaningTrue(self, gen_mock):
218 """Verify behavior of clean_breakpad=True"""
219 with parallel_unittest.ParallelMock():
220 # Dir does not exist, and then does.
221 self.assertNotExists(self.breakpad_dir)
222 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
223 self.board,
224 sysroot=self.board_dir,
225 generate_count=1,
226 clean_breakpad=True,
227 )
228 self.assertEqual(ret, 0)
229 self.assertEqual(gen_mock.call_count, 1)
230 self.assertExists(self.breakpad_dir)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400231
Alex Klein1699fab2022-09-08 08:46:06 -0600232 # Dir exists before & after.
233 # File exists, but then doesn't.
234 stub_file = os.path.join(self.breakpad_dir, "fooooooooo")
235 osutils.Touch(stub_file)
236 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
237 self.board,
238 sysroot=self.board_dir,
239 generate_count=1,
240 clean_breakpad=True,
241 )
242 self.assertEqual(ret, 0)
243 self.assertEqual(gen_mock.call_count, 2)
244 self.assertNotExists(stub_file)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400245
Alex Klein1699fab2022-09-08 08:46:06 -0600246 def testCleaningFalse(self, gen_mock):
247 """Verify behavior of clean_breakpad=False"""
248 with parallel_unittest.ParallelMock():
249 # Dir does not exist, and then does.
250 self.assertNotExists(self.breakpad_dir)
251 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
252 self.board,
253 sysroot=self.board_dir,
254 generate_count=1,
255 clean_breakpad=False,
256 )
257 self.assertEqual(ret, 0)
258 self.assertEqual(gen_mock.call_count, 1)
259 self.assertExists(self.breakpad_dir)
Mike Frysinger9a628bb2013-10-24 15:51:37 -0400260
Alex Klein1699fab2022-09-08 08:46:06 -0600261 # Dir exists before & after.
262 # File exists before & after.
263 stub_file = os.path.join(self.breakpad_dir, "fooooooooo")
264 osutils.Touch(stub_file)
265 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
266 self.board,
267 sysroot=self.board_dir,
268 generate_count=1,
269 clean_breakpad=False,
270 )
271 self.assertEqual(ret, 0)
272 self.assertEqual(gen_mock.call_count, 2)
273 self.assertExists(stub_file)
274
275 def testExclusionList(self, gen_mock):
276 """Verify files in directories of the exclusion list are excluded"""
277 exclude_dirs = ["bin", "usr", "fake/dir/fake"]
278 with parallel_unittest.ParallelMock():
279 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
280 self.board, sysroot=self.board_dir, exclude_dirs=exclude_dirs
281 )
282 self.assertEqual(ret, 0)
283 self.assertEqual(gen_mock.call_count, 3)
284
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400285
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600286class GenerateSymbolTest(cros_test_lib.RunCommandTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600287 """Test GenerateBreakpadSymbol."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400288
Peter Boströma89ee082023-02-23 11:53:56 -0800289 _DUMP_SYMS_BASE_CMD = ["dump_syms", "-v", "-d", "-m"]
290
Alex Klein1699fab2022-09-08 08:46:06 -0600291 def setUp(self):
292 self.elf_file = os.path.join(self.tempdir, "elf")
293 osutils.Touch(self.elf_file)
294 self.debug_dir = os.path.join(self.tempdir, "debug")
295 self.debug_file = os.path.join(self.debug_dir, "elf.debug")
296 osutils.Touch(self.debug_file, makedirs=True)
297 # Not needed as the code itself should create it as needed.
298 self.breakpad_dir = os.path.join(self.debug_dir, "breakpad")
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400299
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800300 self.FILE_OUT = (
301 f"{self.elf_file}: ELF 64-bit LSB pie executable, x86-64, "
302 "version 1 (SYSV), dynamically linked, interpreter "
303 "/lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, "
304 "BuildID[sha1]=cf9a21fa6b14bfb2dfcb76effd713c4536014d95, stripped"
305 )
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -0800306 # A symbol file which would pass validation.
307 MINIMAL_SYMBOL_FILE = (
308 "MODULE OS CPU ID NAME\n"
309 "PUBLIC f10 0 func\n"
310 "STACK CFI INIT f10 22 .cfa: $rsp 8 + .ra: .cfa -8 + ^\n"
311 )
312 self.rc.SetDefaultCmdResult(stdout=MINIMAL_SYMBOL_FILE)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800313 self.rc.AddCmdResult(
314 ["/usr/bin/file", self.elf_file], stdout=self.FILE_OUT
315 )
Alex Klein1699fab2022-09-08 08:46:06 -0600316 self.assertCommandContains = self.rc.assertCommandContains
317 self.sym_file = os.path.join(self.breakpad_dir, "NAME/ID/NAME.sym")
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400318
Alex Klein1699fab2022-09-08 08:46:06 -0600319 self.StartPatcher(FindDebugDirMock(self.debug_dir))
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400320
Alex Klein1699fab2022-09-08 08:46:06 -0600321 def assertCommandArgs(self, i, args):
322 """Helper for looking at the args of the |i|th call"""
323 self.assertEqual(self.rc.call_args_list[i][0][0], args)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400324
Alex Klein1699fab2022-09-08 08:46:06 -0600325 def testNormal(self):
326 """Normal run -- given an ELF and a debug file"""
327 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800328 self.elf_file,
329 self.debug_file,
330 self.breakpad_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600331 )
332 self.assertEqual(ret, self.sym_file)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800333 self.assertEqual(self.rc.call_count, 2)
Alex Klein1699fab2022-09-08 08:46:06 -0600334 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800335 1, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Alex Klein1699fab2022-09-08 08:46:06 -0600336 )
337 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400338
Alex Klein1699fab2022-09-08 08:46:06 -0600339 def testNormalNoCfi(self):
340 """Normal run w/out CFI"""
341 # Make sure the num_errors flag works too.
342 num_errors = ctypes.c_int()
343 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
344 self.elf_file,
345 breakpad_dir=self.breakpad_dir,
346 strip_cfi=True,
347 num_errors=num_errors,
348 )
349 self.assertEqual(ret, self.sym_file)
350 self.assertEqual(num_errors.value, 0)
Peter Boströma89ee082023-02-23 11:53:56 -0800351 self.assertCommandArgs(
352 1, self._DUMP_SYMS_BASE_CMD + ["-c", self.elf_file]
353 )
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800354 self.assertEqual(self.rc.call_count, 2)
Alex Klein1699fab2022-09-08 08:46:06 -0600355 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400356
Alex Klein1699fab2022-09-08 08:46:06 -0600357 def testNormalElfOnly(self):
358 """Normal run -- given just an ELF"""
359 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
360 self.elf_file, breakpad_dir=self.breakpad_dir
361 )
362 self.assertEqual(ret, self.sym_file)
Peter Boströma89ee082023-02-23 11:53:56 -0800363 self.assertCommandArgs(1, self._DUMP_SYMS_BASE_CMD + [self.elf_file])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800364 self.assertEqual(self.rc.call_count, 2)
Alex Klein1699fab2022-09-08 08:46:06 -0600365 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400366
Alex Klein1699fab2022-09-08 08:46:06 -0600367 def testNormalSudo(self):
368 """Normal run where ELF is readable only by root"""
369 with mock.patch.object(os, "access") as mock_access:
370 mock_access.return_value = False
371 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
372 self.elf_file, breakpad_dir=self.breakpad_dir
373 )
374 self.assertEqual(ret, self.sym_file)
375 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800376 1, ["sudo", "--"] + self._DUMP_SYMS_BASE_CMD + [self.elf_file]
Alex Klein1699fab2022-09-08 08:46:06 -0600377 )
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400378
Alex Klein1699fab2022-09-08 08:46:06 -0600379 def testLargeDebugFail(self):
380 """Running w/large .debug failed, but retry worked"""
381 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800382 self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
383 returncode=1,
Alex Klein1699fab2022-09-08 08:46:06 -0600384 )
385 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
386 self.elf_file, self.debug_file, self.breakpad_dir
387 )
388 self.assertEqual(ret, self.sym_file)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800389 self.assertEqual(self.rc.call_count, 4)
Alex Klein1699fab2022-09-08 08:46:06 -0600390 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800391 1, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800392 )
393 # The current fallback from _DumpExpectingSymbols() to
394 # _DumpAllowingBasicFallback() causes the first dump_sums command to get
395 # repeated.
396 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800397 2, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Alex Klein1699fab2022-09-08 08:46:06 -0600398 )
399 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800400 3,
401 self._DUMP_SYMS_BASE_CMD
402 + ["-c", "-r", self.elf_file, self.debug_dir],
Alex Klein1699fab2022-09-08 08:46:06 -0600403 )
404 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400405
Alex Klein1699fab2022-09-08 08:46:06 -0600406 def testDebugFail(self):
407 """Running w/.debug always failed, but works w/out"""
408 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800409 self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir],
410 returncode=1,
Alex Klein1699fab2022-09-08 08:46:06 -0600411 )
412 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800413 self._DUMP_SYMS_BASE_CMD
414 + ["-c", "-r", self.elf_file, self.debug_dir],
Alex Klein1699fab2022-09-08 08:46:06 -0600415 returncode=1,
416 )
417 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
418 self.elf_file, self.debug_file, self.breakpad_dir
419 )
420 self.assertEqual(ret, self.sym_file)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800421 self.assertEqual(self.rc.call_count, 5)
Alex Klein1699fab2022-09-08 08:46:06 -0600422 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800423 1, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800424 )
425 # The current fallback from _DumpExpectingSymbols() to
426 # _DumpAllowingBasicFallback() causes the first dump_sums command to get
427 # repeated.
428 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800429 2, self._DUMP_SYMS_BASE_CMD + [self.elf_file, self.debug_dir]
Alex Klein1699fab2022-09-08 08:46:06 -0600430 )
431 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800432 3,
433 self._DUMP_SYMS_BASE_CMD
434 + ["-c", "-r", self.elf_file, self.debug_dir],
Alex Klein1699fab2022-09-08 08:46:06 -0600435 )
Peter Boströma89ee082023-02-23 11:53:56 -0800436 self.assertCommandArgs(4, self._DUMP_SYMS_BASE_CMD + [self.elf_file])
Alex Klein1699fab2022-09-08 08:46:06 -0600437 self.assertExists(self.sym_file)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400438
Alex Klein1699fab2022-09-08 08:46:06 -0600439 def testCompleteFail(self):
440 """Running dump_syms always fails"""
441 self.rc.SetDefaultCmdResult(returncode=1)
442 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
443 self.elf_file, breakpad_dir=self.breakpad_dir
444 )
445 self.assertEqual(ret, 1)
446 # Make sure the num_errors flag works too.
447 num_errors = ctypes.c_int()
448 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
449 self.elf_file, breakpad_dir=self.breakpad_dir, num_errors=num_errors
450 )
451 self.assertEqual(ret, 1)
452 self.assertEqual(num_errors.value, 1)
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400453
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800454 def testKernelObjects(self):
455 """Kernel object files should call _DumpAllowingBasicFallback()"""
456 ko_file = os.path.join(self.tempdir, "elf.ko")
457 osutils.Touch(ko_file)
458 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800459 self._DUMP_SYMS_BASE_CMD + [ko_file, self.debug_dir],
460 returncode=1,
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800461 )
462 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800463 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", ko_file, self.debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800464 returncode=1,
465 )
466 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
467 ko_file, self.debug_file, self.breakpad_dir
468 )
469 self.assertEqual(ret, self.sym_file)
470 self.assertEqual(self.rc.call_count, 3)
471 # Only one call (at the beginning of _DumpAllowingBasicFallback())
472 # to "dump_syms -v"
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800473 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800474 0, self._DUMP_SYMS_BASE_CMD + [ko_file, self.debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800475 )
Peter Boströma89ee082023-02-23 11:53:56 -0800476 self.assertCommandArgs(
477 1,
478 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", ko_file, self.debug_dir],
479 )
480 self.assertCommandArgs(2, self._DUMP_SYMS_BASE_CMD + [ko_file])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800481 self.assertExists(self.sym_file)
482
483 def testGoBinary(self):
484 """Go binaries should call _DumpAllowingBasicFallback()
485
486 Also tests that dump_syms failing with 'file contains no debugging
487 information' does not fail the script.
488 """
489 go_binary = os.path.join(self.tempdir, "goprogram")
490 osutils.Touch(go_binary)
491 go_debug_file = os.path.join(self.debug_dir, "goprogram.debug")
492 osutils.Touch(go_debug_file, makedirs=True)
493 FILE_OUT_GO = go_binary + (
494 ": ELF 64-bit LSB executable, x86-64, "
495 "version 1 (SYSV), statically linked, "
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -0800496 "Go BuildID=KKXVlL66E8Qmngr4qll9/5kOKGZw9I7TmNhoqKLqq/SiYVJam6w5Fo"
497 "39B3BtDo/ba8_ceezZ-3R4qEv6_-K, not stripped"
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800498 )
499 self.rc.AddCmdResult(["/usr/bin/file", go_binary], stdout=FILE_OUT_GO)
500 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800501 self._DUMP_SYMS_BASE_CMD + [go_binary, self.debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800502 returncode=1,
503 )
504 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800505 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", go_binary, self.debug_dir],
506 returncode=1,
507 )
508 self.rc.AddCmdResult(
509 self._DUMP_SYMS_BASE_CMD + [go_binary],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800510 returncode=1,
511 stderr=(
512 f"{go_binary}: file contains no debugging information "
513 '(no ".stab" or ".debug_info" sections)'
514 ),
515 )
516 num_errors = ctypes.c_int()
517 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
518 go_binary, go_debug_file, self.breakpad_dir
519 )
520 self.assertEqual(ret, 0)
521 self.assertEqual(self.rc.call_count, 4)
522 self.assertCommandArgs(0, ["/usr/bin/file", go_binary])
523 # Only one call (at the beginning of _DumpAllowingBasicFallback())
524 # to "dump_syms -v"
525 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800526 1, self._DUMP_SYMS_BASE_CMD + [go_binary, self.debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800527 )
528 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800529 2,
530 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", go_binary, self.debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800531 )
Peter Boströma89ee082023-02-23 11:53:56 -0800532 self.assertCommandArgs(3, self._DUMP_SYMS_BASE_CMD + [go_binary])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800533 self.assertNotExists(self.sym_file)
534 self.assertEqual(num_errors.value, 0)
535
Ian Barkley-Yeunge1785762023-03-08 18:32:18 -0800536 def _testBinaryIsInLocalFallback(self, directory, filename):
537 binary = os.path.join(self.tempdir, directory, filename)
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800538 osutils.Touch(binary, makedirs=True)
Ian Barkley-Yeunge1785762023-03-08 18:32:18 -0800539 debug_dir = os.path.join(self.debug_dir, directory)
540 debug_file = os.path.join(debug_dir, f"{filename}.debug")
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800541 osutils.Touch(debug_file, makedirs=True)
542 self.rc.AddCmdResult(["/usr/bin/file", binary], stdout=self.FILE_OUT)
543 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800544 self._DUMP_SYMS_BASE_CMD + [binary, debug_dir], returncode=1
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800545 )
546 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800547 self._DUMP_SYMS_BASE_CMD + ["-c", "-r", binary, debug_dir],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800548 returncode=1,
549 )
550 self.rc.AddCmdResult(
Peter Boströma89ee082023-02-23 11:53:56 -0800551 self._DUMP_SYMS_BASE_CMD + [binary],
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800552 returncode=1,
553 stderr=(
554 f"{binary}: file contains no debugging information "
555 '(no ".stab" or ".debug_info" sections)'
556 ),
557 )
558 num_errors = ctypes.c_int()
559 ret = cros_generate_breakpad_symbols.GenerateBreakpadSymbol(
560 binary, debug_file, self.breakpad_dir, sysroot=self.tempdir
561 )
562 self.assertEqual(ret, 0)
563 self.assertEqual(self.rc.call_count, 4)
564 self.assertCommandArgs(0, ["/usr/bin/file", binary])
565 # Only one call (at the beginning of _DumpAllowingBasicFallback())
566 # to "dump_syms -v"
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800567 self.assertCommandArgs(
Peter Boströma89ee082023-02-23 11:53:56 -0800568 1, self._DUMP_SYMS_BASE_CMD + [binary, debug_dir]
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800569 )
Peter Boströma89ee082023-02-23 11:53:56 -0800570 self.assertCommandArgs(
571 2, self._DUMP_SYMS_BASE_CMD + ["-c", "-r", binary, debug_dir]
572 )
573 self.assertCommandArgs(3, self._DUMP_SYMS_BASE_CMD + [binary])
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -0800574 self.assertNotExists(self.sym_file)
575 self.assertEqual(num_errors.value, 0)
576
Ian Barkley-Yeunge1785762023-03-08 18:32:18 -0800577 def testAllowlist(self):
578 """Binaries in the allowlist should call _DumpAllowingBasicFallback()"""
579 self._testBinaryIsInLocalFallback("usr/bin", "goldctl")
580
581 def testUsrLocalSkip(self):
582 """Binaries in /usr/local should call _DumpAllowingBasicFallback()"""
583 self._testBinaryIsInLocalFallback("usr/local", "minidump_stackwalk")
584
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400585
Ian Barkley-Yeungc5b6f582023-03-08 18:23:07 -0800586class ValidateSymbolFileTest(cros_test_lib.TempDirTestCase):
587 """Tests ValidateSymbolFile"""
588
589 def _GetTestdataFile(self, filename: str) -> str:
590 """Gets the path to a file in the testdata directory.
591
592 Args:
593 filename: The base filename of the file.
594
595 Returns:
596 A string with the complete path to the file.
597 """
598 return os.path.join(os.path.dirname(__file__), "testdata", filename)
599
600 def testValidSymbolFiles(self):
601 """Make sure ValidateSymbolFile passes on valid files"""
602
603 # All files are in the testdata/ subdirectory.
604 VALID_SYMBOL_FILES = [
605 # A "normal" symbol file from an executable.
606 "basic.sym",
607 # A "normal" symbol file from a shared library.
608 "basic_lib.sym",
609 # A symbol file with PUBLIC records but no FUNC records.
610 "public_only.sym",
611 # A symbol file with FUNC records but no PUBLIC records.
612 "func_only.sym",
613 # A symbol file with at least one of every line type.
614 "all_line_types.sym",
615 ]
616
617 for file in VALID_SYMBOL_FILES:
618 with self.subTest(
619 file=file
620 ), multiprocessing.Manager() as mp_manager:
621 found_files = mp_manager.list()
622 self.assertTrue(
623 cros_generate_breakpad_symbols.ValidateSymbolFile(
624 self._GetTestdataFile(file),
625 "/build/board/bin/foo",
626 "/build/board",
627 found_files,
628 )
629 )
630 self.assertFalse(found_files)
631
632 def testInvalidSymbolFiles(self):
633 """Make sure ValidateSymbolFile fails on invalid files.
634
635 This test only covers cases that return false, not cases that raise
636 exceptions.
637 """
638
639 class InvalidSymbolFile:
640 """The name of an invalid symbol file + the expected error msg."""
641
642 def __init__(self, filename, expected_errors):
643 self.filename = filename
644 self.expected_errors = expected_errors
645
646 INVALID_SYMBOL_FILES = [
647 InvalidSymbolFile(
648 "bad_no_func_or_public.sym",
649 [
650 "WARNING:root:/build/board/bin/foo: "
651 "Symbol file has no FUNC or PUBLIC records"
652 ],
653 ),
654 InvalidSymbolFile(
655 "bad_no_stack.sym",
656 [
657 "WARNING:root:/build/board/bin/foo: "
658 "Symbol file has no STACK records"
659 ],
660 ),
661 InvalidSymbolFile(
662 "bad_no_module.sym",
663 [
664 "WARNING:root:/build/board/bin/foo: "
665 "Symbol file has 0 MODULE lines"
666 ],
667 ),
668 InvalidSymbolFile(
669 "bad_two_modules.sym",
670 [
671 "WARNING:root:/build/board/bin/foo: "
672 "Symbol file has 2 MODULE lines"
673 ],
674 ),
675 InvalidSymbolFile(
676 "bad_func_no_line_numbers.sym",
677 [
678 "WARNING:root:/build/board/bin/foo: "
679 "Symbol file has FUNC records but no line numbers"
680 ],
681 ),
682 InvalidSymbolFile(
683 "bad_line_numbers_no_file.sym",
684 [
685 "WARNING:root:/build/board/bin/foo: "
686 "Symbol file has line number records but no FILE records"
687 ],
688 ),
689 InvalidSymbolFile(
690 "bad_inline_no_files.sym",
691 [
692 "WARNING:root:/build/board/bin/foo: "
693 "Symbol file has INLINE records but no FILE records"
694 ],
695 ),
696 InvalidSymbolFile(
697 "bad_inline_no_origins.sym",
698 [
699 "WARNING:root:/build/board/bin/foo: "
700 "Symbol file has INLINE records but no INLINE_ORIGIN "
701 "records"
702 ],
703 ),
704 InvalidSymbolFile(
705 "blank.sym",
706 [
707 "WARNING:root:/build/board/bin/foo: "
708 "Symbol file has no STACK records",
709 "WARNING:root:/build/board/bin/foo: "
710 "Symbol file has 0 MODULE lines",
711 "WARNING:root:/build/board/bin/foo: "
712 "Symbol file has no FUNC or PUBLIC records",
713 ],
714 ),
715 ]
716
717 for file in INVALID_SYMBOL_FILES:
718 with self.subTest(
719 file=file.filename
720 ), multiprocessing.Manager() as mp_manager:
721 found_files = mp_manager.list()
722 with self.assertLogs(level=logging.WARNING) as cm:
723 self.assertFalse(
724 cros_generate_breakpad_symbols.ValidateSymbolFile(
725 self._GetTestdataFile(file.filename),
726 "/build/board/bin/foo",
727 "/build/board",
728 found_files,
729 )
730 )
731 self.assertEqual(file.expected_errors, cm.output)
732 self.assertFalse(found_files)
733
734 def testInvalidSymbolFilesWhichRaise(self):
735 """Test ValidateSymbolFile raise exceptions on certain files"""
736
737 class InvalidSymbolFile:
738 """The invalid symbol file + the expected exception message"""
739
740 def __init__(self, filename, expected_exception_regex):
741 self.filename = filename
742 self.expected_exception_regex = expected_exception_regex
743
744 INVALID_SYMBOL_FILES = [
745 InvalidSymbolFile(
746 "bad_unknown_line_type.sym",
747 "symbol file has unknown line type UNKNOWN",
748 ),
749 InvalidSymbolFile(
750 "bad_blank_line.sym",
751 "symbol file has unexpected blank line",
752 ),
753 InvalidSymbolFile(
754 "bad_short_func.sym",
755 r"symbol file has FUNC line with 2 words "
756 r"\(expected 5 or more\)",
757 ),
758 InvalidSymbolFile(
759 "bad_short_line_number.sym",
760 r"symbol file has line number line with 3 words "
761 r"\(expected 4 - 4\)",
762 ),
763 InvalidSymbolFile(
764 "bad_long_line_number.sym",
765 r"symbol file has line number line with 5 words "
766 r"\(expected 4 - 4\)",
767 ),
768 ]
769
770 for file in INVALID_SYMBOL_FILES:
771 with self.subTest(
772 file=file.filename
773 ), multiprocessing.Manager() as mp_manager:
774 found_files = mp_manager.list()
775 self.assertRaisesRegex(
776 ValueError,
777 file.expected_exception_regex,
778 cros_generate_breakpad_symbols.ValidateSymbolFile,
779 self._GetTestdataFile(file.filename),
780 "/build/board/bin/foo",
781 "/build/board",
782 found_files,
783 )
784
785 def testAllowlist(self):
786 """Test that ELFs on the allowlist are allowed to pass."""
787 with multiprocessing.Manager() as mp_manager:
788 found_files = mp_manager.list()
789 self.assertTrue(
790 cros_generate_breakpad_symbols.ValidateSymbolFile(
791 self._GetTestdataFile("bad_no_stack.sym"),
792 "/build/board/opt/google/chrome/nacl_helper_bootstrap",
793 "/build/board",
794 found_files,
795 )
796 )
797 self.assertFalse(found_files)
798
799 def testAllowlistRegex(self):
800 """Test that ELFs on the regex-based allowlist are allowed to pass."""
801 with multiprocessing.Manager() as mp_manager:
802 found_files = mp_manager.list()
803 self.assertTrue(
804 cros_generate_breakpad_symbols.ValidateSymbolFile(
805 self._GetTestdataFile("bad_no_stack.sym"),
806 "/build/board/usr/lib/libcros_ml_core.so",
807 "/build/board",
808 found_files,
809 )
810 )
811 self.assertFalse(found_files)
812
813 def _CreateSymbolFile(
814 self,
815 sym_file: pathlib.Path,
816 func_lines: int = 0,
817 public_lines: int = 0,
818 stack_lines: int = 0,
819 line_number_lines: int = 0,
820 ) -> None:
821 """Creates a symbol file.
822
823 Creates a symbol file with the given number of lines (and enough other
824 lines to pass validation) in the temp directory.
825
826 To pass validation, chrome.sym files must be huge; create them
827 programmatically during the test instead of checking in a real 800MB+
828 chrome symbol file.
829 """
830 with sym_file.open(mode="w", encoding="utf-8") as f:
831 f.write("MODULE OS CPU ID NAME\n")
832 f.write("FILE 0 /path/to/source.cc\n")
833 for func in range(0, func_lines):
834 f.write(f"FUNC {func} 1 0 function{func}\n")
835 for public in range(0, public_lines):
836 f.write(f"PUBLIC {public} 0 Public{public}\n")
837 for line in range(0, line_number_lines):
838 f.write(f"{line} 1 {line} 0\n")
839 for stack in range(0, stack_lines):
840 f.write(f"STACK CFI {stack} .cfa: $esp {stack} +\n")
841
842 def testValidChromeSymbolFile(self):
843 """Test that a chrome symbol file can pass the additional checks"""
844 sym_file = self.tempdir / "chrome.sym"
845 self._CreateSymbolFile(
846 sym_file,
847 func_lines=100000,
848 public_lines=10,
849 stack_lines=1000000,
850 line_number_lines=1000000,
851 )
852 with multiprocessing.Manager() as mp_manager:
853 found_files = mp_manager.list()
854 self.assertTrue(
855 cros_generate_breakpad_symbols.ValidateSymbolFile(
856 str(sym_file),
857 "/build/board/opt/google/chrome/chrome",
858 "/build/board",
859 found_files,
860 )
861 )
862 self.assertEqual(
863 list(found_files),
864 [cros_generate_breakpad_symbols.ExpectedFiles.ASH_CHROME],
865 )
866
867 def testInvalidChromeSymbolFile(self):
868 """Test that a chrome symbol file is held to higher standards."""
869
870 class ChromeSymbolFileTest:
871 """Defines the subtest for an invalid Chrome symbol file."""
872
873 def __init__(
874 self,
875 name,
876 expected_error,
877 func_lines=100000,
878 stack_lines=1000000,
879 line_number_lines=1000000,
880 ):
881 self.name = name
882 self.expected_error = expected_error
883 self.func_lines = func_lines
884 self.stack_lines = stack_lines
885 self.line_number_lines = line_number_lines
886
887 CHROME_SYMBOL_TESTS = [
888 ChromeSymbolFileTest(
889 name="Insufficient FUNC records",
890 func_lines=10000,
891 expected_error="chrome should have at least 100,000 FUNC "
892 "records, found 10000",
893 ),
894 ChromeSymbolFileTest(
895 name="Insufficient STACK records",
896 stack_lines=100000,
897 expected_error="chrome should have at least 1,000,000 STACK "
898 "records, found 100000",
899 ),
900 ChromeSymbolFileTest(
901 name="Insufficient line number records",
902 line_number_lines=100000,
903 expected_error="chrome should have at least 1,000,000 "
904 "line number records, found 100000",
905 ),
906 ]
907 for test in CHROME_SYMBOL_TESTS:
908 with self.subTest(
909 name=test.name
910 ), multiprocessing.Manager() as mp_manager:
911 sym_file = self.tempdir / "chrome.sym"
912 self._CreateSymbolFile(
913 sym_file,
914 func_lines=test.func_lines,
915 public_lines=10,
916 stack_lines=test.stack_lines,
917 line_number_lines=test.line_number_lines,
918 )
919 found_files = mp_manager.list()
920 with self.assertLogs(level=logging.WARNING) as cm:
921 self.assertFalse(
922 cros_generate_breakpad_symbols.ValidateSymbolFile(
923 str(sym_file),
924 "/build/board/opt/google/chrome/chrome",
925 "/build/board",
926 found_files,
927 )
928 )
929 self.assertIn(test.expected_error, cm.output[0])
930 self.assertEqual(len(cm.output), 1)
931
932 def testValidLibcSymbolFile(self):
933 """Test that a libc.so symbol file can pass the additional checks."""
934 with multiprocessing.Manager() as mp_manager:
935 sym_file = self.tempdir / "libc.so.sym"
936 self._CreateSymbolFile(
937 sym_file, public_lines=200, stack_lines=20000
938 )
939 found_files = mp_manager.list()
940 self.assertTrue(
941 cros_generate_breakpad_symbols.ValidateSymbolFile(
942 str(sym_file),
943 "/build/board/lib64/libc.so.6",
944 "/build/board",
945 found_files,
946 )
947 )
948 self.assertEqual(
949 list(found_files),
950 [cros_generate_breakpad_symbols.ExpectedFiles.LIBC],
951 )
952
953 def testInvalidLibcSymbolFile(self):
954 """Test that a libc.so symbol file is held to higher standards."""
955
956 class LibcSymbolFileTest:
957 """Defines the subtest for an invalid libc symbol file."""
958
959 def __init__(
960 self,
961 name,
962 expected_error,
963 public_lines=200,
964 stack_lines=20000,
965 ):
966 self.name = name
967 self.expected_error = expected_error
968 self.public_lines = public_lines
969 self.stack_lines = stack_lines
970
971 LIBC_SYMBOL_TESTS = [
972 LibcSymbolFileTest(
973 name="Insufficient PUBLIC records",
974 public_lines=50,
975 expected_error="/build/board/lib64/libc.so.6 should have at "
976 "least 100 PUBLIC records, found 50",
977 ),
978 LibcSymbolFileTest(
979 name="Insufficient STACK records",
980 stack_lines=1000,
981 expected_error="/build/board/lib64/libc.so.6 should have at "
982 "least 10000 STACK records, found 1000",
983 ),
984 ]
985 for test in LIBC_SYMBOL_TESTS:
986 with self.subTest(
987 name=test.name
988 ), multiprocessing.Manager() as mp_manager:
989 sym_file = self.tempdir / "libc.so.sym"
990 self._CreateSymbolFile(
991 sym_file,
992 public_lines=test.public_lines,
993 stack_lines=test.stack_lines,
994 )
995 found_files = mp_manager.list()
996 with self.assertLogs(level=logging.WARNING) as cm:
997 self.assertFalse(
998 cros_generate_breakpad_symbols.ValidateSymbolFile(
999 str(sym_file),
1000 "/build/board/lib64/libc.so.6",
1001 "/build/board",
1002 found_files,
1003 )
1004 )
1005 self.assertIn(test.expected_error, cm.output[0])
1006 self.assertEqual(len(cm.output), 1)
1007
1008 def testValidCrashReporterSymbolFile(self):
1009 """Test a crash_reporter symbol file can pass the additional checks."""
1010 with multiprocessing.Manager() as mp_manager:
1011 sym_file = self.tempdir / "crash_reporter.sym"
1012 self._CreateSymbolFile(
1013 sym_file,
1014 func_lines=2000,
1015 public_lines=10,
1016 stack_lines=2000,
1017 line_number_lines=20000,
1018 )
1019 found_files = mp_manager.list()
1020 self.assertTrue(
1021 cros_generate_breakpad_symbols.ValidateSymbolFile(
1022 str(sym_file),
1023 "/build/board/sbin/crash_reporter",
1024 "/build/board",
1025 found_files,
1026 )
1027 )
1028 self.assertEqual(
1029 list(found_files),
1030 [cros_generate_breakpad_symbols.ExpectedFiles.CRASH_REPORTER],
1031 )
1032
1033 def testInvalidCrashReporterSymbolFile(self):
1034 """Test that a crash_reporter symbol file is held to higher standards"""
1035
1036 class CrashReporterSymbolFileTest:
1037 """Defines the subtest for an invalid crash_reporter symbol file."""
1038
1039 def __init__(
1040 self,
1041 name,
1042 expected_error,
1043 func_lines=2000,
1044 stack_lines=2000,
1045 line_number_lines=20000,
1046 ):
1047 self.name = name
1048 self.expected_error = expected_error
1049 self.func_lines = func_lines
1050 self.stack_lines = stack_lines
1051 self.line_number_lines = line_number_lines
1052
1053 CRASH_REPORTER_SYMBOL_TESTS = [
1054 CrashReporterSymbolFileTest(
1055 name="Insufficient FUNC records",
1056 func_lines=500,
1057 expected_error="crash_reporter should have at least 1000 FUNC "
1058 "records, found 500",
1059 ),
1060 CrashReporterSymbolFileTest(
1061 name="Insufficient STACK records",
1062 stack_lines=100,
1063 expected_error="crash_reporter should have at least 1000 STACK "
1064 "records, found 100",
1065 ),
1066 CrashReporterSymbolFileTest(
1067 name="Insufficient line number records",
1068 line_number_lines=2000,
1069 expected_error="crash_reporter should have at least 10,000 "
1070 "line number records, found 2000",
1071 ),
1072 ]
1073 for test in CRASH_REPORTER_SYMBOL_TESTS:
1074 with self.subTest(
1075 name=test.name
1076 ), multiprocessing.Manager() as mp_manager:
1077 sym_file = self.tempdir / "crash_reporter.sym"
1078 self._CreateSymbolFile(
1079 sym_file,
1080 func_lines=test.func_lines,
1081 stack_lines=test.stack_lines,
1082 line_number_lines=test.line_number_lines,
1083 )
1084 found_files = mp_manager.list()
1085 with self.assertLogs(level=logging.WARNING) as cm:
1086 self.assertFalse(
1087 cros_generate_breakpad_symbols.ValidateSymbolFile(
1088 str(sym_file),
1089 "/build/board/sbin/crash_reporter",
1090 "/build/board",
1091 found_files,
1092 )
1093 )
1094 self.assertIn(test.expected_error, cm.output[0])
1095 self.assertEqual(len(cm.output), 1)
1096
1097 def testValidLibMetricsSymbolFile(self):
1098 """Test a libmetrics.so symbol file can pass the additional checks."""
1099 with multiprocessing.Manager() as mp_manager:
1100 sym_file = self.tempdir / "libmetrics.so.sym"
1101 self._CreateSymbolFile(
1102 sym_file,
1103 func_lines=200,
1104 public_lines=2,
1105 stack_lines=2000,
1106 line_number_lines=10000,
1107 )
1108 found_files = mp_manager.list()
1109 self.assertTrue(
1110 cros_generate_breakpad_symbols.ValidateSymbolFile(
1111 str(sym_file),
1112 "/build/board/usr/lib64/libmetrics.so",
1113 "/build/board",
1114 found_files,
1115 )
1116 )
1117 self.assertEqual(
1118 list(found_files),
1119 [cros_generate_breakpad_symbols.ExpectedFiles.LIBMETRICS],
1120 )
1121
1122 def testInvalidLibMetricsSymbolFile(self):
1123 """Test that a libmetrics.so symbol file is held to higher standards."""
1124
1125 class LibMetricsSymbolFileTest:
1126 """Defines the subtest for an invalid libmetrics.so symbol file."""
1127
1128 def __init__(
1129 self,
1130 name,
1131 expected_error,
1132 func_lines=200,
1133 public_lines=2,
1134 stack_lines=2000,
1135 line_number_lines=10000,
1136 ):
1137 self.name = name
1138 self.expected_error = expected_error
1139 self.func_lines = func_lines
1140 self.public_lines = public_lines
1141 self.stack_lines = stack_lines
1142 self.line_number_lines = line_number_lines
1143
1144 LIBMETRICS_SYMBOL_TESTS = [
1145 LibMetricsSymbolFileTest(
1146 name="Insufficient FUNC records",
1147 func_lines=10,
1148 expected_error="libmetrics should have at least 100 FUNC "
1149 "records, found 10",
1150 ),
1151 LibMetricsSymbolFileTest(
1152 name="Insufficient PUBLIC records",
1153 public_lines=0,
1154 expected_error="libmetrics should have at least 1 PUBLIC "
1155 "record, found 0",
1156 ),
1157 LibMetricsSymbolFileTest(
1158 name="Insufficient STACK records",
1159 stack_lines=500,
1160 expected_error="libmetrics should have at least 1000 STACK "
1161 "records, found 500",
1162 ),
1163 LibMetricsSymbolFileTest(
1164 name="Insufficient line number records",
1165 line_number_lines=2000,
1166 expected_error="libmetrics should have at least 5000 "
1167 "line number records, found 2000",
1168 ),
1169 ]
1170 for test in LIBMETRICS_SYMBOL_TESTS:
1171 with self.subTest(
1172 name=test.name
1173 ), multiprocessing.Manager() as mp_manager:
1174 sym_file = self.tempdir / "libmetrics.so.sym"
1175 self._CreateSymbolFile(
1176 sym_file,
1177 func_lines=test.func_lines,
1178 public_lines=test.public_lines,
1179 stack_lines=test.stack_lines,
1180 line_number_lines=test.line_number_lines,
1181 )
1182 found_files = mp_manager.list()
1183 with self.assertLogs(level=logging.WARNING) as cm:
1184 self.assertFalse(
1185 cros_generate_breakpad_symbols.ValidateSymbolFile(
1186 str(sym_file),
1187 "/build/board/usr/lib64/libmetrics.so",
1188 "/build/board",
1189 found_files,
1190 )
1191 )
1192 self.assertIn(test.expected_error, cm.output[0])
1193 self.assertEqual(len(cm.output), 1)
1194
1195
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001196class UtilsTestDir(cros_test_lib.TempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -06001197 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001198
Alex Klein1699fab2022-09-08 08:46:06 -06001199 def testReadSymsHeaderGoodFile(self):
1200 """Make sure ReadSymsHeader can parse sym files"""
1201 sym_file = os.path.join(self.tempdir, "sym")
1202 osutils.WriteFile(sym_file, "MODULE Linux x86 s0m31D chrooome")
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -08001203 result = cros_generate_breakpad_symbols.ReadSymsHeader(
1204 sym_file, "unused_elfname"
1205 )
Alex Klein1699fab2022-09-08 08:46:06 -06001206 self.assertEqual(result.cpu, "x86")
1207 self.assertEqual(result.id, "s0m31D")
1208 self.assertEqual(result.name, "chrooome")
1209 self.assertEqual(result.os, "Linux")
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001210
1211
1212class UtilsTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -06001213 """Tests ReadSymsHeader."""
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001214
Alex Klein1699fab2022-09-08 08:46:06 -06001215 def testReadSymsHeaderGoodBuffer(self):
1216 """Make sure ReadSymsHeader can parse sym file handles"""
1217 result = cros_generate_breakpad_symbols.ReadSymsHeader(
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -08001218 io.BytesIO(b"MODULE Linux arm MY-ID-HERE blkid"), "unused_elfname"
Alex Klein1699fab2022-09-08 08:46:06 -06001219 )
1220 self.assertEqual(result.cpu, "arm")
1221 self.assertEqual(result.id, "MY-ID-HERE")
1222 self.assertEqual(result.name, "blkid")
1223 self.assertEqual(result.os, "Linux")
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001224
Alex Klein1699fab2022-09-08 08:46:06 -06001225 def testReadSymsHeaderBadd(self):
1226 """Make sure ReadSymsHeader throws on bad sym files"""
1227 self.assertRaises(
1228 ValueError,
1229 cros_generate_breakpad_symbols.ReadSymsHeader,
1230 io.BytesIO(b"asdf"),
Ian Barkley-Yeungfdaaf2e2023-03-02 17:30:39 -08001231 "unused_elfname",
Alex Klein1699fab2022-09-08 08:46:06 -06001232 )
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001233
Alex Klein1699fab2022-09-08 08:46:06 -06001234 def testBreakpadDir(self):
1235 """Make sure board->breakpad path expansion works"""
1236 expected = "/build/blah/usr/lib/debug/breakpad"
1237 result = cros_generate_breakpad_symbols.FindBreakpadDir("blah")
1238 self.assertEqual(expected, result)
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001239
Alex Klein1699fab2022-09-08 08:46:06 -06001240 def testDebugDir(self):
1241 """Make sure board->debug path expansion works"""
1242 expected = "/build/blah/usr/lib/debug"
1243 result = cros_generate_breakpad_symbols.FindDebugDir("blah")
1244 self.assertEqual(expected, result)
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001245
1246
Mike Frysingerea838d12014-12-08 11:55:32 -05001247def main(_argv):
Alex Klein1699fab2022-09-08 08:46:06 -06001248 # pylint: disable=protected-access
1249 # Set timeouts small so that if the unit test hangs, it won't hang for long.
1250 parallel._BackgroundTask.STARTUP_TIMEOUT = 5
1251 parallel._BackgroundTask.EXIT_TIMEOUT = 5
Mike Frysinger69cb41d2013-08-11 20:08:19 -04001252
Alex Klein1699fab2022-09-08 08:46:06 -06001253 # Run the tests.
1254 cros_test_lib.main(level="info", module=__name__)