Skip to content

Commit c581445

Browse files
authored
Merge pull request #80271 from al45tair/eng/PR-121430255-6.1
[Backtracing] Add JSON backtrace output
2 parents aa6f1d5 + 565022d commit c581445

16 files changed

+1559
-49
lines changed

docs/Backtracing.rst

+206
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,17 @@ follows:
9292
| | | only has effect on platforms that have a symbol |
9393
| | | cache that can be controlled by the runtime. |
9494
+-----------------+---------+--------------------------------------------------+
95+
| format | text | Set to ``json`` to output JSON crash logs rather |
96+
| | | than plain text. |
97+
+-----------------+---------+--------------------------------------------------+
9598
| output-to | stdout | Set to ``stderr`` to send the backtrace to the |
9699
| | | standard error instead of standard output. This |
97100
| | | may be useful in some CI systems. |
101+
| | | |
102+
| | | You may also specify a path; if this points at a |
103+
| | | directory, the backtracer will generate unique |
104+
| | | filenames within that directory. Otherwise it |
105+
| | | is assumed to be a filename. |
98106
+-----------------+---------+--------------------------------------------------+
99107
| symbolicate | full | Options are ``full``, ``fast``, or ``off``. |
100108
| | | Full means to look up source locations and |
@@ -319,3 +327,201 @@ of the backtracer using
319327

320328
If the runtime is unable to locate the backtracer, it will allow your program to
321329
crash as it would have done anyway.
330+
331+
JSON Crash Logs
332+
---------------
333+
334+
JSON crash logs are a structured crash log format that the backtracer is able
335+
to output. Note that addresses are represented in this format as hexadecimal
336+
strings, rather than as numbers, in order to avoid representational issues.
337+
Additionally, boolean fields that are ``false``, as well as fields whose
338+
values are unknown or empty, will normally be completely omitted to save space.
339+
340+
Where hexadecimal *values* are output, they will normally be prefixed with
341+
a ``0x`` prefix. Hexadecimal *data*, by contrast, such as captured memory or
342+
build IDs, will not have a prefix and will be formatted as a string with no
343+
whitespace.
344+
345+
Note that since JSON does not officially support hexadecimal, hexadecimal
346+
values will always be output as strings.
347+
348+
JSON crash logs will always contain the following top level fields:
349+
350+
+-------------------+--------------------------------------------------------+
351+
| Field | Value |
352+
+===================+========================================================+
353+
| timestamp | An ISO-8601 formatted timestamp, as a string. |
354+
+-------------------+--------------------------------------------------------+
355+
| kind | The string ``crashReport``. |
356+
+-------------------+--------------------------------------------------------+
357+
| description | A textual description of the crash or runtime failure. |
358+
+-------------------+--------------------------------------------------------+
359+
| faultAddress | The fault address associated with the crash. |
360+
+-------------------+--------------------------------------------------------+
361+
| platform | A string describing the platform; the first token |
362+
| | identifies the platform itself and is followed by |
363+
| | platform specific version information. |
364+
| | |
365+
| | e.g. "macOS 13.0 (22A380)", |
366+
| | "linux (Ubuntu 22.04.5 LTS)" |
367+
+-------------------+--------------------------------------------------------+
368+
| architecture | The name of the processor architecture for this crash. |
369+
+-------------------+--------------------------------------------------------+
370+
| threads | An array of thread records, one for each thread. |
371+
+-------------------+--------------------------------------------------------+
372+
373+
These will be followed by some or all of the following, according to the
374+
backtracer settings:
375+
376+
+-------------------+--------------------------------------------------------+
377+
| Field | Value |
378+
+===================+========================================================+
379+
| omittedThreads | A count of the number of threads that were omitted, if |
380+
| | the backtracer is set to give a backtrace only for the |
381+
| | crashed thread. Omitted if zero. |
382+
+-------------------+--------------------------------------------------------+
383+
| capturedMemory | A dictionary containing captured memory contents, if |
384+
| | any. This will not be present if the ``sanitize`` |
385+
| | setting is enabled, or if no data was captured. |
386+
| | |
387+
| | The dictionary is keyed by hexadecimal addresses, as |
388+
| | strings (with a ``0x`` prefix); the captured data is |
389+
| | also given as a hexadecimal string, but with no prefix |
390+
| | and no inter-byte whitespace. |
391+
| | |
392+
| | You should make no assumptions about the number of |
393+
| | bytes captured at each address; the backtracer will |
394+
| | currently attempt to grab 16 bytes, but this may |
395+
| | change if only a shorter range is available or in |
396+
| | future according to configuration parameters. |
397+
+-------------------+--------------------------------------------------------+
398+
| omittedImages | If ``images`` is set to ``mentioned``, this is an |
399+
| | integer giving the number of images whose details were |
400+
| | omitted from the crash log. |
401+
+-------------------+--------------------------------------------------------+
402+
| images | Unless ``images`` is ``none``, an array of records |
403+
| | describing the loaded images in the crashed process. |
404+
+-------------------+--------------------------------------------------------+
405+
| backtraceTime | The time taken to generate the crash report, in |
406+
| | seconds. |
407+
+-------------------+--------------------------------------------------------+
408+
409+
Thread Records
410+
^^^^^^^^^^^^^^
411+
412+
A thread record is a dictionary with the following fields:
413+
414+
+-------------------+--------------------------------------------------------+
415+
| Field | Value |
416+
+===================+========================================================+
417+
| name | The name of the thread. Omitted if no name. |
418+
+-------------------+--------------------------------------------------------+
419+
| crashed | ``true`` if the thread is the one that crashed, |
420+
| | omitted otherwise. |
421+
+-------------------+--------------------------------------------------------+
422+
| registers | A dictionary containing the register contents on the |
423+
| | crashed thread. |
424+
| | |
425+
| | The dictionary is keyed by architecture specific |
426+
| | register name; values are given as hexadecimal |
427+
| | strings (with a ``0x`` prefix). |
428+
| | |
429+
| | This field may be omitted for threads other than the |
430+
| | crashed thread, if the ``registers`` setting is set |
431+
| | to ``crashed``. |
432+
+-------------------+--------------------------------------------------------+
433+
| frames | An array of frames forming the backtrace for the |
434+
| | thread. |
435+
+-------------------+--------------------------------------------------------+
436+
437+
Each frame in the backtrace is described by a dictionary containing the
438+
following fields:
439+
440+
+-------------------+--------------------------------------------------------+
441+
| Field | Value |
442+
+===================+========================================================+
443+
| kind | ``programCounter`` if the frame address is a directly |
444+
| | captured program counter/instruction pointer. |
445+
| | |
446+
| | ``returnAddress`` if the frame address is a return |
447+
| | address. |
448+
| | |
449+
| | ``asyncResumePoint`` if the frame address is a |
450+
| | resumption point in an ``async`` function. |
451+
| | |
452+
| | ``omittedFrames`` if this is a frame omission record. |
453+
| | |
454+
| | ``truncated`` to indicate that the backtrace is |
455+
| | truncated at this point and that more frames were |
456+
| | available but not captured. |
457+
+-------------------+--------------------------------------------------------+
458+
| address | The frame address as a string (for records containing |
459+
| | an address). |
460+
+-------------------+--------------------------------------------------------+
461+
| count | The number of frames omitted at this point in the |
462+
| | backtrace (``omittedFrames`` only). |
463+
+-------------------+--------------------------------------------------------+
464+
465+
If the backtrace is symbolicated, the frame record may also contain the
466+
following additional information:
467+
468+
+-------------------+--------------------------------------------------------+
469+
| Field | Value |
470+
+===================+========================================================+
471+
| inlined | ``true`` if this frame is inlined, omitted otherwise. |
472+
+-------------------+--------------------------------------------------------+
473+
| runtimeFailure | ``true`` if this frame represents a Swift runtime |
474+
| | failure, omitted otherwise. |
475+
+-------------------+--------------------------------------------------------+
476+
| thunk | ``true`` if this frame is a compiler-generated thunk |
477+
| | function, omitted otherwise. |
478+
+-------------------+--------------------------------------------------------+
479+
| system | ``true`` if this frame is a system frame, omitted |
480+
| | otherwise. |
481+
+-------------------+--------------------------------------------------------+
482+
483+
If symbol lookup succeeded for the frame address, the following additional
484+
fields will be present:
485+
486+
+-------------------+--------------------------------------------------------+
487+
| Field | Value |
488+
+===================+========================================================+
489+
| symbol | The mangled name of the symbol corresponding to the |
490+
| | frame address. |
491+
+-------------------+--------------------------------------------------------+
492+
| offset | The offset from the symbol to the frame address. |
493+
+-------------------+--------------------------------------------------------+
494+
| description | If demangling is enabled, a human readable description |
495+
| | of the frame address, otherwise omitted. |
496+
+-------------------+--------------------------------------------------------+
497+
| image | The name of the image in which the symbol was found; |
498+
| | omitted if no corresponding image exists. |
499+
+-------------------+--------------------------------------------------------+
500+
| sourceLocation | If the source location of the symbol is known, a |
501+
| | dictionary containing ``file``, ``line`` and |
502+
| | ``column`` keys that identify the location of the |
503+
| | symbol in the source files. |
504+
+-------------------+--------------------------------------------------------+
505+
506+
Image Records
507+
^^^^^^^^^^^^^
508+
509+
An image record is a dictionary with the following fields:
510+
511+
+-------------------+--------------------------------------------------------+
512+
| Field | Value |
513+
+===================+========================================================+
514+
| name | The name of the image (omitted if not known). |
515+
+-------------------+--------------------------------------------------------+
516+
| buildId | The build ID (aka unique ID) of the image (omitted if |
517+
| | not known). Build IDs are formatted as un-prefixed |
518+
| | hexadecimal strings, with no inter-byte whitespace. |
519+
+-------------------+--------------------------------------------------------+
520+
| path | The path to the image (omitted if not known). |
521+
+-------------------+--------------------------------------------------------+
522+
| baseAddress | The base address of the image text, as a hexadecimal |
523+
| | string. |
524+
+-------------------+--------------------------------------------------------+
525+
| endOfText | The end of the image text, as a hexadecimal string. |
526+
+-------------------+--------------------------------------------------------+
527+

stdlib/public/Backtracing/Backtrace.swift

+25
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,31 @@ public struct Backtrace: CustomStringConvertible, Sendable {
148148
public var description: String {
149149
return description(width: MemoryLayout<Address>.size * 2)
150150
}
151+
152+
/// A JSON description of this frame, less the surrounding braces.
153+
/// This is useful if you want to add extra information.
154+
@_spi(Internal)
155+
public var jsonBody: String {
156+
let width = MemoryLayout<Address>.size * 2
157+
switch self {
158+
case let .programCounter(addr):
159+
return "\"kind\": \"programCounter\", \"address\": \"\(hex(addr, width: width))\""
160+
case let .returnAddress(addr):
161+
return "\"kind\": \"returnAddress\", \"address\": \"\(hex(addr, width: width))\""
162+
case let .asyncResumePoint(addr):
163+
return "\"kind\": \"asyncResumePoint\", \"address\": \"\(hex(addr, width: width))\""
164+
case let .omittedFrames(count):
165+
return "\"kind\": \"omittedFrames\", \"count\": \(count)"
166+
case .truncated:
167+
return "\"kind\": \"truncated\""
168+
}
169+
}
170+
171+
/// A JSON description of this frame.
172+
@_spi(Internal)
173+
public var jsonDescription: String {
174+
return "{ \(jsonBody) }"
175+
}
151176
}
152177

153178
/// Represents an image loaded in the process's address space

stdlib/public/Backtracing/BacktraceFormatter.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,8 @@ private func untabify(_ s: String, tabWidth: Int = 8) -> String {
407407
/// @param path The path to sanitize.
408408
///
409409
/// @returns A string containing the sanitized path.
410-
private func sanitizePath(_ path: String) -> String {
410+
@_spi(Formatting)
411+
public func sanitizePath(_ path: String) -> String {
411412
#if os(macOS)
412413
return CRCopySanitizedPath(path,
413414
kCRSanitizePathGlobAllTypes

stdlib/public/Backtracing/SymbolicatedBacktrace.swift

+3
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,9 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
252252
return backtrace.addressWidth
253253
}
254254

255+
/// The architecture on which this backtrace was captured.
256+
public var architecture: String { return backtrace.architecture }
257+
255258
/// A list of captured frame information.
256259
public var frames: [Frame]
257260

stdlib/public/libexec/swift-backtrace/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ set(BACKTRACING_COMPILE_FLAGS
3131
set(BACKTRACING_SOURCES
3232
main.swift
3333
AnsiColor.swift
34+
JSON.swift
35+
OSReleaseScanner.swift
3436
TargetMacOS.swift
3537
TargetLinux.swift
3638
Themes.swift

0 commit comments

Comments
 (0)