-
Notifications
You must be signed in to change notification settings - Fork 15
/
index.js
158 lines (141 loc) · 5.38 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
const fs = require('fs')
const os = require('os')
const path = require('path')
const minidump = require('minidump')
const ProgressBar = require('progress')
const electronMinidump = async (options) => {
const {quiet, force, file} = options
const directory = path.join(__dirname, 'cache', 'breakpad_symbols')
const fh = await fs.promises.open(file)
const buf = Buffer.alloc(4)
const { bytesRead } = await fh.read(buf, 0, 4, 0)
if (bytesRead !== buf.length) {
console.error(`Not a minidump file (file too short): ${file}`)
process.exit(1)
}
if (buf.readUInt32BE(0) !== 0x4D444D50 /* MDMP */) {
// Breakpad .dmp files have some http junk at the beginning.
// read the first 16kb and look for MDMP to see if this is the case.
const {buffer} = await fh.read({position: 0})
for (let offset = 0; offset < buffer.length - 4; offset++) {
if (buffer.readUInt32BE(offset) === 0x4D444D50) {
// Found MDMP, write it to a tmp file
const tmp = path.join(os.tmpdir(), 'electron-minidump-' + ((Math.random()*256*256*256)|0).toString(16).padStart(6, '0') + '.dmp')
await new Promise((resolve, reject) => {
fs.createReadStream(file, {start: offset})
.on('end', resolve)
.on('error', reject)
.pipe(fs.createWriteStream(tmp))
})
const s = await electronMinidump({...options, file: tmp})
await fs.promises.unlink(tmp)
return s
}
}
console.error(`Not a minidump file (MDMP header not found): ${file}`)
process.exit(1)
}
await findSymbols({directory, file, quiet, force})
if (!quiet) console.error("Symbolicating...")
const r = await new Promise((resolve, reject) => {
minidump.walkStack(file, [directory], (err, rep) => {
if (err) reject(err)
resolve(rep)
})
})
return r.toString('utf8')
}
const SYMBOL_BASE_URLS = [
'https://symbols.mozilla.org/try',
'https://symbols.electronjs.org',
]
function fetchSymbol(directory, baseUrl, pdb, id, symbolFileName) {
const url = `${baseUrl}/${encodeURIComponent(pdb)}/${id}/${encodeURIComponent(symbolFileName)}`
const symbolPath = path.join(directory, pdb, id, symbolFileName)
return new Promise((resolve, reject) => {
// We use curl here in order to avoid having to deal with redirects +
// gzip + saving to a file ourselves. It would be more portable to
// handle this in JS rather than by shelling out, though, so TODO.
const child = require('child_process').spawn('curl', [
// We don't need progress bars.
'--silent',
// The Mozilla symbol server redirects to S3, so follow that
// redirect.
'--location',
// We want to create all the parent directories for the target path,
// which is breakpad_symbols/foo.pdb/0123456789ABCDEF/foo.sym
'--create-dirs',
// The .sym file is gzipped, but minidump_stackwalk needs it
// uncompressed, so ask curl to ungzip it for us.
'--compressed',
// If we get a 404, don't write anything and exit with code 22. The
// parent directories will still be created, though.
'--fail',
// Save the file directly into the cache.
'--output', symbolPath,
// This is the URL we want to fetch.
url
])
child.once('close', (code) => {
if (code === 0) {
resolve(true)
} else {
if (code === 22) { // 404
resolve(false)
} else {
reject(new Error(`failed to download ${url} (code ${code})`))
}
}
})
})
}
const findSymbols = async ({directory, file, quiet, force}) => {
// Extract a list of referenced modules using minidump_dump
const modules = []
{
const r = await new Promise((resolve, reject) => {
minidump.dump(file, (err, rep) => {
resolve(rep.toString('utf8'))
})
})
// minidump_dump's output has lines like:
// (debug_file) = "user32.pdb"
// (debug_identifier) = "034AFFE8331738A54EC07A7655CAF0DC1"
// or on a linux dump:
// (debug_file) = "/XXXX/XXXXXX/XXXX/XXX/XXXXXXXXXXXXXXXX/libc.so.6"
// (debug_identifier) = "4B76CFD3972F3EACFE366DDD07AD902F0"
const re = /\(debug_file\)\s+= "(?:.+\/)?([^"]+)"\s+\(debug_identifier\)\s+= "([0-9A-F]+)"/mg
let m;
while (m = re.exec(r)) {
const [, file, id] = m
modules.push([file, id])
}
}
// Fetch any we don't already have from the symbol server
const promises = []
for (const [pdb, id] of modules) {
if (/^0+$/.test(id)) continue
const symbolFileName = pdb.replace(/(\.pdb)?$/, '.sym')
const symbolPath = path.join(directory, pdb, id, symbolFileName)
if (!fs.existsSync(symbolPath) && (!fs.existsSync(path.dirname(symbolPath)) || force)) {
promises.push((async () => {
for (const baseUrl of SYMBOL_BASE_URLS) {
if (await fetchSymbol(directory, baseUrl, pdb, id, symbolFileName))
break
}
if (bar)
bar.tick({ symbol: pdb })
})())
}
}
const bar = quiet ? null : new ProgressBar(' Downloading symbols [:bar] :percent :symbol', {
total: promises.length
})
if (!quiet && promises.length > 0) {
console.error(`Downloading ${promises.length} symbol files from ${SYMBOL_BASE_URLS.join(',')}...`)
}
await Promise.all(promises)
if (!quiet && promises.length > 0)
console.error()
}
module.exports = {minidump: electronMinidump}