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.
@@ -0,0 +1 @@
1
+ include rustsrc.zip version.txt
@@ -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
@@ -0,0 +1,4 @@
1
+ Symbolic
2
+ --------
3
+
4
+ Symbolic is a Python library that can work with debug symbol files.
Binary file
@@ -0,0 +1,11 @@
1
+ [mypy]
2
+ check_untyped_defs = true
3
+ disallow_any_generics = true
4
+ disallow_incomplete_defs = true
5
+ warn_redundant_casts = true
6
+ warn_unused_ignores = true
7
+
8
+ [egg_info]
9
+ tag_build =
10
+ tag_date = 0
11
+
@@ -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
+ ...