Bug 1565033: Add memory testing to crash reporter client. r=afranchuk,fluent-reviewers,supply-chain-reviewers,cmartin,flod,gsvelto

Differential Revision: https://phabricator.services.mozilla.com/D231813
This commit is contained in:
Brian Tsoi
2025-02-13 06:32:47 +00:00
parent f3ce961842
commit 744e1815cf
25 changed files with 2913 additions and 28 deletions

18
Cargo.lock generated
View File

@@ -1107,6 +1107,7 @@ dependencies = [
"libloading",
"lmdb-rkv-sys",
"log",
"memtest",
"minidump-analyzer",
"mozbuild",
"mozilla-central-workspace-hack",
@@ -1114,6 +1115,7 @@ dependencies = [
"once_cell",
"phf",
"phf_codegen",
"rand",
"serde",
"serde_json",
"sha2",
@@ -3776,6 +3778,22 @@ dependencies = [
"autocfg",
]
[[package]]
name = "memtest"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f099da8d7ab73f5fbbaf7374ce3ec2fb220e7c77fe56f193269b5d3294a9c73c"
dependencies = [
"anyhow",
"libc",
"num_cpus",
"rand",
"serde",
"serde_json",
"tracing",
"windows",
]
[[package]]
name = "metal"
version = "0.31.0"

View File

@@ -1326,6 +1326,18 @@
value: 32
mirror: always
# Whether memorytesting is enabled when crash reporter client is launched
- name: browser.crashReporter.memtest
type: RelaxedAtomicBool
value: false
mirror: always
# Space-separated list of prioritized memory test kinds
- name: browser.crashReporter.memtestKinds
type: DataMutexString
value: ""
mirror: always
# Min font device pixel size at which to turn on high quality.
- name: browser.display.auto_quality_min_font_size
type: RelaxedAtomicUint32

View File

@@ -3141,6 +3141,12 @@ who = "Gabriele Svelto <gsvelto@mozilla.com>"
criteria = "safe-to-deploy"
delta = "0.8.0 -> 0.9.0"
[[audits.memtest]]
who = "Brian Tsoi <brian.s.tsoi@gmail.com>"
criteria = "safe-to-deploy"
version = "0.1.3"
notes = "This crate is written and maintained by Mozilla employees."
[[audits.metal]]
who = "Jim Blandy <jimb@red-bean.com>"
criteria = "safe-to-deploy"

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"875ecd06833109491b2b2d22207d851ff9e945e57d876615bf0adf89ad630b97","Cargo.toml":"1e0dbfce20696aa03dd89c1b7ef7178531901c3e9be57afa1884c6b62afc0ef1","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"9317903e5896852844bd769652ac9818c9fec6d5b57424f230e30ff7f82e2e49","examples/usage.rs":"12d610e161b69de31543ad668c47ba5e3486ef45f7574c38dc4b547d60deff95","src/lib.rs":"18f3cdef993b06315073808a51d785688005c30ca73044b2fdea9649ae5d617a","src/memtest.rs":"2613311965bab589be36506d57efa112cd7209dd28c3f60dd08451a03a0fd3a2","src/prelude.rs":"73581af5d71192875b4c9b525c5f2e150b505e3f6df8ec1c837632a5f08b50fd"},"package":"f099da8d7ab73f5fbbaf7374ce3ec2fb220e7c77fe56f193269b5d3294a9c73c"}

498
third_party/rust/memtest/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,498 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "log"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memtest"
version = "0.1.3"
dependencies = [
"anyhow",
"libc",
"num_cpus",
"rand",
"serde",
"serde_json",
"tracing",
"tracing-subscriber",
"windows",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "ryu"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
"windows-core",
"windows-targets",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-strings",
"windows-targets",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result",
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

73
third_party/rust/memtest/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,73 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "memtest"
version = "0.1.3"
authors = ["Brian Tsoi <brian.s.tsoi@gmail.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A library for detecting faulty memory"
homepage = "https://github.com/mozilla/memtest"
readme = "README.md"
keywords = [
"memory",
"stability",
]
license = "MPL-2.0"
repository = "https://github.com/mozilla/memtest"
[lib]
name = "memtest"
path = "src/lib.rs"
[[example]]
name = "usage"
path = "examples/usage.rs"
[dependencies.anyhow]
version = "1.0.69"
[dependencies.num_cpus]
version = "1.16.0"
[dependencies.rand]
version = "0.8.5"
[dependencies.serde]
version = "1.0.214"
features = ["derive"]
[dependencies.serde_json]
version = "1.0.116"
[dependencies.tracing]
version = "0.1.37"
[dev-dependencies.tracing-subscriber]
version = "0.3.18"
[target."cfg(unix)".dependencies.libc]
version = "0.2.158"
[target."cfg(windows)".dependencies.windows]
version = "0.58.0"
features = [
"Win32_Foundation",
"Win32_System_Memory",
"Win32_System_SystemInformation",
"Win32_System_Threading",
]

373
third_party/rust/memtest/LICENSE vendored Normal file
View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

7
third_party/rust/memtest/README.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# memtest
[![Crates.io Version](https://img.shields.io/crates/v/memtest?logo=rust)](https://crates.io/crates/memtest)
[![docs.rs](https://img.shields.io/docsrs/memtest?logo=docs.rs)](https://docs.rs/memtest)
[![License](https://img.shields.io/github/license/BrianShTsoi/memtest)](LICENSE)
A library for detecting faulty memory.

View File

@@ -0,0 +1,98 @@
use {
anyhow::Context,
memtest::{MemtestKind, MemtestRunner, MemtestRunnerArgs},
rand::{seq::SliceRandom, thread_rng},
std::{
mem::size_of,
time::{Duration, Instant},
},
tracing::info,
tracing_subscriber::fmt::format::FmtSpan,
};
// TODO: Command line option for json output
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.with_max_level(tracing::Level::TRACE)
.with_writer(std::io::stderr)
.with_thread_ids(true)
.init();
let start_time = Instant::now();
let (mem_usize_count, memtest_runner_args, memtest_kinds) = match parse_args() {
Ok(parsed_args) => parsed_args,
Err(s) => {
eprintln!(concat!(
"Usage: memtest-runner ",
"<memsize in MiB> ",
"<timeout in ms> ",
"<mem_lock_mode> ",
"<allow_working_set_resize as bool> ",
"<allow_multithread as bool> ",
"<allow_early_temrmination as bool> ",
"<memtest_kinds as space separated string>"
));
anyhow::bail!("Invalid/missing argument '{s}'");
}
};
info!("Running memtest-runner with: {memtest_runner_args:#?}");
let mut memory = vec![0; mem_usize_count];
let report_list = MemtestRunner::from_test_kinds(&memtest_runner_args, memtest_kinds)
.run(&mut memory)
.context("Failed to run memtest-runner")?;
println!("Tester ran for {:?}", start_time.elapsed());
println!("Test results: \n{report_list}");
anyhow::ensure!(
report_list.all_pass(),
"Found failures or errors among memtest reports"
);
Ok(())
}
/// Parse command line arguments to return a usize for the requested memory vector length and
/// other MemtestRunner arguments
fn parse_args() -> Result<(usize, MemtestRunnerArgs, Vec<MemtestKind>), &'static str> {
const KIB: usize = 1024;
const MIB: usize = 1024 * KIB;
let mut iter = std::env::args().skip(1);
macro_rules! parse_next(($n: literal) => {
iter.next().and_then(|s| s.parse().ok()).ok_or($n)?
});
let memsize: usize = parse_next!("memsize");
let mem_usize_count = memsize * MIB / size_of::<usize>();
let memtest_runner_args = MemtestRunnerArgs {
timeout: Duration::from_millis(parse_next!("timeout_ms")),
mem_lock_mode: parse_next!("mem_lock_mode"),
allow_working_set_resize: parse_next!("allow_working_set_resize"),
allow_multithread: parse_next!("allow_multithread"),
allow_early_termination: parse_next!("allow_early_termination"),
};
let memtest_kinds_string: String = parse_next!("memtest_kinds");
let memtest_kinds = memtest_kinds_from_str(&memtest_kinds_string)?;
Ok((mem_usize_count, memtest_runner_args, memtest_kinds))
}
/// Returns a vector of MemtestKind that contains all kinds, but prioritizes the given memtests.
fn memtest_kinds_from_str(str: &str) -> Result<Vec<MemtestKind>, &'static str> {
let specified = str
.split_whitespace()
.map(|s| s.parse().map_err(|_| "memtest_kinds"))
.collect::<Result<Vec<MemtestKind>, &'static str>>()?;
let mut remaining: Vec<_> = MemtestKind::ALL
.iter()
.filter(|k| !specified.contains(k))
.cloned()
.collect();
remaining.shuffle(&mut thread_rng());
Ok([specified, remaining].concat())
}

696
third_party/rust/memtest/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,696 @@
#[cfg(unix)]
use unix::{memory_lock, memory_resize_and_lock};
#[cfg(windows)]
use windows::{memory_lock, memory_resize_and_lock, replace_set_size};
use {
prelude::*,
rand::{seq::SliceRandom, thread_rng},
serde::{Deserialize, Serialize},
std::{
error::Error,
fmt,
mem::size_of_val,
time::{Duration, Instant},
},
};
mod memtest;
mod prelude;
pub use memtest::{
MemtestError, MemtestFailure, MemtestKind, MemtestOutcome, ParseMemtestKindError,
};
#[derive(Debug)]
pub struct MemtestRunner {
test_kinds: Vec<MemtestKind>,
timeout: Duration,
mem_lock_mode: MemLockMode,
#[allow(dead_code)]
allow_working_set_resize: bool,
allow_multithread: bool,
allow_early_termination: bool,
}
// TODO: Replace MemtestRunnerArgs with a Builder struct implementing fluent interface
/// A set of arguments that define the behavior of MemtestRunner
#[derive(Serialize, Deserialize, Debug)]
pub struct MemtestRunnerArgs {
/// How long should MemtestRunner run the test suite before timing out
pub timeout: Duration,
/// Whether memory will be locked before testing and whether the requested memory size of
/// testing can be reduced to accomodate memory locking
/// If memory locking failed but is required, MemtestRunner returns with error
pub mem_lock_mode: MemLockMode,
/// Whether the process working set can be resized to accomodate memory locking
/// This argument is only meaningful for Windows
pub allow_working_set_resize: bool,
/// Whether mulithreading is enabled
pub allow_multithread: bool,
/// Whether MemtestRunner returns immediately if a test fails or continues until all tests are run
pub allow_early_termination: bool,
}
#[derive(Debug)]
pub enum MemtestRunnerError {
MemLockFailed(anyhow::Error),
Other(anyhow::Error),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MemtestReportList {
pub tested_mem_length: usize,
pub mlocked: bool,
pub reports: Vec<MemtestReport>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MemtestReport {
pub test_kind: MemtestKind,
pub outcome: Result<MemtestOutcome, MemtestError>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum MemLockMode {
Resizable,
FixedSize,
Disabled,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParseMemLockModeError;
/// The minimum memory length (in usize) for MemtestRunner to run tests on
/// On a 64-bit machine, this is the size of a page
pub const MIN_MEMORY_LENGTH: usize = 512;
#[derive(Debug)]
struct MemLockGuard {
base_ptr: *mut usize,
mem_size: usize,
}
/// A struct to ensure the test timeouts in a given duration
#[derive(Debug)]
struct TimeoutChecker {
deadline: Instant,
state: Option<TimeoutCheckerState>,
}
#[derive(Debug)]
struct TimeoutCheckerState {
test_start_time: Instant,
expected_iter: u64,
completed_iter: u64,
checkpoint: u64,
}
impl MemtestRunner {
/// Create a MemtestRunner containing all test kinds in random order
pub fn all_tests_random_order(args: &MemtestRunnerArgs) -> MemtestRunner {
let mut test_kinds = MemtestKind::ALL.to_vec();
test_kinds.shuffle(&mut thread_rng());
Self::from_test_kinds(args, test_kinds)
}
/// Create a MemtestRunner with specified test kinds
pub fn from_test_kinds(
args: &MemtestRunnerArgs,
test_kinds: Vec<MemtestKind>,
) -> MemtestRunner {
MemtestRunner {
test_kinds,
timeout: args.timeout,
mem_lock_mode: args.mem_lock_mode,
allow_working_set_resize: args.allow_working_set_resize,
allow_multithread: args.allow_multithread,
allow_early_termination: args.allow_early_termination,
}
}
/// Run the tests, possibly after locking the memory
pub fn run(&self, memory: &mut [usize]) -> Result<MemtestReportList, MemtestRunnerError> {
if memory.len() < MIN_MEMORY_LENGTH {
return Err(anyhow!("Insufficient memory length").into());
}
let deadline = Instant::now() + self.timeout;
if matches!(self.mem_lock_mode, MemLockMode::Disabled) {
return Ok(MemtestReportList {
tested_mem_length: memory.len(),
mlocked: false,
reports: self.run_tests(memory, deadline),
});
}
#[cfg(windows)]
// TODO: When it is MemLockMode::Resizable and working set resize failed, consider shrinking
// the memory region and try again
let _working_set_resize_guard = if self.allow_working_set_resize {
Some(
replace_set_size(size_of_val(memory))
.context("Failed to replace process working set size")?,
)
} else {
None
};
let (memory, _mem_lock_guard) = match self.mem_lock_mode {
MemLockMode::FixedSize => memory_lock(memory),
MemLockMode::Resizable => memory_resize_and_lock(memory),
_ => unreachable!(),
}
.map_err(MemtestRunnerError::MemLockFailed)?;
Ok(MemtestReportList {
tested_mem_length: memory.len(),
mlocked: true,
reports: self.run_tests(memory, deadline),
})
}
/// Run tests
fn run_tests(&self, memory: &mut [usize], deadline: Instant) -> Vec<MemtestReport> {
let mut reports = Vec::new();
let mut timed_out = false;
for test_kind in &self.test_kinds {
let test = match test_kind {
MemtestKind::OwnAddressBasic => memtest::test_own_address_basic,
MemtestKind::OwnAddressRepeat => memtest::test_own_address_repeat,
MemtestKind::RandomVal => memtest::test_random_val,
MemtestKind::Xor => memtest::test_xor,
MemtestKind::Sub => memtest::test_sub,
MemtestKind::Mul => memtest::test_mul,
MemtestKind::Div => memtest::test_div,
MemtestKind::Or => memtest::test_or,
MemtestKind::And => memtest::test_and,
MemtestKind::SeqInc => memtest::test_seq_inc,
MemtestKind::SolidBits => memtest::test_solid_bits,
MemtestKind::Checkerboard => memtest::test_checkerboard,
MemtestKind::BlockSeq => memtest::test_block_seq,
};
let test_result = if timed_out {
Err(MemtestError::Timeout)
} else if self.allow_multithread {
std::thread::scope(|scope| {
let num_threads = num_cpus::get();
let chunk_size = memory.len() / num_threads;
let mut handles = vec![];
for chunk in memory.chunks_exact_mut(chunk_size) {
let handle = scope.spawn(|| test(chunk, TimeoutChecker::new(deadline)));
handles.push(handle);
}
#[allow(clippy::manual_try_fold)]
handles
.into_iter()
.map(|handle| {
handle
.join()
.unwrap_or(Err(MemtestError::Other(anyhow!("Thread panicked"))))
})
.fold(Ok(MemtestOutcome::Pass), |acc, result| {
use {MemtestError::*, MemtestOutcome::*};
match (acc, result) {
(Err(Other(e)), _) | (_, Err(Other(e))) => Err(Other(e)),
(Err(Timeout), _) | (_, Err(Timeout)) => Err(Timeout),
(Ok(Fail(f)), _) | (_, Ok(Fail(f))) => Ok(Fail(f)),
_ => Ok(Pass),
}
})
})
} else {
test(memory, TimeoutChecker::new(deadline))
};
timed_out = matches!(test_result, Err(MemtestError::Timeout));
if matches!(test_result, Ok(MemtestOutcome::Fail(_))) && self.allow_early_termination {
reports.push(MemtestReport::new(*test_kind, test_result));
warn!("Memtest failed, terminating early");
break;
}
reports.push(MemtestReport::new(*test_kind, test_result));
}
reports
}
}
impl fmt::Display for MemtestRunnerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for MemtestRunnerError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
MemtestRunnerError::MemLockFailed(err) | MemtestRunnerError::Other(err) => {
Some(err.as_ref())
}
}
}
}
impl From<anyhow::Error> for MemtestRunnerError {
fn from(err: anyhow::Error) -> MemtestRunnerError {
MemtestRunnerError::Other(err)
}
}
impl std::str::FromStr for MemLockMode {
type Err = ParseMemLockModeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"resizable" => Ok(Self::Resizable),
"fixedsize" => Ok(Self::FixedSize),
"disabled" => Ok(Self::Disabled),
_ => Err(ParseMemLockModeError),
}
}
}
impl fmt::Display for ParseMemLockModeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for ParseMemLockModeError {}
impl fmt::Display for MemtestReportList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "tested_mem_len = {}", self.tested_mem_length)?;
writeln!(f, "mlocked = {}", self.mlocked)?;
for report in &self.reports {
let outcome = match &report.outcome {
Ok(outcome) => format!("{}", outcome),
Err(e) => format!("{}", e),
};
writeln!(
f,
"{:<30} {}",
format!("Ran Test: {:?}", report.test_kind),
outcome
)?;
}
Ok(())
}
}
impl MemtestReportList {
pub fn iter(&self) -> impl Iterator<Item = &MemtestReport> {
self.reports.iter()
}
/// Returns true if all tests were run successfully and all tests passed
pub fn all_pass(&self) -> bool {
self.iter()
.all(|report| matches!(report.outcome, Ok(MemtestOutcome::Pass)))
}
}
impl MemtestReport {
fn new(test_kind: MemtestKind, outcome: Result<MemtestOutcome, MemtestError>) -> MemtestReport {
MemtestReport { test_kind, outcome }
}
}
impl TimeoutChecker {
fn new(deadline: Instant) -> TimeoutChecker {
TimeoutChecker {
deadline,
state: None,
}
}
/// Initialize TimeoutCheckerState
/// This function should be called in the beginning of a memtest.
fn init(&mut self, expected_iter: u64) {
const FIRST_CHECKPOINT: u64 = 8;
assert!(
self.state.is_none(),
"init() should only be called once per test"
);
// The first checkpoint is set to 8 to have a more accurate sample of duration per
// iteration for determining new checkpoint
self.state = Some(TimeoutCheckerState {
test_start_time: Instant::now(),
expected_iter,
completed_iter: 0,
checkpoint: FIRST_CHECKPOINT,
});
}
/// Check if the current iteration is a checkpoint. If so, check if timeout occurred
///
/// This function should be called in every iteration of a memtest.
///
/// To reduce overhead, the function only checks for timeout at specific checkpoints, and
/// early returns otherwise.
///
// It is important to ensure that the "early return" hot path is inlined. This results in a
// 100% improvement in performance.
#[inline(always)]
fn check(&mut self) -> Result<(), MemtestError> {
let state = self
.state
.as_mut()
.expect("init() should be called before check()");
if state.completed_iter < state.checkpoint {
state.completed_iter += 1;
return Ok(());
}
state.on_checkpoint(self.deadline)
}
}
impl TimeoutCheckerState {
fn on_checkpoint(&mut self, deadline: Instant) -> Result<(), MemtestError> {
let current_time = Instant::now();
if current_time >= deadline {
return Err(MemtestError::Timeout);
}
self.trace_progress();
self.set_next_checkpoint(deadline, current_time);
self.completed_iter += 1;
Ok(())
}
// Note: Because `trace_progress()` is only called in `on_checkpoints()`, not every percent
// of the test progress is traced. If memtests are running way ahead of the given deadline, the
// progress may only be traced once or twice. Although this makes the logs less comprehensive,
// it avoids signficiant perforamnce overhead.
fn trace_progress(&mut self) {
if tracing::enabled!(tracing::Level::TRACE) {
trace!(
"Progress on checkpoint: {:.2}%",
self.completed_iter as f64 / self.expected_iter as f64 * 100.0
);
}
}
/// Calculate the remaining time before the deadline and schedule the next check at 75% of that
/// interval, then estimate the number of iterations to get there and set as next checkpoint
fn set_next_checkpoint(&mut self, deadline: Instant, current_time: Instant) {
const DEADLINE_CHECK_RATIO: f64 = 0.75;
let duration_until_next_checkpoint = {
let duration_until_deadline = deadline - current_time;
duration_until_deadline.mul_f64(DEADLINE_CHECK_RATIO)
};
let avg_iter_duration = {
let test_elapsed = current_time - self.test_start_time;
test_elapsed.div_f64(self.completed_iter as f64)
};
let iter_until_next_checkpoint = {
let x =
Self::div_duration_f64(duration_until_next_checkpoint, avg_iter_duration) as u64;
u64::max(x, 1)
};
self.checkpoint += iter_until_next_checkpoint;
}
// This is equivalent to `Duration::div_duration_f64`, but that is not stable on Rust 1.76
fn div_duration_f64(lhs: Duration, rhs: Duration) -> f64 {
const NANOS_PER_SEC: u32 = 1_000_000_000;
let lhs_nanos =
(lhs.as_secs() as f64) * (NANOS_PER_SEC as f64) + (lhs.subsec_nanos() as f64);
let rhs_nanos =
(rhs.as_secs() as f64) * (NANOS_PER_SEC as f64) + (rhs.subsec_nanos() as f64);
lhs_nanos / rhs_nanos
}
}
#[cfg(windows)]
mod windows {
use {
crate::{prelude::*, MemLockGuard},
std::mem::{size_of, size_of_val},
windows::Win32::{
Foundation::ERROR_WORKING_SET_QUOTA,
System::{
Memory::{VirtualLock, VirtualUnlock},
SystemInformation::{
GetNativeSystemInfo, GlobalMemoryStatusEx, MEMORYSTATUSEX, SYSTEM_INFO,
},
Threading::{
GetCurrentProcess, GetProcessWorkingSetSize, SetProcessWorkingSetSize,
},
},
},
};
#[derive(Debug)]
pub struct WorkingSetResizeGuard {
min_set_size: usize,
max_set_size: usize,
}
// TODO: Consider verifying that the process memory is properly sized by using
// `GetProcessMemoryInfo` during memtests to retrieve the number of page faults this process is
// causing. If it's suddenly a very high number, it indicates the set size might be too small
pub(super) fn replace_set_size(memsize: usize) -> anyhow::Result<WorkingSetResizeGuard> {
const ESTIMATED_TEST_MEM_USAGE: usize = 1024 * 1024; // 1MiB
let (min_set_size, max_set_size) = get_set_size()?;
let new_min_set_size = memsize + ESTIMATED_TEST_MEM_USAGE;
let new_max_set_size =
get_physical_memory_size().context("Failed to get physical memory size")?;
unsafe {
SetProcessWorkingSetSize(GetCurrentProcess(), new_min_set_size, new_max_set_size)
.context("Failed to set process working set size")?;
}
Ok(WorkingSetResizeGuard {
min_set_size,
max_set_size,
})
}
impl Drop for WorkingSetResizeGuard {
fn drop(&mut self) {
unsafe {
if let Err(e) = SetProcessWorkingSetSize(
GetCurrentProcess(),
self.min_set_size,
self.max_set_size,
) {
warn!("Failed to restore process working set: {e}");
}
}
}
}
pub(super) fn memory_lock(
memory: &mut [usize],
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
let base_ptr = memory.as_mut_ptr();
let mem_size = size_of_val(memory);
unsafe {
VirtualLock(base_ptr.cast(), mem_size).context("VirtualLock failed")?;
}
info!("Successfully locked {}MB", mem_size);
Ok((memory, MemLockGuard { base_ptr, mem_size }))
}
pub(super) fn memory_resize_and_lock(
mut memory: &mut [usize],
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
// Resizing to system limit first is more efficient than only decrementing by page
// size and retry locking.
let min_set_size_usize = get_set_size()?.0 / size_of::<usize>();
if memory.len() > min_set_size_usize {
memory = &mut memory[0..min_set_size_usize];
warn!(
"Resized memory to system limit ({} bytes)",
size_of_val(memory)
);
}
let usize_per_page = get_page_size()? / std::mem::size_of::<usize>();
loop {
let base_ptr = memory.as_mut_ptr();
let mem_size = size_of_val(memory);
let res = unsafe { VirtualLock(base_ptr.cast(), mem_size) };
let Err(e) = res else {
info!("Successfully locked {} bytes", mem_size);
return Ok((memory, MemLockGuard { base_ptr, mem_size }));
};
ensure!(
e == ERROR_WORKING_SET_QUOTA.into(),
anyhow!(e).context("VirtualLock failed")
);
// Locking with the system limit can still fail as the memory to be locked may not be
// page aligned. In that case retry locking after decrement memory size by a page.
let new_len = memory
.len()
.checked_sub(usize_per_page)
.context("Failed to lock any memory, memory size has been decremented to 0")?;
memory = &mut memory[0..new_len];
warn!(
"Decremented memory size to {} bytes, retry memory locking",
new_len * usize_per_page
);
}
}
impl Drop for MemLockGuard {
fn drop(&mut self) {
unsafe {
if let Err(e) = VirtualUnlock(self.base_ptr.cast(), self.mem_size) {
warn!("Failed to unlock memory: {e}")
}
}
}
}
fn get_set_size() -> anyhow::Result<(usize, usize)> {
let (mut min_set_size, mut max_set_size) = (0, 0);
unsafe {
GetProcessWorkingSetSize(GetCurrentProcess(), &mut min_set_size, &mut max_set_size)
.context("Failed to get process working set")?;
}
Ok((min_set_size, max_set_size))
}
fn get_page_size() -> anyhow::Result<usize> {
Ok((unsafe {
let mut sysinfo: SYSTEM_INFO = std::mem::zeroed();
GetNativeSystemInfo(&mut sysinfo);
sysinfo.dwPageSize
})
.try_into()
.unwrap())
}
fn get_physical_memory_size() -> anyhow::Result<usize> {
let mut memory_status = MEMORYSTATUSEX::default();
memory_status.dwLength = std::mem::size_of_val(&memory_status).try_into().unwrap();
unsafe {
GlobalMemoryStatusEx(&mut memory_status)
.context("Failed to get global memory status")?
};
Ok(memory_status.ullTotalPhys.try_into().unwrap())
}
}
#[cfg(unix)]
mod unix {
use {
crate::{prelude::*, MemLockGuard},
libc::{getrlimit, mlock, munlock, rlimit, sysconf, RLIMIT_MEMLOCK, _SC_PAGESIZE},
std::{
borrow::BorrowMut,
io::{Error, ErrorKind},
mem::{size_of, size_of_val},
},
};
pub(super) fn memory_lock(
memory: &mut [usize],
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
let base_ptr = memory.as_mut_ptr();
let mem_size = size_of_val(memory);
if unsafe { mlock(base_ptr.cast(), mem_size) } == 0 {
info!("Successfully locked {} bytes", mem_size);
Ok((
memory,
MemLockGuard {
base_ptr: base_ptr.cast(),
mem_size,
},
))
} else {
Err(anyhow!(Error::last_os_error()).context("mlock failed"))
}
}
pub(super) fn memory_resize_and_lock(
mut memory: &mut [usize],
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
// Note: Resizing to system limit first is more efficient than only decrementing by page
// size and retry locking, but this may not work as intended when running as a priviledged
// process, since priviledged processes do not need to respect the limit.
let max_mem_lock_usize = get_max_mem_lock()? / size_of::<usize>();
if memory.len() > max_mem_lock_usize {
memory = &mut memory[0..max_mem_lock_usize];
warn!(
"Resized memory to system limit ({} bytes)",
size_of_val(memory)
);
}
let usize_per_page = get_page_size()? / std::mem::size_of::<usize>();
loop {
let base_ptr = memory.as_mut_ptr();
let mem_size = size_of_val(memory);
if unsafe { mlock(base_ptr.cast(), mem_size) } == 0 {
info!("Successfully locked {} bytes", mem_size);
return Ok((memory, MemLockGuard { base_ptr, mem_size }));
}
let e = Error::last_os_error();
ensure!(
e.kind() == ErrorKind::OutOfMemory,
anyhow!(e).context("mlock failed")
);
// Locking with the system limit can still fail as the memory to be locked may not be
// page aligned. In that case retry locking after decrement memory size by a page.
let new_len = memory
.len()
.checked_sub(usize_per_page)
.context("Failed to lock any memory, memory size has been decremented to 0")?;
memory = &mut memory[0..new_len];
warn!(
"Decremented memory size to {} bytes, retry memory locking",
size_of_val(memory)
);
}
}
impl Drop for MemLockGuard {
fn drop(&mut self) {
unsafe {
if munlock(self.base_ptr.cast(), self.mem_size) != 0 {
warn!("Failed to unlock memory: {}", Error::last_os_error())
}
}
}
}
fn get_max_mem_lock() -> anyhow::Result<usize> {
unsafe {
let mut rlim: rlimit = std::mem::zeroed();
ensure!(
getrlimit(RLIMIT_MEMLOCK, rlim.borrow_mut()) == 0,
anyhow!(Error::last_os_error()).context("Failed to get RLIMIT_MEMLOCK")
);
Ok(rlim.rlim_cur.try_into().unwrap())
}
}
fn get_page_size() -> anyhow::Result<usize> {
(unsafe { sysconf(_SC_PAGESIZE) })
.try_into()
.context("Failed to get page size")
}
}

547
third_party/rust/memtest/src/memtest.rs vendored Normal file
View File

@@ -0,0 +1,547 @@
use {
crate::{prelude::*, TimeoutChecker},
rand::random,
serde::{Deserialize, Deserializer, Serialize, Serializer},
std::{error::Error, fmt},
};
// TODO: Intend to convert this module to a standalone `no_std` crate
// TODO: TimeoutChecker will be a trait instead
#[derive(Debug, Serialize, Deserialize)]
#[must_use]
pub enum MemtestOutcome {
Pass,
Fail(MemtestFailure),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum MemtestFailure {
/// Failure due to the actual value read being different from the expected value
UnexpectedValue {
address: usize,
expected: usize,
actual: usize,
},
/// Failure due to the two memory locations being compared returning two different values
/// This is used by tests where memory is split in two and values are written in pairs
MismatchedValues {
address1: usize,
value1: usize,
address2: usize,
value2: usize,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub enum MemtestError {
Timeout,
#[serde(
serialize_with = "serialize_memtest_error_other",
deserialize_with = "deserialize_memtest_error_other"
)]
Other(anyhow::Error),
}
macro_rules! memtest_kinds {{
$($variant: ident),* $(,)?
} => {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MemtestKind {
$($variant,)*
}
impl MemtestKind {
pub const ALL: &'static [Self] = &[
$(Self::$variant),*
];
}
impl std::str::FromStr for MemtestKind {
type Err = ParseMemtestKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$(
stringify!($variant) => Ok(Self::$variant),
)*
_ => Err(ParseMemtestKindError),
}
}
}
}}
memtest_kinds! {
OwnAddressBasic,
OwnAddressRepeat,
RandomVal,
Xor,
Sub,
Mul,
Div,
Or,
And,
SeqInc,
SolidBits,
Checkerboard,
BlockSeq,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ParseMemtestKindError;
/// Write the address of each memory location to itself, then read back the value and check that it
/// matches the expected address.
#[tracing::instrument(skip_all)]
pub fn test_own_address_basic(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
let expected_iter = u64::try_from(memory.len())
.ok()
.and_then(|count| count.checked_mul(2))
.context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
for mem_ref in memory.iter_mut() {
timeout_checker.check()?;
write_volatile_safe(mem_ref, address_from_ref(mem_ref));
}
for mem_ref in memory.iter() {
timeout_checker.check()?;
let address = address_from_ref(mem_ref);
let actual = read_volatile_safe(mem_ref);
if actual != address {
info!("Test failed at 0x{address:x}");
return Ok(MemtestOutcome::Fail(MemtestFailure::UnexpectedValue {
address,
expected: address,
actual,
}));
}
}
Ok(MemtestOutcome::Pass)
}
/// Write the address of each memory location (or its complement) to itself, then read back the
/// value and check that it matches the expected address.
/// This procedure is repeated 16 times.
#[tracing::instrument(skip_all)]
pub fn test_own_address_repeat(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
const NUM_RUNS: u64 = 16;
let expected_iter = u64::try_from(memory.len())
.ok()
.and_then(|count| count.checked_mul(2 * NUM_RUNS))
.context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
let val_to_write = |address: usize, i, j| {
if (i + j) % 2 == 0 {
address
} else {
!(address)
}
};
for i in 0..usize::try_from(NUM_RUNS).unwrap() {
for (j, mem_ref) in memory.iter_mut().enumerate() {
timeout_checker.check()?;
let val = val_to_write(address_from_ref(mem_ref), i, j);
write_volatile_safe(mem_ref, val);
}
for (j, mem_ref) in memory.iter().enumerate() {
timeout_checker.check()?;
let address = address_from_ref(mem_ref);
let expected = val_to_write(address, i, j);
let actual = read_volatile_safe(mem_ref);
if actual != expected {
info!("Test failed at 0x{address:x}");
return Ok(MemtestOutcome::Fail(MemtestFailure::UnexpectedValue {
address,
expected,
actual,
}));
}
}
}
Ok(MemtestOutcome::Pass)
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each
/// pair, write a random value. After all locations are written, read and compare the two halves.
#[tracing::instrument(skip_all)]
pub fn test_random_val(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
let (first_half, second_half) = split_slice_in_half(memory)?;
let expected_iter =
u64::try_from(first_half.len() * 2).context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
timeout_checker.check()?;
let val = random();
write_volatile_safe(first_ref, val);
write_volatile_safe(second_ref, val);
}
compare_regions(first_half, second_half, &mut timeout_checker)
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
/// memory locations in pairs. For each pair, write the XOR result of a random value and the value
/// read from the location. After all locations are written, read and compare the two halves.
#[tracing::instrument(skip_all)]
pub fn test_xor(
memory: &mut [usize],
timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
test_two_regions(memory, timeout_checker, std::ops::BitXor::bitxor)
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
/// memory locations in pairs. For each pair, write the result of subtracting a random value from
/// the value read from the location. After all locations are written, read and compare the two
/// halves.
#[tracing::instrument(skip_all)]
pub fn test_sub(
memory: &mut [usize],
timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
test_two_regions(memory, timeout_checker, usize::wrapping_sub)
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
/// memory locations in pairs. For each pair, write the result of multiplying a random value with
/// the value read from the location. After all locations are written, read and compare the two
/// halves.
#[tracing::instrument(skip_all)]
pub fn test_mul(
memory: &mut [usize],
timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
test_two_regions(memory, timeout_checker, usize::wrapping_mul)
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
/// memory locations in pairs. For each pair, write the result of dividing the value read from the
/// location with a random value. After all locations are written, read and compare the two halves.
#[tracing::instrument(skip_all)]
pub fn test_div(
memory: &mut [usize],
timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
test_two_regions(memory, timeout_checker, |n, d| {
n.wrapping_div(usize::max(d, 1))
})
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
/// memory locations in pairs. For each pair, write the OR result of a random value and the value
/// read from the location. After all locations are written, read and compare the two halves.
#[tracing::instrument(skip_all)]
pub fn test_or(
memory: &mut [usize],
timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
test_two_regions(memory, timeout_checker, std::ops::BitOr::bitor)
}
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
/// memory locations in pairs. For each pair, write the AND result of a random value and the value
/// read from the location. After all locations are written, read and compare the two halves.
#[tracing::instrument(skip_all)]
pub fn test_and(
memory: &mut [usize],
timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
test_two_regions(memory, timeout_checker, std::ops::BitAnd::bitand)
}
/// Base function for `test_xor`, `test_sub`, `test_mul`, `test_div`, `test_or` and `test_and`
///
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
/// memory locations in pairs. Write to each pair using the given `write_val` function. After all
/// locations are written, read and compare the two halves.
fn test_two_regions(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
transform_fn: fn(usize, usize) -> usize,
) -> Result<MemtestOutcome, MemtestError> {
mem_reset(memory);
let (first_half, second_half) = split_slice_in_half(memory)?;
let expected_iter =
u64::try_from(first_half.len() * 2).context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
timeout_checker.check()?;
let mixing_val = random();
let val = read_volatile_safe(first_ref);
let new_val = transform_fn(val, mixing_val);
write_volatile_safe(first_ref, new_val);
let val = read_volatile_safe(second_ref);
let new_val = transform_fn(val, mixing_val);
write_volatile_safe(second_ref, new_val);
}
compare_regions(first_half, second_half, &mut timeout_checker)
}
/// Split given memory into two halves and iterate through memory locations in pairs. Generate a
/// random value at the start. For each pair, write the result of adding the random value and the
/// index of iteration. After all locations are written, read and compare the two halves.
#[tracing::instrument(skip_all)]
pub fn test_seq_inc(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
let (first_half, second_half) = split_slice_in_half(memory)?;
let expected_iter =
u64::try_from(first_half.len() * 2).context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
let mut val: usize = random();
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
timeout_checker.check()?;
val = val.wrapping_add(1);
write_volatile_safe(first_ref, val);
write_volatile_safe(second_ref, val);
}
compare_regions(first_half, second_half, &mut timeout_checker)
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each
/// pair, write to all bits as either 1s or 0s, alternating after each memory location pair.
/// After all locations are written, read and compare the two halves.
/// This procedure is repeated 64 times.
#[tracing::instrument(skip_all)]
pub fn test_solid_bits(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
const NUM_RUNS: u64 = 64;
let (first_half, second_half) = split_slice_in_half(memory)?;
let expected_iter = u64::try_from(first_half.len() * 2)
.ok()
.and_then(|count| count.checked_mul(NUM_RUNS))
.context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
let mut solid_bits = !0;
for _ in 0..NUM_RUNS {
solid_bits = !solid_bits;
let mut val = solid_bits;
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
timeout_checker.check()?;
val = !val;
write_volatile_safe(first_ref, val);
write_volatile_safe(second_ref, val);
}
if let MemtestOutcome::Fail(failure) =
compare_regions(first_half, second_half, &mut timeout_checker)?
{
return Ok(MemtestOutcome::Fail(failure));
}
}
Ok(MemtestOutcome::Pass)
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each pair,
/// write to a pattern of alternating 1s and 0s (in bytes it is either 0x55 or 0xaa, and alternating
/// after each memory location pair). After all locations are written, read and compare the two
/// halves.
/// This procedure is repeated 64 times.
#[tracing::instrument(skip_all)]
pub fn test_checkerboard(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
const NUM_RUNS: u64 = 64;
let (first_half, second_half) = split_slice_in_half(memory)?;
let expected_iter = u64::try_from(first_half.len() * 2)
.ok()
.and_then(|count| count.checked_mul(NUM_RUNS))
.context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
let mut checker_board = usize_filled_from_byte(0xaa);
for _ in 0..NUM_RUNS {
checker_board = !checker_board;
let mut val = checker_board;
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
timeout_checker.check()?;
val = !val;
write_volatile_safe(first_ref, val);
write_volatile_safe(second_ref, val);
}
if let MemtestOutcome::Fail(failure) =
compare_regions(first_half, second_half, &mut timeout_checker)?
{
return Ok(MemtestOutcome::Fail(failure));
}
}
Ok(MemtestOutcome::Pass)
}
/// Split given memory into two halves and iterate through memory locations in pairs. For each pair,
/// write to all bytes with the value i. After all locations are written, read and compare the two
/// halves.
/// This procedure is repeated 256 times, with i corresponding to the iteration number 0-255.
#[tracing::instrument(skip_all)]
pub fn test_block_seq(
memory: &mut [usize],
mut timeout_checker: TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
const NUM_RUNS: u64 = 256;
let (first_half, second_half) = split_slice_in_half(memory)?;
let expected_iter = u64::try_from(first_half.len() * 2)
.ok()
.and_then(|count| count.checked_mul(NUM_RUNS))
.context("Total number of iterations overflowed")?;
timeout_checker.init(expected_iter);
for i in 0..=(u8::try_from(NUM_RUNS - 1).unwrap()) {
let val = usize_filled_from_byte(i);
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
timeout_checker.check()?;
write_volatile_safe(first_ref, val);
write_volatile_safe(second_ref, val);
}
if let MemtestOutcome::Fail(failure) =
compare_regions(first_half, second_half, &mut timeout_checker)?
{
return Ok(MemtestOutcome::Fail(failure));
}
}
Ok(MemtestOutcome::Pass)
}
fn read_volatile_safe<T: Copy>(src: &T) -> T {
unsafe { std::ptr::read_volatile(src) }
}
fn write_volatile_safe<T: Copy>(dst: &mut T, src: T) {
unsafe { std::ptr::write_volatile(dst, src) }
}
fn split_slice_in_half(slice: &mut [usize]) -> anyhow::Result<(&mut [usize], &mut [usize])> {
let mut it = slice.chunks_exact_mut(slice.len() / 2);
let (Some(first), Some(second)) = (it.next(), it.next()) else {
bail!("Insufficient memory length for two-regions memtest");
};
Ok((first, second))
}
fn mem_reset(memory: &mut [usize]) {
for mem_ref in memory.iter_mut() {
write_volatile_safe(mem_ref, !0);
}
}
fn address_from_ref(r: &usize) -> usize {
std::ptr::from_ref(r) as usize
}
/// Return a usize where all bytes are set to to value of `byte`
fn usize_filled_from_byte(byte: u8) -> usize {
let mut val = 0;
unsafe { std::ptr::write_bytes(&mut val, byte, 1) }
val
}
fn compare_regions(
region1: &mut [usize],
region2: &mut [usize],
timeout_checker: &mut TimeoutChecker,
) -> Result<MemtestOutcome, MemtestError> {
for (ref1, ref2) in region1.iter().zip(region2.iter()) {
timeout_checker.check()?;
let address1 = address_from_ref(ref1);
let address2 = address_from_ref(ref2);
let val1 = read_volatile_safe(ref1);
let val2 = read_volatile_safe(ref2);
if val1 != val2 {
info!("Test failed at 0x{address1:x} compared to 0x{address2:x}");
return Ok(MemtestOutcome::Fail(MemtestFailure::MismatchedValues {
address1,
value1: val1,
address2,
value2: val2,
}));
}
}
Ok(MemtestOutcome::Pass)
}
impl fmt::Display for ParseMemtestKindError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for ParseMemtestKindError {}
impl fmt::Display for MemtestOutcome {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Outcome: {:?}", self)
}
}
impl fmt::Display for MemtestError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error: {:?}", self)
}
}
impl Error for MemtestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
MemtestError::Timeout => None,
MemtestError::Other(err) => Some(err.as_ref()),
}
}
}
impl From<anyhow::Error> for MemtestError {
fn from(err: anyhow::Error) -> MemtestError {
MemtestError::Other(err)
}
}
fn serialize_memtest_error_other<S>(error: &anyhow::Error, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{:?}", error))
}
fn deserialize_memtest_error_other<'de, D>(deserializer: D) -> Result<anyhow::Error, D::Error>
where
D: Deserializer<'de>,
{
let str = String::deserialize(deserializer)?;
Ok(anyhow!(str))
}

View File

@@ -0,0 +1,5 @@
#![allow(unused)]
pub use {
anyhow::{anyhow, bail, ensure, Context},
tracing::{debug, error, info, trace, warn},
};

View File

@@ -637,6 +637,12 @@ Marionette:
Set to 1 when Marionette (WebDriver Classic) is enabled.
type: boolean
MemtestOutput:
description: >
The output of memory tests done by the crash reporter client after a crash
in a JSON format
type: string
LinuxUnderMemoryPressure:
description: >
Set to true if the memory pressure watcher was under memory pressure when

View File

@@ -16,11 +16,13 @@ glean = { workspace = true }
intl-memoizer = "0.5"
libloading = "0.8"
log = "0.4.17"
memtest = "0.1.3"
minidump-analyzer = { path = "../../minidump-analyzer" }
mozbuild = "0.1"
mozilla-central-workspace-hack = { version = "0.1", features = ["crashreporter"], optional = true }
once_cell = "1"
phf = "0.11"
rand = "0.8.5"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "^0.10.7"

View File

@@ -32,6 +32,8 @@ pub struct Config {
pub dump_all_threads: bool,
/// Whether to delete the dump files after submission.
pub delete_dump: bool,
/// Whether to run memtest while ui is shown
pub run_memtest: bool,
/// The data directory.
pub data_dir: Option<PathBuf>,
/// The events directory.
@@ -84,6 +86,7 @@ impl Config {
self.auto_submit = env_bool(ekey!("AUTO_SUBMIT"));
self.dump_all_threads = env_bool(ekey!("DUMP_ALL_THREADS"));
self.delete_dump = !env_bool(ekey!("NO_DELETE_DUMP"));
self.run_memtest = env_bool(ekey!("RUN_MEMTEST"));
self.data_dir = env_path(ekey!("DATA_DIRECTORY"));
self.events_dir = env_path(ekey!("EVENTS_DIRECTORY"));
self.ping_dir = env_path(ekey!("PING_DIRECTORY"));

View File

@@ -7,7 +7,7 @@
use crate::std::{
cell::RefCell,
path::PathBuf,
process::Command,
process::{Child, Command},
sync::{
atomic::{AtomicBool, Ordering::Relaxed},
Arc, Mutex,
@@ -32,6 +32,15 @@ pub struct ReportCrash {
settings_file: PathBuf,
attempted_to_send: AtomicBool,
ui: Option<AsyncTask<ReportCrashUIState>>,
memtest: RefCell<Memtest>,
}
/// The memtest child process
struct Memtest {
feature_enabled: bool,
user_setting_enabled: bool,
child: Option<Child>,
extra_file: Option<PathBuf>,
}
impl ReportCrash {
@@ -47,6 +56,13 @@ impl ReportCrash {
Err(_) => Default::default(),
Ok(f) => Settings::from_reader(f)?,
};
let memtest = Memtest {
feature_enabled: config.run_memtest,
user_setting_enabled: settings.test_hardware,
child: None,
extra_file: config.extra_file(),
};
Ok(ReportCrash {
config,
extra,
@@ -54,6 +70,7 @@ impl ReportCrash {
settings: settings.into(),
attempted_to_send: Default::default(),
ui: None,
memtest: memtest.into(),
})
}
@@ -70,6 +87,9 @@ impl ReportCrash {
}
self.sanitize_extra();
self.check_eol_version()?;
self.memtest.borrow_mut().init();
if !self.config.auto_submit {
self.run_ui();
} else {
@@ -426,12 +446,22 @@ impl ReportCrash {
// since the above loop will only exit when `logic_send` is dropped at the end of
// the scope.
self.ui = None;
// Save settings after UI is closed
self.save_settings();
});
barrier.wait();
crash_ui.run()
});
}
/// Update the user setting stored in memtest according to settings
pub fn update_memtest_setting(&self) {
self.memtest
.borrow_mut()
.update_user_setting(self.settings.borrow_mut().test_hardware);
}
}
// These methods may interact with `self.ui`.
@@ -470,7 +500,6 @@ impl ReportCrash {
/// Restart the application and send the crash report.
pub fn restart(&self) {
self.save_settings();
// Get the program restarted before sending the report.
self.restart_process();
let result = self.try_send();
@@ -479,7 +508,6 @@ impl ReportCrash {
/// Quit and send the crash report.
pub fn quit(&self) {
self.save_settings();
let result = self.try_send();
self.close_window(result.is_some());
}
@@ -523,12 +551,21 @@ impl ReportCrash {
return None;
};
// Add memtest output to extra data
let mut extra = self.current_extra_data();
if let Some(output) = self
.memtest
.borrow_mut()
.collect_output_for_submission(&self.ui)
{
extra["MemtestOutput"] = output.into();
}
if let Some(ui) = &self.ui {
ui.push(|r| *r.submit_state.borrow_mut() = SubmitState::InProgress);
}
// Send the report to the server.
let extra = self.current_extra_data();
let memory_file = self.config.memory_file();
let report = net::report::CrashReport {
extra: &extra,
@@ -624,3 +661,161 @@ impl ReportCrash {
self.ui.as_ref().expect("UI remote queue missing")
}
}
impl Memtest {
/// Spawn the memtest process if memtest is enabled
fn init(&mut self) {
if self.should_run() {
self.spawn()
.unwrap_or_else(|e| log::warn!("failed to spawn memtest: {e:#}"));
}
}
/// Spawn the memtest process
fn spawn(&mut self) -> anyhow::Result<()> {
use {
crate::std::{env, process::Stdio, time::Duration},
memtest::MemtestRunnerArgs,
};
let memsize_mb = 1024;
let memtest_runner_args = MemtestRunnerArgs {
timeout: Duration::from_secs(3),
mem_lock_mode: memtest::MemLockMode::Resizable,
allow_working_set_resize: true,
allow_multithread: true,
allow_early_termination: true,
};
let curr_exe = env::current_exe().context("failed to get current exe path")?;
let child = Command::new(curr_exe)
.arg("--memtest")
.arg(memsize_mb.to_string())
.arg(
serde_json::to_string(&memtest_runner_args)
.expect("memtest_runner_args conversion to json string should not fail"),
)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context("failed to spawn memtest process")?;
self.child = Some(child);
Ok(())
}
/// If memtest is allowed to run, change ui SubmitState and wait and return the memtest output,
/// otherwise kill memtest process if it exists and return None
fn collect_output_for_submission(
&mut self,
ui: &Option<AsyncTask<ReportCrashUIState>>,
) -> Option<String> {
if self.should_run() {
if let Some(ui) = ui {
ui.push(|r| *r.submit_state.borrow_mut() = SubmitState::WaitingHardwareTests);
}
if self.child.is_none() {
self.spawn()
.unwrap_or_else(|e| log::warn!("failed to spawn memtest: {e:#}"));
}
match self.wait_with_output() {
Ok(output) => return Some(output),
Err(e) => log::warn!("failed to wait memtest for submission {e:?}"),
}
} else if self.child.is_some() {
self.kill_and_wait()
.unwrap_or_else(|e| log::warn!("failed to kill memtest before submission {e:?}"));
}
None
}
/// Wait on memtest and return its output
fn wait_with_output(&mut self) -> anyhow::Result<String> {
let child = self
.child
.take()
.context("attempted to wait on non-existent memtest process")?;
let output = child
.wait_with_output()
.context("failed to wait on memtest process")?;
if output.status.success() {
String::from_utf8(output.stdout)
.context("failed to get valid string from memtest stdout")
} else {
String::from_utf8(output.stderr)
.context("failed to get valid string from memtest stderr")
}
}
/// Kill and wait on memtest
fn kill_and_wait(&mut self) -> anyhow::Result<()> {
let mut child = self
.child
.take()
.context("attempted to kill non-existent memtest process")?;
child.kill().context("failed to kill memtest process")?;
child
.wait()
.context("failed to wait memtest process after kill")?;
Ok(())
}
fn update_user_setting(&mut self, updated_setting: bool) {
self.user_setting_enabled = updated_setting;
}
/// Whether the memtest process is supposed to be run
fn should_run(&self) -> bool {
self.feature_enabled && self.user_setting_enabled
}
/// Wait or kill memtest process depending on user setting
fn shutdown(&mut self) -> anyhow::Result<()> {
if self.child.is_none() {
return Ok(());
}
if self.user_setting_enabled {
let memtest_output = self.wait_with_output().context("failed to wait memtest")?;
self.add_to_extra_file("MemtestOutput", &memtest_output)
.context("failed to add memtest output to extra file")
} else {
self.kill_and_wait().context("failed to kill memtest")
}
}
/// Add a key value pair to the extra file
fn add_to_extra_file(&self, key: &str, value: &str) -> anyhow::Result<()> {
use crate::std::{fs, io::Read};
let extra_file = self
.extra_file
.as_ref()
.context("extra file path does not exist")?;
let mut extra_file_content = String::new();
fs::File::open(extra_file)
.context("failed to open extra file")?
.read_to_string(&mut extra_file_content)
.context("failed to read extra file")?;
let mut extra: serde_json::Value = serde_json::from_str(&extra_file_content)
.context("failed to parse extra file content")?;
extra[key] = value.into();
let extra_file = fs::File::create(extra_file).context("failed to create new extra file")?;
serde_json::to_writer(extra_file, &extra).context("failed to write to extra file")?;
Ok(())
}
}
impl Drop for Memtest {
fn drop(&mut self) {
self.shutdown()
.unwrap_or_else(|e| log::warn!("failed to handle memtest before shutdown: {e:#}"));
}
}

View File

@@ -65,6 +65,7 @@ mod glean;
mod lang;
mod logging;
mod logic;
mod memory_test;
mod net;
mod process;
mod settings;
@@ -79,14 +80,10 @@ fn main() {
// Determine the mode in which to run. This is very simplistic, but need not be more permissive
// nor flexible since we control how the program is invoked. We don't use the mocked version
// because we want the actual args.
if ::std::env::args_os()
.nth(1)
.map(|s| s == "--analyze")
.unwrap_or(false)
{
analyze::main()
} else {
report_main()
match ::std::env::args_os().nth(1) {
Some(s) if s == "--analyze" => analyze::main(),
Some(s) if s == "--memtest" => memory_test::main(),
_ => report_main(),
}
}

View File

@@ -0,0 +1,84 @@
/* 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/. */
//! A frontend for memory testing.
use {
crate::std::{env, mem::size_of},
anyhow::Context,
memtest::{MemtestKind, MemtestRunner, MemtestRunnerArgs},
rand::{seq::SliceRandom, thread_rng},
serde_json,
};
// runtimeobject is not automatically linked correctly
#[cfg(windows)]
#[link(name = "runtimeobject")]
extern "C" {}
/// Usage: crashreporter --memtest <memsize_mb> <json formatted memtest_runner_args>
/// Set the env var `MOZ_CRASHREPORTER_MEMTEST_KINDS` to specify prioritized memtest kinds
pub fn main() {
let (mem_usize_count, memtest_runner_args, memtest_kinds) = match parse_args() {
Ok(parsed) => parsed,
Err(e) => {
eprintln!("Error: {e:?}");
std::process::exit(1);
}
};
let mut memory = vec![0; mem_usize_count];
let memtest_runner_result = MemtestRunner::from_test_kinds(&memtest_runner_args, memtest_kinds)
.run(&mut memory)
.expect("failed to run memtest-runner");
println!(
"{}",
serde_json::to_string(&memtest_runner_result)
.expect("memtest runner results failed to serialize")
);
}
/// Parse command line arguments and environment to return the parameters for running memtest,
/// including a usize for the requested memory vector length, MemtestRunnerArgs and a vector of
/// MemtestKinds to run.
fn parse_args() -> anyhow::Result<(usize, MemtestRunnerArgs, Vec<MemtestKind>)> {
const KIB: usize = 1024;
const MIB: usize = 1024 * KIB;
let mut iter = env::args_os().skip(2).map_while(|s| s.into_string().ok());
let memsize_mb: usize = iter
.next()
.and_then(|s| s.parse().ok())
.context("missing/invalid memsize_MB")?;
let memtest_runner_args = iter
.next()
.and_then(|s| serde_json::from_str(&s).ok())
.context("missing/invalid memtest_runner_args")?;
Ok((
memsize_mb * MIB / size_of::<usize>(),
memtest_runner_args,
get_memtest_kinds()?,
))
}
/// Returns a vector of MemtestKind that contains all kinds, but prioritizes the given kinds.
fn get_memtest_kinds() -> anyhow::Result<Vec<MemtestKind>> {
let env_string = env::var(ekey!("MEMTEST_KINDS")).unwrap_or_default();
let specified = env_string
.split_whitespace()
.map(|s| s.parse().context("failed to parse memtest kinds"))
.collect::<anyhow::Result<Vec<MemtestKind>>>()?;
let mut remaining: Vec<_> = MemtestKind::ALL
.iter()
.filter(|k| !specified.contains(k))
.cloned()
.collect();
remaining.shuffle(&mut thread_rng());
Ok([specified, remaining].concat())
}

View File

@@ -10,6 +10,10 @@ pub struct Settings {
pub submit_report: bool,
/// Whether the URL that was open should be included in a sent report.
pub include_url: bool,
/// Whether hardware tests (such as memory tests) are enabled
// This is a new field, so might be missing in previously stored settings
#[serde(default = "missing_test_hardware")]
pub test_hardware: bool,
}
impl Default for Settings {
@@ -17,6 +21,7 @@ impl Default for Settings {
Settings {
submit_report: true,
include_url: true,
test_hardware: true,
}
}
}
@@ -37,3 +42,7 @@ impl Settings {
serde_json::to_string_pretty(self).unwrap()
}
}
fn missing_test_hardware() -> bool {
true
}

View File

@@ -360,15 +360,14 @@ impl File {
MockFS.get(|files| {
let name = path.file_name().expect("invalid path");
files.parent_dir(path, move |d| {
if !d.contains_key(name) {
d.insert(
name.to_owned(),
MockFSItem {
content: MockFSContent::File(Ok(Default::default())),
modified: super::time::SystemTime::now().0,
},
);
}
d.remove(name);
d.insert(
name.to_owned(),
MockFSItem {
content: MockFSContent::File(Ok(Default::default())),
modified: super::time::SystemTime::now().0,
},
);
})
})?;
Self::open(path)

View File

@@ -152,6 +152,17 @@ impl Child {
self.ref_wait_with_output().unwrap()
}
pub fn wait(&mut self) -> std::io::Result<ExitStatus> {
self.ref_wait_with_output().unwrap().map(|o| o.status)
}
// This function doesn't actually do anything with the mock Child.
// It would be more accurate to affect the ExitStatus of the Child, but that requires a more
// complete model of `MockCommand`.
pub fn kill(&mut self) -> std::io::Result<()> {
Ok(())
}
fn ref_wait_with_output(&mut self) -> Option<std::io::Result<Output>> {
drop(self.stdin.take());
if let Some(stdin) = self.stdin_data.take() {

View File

@@ -14,7 +14,7 @@ use crate::std::{
fs::{MockFS, MockFiles},
io::ErrorKind,
mock,
process::Command,
process::{Command, Output},
sync::{
atomic::{AtomicUsize, Ordering::Relaxed},
Arc,
@@ -102,6 +102,12 @@ const MOCK_MINIDUMP_EXTRA: &str = r#"{
"SomeNestedJson": { "foo": "bar" },
"URL": "https://url.example.com"
}"#;
macro_rules! MOCK_MINIDUMP_EXTRA_COMPACT {
() => {{
let value: serde_json::Value = serde_json::from_str(MOCK_MINIDUMP_EXTRA).unwrap();
serde_json::to_string(&value).unwrap()
}};
}
// Actual content doesn't matter, aside from the hash that is generated.
const MOCK_MINIDUMP_FILE: &[u8] = &[1, 2, 3, 4];
@@ -185,7 +191,7 @@ impl GuiTest {
)
.add_file_result(
"minidump.extra",
Ok(MOCK_MINIDUMP_EXTRA.into()),
Ok(MOCK_MINIDUMP_EXTRA_COMPACT!().into()),
current_system_time(),
);
@@ -341,11 +347,23 @@ impl AssertFiles {
pub fn pending(&mut self) -> &mut Self {
let dmp = self.data("pending/minidump.dmp");
self.inner
.check(self.data("pending/minidump.extra"), MOCK_MINIDUMP_EXTRA)
.check(
self.data("pending/minidump.extra"),
MOCK_MINIDUMP_EXTRA_COMPACT!(),
)
.check_bytes(dmp, MOCK_MINIDUMP_FILE);
self
}
/// Assert that a crash is pending according to the filesystem, with updated files.
pub fn pending_with_change(&mut self, new_dmp: &[u8], new_extra: &str) -> &mut Self {
let dmp = self.data("pending/minidump.dmp");
self.inner
.check(self.data("pending/minidump.extra"), new_extra)
.check_bytes(dmp, new_dmp);
self
}
/// Assert that a crash ping was sent according to the filesystem.
pub fn ping(&mut self) -> &mut Self {
self.inner.check(
@@ -609,6 +627,7 @@ fn no_submit() {
Settings {
submit_report: true,
include_url: false,
test_hardware: false,
}
.to_string(),
);
@@ -641,6 +660,7 @@ fn no_submit() {
.saved_settings(Settings {
submit_report: false,
include_url: false,
test_hardware: false,
})
.pending();
}
@@ -910,6 +930,7 @@ fn include_url() {
Settings {
submit_report: true,
include_url: setting,
test_hardware: false,
}
.to_string(),
);
@@ -929,6 +950,193 @@ fn include_url() {
}
}
#[test]
fn persistent_settings() {
let mut test = GuiTest::new();
test.run(|interact| {
interact.element("include-url", |_style, c: &model::Checkbox| {
c.checked.set(false)
});
interact.element("send", |_style, c: &model::Checkbox| c.checked.set(false));
interact.element("test-hardware", |_style, c: &model::Checkbox| {
c.checked.set(false)
});
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
});
test.assert_files()
.saved_settings(Settings {
submit_report: false,
include_url: false,
test_hardware: false,
})
.pending();
}
#[test]
fn send_memtest_output() {
let mut test = GuiTest::new();
test.config.run_memtest = true;
let invoked = Counter::new();
let mock_invoked = invoked.clone();
test.mock
.set(
// memtest is the only expected process spawned using current exe
Command::mock("work_dir/crashreporter"),
Box::new(|cmd| assert_mock_memtest(cmd)),
)
.set(
net::report::MockReport,
Box::new(move |report| {
mock_invoked.inc();
assert_eq!(
report.extra.get("MemtestOutput").and_then(|v| v.as_str()),
Some("memtest output")
);
Ok(Ok(format!("CrashID={MOCK_REMOTE_CRASH_ID}")))
}),
);
test.run(|interact| {
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
});
invoked.assert_one();
}
#[test]
fn add_memtest_output_to_extra() {
let mut test = GuiTest::new();
test.config.run_memtest = true;
test.files.add_dir("data_dir").add_file(
"data_dir/crashreporter_settings.json",
Settings {
submit_report: false,
include_url: false,
test_hardware: true,
}
.to_string(),
);
test.mock.set(
Command::mock("work_dir/crashreporter"),
Box::new(|cmd| assert_mock_memtest(cmd)),
);
test.run(|interact| {
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
});
let mut value: serde_json::Value = serde_json::from_str(MOCK_MINIDUMP_EXTRA).unwrap();
value["MemtestOutput"] = "memtest output".into();
let new_extra = serde_json::to_string(&value).unwrap();
test.assert_files()
.saved_settings(Settings {
submit_report: false,
include_url: false,
test_hardware: true,
})
.pending_with_change(MOCK_MINIDUMP_FILE, &new_extra);
}
#[test]
fn toggle_memtest_spawn() {
let mut test = GuiTest::new();
test.config.run_memtest = true;
test.files.add_dir("data_dir").add_file(
"data_dir/crashreporter_settings.json",
Settings {
submit_report: true,
include_url: false,
test_hardware: false,
}
.to_string(),
);
let invoked = Counter::new();
let mock_invoked = invoked.clone();
test.mock
.set(
Command::mock("work_dir/crashreporter"),
Box::new(|cmd| assert_mock_memtest(cmd)),
)
.set(
net::report::MockReport,
Box::new(move |report| {
mock_invoked.inc();
assert_eq!(
report.extra.get("MemtestOutput").and_then(|v| v.as_str()),
Some("memtest output")
);
Ok(Ok(format!("CrashID={MOCK_REMOTE_CRASH_ID}")))
}),
);
test.run(|interact| {
interact.element("test-hardware", |_style, c: &model::Checkbox| {
c.checked.set(true)
});
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
});
invoked.assert_one();
}
#[test]
fn toggle_memtest_kill() {
let mut test = GuiTest::new();
test.config.run_memtest = true;
test.files.add_dir("data_dir").add_file(
"data_dir/crashreporter_settings.json",
Settings {
submit_report: true,
include_url: false,
test_hardware: true,
}
.to_string(),
);
let ran_process = Counter::new();
let mock_ran_process = ran_process.clone();
test.mock
.set(
Command::mock("work_dir/crashreporter"),
Box::new(move |cmd| {
// To allow accurate count of ran_process, Early return when spawning
if cmd.spawning {
return Ok(crate::std::process::success_output());
}
mock_ran_process.inc();
assert_mock_memtest(cmd)
}),
)
.set(
net::report::MockReport,
Box::new(move |report| {
assert!(report.extra.get("MemtestOutput").is_none());
Ok(Ok(format!("CrashID={MOCK_REMOTE_CRASH_ID}")))
}),
);
test.run(|interact| {
interact.element("test-hardware", |_style, c: &model::Checkbox| {
c.checked.set(false)
});
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
});
ran_process.assert_one();
}
fn assert_mock_memtest(cmd: &Command) -> std::io::Result<Output> {
use ::std::borrow::Borrow;
assert_eq!(cmd.args.len(), 3);
assert_eq!(cmd.args[0], "--memtest");
assert!(cmd.args[1].to_string_lossy().parse::<u32>().is_ok());
assert!(serde_json::from_str::<memtest::MemtestRunnerArgs>(
cmd.args[2].to_string_lossy().borrow()
)
.is_ok());
let mut output = crate::std::process::success_output();
output.stdout = "memtest output".into();
Ok(output)
}
#[test]
fn comment() {
const COMMENT: &str = "My program crashed";

View File

@@ -111,6 +111,7 @@ pub fn error_dialog<M: std::fmt::Debug>(config: &Config, message: M) {
pub enum SubmitState {
#[default]
Initial,
WaitingHardwareTests,
InProgress,
Success,
Failure,
@@ -128,6 +129,7 @@ pub struct ReportCrashUI {
pub struct ReportCrashUIState {
pub send_report: data::Synchronized<bool>,
pub include_address: data::Synchronized<bool>,
pub test_hardware: data::Synchronized<bool>,
pub show_details: data::Synchronized<bool>,
pub details: data::Synchronized<String>,
pub comment: data::OnDemand<String>,
@@ -143,11 +145,13 @@ impl ReportCrashUI {
) -> Self {
let send_report = data::Synchronized::new(initial_settings.submit_report);
let include_address = data::Synchronized::new(initial_settings.include_url);
let test_hardware = data::Synchronized::new(initial_settings.test_hardware);
ReportCrashUI {
state: Arc::new(ThreadBound::new(ReportCrashUIState {
send_report,
include_address,
test_hardware,
show_details: Default::default(),
details: Default::default(),
comment: Default::default(),
@@ -182,6 +186,7 @@ impl ReportCrashUI {
send_report,
include_address,
show_details,
test_hardware,
details,
comment,
submit_state,
@@ -196,6 +201,11 @@ impl ReportCrashUI {
let v = *v;
logic.push(move |s| s.settings.borrow_mut().include_url = v);
}});
test_hardware.on_change(cc! { (logic) move |v| {
let v = *v;
logic.push(move |s| {s.settings.borrow_mut().test_hardware = v;
s.update_memtest_setting();});
}});
let input_enabled = submit_state.mapped(|s| s == &SubmitState::Initial);
let send_report_and_input_enabled =
@@ -204,6 +214,7 @@ impl ReportCrashUI {
let submit_status_text = submit_state.mapped(cc! { (config) move |s| {
config.string(match s {
SubmitState::Initial => "crashreporter-submit-status",
SubmitState::WaitingHardwareTests => "crashreporter-submit-waiting-hardware-tests",
SubmitState::InProgress => "crashreporter-submit-in-progress",
SubmitState::Success => "crashreporter-submit-success",
SubmitState::Failure => "crashreporter-submit-failure",
@@ -211,6 +222,7 @@ impl ReportCrashUI {
}});
let progress_visible = submit_state.mapped(|s| s == &SubmitState::InProgress);
let hardware_test_checkbox_visible = data::Synchronized::new(self.config.run_memtest);
let details_window = ui! {
Window["crash-details-window"] title(config.string("crashreporter-view-report-title"))
@@ -237,7 +249,10 @@ impl ReportCrashUI {
Label text(config.string("crashreporter-apology")) bold(true),
Label text(config.string("crashreporter-crashed-and-restore")),
Label text(config.string("crashreporter-plea")),
Checkbox["send"] checked(send_report) label(config.string("crashreporter-send-report"))
Checkbox["test-hardware"] checked(test_hardware)
label(config.string("crashreporter-checkbox-test-hardware"))
enabled(&input_enabled) visible(&hardware_test_checkbox_visible),
Checkbox["send"] checked(send_report) label(config.string("crashreporter-checkbox-send-report"))
enabled(&input_enabled),
VBox margin_start(20) spacing(5) halign(Alignment::Fill) valign(Alignment::Fill) {
Button["details"] enabled(&send_report_and_input_enabled) on_click(cc! { (config, details, show_details, logic) move || {
@@ -259,7 +274,7 @@ impl ReportCrashUI {
halign(Alignment::Fill) valign(Alignment::Fill)
},
Checkbox["include-url"] checked(include_address)
label(config.string("crashreporter-include-url")) enabled(&send_report_and_input_enabled),
label(config.string("crashreporter-checkbox-include-url")) enabled(&send_report_and_input_enabled),
Label text(&submit_status_text) margin_top(20),
Progress halign(Alignment::Fill) visible(&progress_visible),
},

View File

@@ -25,8 +25,10 @@
#include "mozilla/RuntimeExceptionModule.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/ToString.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
@@ -1503,6 +1505,21 @@ static void WriteCrashEventFile(time_t crashTime, const char* crashTimeString,
}
}
void SetUpMemtestEnv() {
if (StaticPrefs::browser_crashReporter_memtest()) {
const char* env = "MOZ_CRASHREPORTER_RUN_MEMTEST=1";
PR_SetEnv(env);
auto memtestKindsLock = StaticPrefs::browser_crashReporter_memtestKinds();
if (!memtestKindsLock->IsEmpty()) {
char* env = strdup(
("MOZ_CRASHREPORTER_MEMTEST_KINDS=" + ToString(*memtestKindsLock))
.c_str());
PR_SetEnv(env);
}
}
}
// Callback invoked from breakpad's exception handler, this writes out the
// last annotations after a crash occurs and launches the crash reporter client.
//
@@ -1583,6 +1600,8 @@ bool MinidumpCallback(
WriteAnnotationsForMainProcessCrash(apiData, addrInfo, crashTime);
}
SetUpMemtestEnv();
if (doReport && isSafeToDump && !BackgroundTasks::IsBackgroundTaskMode()) {
// We launch the crash reporter client/dialog only if we've been explicitly
// asked to report crashes and if we weren't already trying to unset the

View File

@@ -24,11 +24,14 @@ crashreporter-comment-prompt = Add a comment (comments are publicly visible)
crashreporter-report-info = This report also contains technical information about the state of the application when it crashed.
crashreporter-send-report = Tell { -vendor-short-name } about this crash so they can fix it.
crashreporter-checkbox-test-hardware = Check for hardware and configuration problems on my device.
crashreporter-include-url = Include the address of the page I was on.
crashreporter-checkbox-send-report = Tell { -vendor-short-name } about this crash so they can fix it.
crashreporter-checkbox-include-url = Include the address of the page I was on.
crashreporter-submit-status = Your crash report will be submitted before you quit or restart.
crashreporter-submit-waiting-hardware-tests = Checking for hardware and configuration problems…
crashreporter-submit-in-progress = Submitting your report…
crashreporter-submit-success = Report submitted successfully!
crashreporter-submit-failure = There was a problem submitting your report.