symbolic 12.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- symbolic-12.6.2/MANIFEST.in +1 -0
- symbolic-12.6.2/PKG-INFO +16 -0
- symbolic-12.6.2/README +4 -0
- symbolic-12.6.2/rustsrc.zip +0 -0
- symbolic-12.6.2/setup.cfg +11 -0
- symbolic-12.6.2/setup.py +123 -0
- symbolic-12.6.2/symbolic/__init__.py +23 -0
- symbolic-12.6.2/symbolic/cfi.py +60 -0
- symbolic-12.6.2/symbolic/common.py +69 -0
- symbolic-12.6.2/symbolic/debuginfo.py +291 -0
- symbolic-12.6.2/symbolic/exceptions.py +70 -0
- symbolic-12.6.2/symbolic/proguard.py +119 -0
- symbolic-12.6.2/symbolic/sourcemap.py +199 -0
- symbolic-12.6.2/symbolic/sourcemapcache.py +86 -0
- symbolic-12.6.2/symbolic/symcache.py +152 -0
- symbolic-12.6.2/symbolic/utils.py +194 -0
- symbolic-12.6.2/symbolic.egg-info/PKG-INFO +16 -0
- symbolic-12.6.2/symbolic.egg-info/SOURCES.txt +22 -0
- symbolic-12.6.2/symbolic.egg-info/dependency_links.txt +1 -0
- symbolic-12.6.2/symbolic.egg-info/not-zip-safe +1 -0
- symbolic-12.6.2/symbolic.egg-info/requires.txt +1 -0
- symbolic-12.6.2/symbolic.egg-info/top_level.txt +1 -0
- symbolic-12.6.2/version.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include rustsrc.zip version.txt
|
symbolic-12.6.2/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: symbolic
|
|
3
|
+
Version: 12.6.2
|
|
4
|
+
Summary: A python library for dealing with symbol files and more.
|
|
5
|
+
Home-page: UNKNOWN
|
|
6
|
+
Author: Sentry
|
|
7
|
+
Author-email: hello@sentry.io
|
|
8
|
+
License: MIT
|
|
9
|
+
Description: Symbolic
|
|
10
|
+
--------
|
|
11
|
+
|
|
12
|
+
Symbolic is a Python library that can work with debug symbol files.
|
|
13
|
+
|
|
14
|
+
Platform: any
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
symbolic-12.6.2/README
ADDED
|
Binary file
|
symbolic-12.6.2/setup.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import atexit
|
|
5
|
+
import shutil
|
|
6
|
+
import zipfile
|
|
7
|
+
import tempfile
|
|
8
|
+
import subprocess
|
|
9
|
+
from setuptools import setup, find_packages
|
|
10
|
+
from setuptools.command.sdist import sdist
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_version_re = re.compile(r'(?m)^version\s*=\s*"(.*?)"\s*$')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DEBUG_BUILD = os.environ.get("SYMBOLIC_DEBUG") == "1"
|
|
17
|
+
|
|
18
|
+
with open("README") as f:
|
|
19
|
+
readme = f.read()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if os.path.isfile("../symbolic-cabi/Cargo.toml"):
|
|
23
|
+
with open("../symbolic-cabi/Cargo.toml") as f:
|
|
24
|
+
match = _version_re.search(f.read())
|
|
25
|
+
assert match is not None
|
|
26
|
+
version = match[1]
|
|
27
|
+
else:
|
|
28
|
+
with open("version.txt") as f:
|
|
29
|
+
version = f.readline().strip()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def vendor_rust_deps():
|
|
33
|
+
subprocess.Popen(["scripts/git-archive-all", "py/rustsrc.zip"], cwd="..").wait()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def write_version():
|
|
37
|
+
with open("version.txt", "w") as f:
|
|
38
|
+
f.write("%s\n" % version)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CustomSDist(sdist):
|
|
42
|
+
def run(self):
|
|
43
|
+
vendor_rust_deps()
|
|
44
|
+
write_version()
|
|
45
|
+
sdist.run(self)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_native(spec):
|
|
49
|
+
cmd = ["cargo", "build", "-p", "symbolic-cabi"]
|
|
50
|
+
if not DEBUG_BUILD:
|
|
51
|
+
cmd.append("--release")
|
|
52
|
+
target = "release"
|
|
53
|
+
else:
|
|
54
|
+
target = "debug"
|
|
55
|
+
|
|
56
|
+
# Step 0: find rust sources
|
|
57
|
+
if not os.path.isfile("../symbolic-cabi/Cargo.toml"):
|
|
58
|
+
scratchpad = tempfile.mkdtemp()
|
|
59
|
+
|
|
60
|
+
@atexit.register
|
|
61
|
+
def delete_scratchpad():
|
|
62
|
+
try:
|
|
63
|
+
shutil.rmtree(scratchpad)
|
|
64
|
+
except OSError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
zf = zipfile.ZipFile("rustsrc.zip")
|
|
68
|
+
zf.extractall(scratchpad)
|
|
69
|
+
rust_path = scratchpad + "/rustsrc"
|
|
70
|
+
else:
|
|
71
|
+
rust_path = ".."
|
|
72
|
+
scratchpad = None
|
|
73
|
+
|
|
74
|
+
# Step 1: build the rust library
|
|
75
|
+
print("running `{}` ({} target)".format(" ".join(cmd), target))
|
|
76
|
+
build = spec.add_external_build(cmd=cmd, path=rust_path)
|
|
77
|
+
|
|
78
|
+
def find_dylib():
|
|
79
|
+
cargo_target = os.environ.get("CARGO_BUILD_TARGET")
|
|
80
|
+
if cargo_target:
|
|
81
|
+
in_path = f"target/{cargo_target}/{target}"
|
|
82
|
+
else:
|
|
83
|
+
in_path = "target/%s" % target
|
|
84
|
+
return build.find_dylib("symbolic_cabi", in_path=in_path)
|
|
85
|
+
|
|
86
|
+
rtld_flags = ["NOW"]
|
|
87
|
+
if sys.platform == "darwin":
|
|
88
|
+
rtld_flags.append("NODELETE")
|
|
89
|
+
spec.add_cffi_module(
|
|
90
|
+
module_path="symbolic._lowlevel",
|
|
91
|
+
dylib=find_dylib,
|
|
92
|
+
header_filename=lambda: build.find_header(
|
|
93
|
+
"symbolic.h", in_path="symbolic-cabi/include"
|
|
94
|
+
),
|
|
95
|
+
rtld_flags=rtld_flags,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
setup(
|
|
100
|
+
name="symbolic",
|
|
101
|
+
version=version,
|
|
102
|
+
packages=find_packages(),
|
|
103
|
+
author="Sentry",
|
|
104
|
+
license="MIT",
|
|
105
|
+
author_email="hello@sentry.io",
|
|
106
|
+
description="A python library for dealing with symbol files and more.",
|
|
107
|
+
long_description=readme,
|
|
108
|
+
include_package_data=True,
|
|
109
|
+
package_data={"symbolic": ["py.typed", "_lowlevel.pyi"]},
|
|
110
|
+
long_description_content_type="text/markdown",
|
|
111
|
+
zip_safe=False,
|
|
112
|
+
platforms="any",
|
|
113
|
+
install_requires=["milksnake>=0.1.2"],
|
|
114
|
+
# Specify transitive dependencies manually that are required for the build because
|
|
115
|
+
# they are not resolved properly since upgrading to python 3.11 in manylinux.
|
|
116
|
+
# milksnake -> cffi -> pycparser
|
|
117
|
+
# milksnake specifies cffi>=1.6.0 as dependency while cffi does not specify a
|
|
118
|
+
# minimum version for pycparser
|
|
119
|
+
setup_requires=["milksnake>=0.1.2", "cffi>=1.6.0", "pycparser"],
|
|
120
|
+
python_requires=">=3.8",
|
|
121
|
+
milksnake_tasks=[build_native],
|
|
122
|
+
cmdclass={"sdist": CustomSDist},
|
|
123
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__all__ = []
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _import_all():
|
|
5
|
+
import pkgutil
|
|
6
|
+
|
|
7
|
+
glob = globals()
|
|
8
|
+
for _, modname, _ in pkgutil.iter_modules(__path__):
|
|
9
|
+
if modname[:1] == "_":
|
|
10
|
+
continue
|
|
11
|
+
mod = __import__("symbolic.%s" % modname, glob, glob, ["__name__"])
|
|
12
|
+
if not hasattr(mod, "__all__"):
|
|
13
|
+
continue
|
|
14
|
+
__all__.extend(mod.__all__)
|
|
15
|
+
for name in mod.__all__:
|
|
16
|
+
obj = getattr(mod, name)
|
|
17
|
+
if hasattr(obj, "__module__"):
|
|
18
|
+
obj.__module__ = "symbolic"
|
|
19
|
+
glob[name] = obj
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_import_all()
|
|
23
|
+
del _import_all
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Minidump processing"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import shutil
|
|
5
|
+
from typing import IO
|
|
6
|
+
|
|
7
|
+
from symbolic._lowlevel import lib, ffi
|
|
8
|
+
from symbolic.utils import (
|
|
9
|
+
RustObject,
|
|
10
|
+
rustcall,
|
|
11
|
+
encode_path,
|
|
12
|
+
make_buffered_slice_reader,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CfiCache",
|
|
17
|
+
"CFICACHE_LATEST_VERSION",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
# The most recent version for the CFI cache file format
|
|
21
|
+
CFICACHE_LATEST_VERSION = rustcall(lib.symbolic_cficache_latest_version)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CfiCache(RustObject):
|
|
25
|
+
"""A cache for call frame information (CFI) to improve native stackwalking"""
|
|
26
|
+
|
|
27
|
+
__dealloc_func__ = lib.symbolic_cficache_free
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def open(cls, path: str) -> CfiCache:
|
|
31
|
+
"""Loads a cficache from a file via mmap."""
|
|
32
|
+
return cls._from_objptr(rustcall(lib.symbolic_cficache_open, encode_path(path)))
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_object(cls, obj: RustObject) -> CfiCache:
|
|
36
|
+
"""Creates a cficache from the given object."""
|
|
37
|
+
return cls._from_objptr(
|
|
38
|
+
rustcall(lib.symbolic_cficache_from_object, obj._get_objptr())
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def version(self) -> str:
|
|
43
|
+
"""Version of the file format."""
|
|
44
|
+
return self._methodcall(lib.symbolic_cficache_get_version)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def is_latest_version(self) -> bool:
|
|
48
|
+
"""Returns true if this is the latest file format."""
|
|
49
|
+
return self.version >= CFICACHE_LATEST_VERSION
|
|
50
|
+
|
|
51
|
+
def open_stream(self) -> IO[bytes]:
|
|
52
|
+
"""Returns a stream to read files from the internal buffer."""
|
|
53
|
+
buf = self._methodcall(lib.symbolic_cficache_get_bytes)
|
|
54
|
+
size = self._methodcall(lib.symbolic_cficache_get_size)
|
|
55
|
+
return make_buffered_slice_reader(ffi.buffer(buf, size), self)
|
|
56
|
+
|
|
57
|
+
def write_to(self, f: IO[bytes]) -> None:
|
|
58
|
+
"""Writes the CFI cache into a file object."""
|
|
59
|
+
# https://github.com/python/mypy/issues/15031
|
|
60
|
+
shutil.copyfileobj(self.open_stream(), f) # type: ignore[misc]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import overload
|
|
5
|
+
|
|
6
|
+
from symbolic._lowlevel import lib, ffi
|
|
7
|
+
from symbolic.utils import rustcall, encode_str, decode_str
|
|
8
|
+
from symbolic import exceptions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["arch_is_known", "arch_get_ip_reg_name", "normalize_arch", "parse_addr"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ignore_arch_exc = (exceptions.UnknownArchError,)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Make sure we init the lib and turn on rust backtraces
|
|
18
|
+
os.environ["RUST_BACKTRACE"] = "1"
|
|
19
|
+
ffi.init_once(lib.symbolic_init, "init")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def arch_is_known(arch: str) -> bool:
|
|
23
|
+
"""Checks if an architecture is known."""
|
|
24
|
+
if not isinstance(arch, str):
|
|
25
|
+
return False
|
|
26
|
+
return rustcall(lib.symbolic_arch_is_known, encode_str(arch))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def normalize_arch(arch: None) -> None:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@overload
|
|
35
|
+
def normalize_arch(arch: str) -> str:
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def normalize_arch(arch: str | None) -> str | None:
|
|
40
|
+
"""Normalizes an architecture name."""
|
|
41
|
+
if arch is None:
|
|
42
|
+
return None
|
|
43
|
+
if not isinstance(arch, str):
|
|
44
|
+
raise ValueError("Invalid architecture: expected string")
|
|
45
|
+
|
|
46
|
+
normalized = rustcall(lib.symbolic_normalize_arch, encode_str(arch))
|
|
47
|
+
return decode_str(normalized, free=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def arch_get_ip_reg_name(arch: str) -> str | None:
|
|
51
|
+
"""Returns the ip register if known for this arch."""
|
|
52
|
+
try:
|
|
53
|
+
rv = rustcall(lib.symbolic_arch_ip_reg_name, encode_str(arch))
|
|
54
|
+
return str(decode_str(rv, free=True))
|
|
55
|
+
except ignore_arch_exc:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def parse_addr(x: int | str | None) -> int:
|
|
60
|
+
"""Parses an address."""
|
|
61
|
+
if x is None:
|
|
62
|
+
return 0
|
|
63
|
+
if isinstance(x, int):
|
|
64
|
+
return x
|
|
65
|
+
if isinstance(x, str):
|
|
66
|
+
if x[:2] == "0x":
|
|
67
|
+
return int(x[2:], 16)
|
|
68
|
+
return int(x)
|
|
69
|
+
raise ValueError(f"Unsupported address format {x!r}")
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import bisect
|
|
4
|
+
from weakref import WeakValueDictionary
|
|
5
|
+
from typing import overload, Generator
|
|
6
|
+
|
|
7
|
+
from symbolic._lowlevel import lib, ffi
|
|
8
|
+
from symbolic.utils import RustObject, rustcall, decode_str, encode_str
|
|
9
|
+
from symbolic.common import parse_addr, arch_is_known
|
|
10
|
+
from symbolic.symcache import SymCache
|
|
11
|
+
from symbolic.cfi import CfiCache
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Archive",
|
|
16
|
+
"Object",
|
|
17
|
+
"ObjectLookup",
|
|
18
|
+
"BcSymbolMap",
|
|
19
|
+
"UuidMapping",
|
|
20
|
+
"id_from_breakpad",
|
|
21
|
+
"normalize_code_id",
|
|
22
|
+
"normalize_debug_id",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Archive(RustObject):
|
|
27
|
+
__dealloc_func__ = lib.symbolic_archive_free
|
|
28
|
+
|
|
29
|
+
_objcache: WeakValueDictionary[int, Object]
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def open(self, path: str | bytes) -> Archive:
|
|
33
|
+
"""Opens an archive from a given path."""
|
|
34
|
+
if isinstance(path, str):
|
|
35
|
+
path = path.encode("utf-8")
|
|
36
|
+
return Archive._from_objptr(rustcall(lib.symbolic_archive_open, path))
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_bytes(self, data: bytes) -> Archive:
|
|
40
|
+
"""Loads an archive from a binary buffer."""
|
|
41
|
+
return Archive._from_objptr(
|
|
42
|
+
rustcall(lib.symbolic_archive_from_bytes, data, len(data))
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def object_count(self) -> int:
|
|
47
|
+
"""The number of objects in this archive."""
|
|
48
|
+
return self._methodcall(lib.symbolic_archive_object_count)
|
|
49
|
+
|
|
50
|
+
def iter_objects(self) -> Generator[Object, None, None]:
|
|
51
|
+
"""Iterates over all objects."""
|
|
52
|
+
for idx in range(self.object_count):
|
|
53
|
+
try:
|
|
54
|
+
yield self._get_object(idx)
|
|
55
|
+
except LookupError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def get_object(
|
|
59
|
+
self, debug_id: str | None = None, arch: str | None = None
|
|
60
|
+
) -> Object:
|
|
61
|
+
"""Get an object by either arch or id."""
|
|
62
|
+
if debug_id is not None:
|
|
63
|
+
debug_id = debug_id.lower()
|
|
64
|
+
for obj in self.iter_objects():
|
|
65
|
+
if obj.debug_id == debug_id or obj.arch == arch:
|
|
66
|
+
return obj
|
|
67
|
+
raise LookupError("Object not found")
|
|
68
|
+
|
|
69
|
+
def _get_object(self, idx: int) -> Object:
|
|
70
|
+
"""Returns the object at a certain index."""
|
|
71
|
+
cache = getattr(self, "_objcache", None)
|
|
72
|
+
if cache is None:
|
|
73
|
+
cache = self._objcache = WeakValueDictionary()
|
|
74
|
+
rv = cache.get(idx)
|
|
75
|
+
if rv is not None:
|
|
76
|
+
return rv
|
|
77
|
+
ptr = self._methodcall(lib.symbolic_archive_get_object, idx)
|
|
78
|
+
if ptr == ffi.NULL:
|
|
79
|
+
raise LookupError("No object #%d" % idx)
|
|
80
|
+
rv = cache[idx] = Object._from_objptr(ptr)
|
|
81
|
+
return rv
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Object(RustObject):
|
|
85
|
+
__dealloc_func__ = lib.symbolic_object_free
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def arch(self) -> str:
|
|
89
|
+
"""The architecture of the object."""
|
|
90
|
+
# make it an ascii bytestring on 2.x
|
|
91
|
+
arch = self._methodcall(lib.symbolic_object_get_arch)
|
|
92
|
+
return str(decode_str(arch, free=True))
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def code_id(self) -> str | None:
|
|
96
|
+
"""The code identifier of the object. Returns None if there is no code id."""
|
|
97
|
+
code_id = self._methodcall(lib.symbolic_object_get_code_id)
|
|
98
|
+
code_id = decode_str(code_id, free=True)
|
|
99
|
+
if code_id:
|
|
100
|
+
return code_id
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def debug_id(self) -> str:
|
|
105
|
+
"""The debug identifier of the object."""
|
|
106
|
+
debug_id = self._methodcall(lib.symbolic_object_get_debug_id)
|
|
107
|
+
return decode_str(debug_id, free=True)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def kind(self) -> str:
|
|
111
|
+
"""The kind of the object (e.g. executable, debug file, library, ...)."""
|
|
112
|
+
kind = self._methodcall(lib.symbolic_object_get_kind)
|
|
113
|
+
return str(decode_str(kind, free=True))
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def file_format(self) -> str:
|
|
117
|
+
"""The file format of the object file (e.g. MachO, ELF, ...)."""
|
|
118
|
+
format = self._methodcall(lib.symbolic_object_get_file_format)
|
|
119
|
+
return str(decode_str(format, free=True))
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def features(self) -> frozenset[str]:
|
|
123
|
+
"""The list of features offered by this debug file."""
|
|
124
|
+
struct = self._methodcall(lib.symbolic_object_get_features)
|
|
125
|
+
features = set()
|
|
126
|
+
if struct.symtab:
|
|
127
|
+
features.add("symtab")
|
|
128
|
+
if struct.debug:
|
|
129
|
+
features.add("debug")
|
|
130
|
+
if struct.unwind:
|
|
131
|
+
features.add("unwind")
|
|
132
|
+
if struct.sources:
|
|
133
|
+
features.add("sources")
|
|
134
|
+
return frozenset(features)
|
|
135
|
+
|
|
136
|
+
def make_symcache(self) -> SymCache:
|
|
137
|
+
"""Creates a symcache from the object."""
|
|
138
|
+
return SymCache._from_objptr(
|
|
139
|
+
self._methodcall(lib.symbolic_symcache_from_object)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def make_cficache(self) -> CfiCache:
|
|
143
|
+
"""Creates a cficache from the object."""
|
|
144
|
+
return CfiCache._from_objptr(
|
|
145
|
+
self._methodcall(lib.symbolic_cficache_from_object)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def __repr__(self) -> str:
|
|
149
|
+
return "<Object {} {!r}>".format(
|
|
150
|
+
self.debug_id,
|
|
151
|
+
self.arch,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ObjectRef:
|
|
156
|
+
"""Holds a reference to an object in a format."""
|
|
157
|
+
|
|
158
|
+
def __init__(self, data):
|
|
159
|
+
self.addr = parse_addr(data.get("image_addr"))
|
|
160
|
+
# not a real address but why handle it differently
|
|
161
|
+
self.size = parse_addr(data.get("image_size"))
|
|
162
|
+
self.vmaddr = data.get("image_vmaddr")
|
|
163
|
+
self.code_id = data.get("code_id")
|
|
164
|
+
self.code_file = data.get("code_file") or data.get("name")
|
|
165
|
+
self.debug_id = normalize_debug_id(
|
|
166
|
+
data.get("debug_id") or data.get("id") or data.get("uuid") or None
|
|
167
|
+
)
|
|
168
|
+
self.debug_file = data.get("debug_file")
|
|
169
|
+
|
|
170
|
+
if data.get("arch") is not None and arch_is_known(data["arch"]):
|
|
171
|
+
self.arch = data["arch"]
|
|
172
|
+
else:
|
|
173
|
+
self.arch = None
|
|
174
|
+
|
|
175
|
+
# Legacy alias for backwards compatibility
|
|
176
|
+
self.name = self.code_file
|
|
177
|
+
|
|
178
|
+
def __repr__(self) -> str:
|
|
179
|
+
return "<ObjectRef {} {!r}>".format(
|
|
180
|
+
self.debug_id,
|
|
181
|
+
self.arch,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ObjectLookup:
|
|
186
|
+
"""Helper to look up objects based on the info a client provides."""
|
|
187
|
+
|
|
188
|
+
def __init__(self, objects):
|
|
189
|
+
self._addresses = []
|
|
190
|
+
self._by_addr = {}
|
|
191
|
+
self.objects = {}
|
|
192
|
+
for ref_data in objects:
|
|
193
|
+
obj = ObjectRef(ref_data)
|
|
194
|
+
self._addresses.append(obj.addr)
|
|
195
|
+
self._by_addr[obj.addr] = obj
|
|
196
|
+
self.objects[obj.debug_id] = obj
|
|
197
|
+
self._addresses.sort()
|
|
198
|
+
|
|
199
|
+
def iter_objects(self):
|
|
200
|
+
"""Iterates over all objects."""
|
|
201
|
+
return self.objects.values()
|
|
202
|
+
|
|
203
|
+
def get_debug_ids(self):
|
|
204
|
+
"""Returns a list of ids."""
|
|
205
|
+
return sorted(self.objects)
|
|
206
|
+
|
|
207
|
+
def iter_debug_ids(self):
|
|
208
|
+
"""Iterates over all ids."""
|
|
209
|
+
return iter(self.objects)
|
|
210
|
+
|
|
211
|
+
def find_object(self, addr):
|
|
212
|
+
"""Given an instruction address this locates the image this address
|
|
213
|
+
is contained in.
|
|
214
|
+
"""
|
|
215
|
+
idx = bisect.bisect_right(self._addresses, parse_addr(addr))
|
|
216
|
+
if idx > 0:
|
|
217
|
+
rv = self._by_addr[self._addresses[idx - 1]]
|
|
218
|
+
if not rv.size or parse_addr(addr) < rv.addr + rv.size:
|
|
219
|
+
return rv
|
|
220
|
+
|
|
221
|
+
def get_object(self, debug_id):
|
|
222
|
+
"""Finds an object by the given debug id."""
|
|
223
|
+
return self.objects.get(debug_id)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class BcSymbolMap(RustObject):
|
|
227
|
+
"""Object representing an Apple ``.bcsymbolmap`` file."""
|
|
228
|
+
|
|
229
|
+
__dealloc_func__ = lib.symbolic_bcsymbolmap_free
|
|
230
|
+
|
|
231
|
+
@classmethod
|
|
232
|
+
def open(cls, path: str | bytes) -> BcSymbolMap:
|
|
233
|
+
"""Parses a BCSymbolMap file."""
|
|
234
|
+
if isinstance(path, str):
|
|
235
|
+
path = path.encode("utf-8")
|
|
236
|
+
return cls._from_objptr(rustcall(lib.symbolic_bcsymbolmap_open, path))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class UuidMapping(RustObject):
|
|
240
|
+
"""Object representing a mapping from one DebugID to another."""
|
|
241
|
+
|
|
242
|
+
__dealloc_func__ = lib.symbolic_uuidmapping_free
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def from_plist(cls, debug_id: str, path: str | bytes) -> UuidMapping:
|
|
246
|
+
"""Parses a PList."""
|
|
247
|
+
if isinstance(path, str):
|
|
248
|
+
path = path.encode("utf-8")
|
|
249
|
+
return cls._from_objptr(
|
|
250
|
+
rustcall(lib.symbolic_uuidmapping_from_plist, encode_str(debug_id), path)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def id_from_breakpad(breakpad_id):
|
|
255
|
+
"""Converts a Breakpad CodeModuleId to DebugId"""
|
|
256
|
+
if breakpad_id is None:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
s = encode_str(breakpad_id)
|
|
260
|
+
id = rustcall(lib.symbolic_id_from_breakpad, s)
|
|
261
|
+
return decode_str(id, free=True)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def normalize_code_id(code_id):
|
|
265
|
+
"""Normalizes a code identifier to default representation"""
|
|
266
|
+
if code_id is None:
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
s = encode_str(code_id)
|
|
270
|
+
id = rustcall(lib.symbolic_normalize_code_id, s)
|
|
271
|
+
return decode_str(id, free=True)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@overload
|
|
275
|
+
def normalize_debug_id(debug_id: None) -> None:
|
|
276
|
+
...
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@overload
|
|
280
|
+
def normalize_debug_id(debug_id: str) -> str:
|
|
281
|
+
...
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def normalize_debug_id(debug_id: str | None) -> str | None:
|
|
285
|
+
"""Normalizes a debug identifier to default representation"""
|
|
286
|
+
if debug_id is None:
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
s = encode_str(debug_id)
|
|
290
|
+
id = rustcall(lib.symbolic_normalize_debug_id, s)
|
|
291
|
+
return decode_str(id, free=True)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from symbolic._lowlevel import lib
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = ["SymbolicError"]
|
|
8
|
+
exceptions_by_code: dict[int, type[SymbolicError]] = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SymbolicError(Exception):
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
code: int
|
|
14
|
+
else:
|
|
15
|
+
code = None
|
|
16
|
+
|
|
17
|
+
def __init__(self, msg):
|
|
18
|
+
Exception.__init__(self)
|
|
19
|
+
self.message = msg
|
|
20
|
+
self.rust_info = None
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
rv = self.message
|
|
24
|
+
if self.rust_info is not None:
|
|
25
|
+
return f"{rv}\n\n{self.rust_info}"
|
|
26
|
+
return rv
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _make_error(
|
|
30
|
+
error_name: str, base: type[SymbolicError] = SymbolicError, code: int | None = None
|
|
31
|
+
) -> type[SymbolicError]:
|
|
32
|
+
class Exc(base): # type: ignore[misc,valid-type]
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
Exc.__name__ = Exc.__qualname__ = error_name
|
|
36
|
+
if code is not None:
|
|
37
|
+
Exc.code = code
|
|
38
|
+
globals()[Exc.__name__] = Exc
|
|
39
|
+
__all__.append(Exc.__name__)
|
|
40
|
+
return Exc
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_error_base(error_name: str) -> type[SymbolicError]:
|
|
44
|
+
pieces = error_name.split("Error", 1)
|
|
45
|
+
if len(pieces) == 2 and pieces[0] and pieces[1]:
|
|
46
|
+
base_error_name = pieces[0] + "Error"
|
|
47
|
+
base_class = globals().get(base_error_name)
|
|
48
|
+
if base_class is None:
|
|
49
|
+
base_class = _make_error(base_error_name)
|
|
50
|
+
return base_class
|
|
51
|
+
return SymbolicError
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _make_exceptions() -> None:
|
|
55
|
+
for attr in dir(lib):
|
|
56
|
+
if not attr.startswith("SYMBOLIC_ERROR_CODE_"):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
error_name = attr[20:].title().replace("_", "")
|
|
60
|
+
base = _get_error_base(error_name)
|
|
61
|
+
exc = _make_error(error_name, base=base, code=getattr(lib, attr))
|
|
62
|
+
exceptions_by_code[exc.code] = exc
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
_make_exceptions()
|
|
66
|
+
|
|
67
|
+
if TYPE_CHECKING:
|
|
68
|
+
# treat unknown attribute names as exception types
|
|
69
|
+
def __getattr__(name: str) -> type[SymbolicError]:
|
|
70
|
+
...
|