mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1743983 pt2 - Rewrite the minidump-analyzer in Rust r=gsvelto
Differential Revision: https://phabricator.services.mozilla.com/D208391
This commit is contained in:
parent
a92f7373b2
commit
1311d61cb2
3
.gitignore
vendored
3
.gitignore
vendored
@ -351,3 +351,6 @@ mobile/android/annotations/bin/
|
||||
|
||||
# Ignore generated log files under media/libvpx
|
||||
media/libvpx/config/**/config.log
|
||||
|
||||
# Ignore generated files resulting from building the minidump analyzer tests.
|
||||
toolkit/crashreporter/minidump-analyzer/analyzer-test/target/
|
||||
|
@ -351,3 +351,6 @@ toolkit/themes/shared/design-system/node_modules/
|
||||
|
||||
# Ignore generated log files under media/libvpx
|
||||
^media/libvpx/config/.*/config.log
|
||||
|
||||
# Ignore generated files resulting from building the minidump analyzer tests.
|
||||
^toolkit/crashreporter/minidump-analyzer/analyzer-test/target/
|
||||
|
662
toolkit/crashreporter/minidump-analyzer/analyzer-test/Cargo.lock
generated
Normal file
662
toolkit/crashreporter/minidump-analyzer/analyzer-test/Cargo.lock
generated
Normal file
@ -0,0 +1,662 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crash-context"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b85cef661eeca0c6675116310936972c520ebb0a33ddef16fd7efc957f4c1288"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"mach2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crash-handler"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed56890cb467e9ff5e6fe95254571beebb301990b5c7df5e97a7b203e4f0c4f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crash-context",
|
||||
"libc",
|
||||
"mach2",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "goblin"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47"
|
||||
dependencies = [
|
||||
"log",
|
||||
"plain",
|
||||
"scroll",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "json"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidump-analyzer-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crash-handler",
|
||||
"json",
|
||||
"minidumper",
|
||||
"sadness-generator",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidump-common"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bb6eaf88cc770fa58e6ae721cf2e40c2ca6a4c942ae8c7aa324d680bd3c6717"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"debugid",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"range-map",
|
||||
"scroll",
|
||||
"smart-default",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidump-writer"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abcd9c8a1e6e1e9d56ce3627851f39a17ea83e17c96bc510f29d7e43d78a7d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"crash-context",
|
||||
"goblin",
|
||||
"libc",
|
||||
"log",
|
||||
"mach2",
|
||||
"memmap2",
|
||||
"memoffset",
|
||||
"minidump-common",
|
||||
"nix",
|
||||
"procfs-core",
|
||||
"scroll",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidumper"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b99697a8483221ca2d163aedcbee44f3851c4f5916ab0e96ae6397fb6b6deb2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crash-context",
|
||||
"libc",
|
||||
"log",
|
||||
"minidump-writer",
|
||||
"parking_lot",
|
||||
"polling",
|
||||
"scroll",
|
||||
"thiserror",
|
||||
"uds",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "plain"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "procfs-core"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"hex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-map"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sadness-generator"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdaf5eed5d14ebb5d32d864b419e760c9ddf123bcde880a30a90063494592d7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "scroll"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6"
|
||||
dependencies = [
|
||||
"scroll_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scroll_derive"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "smart-default"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||
|
||||
[[package]]
|
||||
name = "uds"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "885c31f06fce836457fe3ef09a59f83fe8db95d270b11cd78f40a4666c4d1661"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.0",
|
||||
"windows_aarch64_msvc 0.48.0",
|
||||
"windows_i686_gnu 0.48.0",
|
||||
"windows_i686_msvc 0.48.0",
|
||||
"windows_x86_64_gnu 0.48.0",
|
||||
"windows_x86_64_gnullvm 0.48.0",
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.5",
|
||||
"windows_aarch64_msvc 0.52.5",
|
||||
"windows_i686_gnu 0.52.5",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.5",
|
||||
"windows_x86_64_gnu 0.52.5",
|
||||
"windows_x86_64_gnullvm 0.52.5",
|
||||
"windows_x86_64_msvc 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "minidump-analyzer-test"
|
||||
description = "Tests minidump-analyzer using crates which we won't vendor."
|
||||
version = "0.1.0"
|
||||
authors = ["Alex Franchuk <alex.franchuk@gmail.com>"]
|
||||
license = "MPL-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[dev-dependencies]
|
||||
crash-handler = "0.6"
|
||||
minidumper = "0.8"
|
||||
json = "0.12.4"
|
||||
sadness-generator = "0.5.0"
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[workspace]
|
@ -0,0 +1,6 @@
|
||||
To run the tests, execute:
|
||||
|
||||
`MINIDUMP_ANALYZER=/path/to/minidump-analyzer cargo test`
|
||||
|
||||
You likely want to set `MINIDUMP_ANALYZER` to `OBJDIR/dist/bin/minidump-analyzer` after running
|
||||
`mach build`.
|
@ -0,0 +1,3 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
@ -0,0 +1,169 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command};
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// Child failure types.
|
||||
///
|
||||
/// These must correspond to tests defined in `client.rs` (per the `Display` implementation).
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum FailureType {
|
||||
RaiseAbort,
|
||||
}
|
||||
|
||||
impl FailureType {
|
||||
pub fn test_name(&self) -> &str {
|
||||
match self {
|
||||
FailureType::RaiseAbort => "raise_abort",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for FailureType {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.test_name().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FailureType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str(self.test_name())
|
||||
}
|
||||
}
|
||||
|
||||
fn start_child(failure: FailureType) -> Child {
|
||||
Command::new("cargo")
|
||||
.args([
|
||||
"test",
|
||||
"--package",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
"--test",
|
||||
"client",
|
||||
"--",
|
||||
"--include-ignored",
|
||||
])
|
||||
.arg(failure)
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()
|
||||
.expect("failed to execute child")
|
||||
}
|
||||
|
||||
// XXX: minidumper is somewhat slow to establish the connection, making the test slow.
|
||||
fn write_minidump(minidump_file: &Path, failure: FailureType) {
|
||||
use minidumper::{LoopAction, MinidumpBinary, Server, ServerHandler};
|
||||
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
|
||||
|
||||
struct Handler {
|
||||
minidump_file: PathBuf,
|
||||
}
|
||||
|
||||
impl ServerHandler for Handler {
|
||||
fn create_minidump_file(&self) -> std::io::Result<(File, PathBuf)> {
|
||||
let f = File::create(&self.minidump_file)?;
|
||||
let p = self.minidump_file.clone();
|
||||
Ok((f, p))
|
||||
}
|
||||
|
||||
fn on_minidump_created(
|
||||
&self,
|
||||
result: Result<MinidumpBinary, minidumper::Error>,
|
||||
) -> LoopAction {
|
||||
result.expect("failed to write minidump");
|
||||
LoopAction::Exit
|
||||
}
|
||||
|
||||
fn on_message(&self, _kind: u32, _buffer: Vec<u8>) {}
|
||||
}
|
||||
|
||||
let mut server =
|
||||
Server::with_name(&failure.to_string()).expect("failed to create minidumper server");
|
||||
let mut child = start_child(failure);
|
||||
|
||||
/// Maximum time we want to wait for the child to execute and crash.
|
||||
const CHILD_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
|
||||
// Run the server.
|
||||
let shutdown = AtomicBool::default();
|
||||
std::thread::scope(|s| {
|
||||
let handle = s.spawn(|| {
|
||||
std::thread::park_timeout(CHILD_TIMEOUT);
|
||||
shutdown.store(true, Relaxed);
|
||||
});
|
||||
server
|
||||
.run(
|
||||
Box::new(Handler {
|
||||
minidump_file: minidump_file.into(),
|
||||
}),
|
||||
&shutdown,
|
||||
None,
|
||||
)
|
||||
.expect("minidumper server failure");
|
||||
handle.thread().unpark();
|
||||
});
|
||||
drop(child.kill());
|
||||
if !minidump_file.exists() {
|
||||
// Likely a timeout occurred
|
||||
panic!("expected child process to crash within {:?}", CHILD_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_basic_minidump() {
|
||||
let dir = tempdir().expect("failed to create temporary directory");
|
||||
let minidump_file = dir.path().join("mini.dump");
|
||||
let extra_file = dir.path().join("mini.extra");
|
||||
|
||||
let Some(analyzer) = std::env::var_os("MINIDUMP_ANALYZER") else {
|
||||
panic!("Specify the path to the minidump analyzer binary as the MINIDUMP_ANALYZER environment variable.");
|
||||
};
|
||||
|
||||
// Create minidump from test.
|
||||
write_minidump(&minidump_file, FailureType::RaiseAbort);
|
||||
|
||||
// Create empty extra file
|
||||
{
|
||||
let mut extra = File::create(&extra_file).expect("failed to create extra json file");
|
||||
write!(&mut extra, "{{}}").expect("failed to write to extra json file");
|
||||
}
|
||||
|
||||
// Run minidump-analyzer
|
||||
{
|
||||
let output = Command::new(analyzer)
|
||||
.env("RUST_BACKTRACE", "1")
|
||||
.arg(&minidump_file)
|
||||
.output()
|
||||
.expect("failed to run minidump-analyzer");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stderr:\n{}",
|
||||
std::str::from_utf8(&output.stderr).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// Check the output JSON
|
||||
// The stack trace will actually be in cargo. It forks and execs the test program; there is no
|
||||
// clean way to make it just exec one or to directly address the binary (without creating a new
|
||||
// crate).
|
||||
{
|
||||
let mut extra_content = String::new();
|
||||
File::open(extra_file)
|
||||
.expect("failed to open extra json file")
|
||||
.read_to_string(&mut extra_content)
|
||||
.expect("failed to read extra json file");
|
||||
|
||||
let extra = json::parse(&extra_content).expect("failed to parse extra json");
|
||||
let stack_traces = &extra["StackTraces"];
|
||||
assert!(stack_traces.is_object());
|
||||
let threads = &stack_traces["threads"];
|
||||
assert!(threads.is_array() && threads.len() == 1);
|
||||
assert!(threads[0].is_object());
|
||||
let frames = &threads[0]["frames"];
|
||||
assert!(frames.is_array() && !frames.is_empty());
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Tests in this file are not intended to be run by default, they are just entrypoints for
|
||||
//! creating minidumps.
|
||||
|
||||
use crash_handler::{CrashContext, CrashEvent, CrashEventResult, CrashHandler};
|
||||
|
||||
struct TestCrashClient {
|
||||
client: minidumper::Client,
|
||||
}
|
||||
|
||||
impl TestCrashClient {
|
||||
pub fn new(name: &str) -> Self {
|
||||
TestCrashClient {
|
||||
client: minidumper::Client::with_name(name)
|
||||
.expect("failed to create minidumper client"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl CrashEvent for TestCrashClient {
|
||||
fn on_crash(&self, context: &CrashContext) -> CrashEventResult {
|
||||
CrashEventResult::Handled(self.client.request_dump(context).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_crashes(name: &str) -> CrashHandler {
|
||||
CrashHandler::attach(Box::new(TestCrashClient::new(name)))
|
||||
.expect("failed to install crash handler")
|
||||
}
|
||||
|
||||
macro_rules! sadness_test {
|
||||
( $name:ident ) => {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn $name() {
|
||||
let _handler = handle_crashes(stringify!($name));
|
||||
unsafe {
|
||||
sadness_generator::$name();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sadness_test!(raise_abort);
|
@ -4,40 +4,4 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
GeckoProgram("minidump-analyzer", linkage=None)
|
||||
|
||||
if CONFIG["OS_TARGET"] == "WINNT":
|
||||
DEFINES["UNICODE"] = True
|
||||
DEFINES["_UNICODE"] = True
|
||||
|
||||
if CONFIG["TARGET_CPU"] == "x86_64":
|
||||
UNIFIED_SOURCES += [
|
||||
"MozStackFrameSymbolizer.cpp",
|
||||
"Win64ModuleUnwindMetadata.cpp",
|
||||
]
|
||||
|
||||
OS_LIBS += ["dbghelp", "imagehlp"]
|
||||
|
||||
if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] in ("gcc", "clang"):
|
||||
# This allows us to use wmain as the entry point on mingw
|
||||
LDFLAGS += [
|
||||
"-municode",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"minidump-analyzer.cpp",
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
"breakpad_processor",
|
||||
"jsoncpp",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"/toolkit/components/jsoncpp/include",
|
||||
]
|
||||
|
||||
if CONFIG["OS_TARGET"] != "WINNT":
|
||||
DisableStlWrapping()
|
||||
|
||||
include("/toolkit/crashreporter/crashreporter.mozbuild")
|
||||
RUST_PROGRAMS = ["minidump-analyzer"]
|
||||
|
@ -0,0 +1,494 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use futures_executor::{block_on, ThreadPool};
|
||||
use minidump::{
|
||||
system_info::Cpu, Minidump, MinidumpException, MinidumpMemoryList, MinidumpMiscInfo,
|
||||
MinidumpModule, MinidumpModuleList, MinidumpSystemInfo, MinidumpThread, MinidumpThreadList,
|
||||
MinidumpUnloadedModule, MinidumpUnloadedModuleList, Module, UnifiedMemoryList,
|
||||
};
|
||||
use minidump_unwind::{
|
||||
symbols::debuginfo::DebugInfoSymbolProvider, symbols::SymbolProvider, walk_stack, CallStack,
|
||||
CallStackInfo, SystemInfo,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::{json, Value as JsonValue};
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
/// Analyze a minidump file to augment a corresponding .extra file with stack trace information.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version)]
|
||||
struct Args {
|
||||
/// Generate all stacks, rather than just those of the crashing thread.
|
||||
#[clap(long = "full")]
|
||||
all_stacks: bool,
|
||||
|
||||
/// The minidump file to analyze.
|
||||
minidump: PathBuf,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
/// Get the extra file.
|
||||
///
|
||||
/// This file is derived from the minidump file path.
|
||||
pub fn extra_file(&self) -> PathBuf {
|
||||
let mut ret = self.minidump.clone();
|
||||
ret.set_extension("extra");
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
mod processor {
|
||||
use super::*;
|
||||
|
||||
pub struct Processor<'a> {
|
||||
runtime: ThreadPool,
|
||||
// We create a Context abstraction to easily spawn tokio tasks (which must be 'static).
|
||||
context: Arc<Ctx<'a>>,
|
||||
}
|
||||
|
||||
struct Ctx<'a> {
|
||||
module_list: MinidumpModuleList,
|
||||
unloaded_module_list: MinidumpUnloadedModuleList,
|
||||
memory_list: UnifiedMemoryList<'a>,
|
||||
system_info: MinidumpSystemInfo,
|
||||
processor_system_info: SystemInfo,
|
||||
exception: MinidumpException<'a>,
|
||||
misc_info: Option<MinidumpMiscInfo>,
|
||||
symbol_provider: BoxedSymbolProvider,
|
||||
}
|
||||
|
||||
/// Concurrently execute the given futures, returning a Vec of the results.
|
||||
async fn concurrently<'a, I, Fut, R>(runtime: &R, iter: I) -> Vec<Fut::Output>
|
||||
where
|
||||
R: futures_util::task::Spawn,
|
||||
I: IntoIterator<Item = Fut>,
|
||||
Fut: std::future::Future + Send + 'a,
|
||||
// It's possible, though very obtuse, to support `'a` on the Output. We don't need it
|
||||
// though, so we keep it `'static` to simplify things.
|
||||
Fut::Output: Send + 'static,
|
||||
{
|
||||
use futures_util::{
|
||||
future::{join_all, BoxFuture, FutureExt},
|
||||
task::SpawnExt,
|
||||
};
|
||||
|
||||
join_all(iter.into_iter().map(|f| {
|
||||
let fut: BoxFuture<'a, Fut::Output> = f.boxed();
|
||||
// Safety: It is safe to transmute to a static lifetime because we await the output of
|
||||
// the future while the `'a` lifetime is guaranteed to be valid (before exit from this
|
||||
// function).
|
||||
let fut: BoxFuture<'static, Fut::Output> = unsafe { std::mem::transmute(fut) };
|
||||
runtime.spawn_with_handle(fut).expect("spawn failed")
|
||||
}))
|
||||
.await
|
||||
}
|
||||
|
||||
impl<'a> Processor<'a> {
|
||||
pub fn new<T>(minidump: &'a Minidump<T>) -> anyhow::Result<Self>
|
||||
where
|
||||
T: std::ops::Deref<Target = [u8]>,
|
||||
{
|
||||
let system_info = minidump.get_stream::<MinidumpSystemInfo>()?;
|
||||
let misc_info = minidump.get_stream::<MinidumpMiscInfo>().ok();
|
||||
let module_list = minidump
|
||||
.get_stream::<MinidumpModuleList>()
|
||||
.unwrap_or_default();
|
||||
let unloaded_module_list = minidump
|
||||
.get_stream::<MinidumpUnloadedModuleList>()
|
||||
.unwrap_or_default();
|
||||
let memory_list = minidump
|
||||
.get_stream::<MinidumpMemoryList>()
|
||||
.unwrap_or_default();
|
||||
let exception = minidump.get_stream::<MinidumpException>()?;
|
||||
|
||||
// TODO Something like SystemInfo::current() to get the active system's info?
|
||||
let processor_system_info = SystemInfo {
|
||||
os: system_info.os,
|
||||
os_version: None,
|
||||
os_build: None,
|
||||
cpu: system_info.cpu,
|
||||
cpu_info: None,
|
||||
cpu_microcode_version: None,
|
||||
cpu_count: 1,
|
||||
};
|
||||
|
||||
let symbol_provider = BoxedSymbolProvider(match system_info.cpu {
|
||||
// DebugInfoSymbolProvider only supports x86_64 and Arm64 right now
|
||||
Cpu::X86_64 | Cpu::Arm64 => Box::new(block_on(DebugInfoSymbolProvider::new(
|
||||
&system_info,
|
||||
&module_list,
|
||||
))),
|
||||
_ => Box::new(breakpad_symbols::Symbolizer::new(
|
||||
breakpad_symbols::SimpleSymbolSupplier::new(vec![]),
|
||||
)),
|
||||
});
|
||||
|
||||
Ok(Processor {
|
||||
runtime: ThreadPool::new()?,
|
||||
context: Arc::new(Ctx {
|
||||
module_list,
|
||||
unloaded_module_list,
|
||||
memory_list: UnifiedMemoryList::Memory(memory_list),
|
||||
system_info,
|
||||
processor_system_info,
|
||||
exception,
|
||||
misc_info,
|
||||
symbol_provider,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the minidump system info.
|
||||
pub fn system_info(&self) -> &MinidumpSystemInfo {
|
||||
&self.context.system_info
|
||||
}
|
||||
|
||||
/// Get the minidump exception.
|
||||
pub fn exception(&self) -> &MinidumpException {
|
||||
&self.context.exception
|
||||
}
|
||||
|
||||
/// Get call stacks for the given threads.
|
||||
///
|
||||
/// Call stacks will be concurrently calculated.
|
||||
pub fn thread_call_stacks<'b>(
|
||||
&self,
|
||||
threads: impl IntoIterator<Item = &'b MinidumpThread<'b>>,
|
||||
) -> anyhow::Result<Vec<CallStack>> {
|
||||
Ok(block_on(concurrently(
|
||||
&self.runtime,
|
||||
threads
|
||||
.into_iter()
|
||||
.map(|thread| self.context.thread_call_stack(thread)),
|
||||
))
|
||||
.into_iter()
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Get all modules, ordered by address.
|
||||
#[cfg(windows)]
|
||||
pub fn ordered_modules(&self) -> impl Iterator<Item = &MinidumpModule> {
|
||||
self.context.module_list.by_addr()
|
||||
}
|
||||
|
||||
/// Get all unloaded modules, ordered by address.
|
||||
pub fn unloaded_modules(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> {
|
||||
self.context.unloaded_module_list.by_addr()
|
||||
}
|
||||
|
||||
/// Get the index of the main module.
|
||||
///
|
||||
/// Returns `None` when no main module exists (only when there are modules).
|
||||
pub fn main_module(&self) -> Option<&MinidumpModule> {
|
||||
self.context.module_list.main_module()
|
||||
}
|
||||
|
||||
/// Get the json representation of module signature information.
|
||||
#[cfg(windows)]
|
||||
pub fn module_signature_info(&self) -> JsonValue {
|
||||
// JSON with structure { <binary_org_name>: [<code_file filename>...], ... }
|
||||
let mut ret = json!({});
|
||||
for module in self
|
||||
.ordered_modules()
|
||||
.map(|m| m as &dyn Module)
|
||||
.chain(self.unloaded_modules().map(|m| m as &dyn Module))
|
||||
{
|
||||
let code_file = module.code_file();
|
||||
let code_file_path: &std::path::Path = code_file.as_ref().as_ref();
|
||||
if let Some(org_name) = windows::binary_org_name(code_file_path) {
|
||||
let entry = &mut ret[org_name];
|
||||
|
||||
if entry.is_null() {
|
||||
*entry = json!([]);
|
||||
}
|
||||
entry.as_array_mut().unwrap().push(
|
||||
code_file_path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy())
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
log::warn!("couldn't get binary org name for {code_file}");
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Get the json representation of module signature information.
|
||||
///
|
||||
/// This is currently unimplemented and returns null.
|
||||
#[cfg(unix)]
|
||||
pub fn module_signature_info(&self) -> JsonValue {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl Ctx<'_> {
|
||||
/// Compute the call stack for a single thread.
|
||||
pub async fn thread_call_stack(&self, thread: &MinidumpThread<'_>) -> CallStack {
|
||||
let context = if thread.raw.thread_id == self.exception.get_crashing_thread_id() {
|
||||
self.exception
|
||||
.context(&self.system_info, self.misc_info.as_ref())
|
||||
} else {
|
||||
thread.context(&self.system_info, self.misc_info.as_ref())
|
||||
}
|
||||
.map(|c| c.into_owned());
|
||||
let stack_memory = thread.stack_memory(&self.memory_list);
|
||||
let Some(mut call_stack) = context.map(CallStack::with_context) else {
|
||||
return CallStack::with_info(thread.raw.thread_id, CallStackInfo::MissingContext);
|
||||
};
|
||||
|
||||
walk_stack(
|
||||
0,
|
||||
(),
|
||||
&mut call_stack,
|
||||
stack_memory,
|
||||
&self.module_list,
|
||||
&self.processor_system_info,
|
||||
&self.symbol_provider,
|
||||
)
|
||||
.await;
|
||||
|
||||
call_stack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BoxedSymbolProvider(Box<dyn SymbolProvider + Send + Sync>);
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SymbolProvider for BoxedSymbolProvider {
|
||||
async fn fill_symbol(
|
||||
&self,
|
||||
module: &(dyn Module + Sync),
|
||||
frame: &mut (dyn minidump_unwind::FrameSymbolizer + Send),
|
||||
) -> Result<(), minidump_unwind::FillSymbolError> {
|
||||
self.0.fill_symbol(module, frame).await
|
||||
}
|
||||
|
||||
async fn walk_frame(
|
||||
&self,
|
||||
module: &(dyn Module + Sync),
|
||||
walker: &mut (dyn minidump_unwind::FrameWalker + Send),
|
||||
) -> Option<()> {
|
||||
self.0.walk_frame(module, walker).await
|
||||
}
|
||||
|
||||
async fn get_file_path(
|
||||
&self,
|
||||
module: &(dyn Module + Sync),
|
||||
file_kind: minidump_unwind::FileKind,
|
||||
) -> Result<PathBuf, minidump_unwind::FileError> {
|
||||
self.0.get_file_path(module, file_kind).await
|
||||
}
|
||||
|
||||
fn stats(&self) -> std::collections::HashMap<String, minidump_unwind::SymbolStats> {
|
||||
self.0.stats()
|
||||
}
|
||||
|
||||
fn pending_stats(&self) -> minidump_unwind::PendingSymbolStats {
|
||||
self.0.pending_stats()
|
||||
}
|
||||
}
|
||||
|
||||
use processor::Processor;
|
||||
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
|
||||
if let Err(e) = try_main() {
|
||||
eprintln!("{e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let extra_file = args.extra_file();
|
||||
|
||||
log::info!("minidump file path: {}", args.minidump.display());
|
||||
log::info!("extra file path: {}", extra_file.display());
|
||||
|
||||
let minidump = Minidump::read_path(&args.minidump).context("while reading minidump")?;
|
||||
|
||||
let mut extra_json: JsonValue = {
|
||||
let mut extra_file_content = String::new();
|
||||
File::open(&extra_file)
|
||||
.context("while opening extra file")?
|
||||
.read_to_string(&mut extra_file_content)
|
||||
.context("while reading extra file")?;
|
||||
|
||||
serde_json::from_str(&extra_file_content).context("while parsing extra file JSON")?
|
||||
};
|
||||
|
||||
// Read relevant information from the minidump.
|
||||
let proc = Processor::new(&minidump)?;
|
||||
let thread_list = minidump.get_stream::<MinidumpThreadList>()?;
|
||||
|
||||
// Derive additional arguments used in stack walking.
|
||||
let crashing_thread = thread_list
|
||||
.get_thread(proc.exception().get_crashing_thread_id())
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"exception thread id missing in thread list"
|
||||
))?;
|
||||
|
||||
let (crashing_thread_idx, call_stacks) = if args.all_stacks {
|
||||
(
|
||||
thread_list
|
||||
.threads
|
||||
.iter()
|
||||
.position(|t| t.raw.thread_id == crashing_thread.raw.thread_id)
|
||||
.expect("get_thread() returned a thread that doesn't exist"),
|
||||
proc.thread_call_stacks(&thread_list.threads)?,
|
||||
)
|
||||
} else {
|
||||
(0, proc.thread_call_stacks([crashing_thread])?)
|
||||
};
|
||||
|
||||
let crash_type = proc
|
||||
.exception()
|
||||
.get_crash_reason(proc.system_info().os, proc.system_info().cpu)
|
||||
.to_string();
|
||||
let crash_address = proc
|
||||
.exception()
|
||||
.get_crash_address(proc.system_info().os, proc.system_info().cpu);
|
||||
|
||||
let used_modules = {
|
||||
let mut v = call_stacks
|
||||
.iter()
|
||||
.flat_map(|call_stack| call_stack.frames.iter())
|
||||
.filter_map(|frame| frame.module.as_ref())
|
||||
// Always include the main module.
|
||||
.chain(proc.main_module())
|
||||
.collect::<Vec<_>>();
|
||||
v.sort_by_key(|m| m.base_address());
|
||||
v.dedup_by_key(|m| m.base_address());
|
||||
v
|
||||
};
|
||||
|
||||
extra_json["StackTraces"] = json!({
|
||||
"status": call_stack_status(&call_stacks),
|
||||
"crash_info": {
|
||||
"type": crash_type,
|
||||
"address": format!("{crash_address:#x}"),
|
||||
"crashing_thread": crashing_thread_idx
|
||||
// TODO: "assertion" when there's no crash indicator
|
||||
},
|
||||
"main_module": proc.main_module().and_then(|m| module_index(&used_modules, m)),
|
||||
"modules": used_modules.iter().map(|module| {
|
||||
let code_file = module.code_file();
|
||||
let code_file_path: &std::path::Path = code_file.as_ref().as_ref();
|
||||
json!({
|
||||
"base_addr": format!("{:#x}", module.base_address()),
|
||||
"end_addr": format!("{:#x}", module.base_address() + module.size()),
|
||||
"filename": code_file_path.file_name().map(|s| s.to_string_lossy()),
|
||||
"code_id": module.code_identifier().as_ref().map(|id| id.as_str()),
|
||||
"debug_file": module.debug_file().as_deref(),
|
||||
"debug_id": module.debug_identifier().map(|debug| debug.breakpad().to_string()),
|
||||
"version": module.version().as_deref()
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
"unloaded_modules": proc.unloaded_modules().map(|module| {
|
||||
let code_file = module.code_file();
|
||||
let code_file_path: &std::path::Path = code_file.as_ref().as_ref();
|
||||
json!({
|
||||
"base_addr": format!("{:#x}", module.base_address()),
|
||||
"end_addr": format!("{:#x}", module.base_address() + module.size()),
|
||||
"filename": code_file_path.file_name().map(|s| s.to_string_lossy()),
|
||||
"code_id": module.code_identifier().as_ref().map(|id| id.as_str()),
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
"threads": call_stacks.iter().map(|call_stack| call_stack_to_json(call_stack, &used_modules)).collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
// StackTraces should not have null values (upstream processing expects the values to be
|
||||
// omitted).
|
||||
remove_nulls(&mut extra_json["StackTraces"]);
|
||||
|
||||
let module_signature_info = proc.module_signature_info();
|
||||
if !module_signature_info.is_null() {
|
||||
// ModuleSignatureInfo is sent as a crash annotation so must be string. This differs from
|
||||
// StackTraces which isn't actually sent (it's just read and removed by the crash
|
||||
// reporter client).
|
||||
extra_json["ModuleSignatureInfo"] = serde_json::to_string(&module_signature_info)
|
||||
.unwrap()
|
||||
.into();
|
||||
}
|
||||
|
||||
std::fs::write(&extra_file, extra_json.to_string())
|
||||
.context("while writing modified extra file")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the index of `needle` in `modules`.
|
||||
fn module_index(modules: &[&MinidumpModule], needle: &MinidumpModule) -> Option<usize> {
|
||||
modules
|
||||
.iter()
|
||||
.position(|o| o.base_address() == needle.base_address())
|
||||
}
|
||||
|
||||
/// Convert a call stack to json (in a form appropriate for the extra json file).
|
||||
fn call_stack_to_json(call_stack: &CallStack, modules: &[&MinidumpModule]) -> JsonValue {
|
||||
json!({
|
||||
"frames": call_stack.frames.iter().map(|frame| {
|
||||
json!({
|
||||
"ip": format!("{:#x}", frame.instruction),
|
||||
"module_index": frame.module.as_ref().and_then(|m| module_index(modules, m)),
|
||||
"trust": frame.trust.as_str(),
|
||||
})
|
||||
}).collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
|
||||
fn call_stack_status(stacks: &[CallStack]) -> JsonValue {
|
||||
let mut error_string = String::new();
|
||||
|
||||
for (_i, s) in stacks.iter().enumerate() {
|
||||
match s.info {
|
||||
CallStackInfo::Ok | CallStackInfo::DumpThreadSkipped => (),
|
||||
CallStackInfo::UnsupportedCpu => {
|
||||
// If the CPU is unsupported, it ought to be the same error for every thread.
|
||||
error_string = "unsupported cpu".into();
|
||||
break;
|
||||
}
|
||||
// We ignore these errors as they are permissible wrt the overall status.
|
||||
CallStackInfo::MissingContext | CallStackInfo::MissingMemory => (),
|
||||
}
|
||||
}
|
||||
if error_string.is_empty() {
|
||||
"OK".into()
|
||||
} else {
|
||||
error_string.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all object entries which have null values.
|
||||
fn remove_nulls(value: &mut JsonValue) {
|
||||
match value {
|
||||
JsonValue::Array(vals) => {
|
||||
for v in vals {
|
||||
remove_nulls(v);
|
||||
}
|
||||
}
|
||||
JsonValue::Object(kvs) => {
|
||||
kvs.retain(|_, v| !v.is_null());
|
||||
for v in kvs.values_mut() {
|
||||
remove_nulls(v);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
433
toolkit/crashreporter/minidump-analyzer/src/windows/mod.rs
Normal file
433
toolkit/crashreporter/minidump-analyzer/src/windows/mod.rs
Normal file
@ -0,0 +1,433 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::ffi::{c_void, OsString};
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle};
|
||||
use std::path::Path;
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{BOOL, HWND, INVALID_HANDLE_VALUE},
|
||||
Security::Cryptography::*,
|
||||
Security::WinTrust::*,
|
||||
Storage::FileSystem::{
|
||||
FILE_FLAG_SEQUENTIAL_SCAN, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
|
||||
},
|
||||
UI::WindowsAndMessaging::CharUpperBuffW,
|
||||
};
|
||||
|
||||
// Our windows-targets doesn't link wintrust correctly.
|
||||
#[link(name = "wintrust", kind = "static")]
|
||||
extern "C" {}
|
||||
|
||||
type DWORD = u32;
|
||||
|
||||
mod strings;
|
||||
mod wintrust;
|
||||
|
||||
use strings::WideString;
|
||||
|
||||
pub fn binary_org_name(binary: &Path) -> Option<String> {
|
||||
log::trace!("binary_org_name({})", binary.display());
|
||||
let binary_wide = match WideString::new(binary) {
|
||||
Err(e) => {
|
||||
log::error!("failed to create wide string of binary path: {e}");
|
||||
return None;
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
|
||||
// Verify trust for the binary and get the certificate context.
|
||||
let mut cert_store = CertStore::default();
|
||||
let mut crypt_msg = CryptMsg::default();
|
||||
|
||||
let result = unsafe {
|
||||
CryptQueryObject(
|
||||
CERT_QUERY_OBJECT_FILE,
|
||||
binary_wide.pcwstr() as *const _,
|
||||
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
|
||||
CERT_QUERY_FORMAT_FLAG_BINARY,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
&mut *cert_store,
|
||||
&mut *crypt_msg,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
let verified = if result != 0 {
|
||||
// We got a result.
|
||||
let mut file_info = WINTRUST_FILE_INFO {
|
||||
cbStruct: std::mem::size_of::<WINTRUST_FILE_INFO>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
pcwszFilePath: binary_wide.pcwstr(),
|
||||
hFile: 0,
|
||||
pgKnownSubject: std::ptr::null_mut(),
|
||||
};
|
||||
verify_trust(|data| {
|
||||
data.dwUnionChoice = WTD_CHOICE_FILE;
|
||||
data.Anonymous = WINTRUST_DATA_0 {
|
||||
pFile: &mut file_info,
|
||||
};
|
||||
})
|
||||
} else {
|
||||
log::debug!("checking catalogs for binary org name");
|
||||
// We didn't find anything in the binary, so try catalogs.
|
||||
let cat_admin = wintrust::CATAdmin::acquire()?;
|
||||
|
||||
log::trace!("acquired CATAdmin");
|
||||
|
||||
// Hash the binary
|
||||
let file = match OpenOptions::new()
|
||||
.read(true)
|
||||
.share_mode(FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE)
|
||||
.custom_flags(FILE_FLAG_SEQUENTIAL_SCAN)
|
||||
.open(binary)
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("failed to open file {}: {e}", binary.display());
|
||||
return None;
|
||||
}
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
let mut file_hash = cat_admin.calculate_file_hash(&file)?;
|
||||
|
||||
log::trace!("{} hashed to {file_hash:?}", binary.display());
|
||||
|
||||
// Now query the catalog system to see if any catalogs reference a binary with our hash.
|
||||
let catalog_info = cat_admin
|
||||
.catalog_from_hash(&mut file_hash)
|
||||
.and_then(|h| h.get_info())?;
|
||||
|
||||
log::trace!("found catalog info");
|
||||
|
||||
unsafe {
|
||||
CryptQueryObject(
|
||||
CERT_QUERY_OBJECT_FILE,
|
||||
catalog_info.wszCatalogFile.as_ptr() as *const _,
|
||||
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
|
||||
CERT_QUERY_FORMAT_FLAG_BINARY,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
&mut *cert_store,
|
||||
&mut *crypt_msg,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
.into_option()?;
|
||||
|
||||
// WINTRUST_CATALOG_INFO::pcwszMemberTag is commonly set to the string
|
||||
// representation of the file hash, so we build that here.
|
||||
let mut strlength = DWORD::default();
|
||||
let file_hash_to_string = |buf: *mut u16, len: *mut DWORD| {
|
||||
unsafe {
|
||||
CryptBinaryToStringW(
|
||||
file_hash.as_ptr(),
|
||||
file_hash.len() as DWORD,
|
||||
CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF,
|
||||
buf,
|
||||
len,
|
||||
)
|
||||
}
|
||||
.into_option()
|
||||
};
|
||||
file_hash_to_string(std::ptr::null_mut(), &mut strlength as *mut _)?;
|
||||
let mut file_hash_string = vec![0u16; strlength as usize];
|
||||
file_hash_to_string(file_hash_string.as_mut_ptr(), &mut strlength as *mut _)?;
|
||||
|
||||
// Ensure the string is uppercase for WinVerifyTrust
|
||||
unsafe {
|
||||
CharUpperBuffW(
|
||||
file_hash_string.as_mut_ptr(),
|
||||
file_hash_string.len().try_into().unwrap(),
|
||||
)
|
||||
};
|
||||
|
||||
let mut info = WINTRUST_CATALOG_INFO {
|
||||
cbStruct: std::mem::size_of::<WINTRUST_CATALOG_INFO>()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
dwCatalogVersion: 0,
|
||||
pcwszCatalogFilePath: catalog_info.wszCatalogFile.as_ptr(),
|
||||
pcwszMemberTag: file_hash_string.as_ptr(),
|
||||
pcwszMemberFilePath: binary_wide.pcwstr(),
|
||||
hMemberFile: file.as_raw_handle() as _,
|
||||
// These two weren't used in Authenticode.cpp, though we have the information.
|
||||
pbCalculatedFileHash: std::ptr::null_mut(),
|
||||
cbCalculatedFileHash: 0,
|
||||
pcCatalogContext: std::ptr::null_mut(),
|
||||
hCatAdmin: *cat_admin,
|
||||
};
|
||||
|
||||
verify_trust(|data| {
|
||||
data.dwUnionChoice = WTD_CHOICE_CATALOG;
|
||||
data.Anonymous = WINTRUST_DATA_0 {
|
||||
pCatalog: &mut info,
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
if !verified {
|
||||
log::warn!("could not verify trust for {}", binary.display());
|
||||
return None;
|
||||
}
|
||||
|
||||
let cert_context = crypt_msg
|
||||
.get_certificate_info()
|
||||
.and_then(|info| cert_store.find_certificate(&info))?;
|
||||
|
||||
cert_context
|
||||
.get_name_string()
|
||||
.and_then(|oss| oss.into_string().ok())
|
||||
}
|
||||
|
||||
/// Call WinVerifyTrust, first calling `setup` on the `WINTRUST_DATA`. This is expected to set the
|
||||
/// `dwUnionChoice` and `u` members.
|
||||
fn verify_trust<F>(setup: F) -> bool
|
||||
where
|
||||
F: FnOnce(&mut WINTRUST_DATA),
|
||||
{
|
||||
let mut guid = WINTRUST_ACTION_GENERIC_VERIFY_V2;
|
||||
let mut data = WINTRUST_DATA {
|
||||
cbStruct: std::mem::size_of::<WINTRUST_DATA>().try_into().unwrap(),
|
||||
pPolicyCallbackData: std::ptr::null_mut(),
|
||||
pSIPClientData: std::ptr::null_mut(),
|
||||
dwUIChoice: WTD_UI_NONE,
|
||||
fdwRevocationChecks: WTD_REVOKE_NONE,
|
||||
// setup should set these two fields
|
||||
dwUnionChoice: Default::default(),
|
||||
Anonymous: unsafe { std::mem::zeroed::<WINTRUST_DATA_0>() },
|
||||
dwStateAction: WTD_STATEACTION_VERIFY,
|
||||
hWVTStateData: 0,
|
||||
pwszURLReference: std::ptr::null_mut(),
|
||||
dwProvFlags: WTD_CACHE_ONLY_URL_RETRIEVAL,
|
||||
dwUIContext: 0,
|
||||
pSignatureSettings: std::ptr::null_mut(),
|
||||
};
|
||||
setup(&mut data);
|
||||
|
||||
let result = unsafe {
|
||||
WinVerifyTrust(
|
||||
INVALID_HANDLE_VALUE as HWND,
|
||||
&mut guid,
|
||||
&mut data as *mut WINTRUST_DATA as *mut _,
|
||||
)
|
||||
};
|
||||
|
||||
data.dwStateAction = WTD_STATEACTION_CLOSE;
|
||||
unsafe {
|
||||
WinVerifyTrust(
|
||||
INVALID_HANDLE_VALUE as HWND,
|
||||
&mut guid,
|
||||
&mut data as *mut WINTRUST_DATA as *mut _,
|
||||
)
|
||||
};
|
||||
|
||||
result == 0
|
||||
}
|
||||
|
||||
/// Convenience trait for handling windows results.
|
||||
trait HResult {
|
||||
type Value;
|
||||
|
||||
fn into_option(self) -> Option<Self::Value>;
|
||||
}
|
||||
|
||||
impl HResult for BOOL {
|
||||
type Value = ();
|
||||
|
||||
fn into_option(self) -> Option<Self::Value> {
|
||||
(self != 0).then_some(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A certificate store handle.
|
||||
struct CertStore(HCERTSTORE);
|
||||
|
||||
impl Default for CertStore {
|
||||
fn default() -> Self {
|
||||
CertStore(std::ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl CertStore {
|
||||
pub fn find_certificate(&self, cert_info: &CertInfo) -> Option<CertContext> {
|
||||
let ctx = unsafe {
|
||||
CertFindCertificateInStore(
|
||||
self.0,
|
||||
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
0,
|
||||
CERT_FIND_SUBJECT_CERT,
|
||||
cert_info.as_ptr() as *const _,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if ctx.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CertContext(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CertStore {
|
||||
type Target = HCERTSTORE;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for CertStore {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CertStore {
|
||||
fn drop(&mut self) {
|
||||
if !self.0.is_null() {
|
||||
unsafe { CertCloseStore(self.0, 0) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A crypt message handle.
|
||||
type HCRYPTMSG = *mut c_void;
|
||||
struct CryptMsg(HCRYPTMSG);
|
||||
|
||||
impl Default for CryptMsg {
|
||||
fn default() -> Self {
|
||||
CryptMsg(std::ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl CryptMsg {
|
||||
pub fn get_certificate_info(&self) -> Option<CertInfo> {
|
||||
let mut cert_info_length: DWORD = 0;
|
||||
unsafe {
|
||||
CryptMsgGetParam(
|
||||
self.0,
|
||||
CMSG_SIGNER_CERT_INFO_PARAM,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
&mut cert_info_length,
|
||||
)
|
||||
}
|
||||
.into_option()?;
|
||||
|
||||
let mut buffer = vec![0u8; cert_info_length as usize];
|
||||
|
||||
unsafe {
|
||||
CryptMsgGetParam(
|
||||
self.0,
|
||||
CMSG_SIGNER_CERT_INFO_PARAM,
|
||||
0,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
&mut cert_info_length,
|
||||
)
|
||||
}
|
||||
.into_option()?;
|
||||
buffer.resize(cert_info_length as usize, 0);
|
||||
|
||||
Some(CertInfo { buffer })
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CryptMsg {
|
||||
type Target = HCRYPTMSG;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for CryptMsg {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CryptMsg {
|
||||
fn drop(&mut self) {
|
||||
if !self.0.is_null() {
|
||||
unsafe { CryptMsgClose(self.0) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Certificate information.
|
||||
struct CertInfo {
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CertInfo {
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
self.buffer.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// A certificate context.
|
||||
#[allow(non_camel_case_types)]
|
||||
type PCCERT_CONTEXT = *const CERT_CONTEXT;
|
||||
struct CertContext(PCCERT_CONTEXT);
|
||||
|
||||
impl CertContext {
|
||||
pub fn get_name_string(&self) -> Option<OsString> {
|
||||
let char_count = unsafe {
|
||||
CertGetNameStringW(
|
||||
self.0,
|
||||
CERT_NAME_SIMPLE_DISPLAY_TYPE,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if char_count <= 1 {
|
||||
return None;
|
||||
}
|
||||
let mut name = vec![0u16; char_count as usize];
|
||||
let char_count = unsafe {
|
||||
CertGetNameStringW(
|
||||
self.0,
|
||||
CERT_NAME_SIMPLE_DISPLAY_TYPE,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
name.as_mut_ptr(),
|
||||
char_count,
|
||||
)
|
||||
};
|
||||
assert!(char_count > 1);
|
||||
|
||||
// Subtract one for the null termination byte.
|
||||
Some(OsString::from_wide(&name[0..(char_count as usize - 1)]))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CertContext {
|
||||
type Target = PCCERT_CONTEXT;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for CertContext {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CertContext {
|
||||
fn drop(&mut self) {
|
||||
if !self.0.is_null() {
|
||||
unsafe { CertFreeCertificateContext(self.0) };
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use std::ffi::CString;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
|
||||
/// Windows wide strings.
|
||||
///
|
||||
/// These are utf16 encoded with a terminating nul character (0).
|
||||
#[derive(Debug)]
|
||||
pub struct WideString(Vec<u16>);
|
||||
|
||||
/// An error indicating that an interior nul byte was found.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct NulError(usize, Vec<u16>);
|
||||
|
||||
impl std::fmt::Display for NulError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "nul byte found in provided data at position: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NulError {}
|
||||
|
||||
impl WideString {
|
||||
pub fn new(os_str: impl AsRef<OsStr>) -> Result<Self, NulError> {
|
||||
let mut v: Vec<u16> = os_str.as_ref().encode_wide().collect();
|
||||
if let Some(p) = v.iter().position(|c| *c == 0) {
|
||||
Err(NulError(p, v))
|
||||
} else {
|
||||
v.push(0);
|
||||
Ok(WideString(v))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pcwstr(&self) -> windows_sys::core::PCWSTR {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over wide characters in a wide string.
|
||||
///
|
||||
/// This is useful for wide string constants.
|
||||
#[derive(Debug)]
|
||||
pub struct FfiWideCharIterator(*const u16);
|
||||
|
||||
impl FfiWideCharIterator {
|
||||
pub fn new(ptr: *const u16) -> Self {
|
||||
FfiWideCharIterator(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for FfiWideCharIterator {
|
||||
type Item = u16;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let c = unsafe { self.0.read() };
|
||||
if c == 0 {
|
||||
None
|
||||
} else {
|
||||
self.0 = unsafe { self.0.add(1) };
|
||||
Some(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a utf16 ptr to an ascii CString.
|
||||
pub fn utf16_ptr_to_ascii(ptr: *const u16) -> Option<CString> {
|
||||
char::decode_utf16(FfiWideCharIterator::new(ptr))
|
||||
// Using try_into() accepts extended ascii as well; we don't care much about the
|
||||
// distinction here, it'll still be a valid conversion.
|
||||
.map(|res| res.ok().and_then(|c| c.try_into().ok()))
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.map(CString::new)
|
||||
.and_then(|res| {
|
||||
if res.is_err() {
|
||||
log::error!("FfiWideCharIterator provided nul character");
|
||||
}
|
||||
res.ok()
|
||||
})
|
||||
}
|
173
toolkit/crashreporter/minidump-analyzer/src/windows/wintrust.rs
Normal file
173
toolkit/crashreporter/minidump-analyzer/src/windows/wintrust.rs
Normal file
@ -0,0 +1,173 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use super::strings::utf16_ptr_to_ascii;
|
||||
use super::HResult;
|
||||
use std::fs::File;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{GetLastError, BOOL, ERROR_INSUFFICIENT_BUFFER, HANDLE, MAX_PATH},
|
||||
Security::Cryptography::{
|
||||
szOID_CERT_STRONG_SIGN_OS_1, szOID_CERT_STRONG_SIGN_OS_CURRENT,
|
||||
Catalog::{
|
||||
CryptCATAdminAcquireContext2, CryptCATAdminCalcHashFromFileHandle2,
|
||||
CryptCATAdminEnumCatalogFromHash, CryptCATAdminReleaseCatalogContext,
|
||||
CryptCATAdminReleaseContext, CryptCATCatalogInfoFromContext, CATALOG_INFO,
|
||||
},
|
||||
BCRYPT_SHA256_ALGORITHM, CERT_STRONG_SIGN_OID_INFO_CHOICE, CERT_STRONG_SIGN_PARA,
|
||||
CERT_STRONG_SIGN_PARA_0,
|
||||
},
|
||||
};
|
||||
|
||||
pub type HCATADMIN = HANDLE;
|
||||
pub type HCATINFO = HANDLE;
|
||||
|
||||
/// A catalog admin handle.
|
||||
pub struct CATAdmin(HCATADMIN);
|
||||
|
||||
impl Default for CATAdmin {
|
||||
fn default() -> Self {
|
||||
CATAdmin(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl CATAdmin {
|
||||
/// Acquire a handle.
|
||||
pub fn acquire() -> Option<Self> {
|
||||
let mut ret = Self::default();
|
||||
// Annoyingly, szOID_CERT_STRONG_SIGN_OS_CURRENT is a wide string, but all other such
|
||||
// constants are C strings.
|
||||
let oid_string = utf16_ptr_to_ascii(szOID_CERT_STRONG_SIGN_OS_CURRENT);
|
||||
let policy = CERT_STRONG_SIGN_PARA {
|
||||
cbSize: std::mem::size_of::<CERT_STRONG_SIGN_PARA>() as u32,
|
||||
dwInfoChoice: CERT_STRONG_SIGN_OID_INFO_CHOICE,
|
||||
Anonymous: CERT_STRONG_SIGN_PARA_0 {
|
||||
pszOID: oid_string
|
||||
.as_ref()
|
||||
.map(|c| c.as_ptr() as *mut u8)
|
||||
.unwrap_or(szOID_CERT_STRONG_SIGN_OS_1 as *mut u8),
|
||||
},
|
||||
};
|
||||
unsafe {
|
||||
CryptCATAdminAcquireContext2(
|
||||
&mut *ret,
|
||||
std::ptr::null(),
|
||||
BCRYPT_SHA256_ALGORITHM,
|
||||
&policy as *const _,
|
||||
0,
|
||||
)
|
||||
}
|
||||
.into_option()?;
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
/// Calculate the hash of the given file.
|
||||
pub fn calculate_file_hash(&self, file: &File) -> Option<Vec<u8>> {
|
||||
let calc_hash = |size: *mut u32, dest: *mut u8| -> BOOL {
|
||||
unsafe {
|
||||
CryptCATAdminCalcHashFromFileHandle2(
|
||||
self.0,
|
||||
file.as_raw_handle() as _,
|
||||
size,
|
||||
dest,
|
||||
0,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// First call to retrieve the hash size.
|
||||
let mut size: u32 = 0;
|
||||
calc_hash(&mut size, std::ptr::null_mut())
|
||||
.into_option()
|
||||
// If ERROR_INSUFFICIENT_BUFFER is the last error, `size` has been set.
|
||||
.or_else(|| (unsafe { GetLastError() } == ERROR_INSUFFICIENT_BUFFER).then_some(()))?;
|
||||
|
||||
// Second call to get the hash.
|
||||
let mut hash = vec![0; size as usize];
|
||||
calc_hash(&mut size as *mut _, hash.as_mut_ptr()).into_option()?;
|
||||
|
||||
Some(hash)
|
||||
}
|
||||
|
||||
/// Find the first catalog that contains the given hash.
|
||||
pub fn catalog_from_hash(&self, hash: &mut [u8]) -> Option<CATInfo> {
|
||||
let ptr = unsafe {
|
||||
CryptCATAdminEnumCatalogFromHash(
|
||||
self.0,
|
||||
hash.as_mut_ptr(),
|
||||
hash.len().try_into().unwrap(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if ptr == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(CATInfo {
|
||||
cat_admin_context: self,
|
||||
handle: ptr,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CATAdmin {
|
||||
type Target = HCATADMIN;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for CATAdmin {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CATAdmin {
|
||||
fn drop(&mut self) {
|
||||
if self.0 != 0 {
|
||||
unsafe { CryptCATAdminReleaseContext(self.0, 0) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CATInfo<'a> {
|
||||
cat_admin_context: &'a CATAdmin,
|
||||
handle: HCATINFO,
|
||||
}
|
||||
|
||||
impl CATInfo<'_> {
|
||||
pub fn get_info(&self) -> Option<CATALOG_INFO> {
|
||||
let mut ret = CATALOG_INFO {
|
||||
cbStruct: std::mem::size_of::<CATALOG_INFO>().try_into().unwrap(),
|
||||
wszCatalogFile: [0u16; MAX_PATH as usize],
|
||||
};
|
||||
unsafe { CryptCATCatalogInfoFromContext(self.handle, &mut ret as *mut _, 0) }
|
||||
.into_option()?;
|
||||
Some(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CATInfo<'_> {
|
||||
type Target = HCATINFO;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.handle
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for CATInfo<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.handle
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CATInfo<'_> {
|
||||
fn drop(&mut self) {
|
||||
// Unwrap because this function must exist if we got a handle.
|
||||
unsafe { CryptCATAdminReleaseCatalogContext(**self.cat_admin_context, self.handle, 0) };
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user