blob: 6afd8a7ad5a2597500bdc79e71b36ea7d86d229d [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Unittests for ndk-stack.py"""
import unittest
from pathlib import Path, PurePosixPath
from unittest import mock
from unittest.mock import Mock, patch
from zipfile import ZipFile
import pytest
import ndkstack
class TestFindLlvmToolsBin:
def test_find_in_prebuilt(self, tmp_path: Path) -> None:
ndk_path = tmp_path / "ndk"
symbolizer_path = (
ndk_path / "toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-symbolizer"
)
symbolizer_path = symbolizer_path.with_suffix(ndkstack.EXE_SUFFIX)
symbolizer_path.parent.mkdir(parents=True)
symbolizer_path.touch()
assert (
ndkstack.find_llvm_tools_bin(ndk_path, ndk_path / "bin", "linux-x86_64")
== symbolizer_path.parent
)
def test_find_in_standalone_toolchain(self, tmp_path: Path) -> None:
ndk_path = tmp_path / "ndk"
symbolizer_path = ndk_path / "bin/llvm-symbolizer"
symbolizer_path = symbolizer_path.with_suffix(ndkstack.EXE_SUFFIX)
symbolizer_path.parent.mkdir(parents=True)
symbolizer_path.touch()
assert (
ndkstack.find_llvm_tools_bin(ndk_path, ndk_path / "bin", "linux-x86_64")
== symbolizer_path.parent
)
def test_not_found(self, tmp_path: Path) -> None:
with pytest.raises(RuntimeError, match="Unable to find LLVM tools directory"):
ndkstack.find_llvm_tools_bin(tmp_path, tmp_path / "bin", "linux-x86_64")
class FrameTests(unittest.TestCase):
"""Test parsing of backtrace lines."""
def test_line_with_map_name(self) -> None:
line = b" #14 pc 00001000 /fake/libfake.so"
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#14", frame_info.num)
self.assertEqual(b"00001000", frame_info.pc)
self.assertEqual(b"/fake/libfake.so", frame_info.tail)
self.assertEqual(PurePosixPath("/fake/libfake.so"), frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_function(self) -> None:
line = b" #08 pc 00001040 /fake/libfake.so (func())"
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#08", frame_info.num)
self.assertEqual(b"00001040", frame_info.pc)
self.assertEqual(b"/fake/libfake.so (func())", frame_info.tail)
self.assertEqual(PurePosixPath("/fake/libfake.so"), frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_offset(self) -> None:
line = b" #04 pc 00002050 /fake/libfake.so (offset 0x2000)"
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#04", frame_info.num)
self.assertEqual(b"00002050", frame_info.pc)
self.assertEqual(b"/fake/libfake.so (offset 0x2000)", frame_info.tail)
self.assertEqual(PurePosixPath("/fake/libfake.so"), frame_info.elf_file)
self.assertEqual(0x2000, frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_build_id(self) -> None:
line = b" #03 pc 00002050 /fake/libfake.so (BuildId: d1d420a58366bf29f1312ec826f16564)"
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#03", frame_info.num)
self.assertEqual(b"00002050", frame_info.pc)
self.assertEqual(
b"/fake/libfake.so (BuildId: d1d420a58366bf29f1312ec826f16564)",
frame_info.tail,
)
self.assertEqual(PurePosixPath("/fake/libfake.so"), frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertEqual(b"d1d420a58366bf29f1312ec826f16564", frame_info.build_id)
def test_line_with_container_file(self) -> None:
line = b" #10 pc 00003050 /fake/fake.apk!libc.so"
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#10", frame_info.num)
self.assertEqual(b"00003050", frame_info.pc)
self.assertEqual(b"/fake/fake.apk!libc.so", frame_info.tail)
self.assertEqual(PurePosixPath("libc.so"), frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertEqual(PurePosixPath("/fake/fake.apk"), frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_container_file_and_no_library(self) -> None:
line = b" #10 pc 00003050 /fake/fake.apk (offset 0x2000)"
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#10", frame_info.num)
self.assertEqual(b"00003050", frame_info.pc)
self.assertEqual(b"/fake/fake.apk (offset 0x2000)", frame_info.tail)
self.assertIsNone(frame_info.elf_file)
self.assertEqual(frame_info.offset, 0x2000)
self.assertEqual(PurePosixPath("/fake/fake.apk"), frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_with_container_and_elf_equal(self) -> None:
line = b" #12 pc 00004050 /fake/libc.so!lib/libc.so"
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#12", frame_info.num)
self.assertEqual(b"00004050", frame_info.pc)
self.assertEqual(b"/fake/libc.so!lib/libc.so", frame_info.tail)
self.assertEqual(PurePosixPath("/fake/libc.so"), frame_info.elf_file)
self.assertFalse(frame_info.offset)
self.assertFalse(frame_info.container_file)
self.assertFalse(frame_info.build_id)
def test_line_everything(self) -> None:
line = (
b" #07 pc 00823fc /fake/fake.apk!libc.so (__start_thread+64) "
b"(offset 0x1000) (BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)"
)
frame_info = ndkstack.FrameInfo.from_line(line)
assert frame_info is not None
self.assertEqual(b"#07", frame_info.num)
self.assertEqual(b"00823fc", frame_info.pc)
self.assertEqual(
b"/fake/fake.apk!libc.so (__start_thread+64) "
b"(offset 0x1000) (BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)",
frame_info.tail,
)
self.assertEqual(PurePosixPath("libc.so"), frame_info.elf_file)
self.assertEqual(0x1000, frame_info.offset)
self.assertEqual(PurePosixPath("/fake/fake.apk"), frame_info.container_file)
self.assertEqual(b"6a0c10d19d5bf39a5a78fa514371dab3", frame_info.build_id)
def test_0x_prefixed_address(self) -> None:
"""Tests that addresses beginning with 0x are parsed correctly."""
frame_info = ndkstack.FrameInfo.from_line(
b" #00 pc 0x000000000006263c "
b"/apex/com.android.runtime/lib/bionic/libc.so (abort+172)"
)
assert frame_info is not None
assert frame_info.pc == b"000000000006263c"
class FakeElfReader(ndkstack.ElfReader):
def __init__(
self, build_id: bytes | None = None, has_debug_info: bool = True
) -> None:
self._build_id = build_id
self._has_debug_info = has_debug_info
def build_id(self, path: Path) -> bytes | None:
return self._build_id
def has_debug_info(self, path: Path) -> bool:
return self._has_debug_info
class PathSuffixFakeBuildIdReader(ndkstack.ElfReader):
def __init__(
self, build_id_path_map: dict[Path, bytes | None], suffix_components: int
) -> None:
self.build_id_path_map = build_id_path_map
self.suffix_components = suffix_components
def build_id(self, path: Path) -> bytes | None:
# Construct a new Path using only the final suffix_components parts of the path.
# This will turn a/b/c/d into c/d if suffix_components is 2.
suffix = Path(*path.parts[-self.suffix_components :])
return self.build_id_path_map.get(suffix)
def has_debug_info(self, path: Path) -> bool:
return True
class TestElfSymbolSource:
def test_rejects_mismatched_file_names_with_no_build_id(self) -> None:
source = ndkstack.ElfSymbolSource(Path("libs/libapp.so"), FakeElfReader())
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 /fake/libfake.so")
assert frame is not None
assert source.find_providing_elf_file(frame) is None
def test_accepts_matching_file_names_with_no_build_id(self) -> None:
source = ndkstack.ElfSymbolSource(Path("libs/libapp.so"), FakeElfReader())
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 /fake/libapp.so")
assert frame is not None
assert source.find_providing_elf_file(frame) == Path("libs/libapp.so")
def test_accepts_matching_build_id_with_different_file_name(self) -> None:
source = ndkstack.ElfSymbolSource(
Path("libs/libapp.so"), FakeElfReader(b"d1d420a58366bf29f1312ec826f16564")
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 /fake/libfake.so (BuildId: d1d420a58366bf29f1312ec826f16564)"
)
assert frame is not None
assert source.find_providing_elf_file(frame) == Path("libs/libapp.so")
def test_rejects_mismatched_build_id_with_same_file_name(self) -> None:
source = ndkstack.ElfSymbolSource(
Path("libs/libfake.so"), FakeElfReader(b"6a0c10d19d5bf39a5a78fa514371dab3")
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 /fake/libfake.so (BuildId: d1d420a58366bf29f1312ec826f16564)"
)
assert frame is not None
assert source.find_providing_elf_file(frame) is None
def test_accepts_matching_build_id_with_same_file_name(self) -> None:
source = ndkstack.ElfSymbolSource(
Path("libs/libapp.so"), FakeElfReader(b"d1d420a58366bf29f1312ec826f16564")
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 /fake/libapp.so (BuildId: d1d420a58366bf29f1312ec826f16564)"
)
assert frame is not None
assert source.find_providing_elf_file(frame) == Path("libs/libapp.so")
def test_rejects_file_without_debug_info(self) -> None:
source = ndkstack.ElfSymbolSource(
Path("libs/libfake.so"), FakeElfReader(has_debug_info=False)
)
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 /fake/libfake.so")
assert frame is not None
assert source.find_providing_elf_file(frame) is None
def test_matches_alternate_name(self) -> None:
source = ndkstack.ElfSymbolSource(
Path("libs/libfake.so.dbg"), FakeElfReader(), name_for_match="libfake.so"
)
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 /fake/libfake.so")
assert frame is not None
assert source.find_providing_elf_file(frame) == Path("libs/libfake.so.dbg")
class TestApkSymbolSource:
def test_rejects_apks_with_no_file_at_offset(self, tmp_path: Path) -> None:
apk_path = tmp_path / "Test.apk"
with ZipFile(apk_path, mode="w"):
# Intentionally empty so no offset matches.
pass
source = ndkstack.ApkSymbolSource(apk_path, FakeElfReader(), tmp_path)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 /fake/fake.apk!libtest.so (offset 0x2000)"
)
assert frame is not None
assert source.find_providing_elf_file(frame) is None
def test_rejects_mismatched_build_ids(self, tmp_path: Path) -> None:
apk_path = tmp_path / "Test.apk"
with ZipFile(apk_path, mode="w") as zip_file:
zip_file.writestr("libtest.so", "")
offset = zip_file.getinfo("libtest.so").header_offset
source = ndkstack.ApkSymbolSource(
apk_path,
FakeElfReader(b"d1d420a58366bf29f1312ec826f16564"),
tmp_path,
)
frame = ndkstack.FrameInfo.from_line(
(
f" #03 pc 00002050 /fake/fake.apk!libtest.so (offset 0x{offset:02x}) "
"(BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)"
).encode("utf-8")
)
assert frame is not None
assert source.find_providing_elf_file(frame) is None
def test_finds_file_in_apk(self, tmp_path: Path) -> None:
apk_path = tmp_path / "Test.apk"
with ZipFile(apk_path, mode="w") as zip_file:
zip_file.writestr("libtest.so", "")
offset = zip_file.getinfo("libtest.so").header_offset
source = ndkstack.ApkSymbolSource(
apk_path,
FakeElfReader(b"6a0c10d19d5bf39a5a78fa514371dab3"),
tmp_path,
)
frame = ndkstack.FrameInfo.from_line(
(
f" #03 pc 00002050 /fake/fake.apk!libtest.so (offset 0x{offset:02x}) "
"(BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)"
).encode("utf-8")
)
assert frame is not None
assert source.find_providing_elf_file(frame) == tmp_path / "libtest.so"
def test_finds_file_in_container_only_frame(self, tmp_path: Path) -> None:
apk_path = tmp_path / "Test.apk"
with ZipFile(apk_path, mode="w") as zip_file:
zip_file.writestr("libtest.so", "")
offset = zip_file.getinfo("libtest.so").header_offset
source = ndkstack.ApkSymbolSource(
apk_path,
FakeElfReader(b"6a0c10d19d5bf39a5a78fa514371dab3"),
tmp_path,
)
frame = ndkstack.FrameInfo.from_line(
(
f" #03 pc 00002050 /fake/fake.apk (offset 0x{offset:02x}) "
"(BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)"
).encode("utf-8")
)
assert frame is not None
assert source.find_providing_elf_file(frame) == tmp_path / "libtest.so"
class TestPlayDebugZipSymbolSource:
def test_finds_matched_build_id(self, tmp_path: Path) -> None:
zip_path = tmp_path / "native-debug-symbols.zip"
with ZipFile(zip_path, mode="w") as zip_file:
zip_file.writestr("arm64-v8a/libapp.so.dbg", "arm64-v8a/libapp.so")
zip_file.writestr("armeabi-v7a/libapp.so.dbg", "armeabi-v7a/libapp.so")
zip_file.writestr("x86/libapp.so.dbg", "x86/libapp.so")
zip_file.writestr("x86_64/libapp.so.dbg", "x86_64/libapp.so")
source = ndkstack.PlayDebugZipSymbolSource(
zip_path,
PathSuffixFakeBuildIdReader(
{
Path("arm64-v8a/libapp.so.dbg"): b"0123",
Path("armeabi-v7a/libapp.so.dbg"): b"4567",
Path("x86/libapp.so.dbg"): b"89ab",
Path("x86_64/libapp.so.dbg"): b"cdef",
},
suffix_components=2,
),
tmp_path,
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 libtest.so (BuildId: 4567)"
)
assert frame is not None
provider = source.find_providing_elf_file(frame)
assert provider is not None
assert provider.read_text() == "armeabi-v7a/libapp.so"
def test_rejects_unmatch_build_id(self, tmp_path: Path) -> None:
zip_path = tmp_path / "native-debug-symbols.zip"
with ZipFile(zip_path, mode="w") as zip_file:
zip_file.writestr("arm64-v8a/libapp.so.dbg", "arm64-v8a/libapp.so")
zip_file.writestr("armeabi-v7a/libapp.so.dbg", "armeabi-v7a/libapp.so")
zip_file.writestr("x86/libapp.so.dbg", "x86/libapp.so")
zip_file.writestr("x86_64/libapp.so.dbg", "x86_64/libapp.so")
source = ndkstack.PlayDebugZipSymbolSource(
zip_path,
PathSuffixFakeBuildIdReader(
{
Path("arm64-v8a/libapp.so.dbg"): b"0123",
Path("armeabi-v7a/libapp.so.dbg"): b"4567",
Path("x86/libapp.so.dbg"): b"89ab",
Path("x86_64/libapp.so.dbg"): b"cdef",
},
suffix_components=2,
),
tmp_path,
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 libtest.so (BuildId: 4827)"
)
assert frame is not None
provider = source.find_providing_elf_file(frame)
assert provider is None
def test_finds_correct_abi_without_build_id(self, tmp_path: Path) -> None:
zip_path = tmp_path / "native-debug-symbols.zip"
with ZipFile(zip_path, mode="w") as zip_file:
zip_file.writestr("arm64-v8a/libapp.so.dbg", "arm64-v8a/libapp.so")
zip_file.writestr("armeabi-v7a/libapp.so.dbg", "armeabi-v7a/libapp.so")
zip_file.writestr("x86/libapp.so.dbg", "x86/libapp.so")
zip_file.writestr("x86_64/libapp.so.dbg", "x86_64/libapp.so")
source = ndkstack.PlayDebugZipSymbolSource(zip_path, FakeElfReader(), tmp_path)
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 libapp.so", abi="x86")
assert frame is not None
provider = source.find_providing_elf_file(frame)
assert provider is not None
assert provider.read_text() == "x86/libapp.so"
class TestDirectorySymbolSource:
def test_finds_file_in_directory(self, tmp_path: Path) -> None:
(tmp_path / "libapp.so").touch()
source = ndkstack.DirectorySymbolSource(
tmp_path, FakeElfReader(), tmp_path / "tmp"
)
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 /fake/libapp.so")
assert frame is not None
assert source.find_providing_elf_file(frame) == tmp_path / "libapp.so"
def test_finds_file_in_apk(self, tmp_path: Path) -> None:
apk_path = tmp_path / "Test.apk"
with ZipFile(apk_path, mode="w") as zip_file:
zip_file.writestr("libapp.so", "")
offset = zip_file.getinfo("libapp.so").header_offset
source = ndkstack.DirectorySymbolSource(
tmp_path, FakeElfReader(), tmp_path / "tmp"
)
frame = ndkstack.FrameInfo.from_line(
(
f" #03 pc 00002050 /fake/fake.apk (offset 0x{offset:02x}) "
"(BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)"
).encode("utf-8")
)
assert frame is not None
assert source.find_providing_elf_file(frame) == tmp_path / "tmp/libapp.so"
def test_prefers_non_container_source(self, tmp_path: Path) -> None:
# This test is not (and cannot) be completely reliable. The implementation uses
# Path.iterdir() internally, whose iteration order is not documented, but is
# almost certainly directory order, which varies by file system. If this test
# fails, there is a bug in the implementation, but it may incorrectly pass.
#
# Try to get the APK to show up first by making sure it's both the first file
# added to the directory, and alphabetically first. It's not a guarantee, but
# it's correct on at least some systems.
apk_path = tmp_path / "0Test.apk"
with ZipFile(apk_path, mode="w") as zip_file:
zip_file.writestr("libapp.so", "")
offset = zip_file.getinfo("libapp.so").header_offset
(tmp_path / "libapp.so").touch()
source = ndkstack.DirectorySymbolSource(
tmp_path, FakeElfReader(), tmp_path / "tmp"
)
frame = ndkstack.FrameInfo.from_line(
(
f" #03 pc 00002050 /fake/0Test.apk!libapp.so (offset 0x{offset:02x}) "
"(BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)"
).encode("utf-8")
)
assert frame is not None
assert source.find_providing_elf_file(frame) == tmp_path / "libapp.so"
def test_finds_build_id_match_without_apk(self, tmp_path: Path) -> None:
(tmp_path / "libapp.so").touch()
source = ndkstack.DirectorySymbolSource(
tmp_path,
FakeElfReader(b"6a0c10d19d5bf39a5a78fa514371dab3"),
tmp_path / "tmp",
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 /fake/fake.apk (offset 0x0) "
b"(BuildId: 6a0c10d19d5bf39a5a78fa514371dab3)"
)
assert frame is not None
assert source.find_providing_elf_file(frame) == tmp_path / "libapp.so"
class FakeSingleUseSymbolSource(ndkstack.SymbolSource):
def __init__(
self,
build_id_matches: dict[bytes, Path],
path_matches: dict[str, Path],
container_offset_matches: dict[tuple[str, int], Path],
) -> None:
self.build_id_matches = build_id_matches
self.path_matches = path_matches
self.container_offset_matches = container_offset_matches
self.used = False
def find_providing_elf_file(self, frame_info: ndkstack.FrameInfo) -> Path | None:
if self.used:
pytest.fail("FakeSingleUseSymbolSource was queried more than once")
provider = self._find_providing_elf_file(frame_info)
if provider is not None:
self.used = True
return provider
def _find_providing_elf_file(self, frame_info: ndkstack.FrameInfo) -> Path | None:
if (
frame_info.build_id is not None
and frame_info.build_id in self.build_id_matches
):
return self.build_id_matches[frame_info.build_id]
if (
frame_info.elf_file is not None
and frame_info.elf_file.name in self.path_matches
):
return self.path_matches[frame_info.elf_file.name]
if frame_info.container_file is not None:
assert frame_info.offset is not None
return self.container_offset_matches.get(
(frame_info.container_file.name, frame_info.offset)
)
return None
class TestCachedSymbolSource:
def test_finds_cached_build_id(self) -> None:
source = ndkstack.CachingSymbolSource(
FakeSingleUseSymbolSource(
{b"1234": Path("build-id/1234")},
{"libapp.so": Path("symbols/libapp.so")},
{("test.apk", 0x1000): Path("extracted/libapp.so")},
)
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 /fake/libfake.so (BuildId: 1234)"
)
assert frame is not None
assert source.find_providing_elf_file(frame) == Path("build-id/1234")
assert source.find_providing_elf_file(frame) == Path("build-id/1234")
def test_finds_cached_name(self) -> None:
source = ndkstack.CachingSymbolSource(
FakeSingleUseSymbolSource(
{b"1234": Path("build-id/1234")},
{"libapp.so": Path("symbols/libapp.so")},
{("test.apk", 0x1000): Path("extracted/libapp.so")},
)
)
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 /fake/libapp.so")
assert frame is not None
assert source.find_providing_elf_file(frame) == Path("symbols/libapp.so")
assert source.find_providing_elf_file(frame) == Path("symbols/libapp.so")
def test_finds_cached_container_and_offset(self) -> None:
source = ndkstack.CachingSymbolSource(
FakeSingleUseSymbolSource(
{b"1234": Path("build-id/1234")},
{"libapp.so": Path("symbols/libapp.so")},
{("test.apk", 0x1000): Path("extracted/libapp.so")},
)
)
frame = ndkstack.FrameInfo.from_line(
b" #03 pc 00002050 /fake/test.apk (offset 0x1000)"
)
assert frame is not None
assert source.find_providing_elf_file(frame) == Path("extracted/libapp.so")
assert source.find_providing_elf_file(frame) == Path("extracted/libapp.so")
def test_rejects_missing(self) -> None:
source = ndkstack.CachingSymbolSource(
FakeSingleUseSymbolSource(
{b"1234": Path("build-id/1234")},
{"libapp.so": Path("symbols/libapp.so")},
{("test.apk", 0x1000): Path("extracted/libapp.so")},
)
)
frame = ndkstack.FrameInfo.from_line(b" #03 pc 00002050 /fake/libmissing.so")
assert frame is not None
assert source.find_providing_elf_file(frame) is None
class GetZipInfoFromOffsetTests(unittest.TestCase):
"""Tests of get_zip_info_from_offset()."""
def setUp(self) -> None:
self.mock_zip = mock.MagicMock()
self.mock_zip.filename = "/fake/zip.apk"
self.mock_zip.infolist.return_value = []
def test_file_does_not_exist(self) -> None:
with self.assertRaises(IOError):
_ = ndkstack.get_zip_info_from_offset(self.mock_zip, 0x1000)
@patch("os.stat")
def test_offset_ge_file_size(self, mock_stat: Mock) -> None:
mock_stat.return_value.st_size = 0x1000
self.assertFalse(ndkstack.get_zip_info_from_offset(self.mock_zip, 0x1000))
self.assertFalse(ndkstack.get_zip_info_from_offset(self.mock_zip, 0x1100))
@patch("os.stat")
def test_empty_infolist(self, mock_stat: Mock) -> None:
mock_stat.return_value.st_size = 0x1000
self.assertFalse(ndkstack.get_zip_info_from_offset(self.mock_zip, 0x900))
@patch("os.stat")
def test_zip_info_single_element(self, mock_stat: Mock) -> None:
mock_stat.return_value.st_size = 0x2000
mock_zip_info = mock.MagicMock()
mock_zip_info.header_offset = 0x100
self.mock_zip.infolist.return_value = [mock_zip_info]
self.assertFalse(ndkstack.get_zip_info_from_offset(self.mock_zip, 0x50))
self.assertFalse(ndkstack.get_zip_info_from_offset(self.mock_zip, 0x2000))
zip_info = ndkstack.get_zip_info_from_offset(self.mock_zip, 0x200)
assert zip_info is not None
self.assertEqual(0x100, zip_info.header_offset)
@patch("os.stat")
def test_zip_info_checks(self, mock_stat: Mock) -> None:
mock_stat.return_value.st_size = 0x2000
mock_zip_info1 = mock.MagicMock()
mock_zip_info1.header_offset = 0x100
mock_zip_info2 = mock.MagicMock()
mock_zip_info2.header_offset = 0x1000
self.mock_zip.infolist.return_value = [mock_zip_info1, mock_zip_info2]
self.assertFalse(ndkstack.get_zip_info_from_offset(self.mock_zip, 0x50))
zip_info = ndkstack.get_zip_info_from_offset(self.mock_zip, 0x200)
assert zip_info is not None
self.assertEqual(0x100, zip_info.header_offset)
zip_info = ndkstack.get_zip_info_from_offset(self.mock_zip, 0x100)
assert zip_info is not None
self.assertEqual(0x100, zip_info.header_offset)
zip_info = ndkstack.get_zip_info_from_offset(self.mock_zip, 0x1000)
assert zip_info is not None
self.assertEqual(0x1000, zip_info.header_offset)
class TestParseAbi:
def test_parse_abi_from_line(self) -> None:
assert (
ndkstack.parse_abi_from_line(
b"12-12 15:10:14.473 8156 8156 F DEBUG : ABI: 'arm'"
)
== "armeabi-v7a"
)
assert (
ndkstack.parse_abi_from_line(
b"12-12 15:10:14.473 8156 8156 F DEBUG : ABI: 'arm64'"
)
== "arm64-v8a"
)
assert (
ndkstack.parse_abi_from_line(
b"12-12 15:10:14.473 8156 8156 F DEBUG : ABI: 'x86'"
)
== "x86"
)
assert (
ndkstack.parse_abi_from_line(
b"12-12 15:10:14.473 8156 8156 F DEBUG : ABI: 'x86_64'"
)
== "x86_64"
)
if __name__ == "__main__":
unittest.main()