Script generating this graph (because I can't embed svg)
#!/home/mikolas/Stažené/swift-5.8.1-RELEASE-ubuntu22.04/swift-5.8.1-RELEASE-ubuntu22.04/usr/bin/swift
// By Mikolas Stuchlik, 2023
import Foundation
enum Color: String {
case black, gray, lightgray, white
}
enum Style: String {
case invis, solid, dashed, dotted, bold
}
enum RankDir: String {
case topBottom = "TB"
case leftRight = "LR"
case bottomTop = "BT"
case rightLeft = "RL"
}
protocol Body {
var name: String { get }
func topLevelRender(indent: String) -> String
}
struct TableNode: Body {
let name: String
let label: String
}
struct Cluster: Body {
let name: String
let rankdir: RankDir?
let bodies: [Body]
}
enum RankType: String {
case same, min, source, max, sink
}
struct Rank {
let type: RankType
let nodes: [String]
}
struct Reference {
let node: String
let port: String?
}
struct Edge {
enum Direction {
case `default`
case w2w
case e2w
}
let src: Reference
let dest: Reference
let label: String
let style: Style
let direction: Direction
}
struct NodeOffset: Hashable {
let name: String
let offset: Int
}
struct Decorate {
let offsets: ClosedRange<Int>
let backgroundColor: Color?
let textColor: Color?
}
struct Note {
let port: String
let label: String
}
enum GlobalOffsetIndex {
private static var globalOffsetIndex: [NodeOffset: String] = [:]
@discardableResult
static func register(node: String, line: String, port: String) -> Int? {
let regex = #/(?:\<\;|\\\<|\<)\+(?<offset>[\d]+)(?:\>\;|\\\>|\>)/#
guard let result = line.firstMatch(of: regex), let line = Int(result.output.offset) else {
return nil
}
register(node: node, offset: line, port: port)
return line
}
static func register(node: String, offset: Int, port: String) {
globalOffsetIndex[.init(name: node, offset: offset)] = port
}
static func port(for name: String, offset: Int) -> String! {
globalOffsetIndex[.init(name: name, offset: offset)]
}
static func reference(for name: String, offset: Int) -> Reference! {
port(for: name, offset: offset).flatMap { .init(node: name, port: $0) }
}
static func dump() -> String {
"\(globalOffsetIndex)"
}
}
extension Edge.Direction {
func dot(isSource: Bool) -> String {
switch (self, isSource) {
case (.default, _):
return ""
case (.w2w, _):
return ":w"
case (.e2w, true):
return ":e"
case (.e2w, false):
return ":w"
}
}
}
extension Edge {
func dot(with id: Int) -> String {
let srcDecorators = (src.port.flatMap { ":" + $0 } ?? "") + direction.dot(isSource: true)
let destDecorators = (dest.port.flatMap { ":" + $0 } ?? "") + direction.dot(isSource: false)
var result =
#"""
"\#(src.node)"\#(srcDecorators) -> "\#(dest.node)"\#(destDecorators) [
id = \#(id)
style = \#(style.rawValue)
"""#
if !label.isEmpty {
result +=
#"""
label = "\#(label)"
"""#
}
result +=
#"""
];
"""#
return result
}
}
func asm2dot(_ asm: String, name: String, linePrefix: String = "l", decorate: [Decorate]) -> TableNode {
let escaped = asm
.replacingOccurrences(of: "<", with: #"<"#)
.replacingOccurrences(of: ">", with: #">"#)
.replacingOccurrences(of: " ", with: #" "#)
var components = escaped.components(separatedBy: "\n")
var lineCounter = 0
for index in components.indices {
let port = linePrefix + String(lineCounter)
let line = GlobalOffsetIndex.register(node: name, line: components[index], port: port)
var tdAttributes: [String] = ["align='left'", "port='\(port)'"]
var fontAttributes: [String] = []
if let line {
for decorator in decorate where decorator.offsets.contains(line) {
if let bgColor = decorator.backgroundColor {
tdAttributes.append("bgcolor='\(bgColor.rawValue)'")
}
if let textColor = decorator.textColor {
fontAttributes.append("color='\(textColor.rawValue)'")
}
}
}
let source = components[index]
let fontApplied = fontAttributes.isEmpty
? source
: "<font \(fontAttributes.joined(separator: " "))>\(source)</font>"
let tdApplied = "<td \(tdAttributes.joined(separator: " "))>\(fontApplied)</td>"
components[index] =
#"""
<tr>\#(tdApplied)</tr>
"""#
lineCounter += 1
}
let dot =
#"""
<table border='1' cellborder='0'>
\#(components.joined(separator: "\n"))
</table>
"""#
return .init(name: name, label: dot)
}
extension Note {
func table(indent: String) -> String {
let components = label.components(separatedBy: "\n").map {
#"""
\#(indent) <tr><td align='left'>\#($0)</td></tr>
"""#
}
let dot =
#"""
\#(indent)<table border='0' cellborder='0'>
\#(components.joined(separator: "\n"))
\#(indent)</table>
"""#
return dot
}
}
func noteNode(name: String, notes: [Note]) -> TableNode {
let cells = notes.map { note in
#"""
<tr><td align='left' port='\#(note.port)'>
\#(note.table(indent: " " + " " + " " + " "))
</td></tr>
"""#
}
let dot =
#"""
<table border='0' cellborder='1'>
\#(cells.joined(separator: "\n"))
</table>
"""#
return .init(name: name, label: dot)
}
func cluster(name: String, rankdir: RankDir?, bodies: [Body]) -> Cluster {
assert(name.hasPrefix("cluster_"), "Clusters are required to have prefix 'cluster_' by graphviz specification.")
return .init(name: name, rankdir: rankdir, bodies: bodies)
}
extension TableNode {
func topLevelRender(indent: String) -> String {
let indentLabel = label.split(separator: "\n").map { indent + $0 }.joined(separator: "\n")
return
#"""
\#(indent)"\#(name)" [
\#(indent) label = <
\#(indentLabel)
\#(indent) >
\#(indent) shape = "plaintext"
\#(indent)];
"""#
}
}
extension Cluster {
func topLevelRender(indent: String) -> String {
var result =
#"""
\#(indent)subgraph "\#(name)" {
"""#
if let rankdir {
result +=
#"""
\#(indent) rankdir=\#(rankdir.rawValue);
"""#
}
result +=
#"""
\#(bodies.map { $0.topLevelRender(indent: indent + " ") }.joined(separator: "\n"))
\#(indent)};
"""#
return result
}
}
func embed(bodies: [Body], ranks: [Rank], edges: [Edge]) -> String {
let dotNodes = bodies.map { $0.topLevelRender(indent: " ") }
let dotEdges = edges.enumerated().map { $0.element.dot(with: $0.offset) }
let dotRanks = ranks.map { rank in
#"""
{rank = \#(rank.type.rawValue); \#(rank.nodes.map { $0 + ";"}.joined(separator: " "))}
"""#
}
let graph: String =
#"""
digraph ams {
fontname="DejaVu Sans Mono,SF Mono,Consolas"
nodesep=1;
node [fontname="DejaVu Sans Mono,SF Mono,Consolas"]
edge [fontname="DejaVu Sans Mono,SF Mono,Consolas"]
\#(dotNodes.joined(separator: "\n"))
\#(dotRanks.joined(separator: "\n"))
\#(dotEdges.joined(separator: "\n"))
}
"""#
return graph
}
let asm1: String =
#"""
liblldb.so.13git`Function.reversedInstructions.getter:
0x7fffe6488cf0 <+0>: push rbp
0x7fffe6488cf1 <+1>: mov rbp, rsp
0x7fffe6488cf4 <+4>: push r13
0x7fffe6488cf6 <+6>: sub rsp, 0x88
0x7fffe6488cfd <+13>: mov qword ptr [rbp - 0x10], 0x0
0x7fffe6488d05 <+21>: mov qword ptr [rbp - 0x10], r13
0x7fffe6488d09 <+25>: call 0x7fffe64729e0 ; SIL.Function.blocks.getter : SIL.BasicBlockList at Function.swift:42
0x7fffe6488d0e <+30>: mov rdi, rax
0x7fffe6488d11 <+33>: mov qword ptr [rbp - 0x88], rdi
0x7fffe6488d18 <+40>: call 0x7fffe6488e00 ; SIL.BasicBlockList.reversed() -> SIL.ReverseBasicBlockList at Function.swift:422
0x7fffe6488d1d <+45>: mov rdi, qword ptr [rbp - 0x88]
0x7fffe6488d24 <+52>: mov qword ptr [rbp - 0x70], rax
0x7fffe6488d28 <+56>: call 0x7fffe4c17010 ; symbol stub for: swift_release
0x7fffe6488d2d <+61>: mov rax, qword ptr [rbp - 0x70]
0x7fffe6488d31 <+65>: mov qword ptr [rbp - 0x30], rax
0x7fffe6488d35 <+69>: mov rdi, qword ptr [rbp - 0x30]
0x7fffe6488d39 <+73>: mov qword ptr [rbp - 0x80], rdi
0x7fffe6488d3d <+77>: call 0x7fffe4c20e80 ; symbol stub for: swift_retain
0x7fffe6488d42 <+82>: mov rax, qword ptr [rbp - 0x80]
0x7fffe6488d46 <+86>: mov qword ptr [rbp - 0x40], rax
0x7fffe6488d4a <+90>: call 0x7fffe6459110 ; lazy protocol witness table accessor for type SIL.ReverseBasicBlockList and conformance SIL.ReverseBasicBlockList : Swift.Sequence in SIL at <compiler-generated>
0x7fffe6488d4f <+95>: mov rsi, rax
0x7fffe6488d52 <+98>: lea rdi, [rip + 0x10faf417]
0x7fffe6488d59 <+105>: lea rax, [rbp - 0x38]
0x7fffe6488d5d <+109>: lea r13, [rbp - 0x40]
0x7fffe6488d61 <+113>: mov qword ptr [rbp - 0x78], r13
0x7fffe6488d65 <+117>: call 0x7fffe4c1e820 ; symbol stub for: Swift.Sequence.lazy.getter : Swift.LazySequence<τ_0_0>
0x7fffe6488d6a <+122>: mov rdi, qword ptr [rbp - 0x78]
0x7fffe6488d6e <+126>: call 0x7fffe6459160 ; outlined destroy of SIL.ReverseBasicBlockList at <compiler-generated>
0x7fffe6488d73 <+131>: mov rdi, qword ptr [rbp - 0x70]
0x7fffe6488d77 <+135>: mov rax, qword ptr [rbp - 0x38]
0x7fffe6488d7b <+139>: mov qword ptr [rbp - 0x68], rax
0x7fffe6488d7f <+143>: call 0x7fffe4c17010 ; symbol stub for: swift_release
0x7fffe6488d84 <+148>: mov rax, qword ptr [rbp - 0x68]
0x7fffe6488d88 <+152>: mov qword ptr [rbp - 0x48], rax
0x7fffe6488d8c <+156>: lea rdi, [rip + 0x1184dfa5]
0x7fffe6488d93 <+163>: call 0x7fffe63b9920 ; __swift_instantiateConcreteTypeFromMangledName at <compiler-generated>
0x7fffe6488d98 <+168>: mov qword ptr [rbp - 0x60], rax
0x7fffe6488d9c <+172>: call 0x7fffe64bed40 ; lazy protocol witness table accessor for type Swift.LazySequence<SIL.ReverseBasicBlockList> and conformance Swift.LazySequence<A> : Swift.LazySequenceProtocol in Swift at <compiler-generated>
0x7fffe6488da1 <+177>: mov qword ptr [rbp - 0x58], rax
0x7fffe6488da5 <+181>: call 0x7fffe6454040 ; lazy protocol witness table accessor for type SIL.ReverseInstructionList and conformance SIL.ReverseInstructionList : Swift.Sequence in SIL at <compiler-generated>
0x7fffe6488daa <+186>: mov rdx, qword ptr [rbp - 0x60]
0x7fffe6488dae <+190>: mov r8, qword ptr [rbp - 0x58]
0x7fffe6488db2 <+194>: mov r9, rax
0x7fffe6488db5 <+197>: lea rdi, [rip + 0x144] ; closure #1 (SIL.BasicBlock) -> SIL.ReverseInstructionList in SIL.Function.reversedInstructions.getter : Swift.LazySequence<Swift.FlattenSequence<Swift.LazyMapSequence<SIL.ReverseBasicBlockList, SIL.ReverseInstructionList>>> at Function.swift:56
0x7fffe6488dbc <+204>: lea rcx, [rip + 0x10faea9d]
0x7fffe6488dc3 <+211>: xor eax, eax
0x7fffe6488dc5 <+213>: mov esi, eax
0x7fffe6488dc7 <+215>: lea rax, [rbp - 0x28]
0x7fffe6488dcb <+219>: lea r13, [rbp - 0x48]
0x7fffe6488dcf <+223>: mov qword ptr [rbp - 0x50], r13
0x7fffe6488dd3 <+227>: call 0x7fffe4c1edf0 ; symbol stub for: Swift.LazySequenceProtocol.flatMap<τ_0_0 where τ_1_0: Swift.Sequence>((τ_0_0.Element) -> τ_1_0) -> Swift.LazySequence<Swift.FlattenSequence<Swift.LazyMapSequence<τ_0_0.Elements, τ_1_0>>>
0x7fffe6488dd8 <+232>: mov rdi, qword ptr [rbp - 0x50]
0x7fffe6488ddc <+236>: call 0x7fffe64bede0 ; outlined destroy of Swift.LazySequence<SIL.ReverseBasicBlockList> at <compiler-generated>
0x7fffe6488de1 <+241>: mov rax, qword ptr [rbp - 0x28]
0x7fffe6488de5 <+245>: mov rdx, qword ptr [rbp - 0x20]
0x7fffe6488de9 <+249>: mov rcx, qword ptr [rbp - 0x18]
0x7fffe6488ded <+253>: add rsp, 0x88
0x7fffe6488df4 <+260>: pop r13
0x7fffe6488df6 <+262>: pop rbp
0x7fffe6488df7 <+263>: ret
"""#
let asm2: String =
#"""
liblldb.so.13git`lazy protocol witness table accessor for type LazySequence<ReverseBasicBlockList> and conformance LazySequence<A>:
0x7fffe64bed40 <+0>: push rbp
0x7fffe64bed41 <+1>: mov rbp, rsp
0x7fffe64bed44 <+4>: sub rsp, 0x10
0x7fffe64bed48 <+8>: mov rax, qword ptr [rip + 0x118a3aa9]
0x7fffe64bed4f <+15>: cmp rax, 0x0
0x7fffe64bed53 <+19>: mov qword ptr [rbp - 0x8], rax
0x7fffe64bed57 <+23>: jne 0x7fffe64bed85 ; <+69> at <compiler-generated>
0x7fffe64bed59 <+25>: lea rdi, [rip + 0x11817fd8]
0x7fffe64bed60 <+32>: call 0x7fffe6423430 ; __swift_instantiateConcreteTypeFromMangledNameAbstract at <compiler-generated>
0x7fffe64bed65 <+37>: mov rsi, rax
0x7fffe64bed68 <+40>: mov rdi, qword ptr [rip + 0x117aadd1]
0x7fffe64bed6f <+47>: call 0x7fffe4c20010 ; symbol stub for: swift_getWitnessTable
0x7fffe64bed74 <+52>: mov rcx, rax
0x7fffe64bed77 <+55>: mov rax, rcx
0x7fffe64bed7a <+58>: mov qword ptr [rip + 0x118a3a77], rcx
0x7fffe64bed81 <+65>: mov qword ptr [rbp - 0x8], rax
0x7fffe64bed85 <+69>: mov rax, qword ptr [rbp - 0x8]
0x7fffe64bed89 <+73>: add rsp, 0x10
0x7fffe64bed8d <+77>: pop rbp
0x7fffe64bed8e <+78>: ret
"""#
let asm3: String =
#"""
liblldb.so.13git`__swift_instantiateConcreteTypeFromMangledNameAbstract:
0x7fffe6423430 <+0>: push rbp
0x7fffe6423431 <+1>: mov rbp, rsp
0x7fffe6423434 <+4>: sub rsp, 0x20
0x7fffe6423438 <+8>: mov qword ptr [rbp - 0x18], rdi
0x7fffe642343c <+12>: mov rax, qword ptr [rdi]
0x7fffe642343f <+15>: mov qword ptr [rbp - 0x10], rax
0x7fffe6423443 <+19>: cmp rax, 0x0
0x7fffe6423447 <+23>: setl cl
0x7fffe642344a <+26>: test cl, 0x1
0x7fffe642344d <+29>: mov qword ptr [rbp - 0x8], rax
0x7fffe6423451 <+33>: jne 0x7fffe642345d ; <+45> at <compiler-generated>
0x7fffe6423453 <+35>: mov rax, qword ptr [rbp - 0x8]
0x7fffe6423457 <+39>: add rsp, 0x20
0x7fffe642345b <+43>: pop rbp
0x7fffe642345c <+44>: ret
0x7fffe642345d <+45>: mov rsi, qword ptr [rbp - 0x18]
0x7fffe6423461 <+49>: mov rax, qword ptr [rbp - 0x10]
0x7fffe6423465 <+53>: mov rdx, rax
0x7fffe6423468 <+56>: sar rdx, 0x20
0x7fffe642346c <+60>: neg rdx
0x7fffe642346f <+63>: cdqe
0x7fffe6423471 <+65>: add rsi, rax
0x7fffe6423474 <+68>: xor eax, eax
0x7fffe6423476 <+70>: mov r8d, eax
0x7fffe6423479 <+73>: mov edi, 0xff
0x7fffe642347e <+78>: mov rcx, r8
0x7fffe6423481 <+81>: call 0x7fffe4c17210 ; symbol stub for: swift_getTypeByMangledNameInContextInMetadataState2
0x7fffe6423486 <+86>: mov rcx, qword ptr [rbp - 0x18]
0x7fffe642348a <+90>: mov rdx, rax
0x7fffe642348d <+93>: mov rax, rdx
0x7fffe6423490 <+96>: mov qword ptr [rcx], rdx
0x7fffe6423493 <+99>: mov qword ptr [rbp - 0x8], rax
0x7fffe6423497 <+103>: jmp 0x7fffe6423453 ; <+35> at <compiler-generated>
"""#
let result = embed(
bodies: [
asm2dot(
asm1,
name: "asm1",
decorate: [
.init(offsets: 0...168, backgroundColor: nil, textColor: .gray),
.init(offsets: 177...263, backgroundColor: nil, textColor: .gray),
]
),
asm2dot(
asm2,
name: "asm2",
decorate: [
.init(offsets: 8...8, backgroundColor: .lightgray, textColor: nil),
.init(offsets: 25...25, backgroundColor: .lightgray, textColor: nil),
.init(offsets: 32...32, backgroundColor: .lightgray, textColor: nil),
.init(offsets: 47...47, backgroundColor: .lightgray, textColor: nil),
.init(offsets: 52...78, backgroundColor: nil, textColor: .gray),
]
),
asm2dot(
asm3,
name: "asm3",
decorate: [
.init(offsets: 12...12, backgroundColor: .lightgray, textColor: nil),
.init(offsets: 19...19, backgroundColor: .lightgray, textColor: nil),
.init(offsets: 33...33, backgroundColor: .lightgray, textColor: nil),
.init(offsets: 45...103, backgroundColor: nil, textColor: .gray),
]
),
noteNode(
name: "asm2_notes",
notes: [
.init(
port: "n0",
label:
#"""
Should be probably labeled:
lazy protocol witness table cache variable for type LazySequence<ReverseBasicBlockList> and conformance LazySequence<A> : Swift.Sequence in Swift
"""#
),
.init(
port: "n1",
label:
"""
Shoudl be probably labeled:
demangling cache variable for type metadata for LazySequence<ReverseBasicBlockList>
"""
),
.init(
port: "n2",
label:
#"""
<b>(lldb)</b> reg read rdi
rdi = 0x00007ffff7cd6d38
<b>(lldb)</b> mem read -fp -c1 0x00007ffff7cd6d38
0x7ffff7cd6d38: 0x0000000000000000
"""#
),
.init(
port: "n3",
label:
#"""
1 const WitnessTable * swift::swift_getWitnessTable(
2 const ProtocolConformanceDescriptor *conformance,
3 const Metadata *type,
4 const void * const *instantiationArgs
5 )
conformance >- some value from offset +40
type >- the problematic 0x0 value returned from function call
instantiationArgs >- I could not find where rdx is set so ... garbage?
"""#
),
]
),
noteNode(
name: "asm3_notes",
notes: [
.init(
port: "n0",
label:
#"""
rax = *(rdi)
------------
rdi = 0x00007ffff7cd6d38
rax = 0x0000000000000000
"""#
),
.init(
port: "n1",
label:
#"""
rax == 0x0
----------
TRUE
"""#
),
.init(
port: "n2",
label:
#"""
jump not taken, rax was equal 0x0
"""#
),
]
),
],
ranks: [
.init(type: .same, nodes: ["asm2", "asm2_notes"]),
.init(type: .same, nodes: ["asm3", "asm3_notes"]),
],
edges: [
.init(
src: GlobalOffsetIndex.reference(for: "asm1", offset: 172),
dest: GlobalOffsetIndex.reference(for: "asm2", offset: 0),
label: "",
style: .solid,
direction: .w2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm2", offset: 32),
dest: GlobalOffsetIndex.reference(for: "asm3", offset: 0),
label: "rdi = 0x00007ffff7cd6d38",
style: .solid,
direction: .w2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm2", offset: 8),
dest: .init(node: "asm2_notes", port: "n0"),
label: "",
style: .dotted,
direction: .e2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm2", offset: 25),
dest: .init(node: "asm2_notes", port: "n1"),
label: "",
style: .dotted,
direction: .e2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm2", offset: 32),
dest: .init(node: "asm2_notes", port: "n2"),
label: "",
style: .dotted,
direction: .e2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm3", offset: 12),
dest: .init(node: "asm3_notes", port: "n0"),
label: "",
style: .dotted,
direction: .e2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm3", offset: 19),
dest: .init(node: "asm3_notes", port: "n1"),
label: "",
style: .dotted,
direction: .e2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm3", offset: 33),
dest: .init(node: "asm3_notes", port: "n2"),
label: "",
style: .dotted,
direction: .e2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm3", offset: 44),
dest: GlobalOffsetIndex.reference(for: "asm2", offset: 37),
label: "rax = 0x0",
style: .dashed,
direction: .w2w
),
.init(
src: GlobalOffsetIndex.reference(for: "asm2", offset: 47),
dest: .init(node: "asm2_notes", port: "n3"),
label: "",
style: .dotted,
direction: .e2w
),
]
)
print(result)