├── .gitmodules ├── .github ├── CODEOWNERS ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── rust-ci.yml ├── .gitignore ├── crash-handler ├── tests │ ├── abort.rs │ ├── trap.rs │ ├── segv.rs │ ├── bus.rs │ ├── illegal.rs │ ├── purecall.rs │ ├── guard.rs │ ├── heap_corruption.rs │ ├── invalid_param.rs │ ├── stack_overflow.rs │ ├── fpe.rs │ ├── stack_overflow_pthread.rs │ └── shared.rs ├── src │ ├── unix.rs │ ├── error.rs │ ├── windows │ │ ├── signal.rs │ │ └── jmp.rs │ ├── mac │ │ ├── signal.rs │ │ └── ffi.rs │ ├── windows.rs │ ├── mac.rs │ ├── linux │ │ └── jmp.rs │ ├── linux.rs │ ├── lib.rs │ └── unix │ │ └── pthread_interpose.rs ├── release.toml ├── LICENSE-MIT ├── Cargo.toml ├── README.md └── CHANGELOG.md ├── Cross.toml ├── minidumper-test ├── tests │ ├── trap.rs │ ├── abort.rs │ ├── segfault.rs │ ├── illegal.rs │ ├── bus.rs │ ├── macos.rs │ ├── fpe.rs │ ├── windows.rs │ └── stack_overflow.rs ├── crash-client │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── Cargo.toml └── src │ └── main.rs ├── sadness-generator ├── src │ ├── bindings.toml │ └── win_bindings.rs ├── release.toml ├── Cargo.toml └── CHANGELOG.md ├── Cargo.toml ├── .mergify.yml ├── minidumper ├── release.toml ├── LICENSE-MIT ├── Cargo.toml ├── src │ ├── errors.rs │ ├── lib.rs │ ├── ipc.rs │ └── ipc │ │ ├── client.rs │ │ └── mac.rs ├── README.md ├── CHANGELOG.md ├── examples │ └── diskwrite.rs └── tests │ └── ipc.rs ├── crash-context ├── release.toml ├── Cargo.toml ├── src │ ├── linux │ │ ├── getcontext.rs │ │ └── getcontext │ │ │ ├── arm.rs │ │ │ ├── x86.rs │ │ │ ├── riscv64.rs │ │ │ ├── aarch64.rs │ │ │ └── x86_64.rs │ ├── mac.rs │ ├── lib.rs │ ├── mac │ │ └── guard.rs │ └── windows.rs ├── LICENSE-MIT ├── tests │ └── capture_context.rs ├── README.md └── CHANGELOG.md ├── LICENSE-MIT ├── SECURITY.md ├── deny.toml ├── .cargo └── config.toml ├── CODE_OF_CONDUCT.md ├── README.md └── CONTRIBUTING.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Jake-Shadle 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .dumps 5 | -------------------------------------------------------------------------------- /crash-handler/tests/abort.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | #[test] 4 | fn handles_abort() { 5 | shared::handles_crash(shared::SadnessFlavor::Abort); 6 | } 7 | -------------------------------------------------------------------------------- /crash-handler/tests/trap.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | #[test] 4 | fn handles_trap() { 5 | shared::handles_crash(shared::SadnessFlavor::Trap); 6 | } 7 | -------------------------------------------------------------------------------- /crash-handler/tests/segv.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | #[test] 4 | fn handles_segv() { 5 | shared::handles_crash(shared::SadnessFlavor::Segfault); 6 | } 7 | -------------------------------------------------------------------------------- /crash-handler/tests/bus.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | mod shared; 4 | 5 | #[test] 6 | fn handles_bus() { 7 | shared::handles_crash(shared::SadnessFlavor::Bus); 8 | } 9 | -------------------------------------------------------------------------------- /crash-handler/tests/illegal.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | #[test] 4 | fn handles_illegal_instruction() { 5 | shared::handles_crash(shared::SadnessFlavor::Illegal); 6 | } 7 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.arm-unknown-linux-gnueabihf] 2 | image = "ghcr.io/cross-rs/arm-unknown-linux-gnueabihf@sha256:2c7f685d79baa15a6437c9ccb76381ad2f597eec62e1a27f9c11fea5ce2adbf6" 3 | -------------------------------------------------------------------------------- /crash-handler/tests/purecall.rs: -------------------------------------------------------------------------------- 1 | #![cfg(windows)] 2 | 3 | mod shared; 4 | 5 | #[test] 6 | fn handles_purecall() { 7 | shared::handles_crash(shared::SadnessFlavor::Purecall); 8 | } 9 | -------------------------------------------------------------------------------- /crash-handler/tests/guard.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_os = "macos")] 2 | 3 | mod shared; 4 | 5 | #[test] 6 | fn handles_guard() { 7 | shared::handles_crash(shared::SadnessFlavor::Guard); 8 | } 9 | -------------------------------------------------------------------------------- /crash-handler/tests/heap_corruption.rs: -------------------------------------------------------------------------------- 1 | #![cfg(windows)] 2 | 3 | mod shared; 4 | 5 | #[test] 6 | fn handles_heap_corruption() { 7 | shared::handles_crash(shared::SadnessFlavor::HeapCorruption); 8 | } 9 | -------------------------------------------------------------------------------- /crash-handler/tests/invalid_param.rs: -------------------------------------------------------------------------------- 1 | #![cfg(windows)] 2 | 3 | mod shared; 4 | 5 | #[test] 6 | fn handles_invalid_param() { 7 | shared::handles_crash(shared::SadnessFlavor::InvalidParameter); 8 | } 9 | -------------------------------------------------------------------------------- /minidumper-test/tests/trap.rs: -------------------------------------------------------------------------------- 1 | use minidumper_test::*; 2 | 3 | #[test] 4 | fn trap_simple() { 5 | run_test(Signal::Trap, 0, false); 6 | } 7 | 8 | #[test] 9 | fn trap_threaded() { 10 | run_threaded_test(Signal::Trap); 11 | } 12 | -------------------------------------------------------------------------------- /minidumper-test/tests/abort.rs: -------------------------------------------------------------------------------- 1 | use minidumper_test::*; 2 | 3 | #[test] 4 | fn abort_simple() { 5 | run_test(Signal::Abort, 0, false); 6 | } 7 | 8 | #[test] 9 | fn abort_threaded() { 10 | run_threaded_test(Signal::Abort); 11 | } 12 | -------------------------------------------------------------------------------- /minidumper-test/tests/segfault.rs: -------------------------------------------------------------------------------- 1 | use minidumper_test::*; 2 | 3 | #[test] 4 | fn segfault_simple() { 5 | run_test(Signal::Segv, 0, false); 6 | } 7 | 8 | #[test] 9 | fn segfault_threaded() { 10 | run_threaded_test(Signal::Segv); 11 | } 12 | -------------------------------------------------------------------------------- /minidumper-test/tests/illegal.rs: -------------------------------------------------------------------------------- 1 | use minidumper_test::*; 2 | 3 | #[test] 4 | fn illegal_simple() { 5 | run_test(Signal::Illegal, 0, false); 6 | } 7 | 8 | #[test] 9 | fn illegal_threaded() { 10 | run_threaded_test(Signal::Illegal); 11 | } 12 | -------------------------------------------------------------------------------- /crash-handler/tests/stack_overflow.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | #[test] 4 | fn handles_stack_overflow() { 5 | shared::handles_crash(shared::SadnessFlavor::StackOverflow { 6 | non_rust_thread: false, 7 | long_jumps: false, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /minidumper-test/tests/bus.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | use minidumper_test::*; 4 | 5 | #[test] 6 | fn bus_simple() { 7 | run_test(Signal::Bus, 0, false); 8 | } 9 | 10 | #[test] 11 | fn bus_threaded() { 12 | run_threaded_test(Signal::Bus); 13 | } 14 | -------------------------------------------------------------------------------- /crash-handler/src/unix.rs: -------------------------------------------------------------------------------- 1 | mod pthread_interpose; 2 | 3 | // Force this function to be linked, but it shouldn't actually be called by 4 | // users directly as it interposes the libc `pthread_create` 5 | #[doc(hidden)] 6 | #[cfg(not(miri))] 7 | pub use pthread_interpose::pthread_create; 8 | -------------------------------------------------------------------------------- /minidumper-test/tests/macos.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_os = "macos")] 2 | 3 | use minidumper_test::*; 4 | 5 | #[test] 6 | fn guard_simple() { 7 | // Note that since the guard exception uses a particular file path and a 8 | // guard id, we don't run this threaded 9 | run_test(Signal::Guard, 0, false); 10 | } 11 | -------------------------------------------------------------------------------- /sadness-generator/src/bindings.toml: -------------------------------------------------------------------------------- 1 | output = "win_bindings.rs" 2 | binds = ["LoadLibraryA", "GetProcAddress", "GetProcessHeap", "HeapFree"] 3 | 4 | [bind-mode] 5 | mode = "minwin" 6 | 7 | [bind-mode.config] 8 | enum-style = "minwin" 9 | fix-naming = true 10 | use-rust-casing = true 11 | linking-style = "raw-dylib" 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "crash-context", 5 | "crash-handler", 6 | "minidumper", 7 | "minidumper-test", 8 | "sadness-generator", 9 | ] 10 | 11 | [profile.dev] 12 | debug = 2 13 | 14 | [workspace.dependencies] 15 | cfg-if = "1.0" 16 | crash-context = "0.6" 17 | libc = "0.2" 18 | mach2 = "0.4" 19 | parking_lot = "0.12" 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Checklist 2 | 3 | * [ ] I have read the [Contributor Guide](../../CONTRIBUTING.md) 4 | * [ ] I have read and agree to the [Code of Conduct](../../CODE_OF_CONDUCT.md) 5 | * [ ] I have added a description of my changes and why I'd like them included in the section below 6 | 7 | ### Description of Changes 8 | 9 | Describe your changes here 10 | 11 | ### Related Issues 12 | 13 | List related issues here 14 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatic merge when CI passes and 1 reviews 3 | conditions: 4 | - "#approved-reviews-by>=1" 5 | - "#review-requested=0" 6 | - "#changes-requested-reviews-by=0" 7 | - base=main 8 | actions: 9 | merge: 10 | method: squash 11 | - name: delete head branch after merge 12 | conditions: 13 | - merged 14 | actions: 15 | delete_head_branch: {} 16 | -------------------------------------------------------------------------------- /crash-handler/tests/fpe.rs: -------------------------------------------------------------------------------- 1 | //! This is ignored on mac when targeting arm as the way sadness-generator does 2 | //! this on that arch is to explicitly raise SIGFPE, however we don't actually 3 | //! handle that signal and rely on the exception port instead 4 | #![cfg(not(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64"))))] 5 | 6 | mod shared; 7 | 8 | #[test] 9 | fn handles_fpe() { 10 | shared::handles_crash(shared::SadnessFlavor::DivideByZero); 11 | } 12 | -------------------------------------------------------------------------------- /minidumper-test/tests/fpe.rs: -------------------------------------------------------------------------------- 1 | //! This is ignored on mac when targeting arm as the way sadness-generator does 2 | //! this on that arch is to explicitly raise SIGFPE, however we don't actually 3 | //! handle that signal and rely on the exception port instead 4 | #![cfg(not(all(target_os = "macos", any(target_arch = "arm", target_arch = "aarch64"))))] 5 | 6 | use minidumper_test::*; 7 | 8 | #[test] 9 | fn fpe_simple() { 10 | run_test(Signal::Fpe, 0, false); 11 | } 12 | 13 | #[test] 14 | fn fpe_threaded() { 15 | run_threaded_test(Signal::Fpe); 16 | } 17 | -------------------------------------------------------------------------------- /minidumper-test/tests/windows.rs: -------------------------------------------------------------------------------- 1 | #![cfg(windows)] 2 | 3 | use minidumper_test::*; 4 | 5 | #[test] 6 | fn purecall_simple() { 7 | run_test(Signal::Purecall, 0, false); 8 | } 9 | 10 | #[test] 11 | fn purecall_threaded() { 12 | run_threaded_test(Signal::Purecall); 13 | } 14 | 15 | #[test] 16 | fn invalid_param_simple() { 17 | run_test(Signal::InvalidParameter, 0, false); 18 | } 19 | 20 | #[test] 21 | fn invalid_param_threaded() { 22 | run_threaded_test(Signal::InvalidParameter); 23 | } 24 | 25 | #[test] 26 | fn heap_corruption() { 27 | run_threaded_test(Signal::HeapCorruption); 28 | } 29 | -------------------------------------------------------------------------------- /minidumper-test/tests/stack_overflow.rs: -------------------------------------------------------------------------------- 1 | use minidumper_test::*; 2 | 3 | #[test] 4 | fn stack_overflow_simple() { 5 | run_test(Signal::StackOverflow, 0, false); 6 | } 7 | 8 | #[test] 9 | fn stack_overflow_threaded() { 10 | run_threaded_test(Signal::StackOverflow); 11 | } 12 | 13 | #[cfg(all(unix, not(target_os = "macos")))] 14 | #[test] 15 | fn stack_overflow_c_thread() { 16 | run_test(Signal::StackOverflowCThread, 0, false); 17 | } 18 | 19 | #[cfg(all(unix, not(target_os = "macos")))] 20 | #[test] 21 | fn stack_overflow_c_thread_threaded() { 22 | run_threaded_test(Signal::StackOverflowCThread); 23 | } 24 | -------------------------------------------------------------------------------- /crash-handler/tests/stack_overflow_pthread.rs: -------------------------------------------------------------------------------- 1 | //! Not tested for windows/macos since the point of testing this is to ensure 2 | //! an alternate stack is always installed when anything Rust or otherwise does 3 | //! a `pthread_create`. Windows doesn't use pthreads, and macos sends stack overflow 4 | //! exceptions to a completely separate thread from the one that overflowed, 5 | //! avoiding the problem altogether 6 | #![cfg(all(unix, not(target_os = "macos")))] 7 | 8 | mod shared; 9 | 10 | #[test] 11 | fn handles_stack_overflow_in_c_thread() { 12 | shared::handles_crash(shared::SadnessFlavor::StackOverflow { 13 | non_rust_thread: true, 14 | long_jumps: false, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /minidumper/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release minidumper-{{version}}" 2 | tag-message = "Release minidumper-{{version}}" 3 | tag-name = "minidumper-{{version}}" 4 | pre-release-replacements = [ 5 | { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, 6 | { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, 7 | { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, 8 | { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, 9 | { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/{{tag_name}}...HEAD" }, 10 | ] 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Device:** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /crash-context/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release crash-context-{{version}}" 2 | tag-message = "Release crash-context-{{version}}" 3 | tag-name = "crash-context-{{version}}" 4 | pre-release-replacements = [ 5 | { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, 6 | { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, 7 | { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, 8 | { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, 9 | { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/{{tag_name}}...HEAD" }, 10 | ] 11 | -------------------------------------------------------------------------------- /crash-handler/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release crash-handler-{{version}}" 2 | tag-message = "Release crash-handler-{{version}}" 3 | tag-name = "crash-handler-{{version}}" 4 | pre-release-replacements = [ 5 | { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, 6 | { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, 7 | { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, 8 | { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, 9 | { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/{{tag_name}}...HEAD" }, 10 | ] 11 | -------------------------------------------------------------------------------- /minidumper-test/crash-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crash-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | minidumper-test = { path = "../" } 11 | anyhow = "1.0" 12 | clap = { version = "4.0", features = ["derive"] } 13 | cfg-if = "1.0" 14 | crash-handler = { path = "../../crash-handler" } 15 | minidump = "0.21" 16 | minidump-common = "0.21" 17 | minidumper = { path = "../../minidumper" } 18 | # This has some crazy dependencies, can enable manually if needed 19 | #notify-rust = "4.5" 20 | rayon = "1.5" 21 | sadness-generator = { path = "../../sadness-generator" } 22 | tracing-subscriber = { version = "0.3" } 23 | 24 | [workspace] 25 | -------------------------------------------------------------------------------- /sadness-generator/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "Release sadness-generator-{{version}}" 2 | tag-message = "Release sadness-generator-{{version}}" 3 | tag-name = "sadness-generator-{{version}}" 4 | pre-release-replacements = [ 5 | { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, 6 | { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, 7 | { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, 8 | { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, 9 | { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/{{tag_name}}...HEAD" }, 10 | ] 11 | -------------------------------------------------------------------------------- /minidumper-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minidumper-test" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | publish = false 7 | 8 | [[bin]] 9 | name = "minidumper-test" 10 | 11 | [[bin]] 12 | name = "crash-client" 13 | path = "crash-client/src/main.rs" 14 | 15 | [dependencies] 16 | anyhow = "1.0" 17 | clap = { version = "4.0", features = ["derive"] } 18 | cfg-if = "1.0" 19 | crash-handler = { path = "../crash-handler" } 20 | minidump = "0.26" 21 | minidump-common = "0.26" 22 | minidumper = { path = "../minidumper" } 23 | # This has some crazy dependencies, can enable manually if needed 24 | #notify-rust = "4.5" 25 | rayon = "1.5" 26 | sadness-generator = { path = "../sadness-generator" } 27 | tracing-subscriber = { version = "0.3" } 28 | 29 | [package.metadata.release] 30 | release = false 31 | -------------------------------------------------------------------------------- /sadness-generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sadness-generator" 3 | version = "0.6.0" 4 | description = "Provides various ways to make your program sad" 5 | repository = "https://github.com/EmbarkStudios/crash-handling" 6 | authors = [ 7 | "Embark ", 8 | "Jake Shadle ", 9 | ] 10 | edition = "2024" 11 | license = "MIT OR Apache-2.0" 12 | documentation = "https://docs.rs/sadness-generator" 13 | homepage = "https://github.com/EmbarkStudios/crash-handling/tree/main/sadness-generator" 14 | # Unfortunately there is no good category for this on crates.io atm, 15 | # so we use simulation, the simulation is so good it's real! 16 | categories = ["simulation"] 17 | keywords = ["crash", "sadness", "signal", "exception"] 18 | # We use `asm!` and rawdylib Windows bindings 19 | rust-version = "1.85.0" 20 | 21 | [dependencies] 22 | libc.workspace = true 23 | -------------------------------------------------------------------------------- /crash-context/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crash-context" 3 | version = "0.6.3" 4 | description = "Provides portable types containing target specific contextual information at the time of a crash" 5 | repository = "https://github.com/EmbarkStudios/crash-handling" 6 | authors = [ 7 | "Embark ", 8 | "Jake Shadle ", 9 | ] 10 | edition = "2021" 11 | license = "MIT OR Apache-2.0" 12 | readme = "README.md" 13 | documentation = "https://docs.rs/crash-context" 14 | homepage = "https://github.com/EmbarkStudios/crash-handling/tree/main/crash-context" 15 | categories = ["external-ffi-bindings"] 16 | keywords = ["crash", "libc", "getcontext"] 17 | rust-version = "1.62.0" # We use `global_asm!` 18 | 19 | [dependencies] 20 | # Nicer cfg handling 21 | cfg-if.workspace = true 22 | 23 | [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] 24 | libc.workspace = true 25 | 26 | [target.'cfg(target_os = "macos")'.dependencies] 27 | # provides bindings to mach specifics 28 | mach2.workspace = true 29 | -------------------------------------------------------------------------------- /sadness-generator/src/win_bindings.rs: -------------------------------------------------------------------------------- 1 | //! Bindings generated by `minwin` 0.1.0 2 | #![allow( 3 | non_snake_case, 4 | non_upper_case_globals, 5 | non_camel_case_types, 6 | clippy::upper_case_acronyms 7 | )] 8 | #[link(name = "kernel32", kind = "raw-dylib")] 9 | unsafe extern "system" { 10 | #[link_name = "GetProcAddress"] 11 | pub fn get_proc_address(module: Hmodule, proc_name: Pcstr) -> Farproc; 12 | #[link_name = "GetProcessHeap"] 13 | pub fn get_process_heap() -> HeapHandle; 14 | #[link_name = "HeapFree"] 15 | pub fn heap_free( 16 | heap: HeapHandle, 17 | flags: HeapFlags::Enum, 18 | mem: *const ::core::ffi::c_void, 19 | ) -> Bool; 20 | #[link_name = "LoadLibraryA"] 21 | pub fn load_library_a(lib_file_name: Pcstr) -> Hmodule; 22 | } 23 | pub type Bool = i32; 24 | pub type Farproc = ::core::option::Option isize>; 25 | pub mod HeapFlags { 26 | pub type Enum = u32; 27 | } 28 | pub type HeapHandle = isize; 29 | pub type Hmodule = isize; 30 | pub type Pcstr = *const i8; 31 | -------------------------------------------------------------------------------- /crash-context/src/linux/getcontext.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of [`getcontext`](https://man7.org/linux/man-pages/man3/getcontext.3.html) 2 | 3 | unsafe extern "C" { 4 | /// A portable implementation of [`getcontext`](https://man7.org/linux/man-pages/man3/getcontext.3.html) 5 | /// since it is not supported by all libc implementations, namely `musl`, as 6 | /// it has been deprecated from POSIX for over a decade 7 | /// 8 | /// The implementation is ported from Breakpad, which is itself ported from 9 | /// libunwind 10 | #[cfg_attr(target_arch = "aarch64", allow(improper_ctypes))] 11 | pub fn crash_context_getcontext(ctx: *mut super::ucontext_t) -> i32; 12 | } 13 | 14 | cfg_if::cfg_if! { 15 | if #[cfg(target_arch = "x86_64")] { 16 | mod x86_64; 17 | } else if #[cfg(target_arch = "x86")] { 18 | mod x86; 19 | } else if #[cfg(target_arch = "aarch64")] { 20 | mod aarch64; 21 | } else if #[cfg(target_arch = "arm")] { 22 | mod arm; 23 | } else if #[cfg(target_arch = "riscv64")] { 24 | mod riscv64; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Embark Studios 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /minidumper/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Embark Studios 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /crash-context/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Embark Studios 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /crash-context/src/mac.rs: -------------------------------------------------------------------------------- 1 | pub mod guard; 2 | pub mod ipc; 3 | pub mod resource; 4 | 5 | use mach2::mach_types as mt; 6 | 7 | /// Information on the exception that caused the crash 8 | #[derive(Copy, Clone, Debug)] 9 | pub struct ExceptionInfo { 10 | /// The exception kind 11 | pub kind: u32, 12 | /// The exception code 13 | pub code: u64, 14 | /// Optional subcode with different meanings depending on the exception type 15 | /// * `EXC_BAD_ACCESS` - The address that caused the exception 16 | /// * `EXC_GUARD` - The unique guard identifier that was guarding a resource 17 | /// * `EXC_RESOURCE` - Additional details depending on the resource type 18 | pub subcode: Option, 19 | } 20 | 21 | /// Full Macos crash context 22 | #[derive(Debug)] 23 | pub struct CrashContext { 24 | /// The process which crashed 25 | pub task: mt::task_t, 26 | /// The thread in the process that crashed 27 | pub thread: mt::thread_t, 28 | /// The thread that handled the exception. This may be useful to ignore. 29 | pub handler_thread: mt::thread_t, 30 | /// Optional exception information 31 | pub exception: Option, 32 | } 33 | -------------------------------------------------------------------------------- /crash-handler/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Embark Studios 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | Protecting your privacy and our products is paramount to establishing trust with our players and users. Therefore, we consistently tweak and enhance our ways of working with security, and aim to be as transparent as possible about this effort. 4 | 5 | We truly appreciate efforts to discover and disclose security issues responsibly. 6 | 7 | If you’re a security researcher looking to submit a report about a security vulnerability, this repository has GitHub's [Private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) enabled where you directly can submit reports to us privately and securely, as well as optionally create a private fork to help fix the issue. 8 | 9 | This is repository is also part of our [Security Bug Bounty Program](https://www.intigriti.com/programs) hosted by Intigriti. Please note that in order to report via Intigriti, you need to be a registered user. 10 | 11 | If you’d like to report a security issue found in any of our other products or have security concerns regarding Embark Studios, please reach out to security@embark-studios.com. 12 | -------------------------------------------------------------------------------- /minidumper-test/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Parser, ValueEnum}; 4 | use minidumper_test::*; 5 | 6 | #[derive(Parser)] 7 | struct Command { 8 | // The unique identifier for the socket connection and the minidump file 9 | // that should be produced when this client crashes 10 | //#[clap(long)] 11 | //id: String, 12 | /// The signal/exception to raise 13 | #[clap(action, long)] 14 | signal: Option, 15 | /// Raises the signal on a separate thread rather than the main thread 16 | #[clap(action, long)] 17 | use_thread: bool, 18 | /// Waits on a debugger to attach 19 | #[clap(action, long)] 20 | wait_on_debugger: bool, 21 | 22 | #[clap(action, long)] 23 | list: bool, 24 | 25 | #[clap(action, long)] 26 | dump: Option, 27 | } 28 | 29 | fn main() { 30 | let cli = Command::parse(); 31 | 32 | if cli.list { 33 | for variant in Signal::value_variants() { 34 | println!("{variant}"); 35 | } 36 | } else if let Some(signal) = cli.signal { 37 | dump_test(signal, cli.use_thread, cli.dump); 38 | } else { 39 | println!("must pass --signal (see available choices with --list)"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crash-handler/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// An error that can occur when attaching or detaching a [`crate::CrashHandler`] 4 | #[derive(Debug)] 5 | pub enum Error { 6 | /// Unable to `mmap` memory 7 | OutOfMemory, 8 | /// For simplicity sake, only one [`crate::CrashHandler`] can be registered 9 | /// at any one time. 10 | HandlerAlreadyInstalled, 11 | /// An I/O or other syscall failed 12 | Io(std::io::Error), 13 | } 14 | 15 | impl std::error::Error for Error { 16 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 17 | match self { 18 | Self::Io(inner) => Some(inner), 19 | _ => None, 20 | } 21 | } 22 | } 23 | 24 | impl fmt::Display for Error { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | match self { 27 | Self::OutOfMemory => f.write_str("unable to allocate memory"), 28 | Self::HandlerAlreadyInstalled => { 29 | f.write_str("an exception handler is already installed") 30 | } 31 | Self::Io(e) => write!(f, "{}", e), 32 | } 33 | } 34 | } 35 | 36 | impl From for Error { 37 | fn from(e: std::io::Error) -> Self { 38 | Self::Io(e) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [graph] 2 | targets = [ 3 | "x86_64-unknown-linux-gnu", 4 | "x86_64-unknown-linux-musl", 5 | "x86_64-pc-windows-msvc", 6 | "x86_64-apple-darwin", 7 | "aarch64-apple-darwin", 8 | ] 9 | all-features = true 10 | # This crate is not published, exclude it since it triggers various warnings 11 | # downstream users wouldn't see 12 | exclude = ["minidumper-test"] 13 | 14 | [advisories] 15 | ignore = [] 16 | 17 | [licenses] 18 | allow = ["MIT", "Apache-2.0"] 19 | confidence-threshold = 0.8 20 | exceptions = [{ allow = ["Unicode-3.0"], name = "unicode-ident" }] 21 | 22 | [bans] 23 | multiple-versions = "deny" 24 | deny = [ 25 | # Incredibly heavyweight, we should never have a dependency on this 26 | "windows", 27 | # We should never have a dependency on openssl 28 | "openssl-sys", 29 | ] 30 | skip = [ 31 | # The crate is in the repo, so we have the path, but it's also a crates.io 32 | # dependency 33 | "crash-context", 34 | ] 35 | skip-tree = [ 36 | { crate = "polling@3.7.4", reason = "pulls in old rustix/linux-raw-sys" }, 37 | ] 38 | 39 | [bans.workspace-dependencies] 40 | 41 | [sources] 42 | unknown-registry = "deny" 43 | unknown-git = "deny" 44 | allow-git = [] 45 | 46 | [sources.allow-org] 47 | #github = ["rust-minidump"] 48 | -------------------------------------------------------------------------------- /crash-handler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crash-handler" 3 | description = "Allows running of user code during crash events" 4 | repository = "https://github.com/EmbarkStudios/crash-handling" 5 | version = "0.6.3" 6 | authors = ["Embark "] 7 | edition = "2024" 8 | license = "MIT OR Apache-2.0" 9 | readme = "README.md" 10 | documentation = "https://docs.rs/crash-handler" 11 | homepage = "https://github.com/EmbarkStudios/crash-handling/tree/main/crash-handler" 12 | keywords = ["breakpad", "minidump", "crash", "signal", "exception"] 13 | 14 | [features] 15 | default = [] 16 | # If enabled, will log out information when a signal is raised/exception thrown 17 | # but logged in a manner that is safe. 18 | debug-print = [] 19 | 20 | [dependencies] 21 | # Nicer handling of complex cfg expressions 22 | cfg-if.workspace = true 23 | # Definition of a portable crash-context that can be shared between various 24 | # crates 25 | crash-context.workspace = true 26 | # Wrapper around libc 27 | libc.workspace = true 28 | # Nicer sync primitives 29 | parking_lot.workspace = true 30 | 31 | [target.'cfg(target_os = "macos")'.dependencies] 32 | # Bindings to MacOS specific APIs that are used. Note we don't use the `mach` 33 | # crate as it is unmaintained 34 | mach2.workspace = true 35 | 36 | [dev-dependencies] 37 | sadness-generator = { path = "../sadness-generator" } 38 | -------------------------------------------------------------------------------- /crash-handler/src/windows/signal.rs: -------------------------------------------------------------------------------- 1 | //! Windows doesn't have an exception for process aborts, so we hook `SIGABRT` 2 | 3 | /// Installs our `SIGABRT` handler, returning any previously registered handler, 4 | /// which should be restored later 5 | /// 6 | /// # Safety 7 | /// 8 | /// Performs syscalls 9 | pub(crate) unsafe fn install_abort_handler() -> Result { 10 | // It would be nice to use sigaction here since it's better, but it isn't 11 | // supported on Windows :p 12 | unsafe { 13 | let old_handler = libc::signal(libc::SIGABRT, signal_handler as usize); 14 | if old_handler != usize::MAX { 15 | Ok(old_handler) 16 | } else { 17 | Err(std::io::Error::last_os_error()) 18 | } 19 | } 20 | } 21 | 22 | /// Restores the action for `SIGABRT` to the specified handler 23 | /// 24 | /// # Safety 25 | /// 26 | /// Performs syscalls 27 | #[inline] 28 | pub(crate) unsafe fn restore_abort_handler(handler: libc::sighandler_t) { 29 | unsafe { libc::signal(libc::SIGABRT, handler) }; 30 | } 31 | 32 | unsafe extern "C" fn signal_handler(signal: i32, _subcode: i32) { 33 | // Sanity check 34 | assert_eq!(signal, libc::SIGABRT); 35 | 36 | // https://github.com/chromium/crashpad/blob/fca8871ca3fb721d3afab370ca790122f9333bfd/client/crashpad_client_win.cc#L197 37 | unsafe { super::state::simulate_exception(Some(super::ExceptionCode::Abort as _)) }; 38 | } 39 | -------------------------------------------------------------------------------- /crash-context/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate exposes a platform specific [`CrashContext`] which contains the 2 | //! details for a crash (signal, exception, etc). This crate is fairly minimal 3 | //! since the intended use case is to more easily share these crash details 4 | //! between different crates without requiring lots of dependencies, and is 5 | //! currently only really made for the purposes of the crates in this repo, and 6 | //! [minidump-writer](https://github.com/rust-minidump/minidump-writer). 7 | //! 8 | //! ## Linux/Android 9 | //! 10 | //! This crate also contains a portable implementation of [`getcontext`]( 11 | //! https://man7.org/linux/man-pages/man3/getcontext.3.html), as not all libc 12 | //! implementations (notably `musl`) implement it as it has been deprecated from 13 | //! POSIX. 14 | //! 15 | //! ## Macos 16 | //! 17 | //! One major difference on Macos is that the details in the [`CrashContext`] 18 | //! cannot be transferred to another process via normal methods (eg. sockets) 19 | //! and must be sent via the criminally undocumented mach ports. This crate 20 | //! provides a `Client` and `Server` that can be used to send and receive a 21 | //! [`CrashContext`] across processes so that you don't have to suffer like I 22 | //! did. 23 | 24 | // crate-specific exceptions: 25 | #![allow(unsafe_code, nonstandard_style)] 26 | 27 | cfg_if::cfg_if! { 28 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 29 | mod linux; 30 | pub use linux::*; 31 | } else if #[cfg(target_os = "windows")] { 32 | mod windows; 33 | pub use windows::*; 34 | } else if #[cfg(target_os = "macos")] { 35 | mod mac; 36 | pub use mac::*; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /minidumper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minidumper" 3 | version = "0.8.3" 4 | description = "IPC impl for creating a minidump for a crashed process" 5 | repository = "https://github.com/EmbarkStudios/crash-handling" 6 | authors = [ 7 | "Embark ", 8 | "Jake Shadle ", 9 | ] 10 | edition = "2024" 11 | license = "MIT OR Apache-2.0" 12 | readme = "README.md" 13 | documentation = "https://docs.rs/minidumper" 14 | homepage = "https://github.com/EmbarkStudios/crash-handling/tree/main/minidumper" 15 | # Unfortunately there is no good category for this on crates.io atm 16 | categories = ["os"] 17 | keywords = ["crash", "minidump", "ipc", "out-of-process"] 18 | 19 | [dependencies] 20 | # Nicer cfg handling 21 | cfg-if.workspace = true 22 | crash-context.workspace = true 23 | libc.workspace = true 24 | # Basic log emitting 25 | log = "0.4" 26 | # Minidump writing 27 | minidump-writer = "0.11" 28 | # Event loop 29 | polling = "3.2" 30 | # Nicer locking primitives 31 | parking_lot.workspace = true 32 | # Nicer error creation 33 | thiserror = "2.0" 34 | 35 | [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] 36 | # Improved Unix domain socket support, includes features that are not available in std 37 | uds = "0.4" 38 | 39 | [target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies] 40 | # Nicer binary interop, keep aligned with minidump-writer 41 | scroll = { version = "0.12", features = ["derive"] } 42 | 43 | [dev-dependencies] 44 | # Diskwrite example 45 | crash-handler = { path = "../crash-handler" } 46 | pretty_env_logger = "0.5.0" 47 | # uuid generation 48 | uuid = { version = "1.0", features = ["v4"] } 49 | -------------------------------------------------------------------------------- /crash-context/tests/capture_context.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn captures() { 3 | fn one() { 4 | two(); 5 | } 6 | 7 | fn two() { 8 | three(); 9 | } 10 | 11 | #[allow(unsafe_code)] 12 | fn three() { 13 | cfg_if::cfg_if! { 14 | if #[cfg(target_os = "windows")] { 15 | let ctx = unsafe { 16 | let mut ctx = std::mem::MaybeUninit::zeroed(); 17 | crash_context::capture_context(ctx.as_mut_ptr()); 18 | 19 | ctx.assume_init() 20 | }; 21 | 22 | cfg_if::cfg_if! { 23 | if #[cfg(target_arch = "x86_64")] { 24 | assert!(ctx.Rbp != 0); 25 | assert!(ctx.Rsp != 0); 26 | assert!(ctx.Rip != 0); 27 | } else if #[cfg(target_arch = "x86")] { 28 | assert!(ctx.Ebp != 0); 29 | assert!(ctx.Esp != 0); 30 | assert!(ctx.Eip != 0); 31 | } 32 | } 33 | } else if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { 34 | let ctx = unsafe { 35 | let mut ctx = std::mem::MaybeUninit::zeroed(); 36 | assert_eq!(crash_context::crash_context_getcontext(ctx.as_mut_ptr()), 0); 37 | ctx.assume_init() 38 | }; 39 | 40 | let gregs = &ctx.uc_mcontext.gregs; 41 | assert!(gregs[libc::REG_RBP as usize] != 0); 42 | assert!(gregs[libc::REG_RSP as usize] != 0); 43 | assert!(gregs[libc::REG_RIP as usize] != 0); 44 | } 45 | } 46 | } 47 | 48 | one(); 49 | } 50 | -------------------------------------------------------------------------------- /crash-context/src/linux/getcontext/arm.rs: -------------------------------------------------------------------------------- 1 | std::arch::global_asm! { 2 | ".text", 3 | ".global crash_context_getcontext", 4 | ".hidden crash_context_getcontext", 5 | ".type crash_context_getcontext, #function", 6 | ".align 0", 7 | ".fnstart", 8 | "crash_context_getcontext:", 9 | 10 | // First, save r4-r11 11 | "add r1, r0, #(32 + 4 * 4)", 12 | "stm r1, {{r4-r11}}", 13 | 14 | // r12 is a scratch register, don't save it 15 | 16 | // Save sp and lr explicitly. 17 | // - sp can't be stored with stmia in Thumb-2 18 | // - STM instructions that store sp and pc are deprecated in ARM 19 | "str sp, [r0, #(32 + 13 * 4)]", 20 | "str lr, [r0, #(32 + 14 * 4)]", 21 | 22 | // Save the caller's address in 'pc' 23 | "str lr, [r0, #(32 + 15 * 4)]", 24 | 25 | // Save ucontext_t* pointer across next call 26 | "mov r4, r0", 27 | 28 | // Call sigprocmask(SIG_BLOCK, NULL, &(ucontext->uc_sigmask)) 29 | "mov r0, #0", // SIG_BLOCK 30 | "mov r1, #0", // NULL 31 | "add r2, r4, #104", // UCONTEXT_SIGMASK_OFFSET 32 | "bl sigprocmask(PLT)", 33 | 34 | /* Intentionally do not save the FPU state here. This is because on 35 | * Linux/ARM, one should instead use ptrace(PTRACE_GETFPREGS) or 36 | * ptrace(PTRACE_GETVFPREGS) to get it. 37 | * 38 | * Note that a real implementation of getcontext() would need to save 39 | * this here to allow setcontext()/swapcontext() to work correctly. 40 | */ 41 | 42 | // Restore the values of r4 and lr 43 | "mov r0, r4", 44 | "ldr lr, [r0, #(32 + 14 * 4)]", 45 | "ldr r4, [r0, #(32 + 4 * 4)]", 46 | 47 | // Return 0 48 | "mov r0, #0", 49 | "mov pc, lr", 50 | 51 | ".fnend", 52 | ".size crash_context_getcontext, . - crash_context_getcontext", 53 | } 54 | -------------------------------------------------------------------------------- /crash-handler/src/mac/signal.rs: -------------------------------------------------------------------------------- 1 | //! Macos doesn't have an exception for process aborts, so we hook `SIGABRT` 2 | use std::mem; 3 | 4 | /// Installs our `SIGABRT` handler, returning any previously registered handler, 5 | /// which should be restored later 6 | /// 7 | /// # Safety 8 | /// 9 | /// Performs syscalls 10 | pub(crate) unsafe fn install_abort_handler() -> Result { 11 | unsafe { 12 | let mut sa: libc::sigaction = mem::zeroed(); 13 | libc::sigemptyset(&mut sa.sa_mask); 14 | libc::sigaddset(&mut sa.sa_mask, libc::SIGABRT); 15 | sa.sa_sigaction = signal_handler as usize; 16 | sa.sa_flags = libc::SA_SIGINFO; 17 | 18 | let mut old_action = mem::MaybeUninit::uninit(); 19 | 20 | if libc::sigaction(libc::SIGABRT, &sa, old_action.as_mut_ptr()) != -1 { 21 | Ok(old_action.assume_init()) 22 | } else { 23 | Err(std::io::Error::last_os_error()) 24 | } 25 | } 26 | } 27 | 28 | /// Restores the action for `SIGABRT` to the specified handler 29 | /// 30 | /// # Safety 31 | /// 32 | /// Performs syscalls 33 | #[inline] 34 | pub(crate) unsafe fn restore_abort_handler(handler: libc::sigaction) { 35 | unsafe { libc::sigaction(libc::SIGABRT, &handler, std::ptr::null_mut()) }; 36 | } 37 | 38 | /// Our signal handler, transforms the signal into a [`crash_context::ExceptionInfo`] 39 | /// and sends it to the thread that handles all exceptions 40 | unsafe extern "C" fn signal_handler( 41 | signal: i32, 42 | _info: *mut libc::siginfo_t, 43 | _uc: *mut std::ffi::c_void, 44 | ) { 45 | use super::ffi; 46 | 47 | // Sanity check 48 | assert_eq!(signal, libc::SIGABRT); 49 | 50 | super::state::simulate_exception(Some(crash_context::ExceptionInfo { 51 | kind: ffi::et::EXC_SOFTWARE, 52 | code: ffi::EXC_SOFT_SIGNAL as u64, // Unix signal 53 | subcode: Some(signal as _), 54 | })); 55 | } 56 | -------------------------------------------------------------------------------- /minidumper/src/errors.rs: -------------------------------------------------------------------------------- 1 | /// Error that can occur when creating a [`crate::Client`] or [`crate::Server`], 2 | /// or generating minidumps 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum Error { 5 | /// The provided socket name or path was invalid 6 | #[error("the socket name is invalid")] 7 | InvalidName, 8 | #[cfg(target_os = "macos")] 9 | /// The provided socket name or path was invalid as a Mach port name 10 | #[error("the mach port name is invalid")] 11 | InvalidPortName, 12 | /// An error occurred while creating or communicating with a Mach port 13 | #[cfg(target_os = "macos")] 14 | #[error("the mach port name is invalid")] 15 | PortError(#[from] crash_context::ipc::Error), 16 | /// An I/O or other syscall failed 17 | #[error(transparent)] 18 | Io(#[from] std::io::Error), 19 | /// A crash request received by the server could not be processed as the 20 | /// PID for the client process was unknown or invalid 21 | #[error("client process requesting crash dump has an unknown or invalid pid")] 22 | UnknownClientPid, 23 | /// An error occurred during minidump generation 24 | #[cfg(any(target_os = "linux", target_os = "android"))] 25 | #[error(transparent)] 26 | Writer(Box), 27 | /// An error occurred during minidump generation 28 | #[cfg(target_os = "windows")] 29 | #[error(transparent)] 30 | Writer(#[from] minidump_writer::errors::Error), 31 | /// An error occurred during minidump generation 32 | #[cfg(target_os = "macos")] 33 | #[error(transparent)] 34 | Writer(#[from] minidump_writer::errors::WriterError), 35 | /// An error occurred reading or writing binary data 36 | #[cfg(any(target_os = "windows", target_os = "macos"))] 37 | #[error(transparent)] 38 | Scroll(#[from] scroll::Error), 39 | #[error("protocol error occurred: {0}")] 40 | ProtocolError(&'static str), 41 | } 42 | 43 | #[cfg(any(target_os = "linux", target_os = "android"))] 44 | impl From for Error { 45 | fn from(we: minidump_writer::minidump_writer::errors::WriterError) -> Self { 46 | Self::Writer(Box::new(we)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crash-context/src/linux/getcontext/x86.rs: -------------------------------------------------------------------------------- 1 | // Unfortunately, the asm! macro has a few really annoying limitations at the 2 | // moment 3 | // 4 | // 1. const operands are unstable 5 | // 2. cfg attributes can't be used inside the asm macro at all 6 | // 7 | // and the worst part is we need it for literally only 1 thing, using a different 8 | // offset to the fpstate in ucontext depending on whether we are targeting android 9 | // or not :( 10 | macro_rules! asm_func { 11 | ($offset:expr) => { 12 | std::arch::global_asm! { 13 | ".text", 14 | ".global crash_context_getcontext", 15 | ".hidden crash_context_getcontext", 16 | ".align 4", 17 | ".type crash_context_getcontext, @function", 18 | "crash_context_getcontext:", 19 | "movl 4(%esp), %eax", // eax = uc 20 | 21 | // Save register values 22 | "movl %ecx, 0x3c(%eax)", 23 | "movl %edx, 0x38(%eax)", 24 | "movl %ebx, 0x34(%eax)", 25 | "movl %edi, 0x24(%eax)", 26 | "movl %esi, 0x28(%eax)", 27 | "movl %ebp, 0x2c(%eax)", 28 | 29 | "movl (%esp), %edx", /* return address */ 30 | "lea 4(%esp), %ecx", /* exclude return address from stack */ 31 | "mov %edx, 0x4c(%eax)", 32 | "mov %ecx, 0x30(%eax)", 33 | 34 | "xorl %ecx, %ecx", 35 | "movw %fs, %cx", 36 | "mov %ecx, 0x18(%eax)", 37 | 38 | "movl $0, 0x40(%eax)", 39 | 40 | // Save floating point state to fpregstate, then update 41 | // the fpregs pointer to point to it 42 | stringify!(leal $offset(%eax),%ecx), 43 | "fnstenv (%ecx)", 44 | "fldenv (%ecx)", 45 | "mov %ecx, 0x60(%eax)", 46 | 47 | // Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) 48 | "leal 0x6c(%eax), %edx", 49 | "xorl %ecx, %ecx", 50 | "push %edx", /* &uc->uc_sigmask */ 51 | "push %ecx", /* NULL */ 52 | "push %ecx", /* SIGBLOCK == 0 on i386 */ 53 | "call sigprocmask@PLT", 54 | "addl $12, %esp", 55 | 56 | "movl $0, %eax", 57 | "ret", 58 | 59 | ".size crash_context_getcontext, . - crash_context_getcontext", 60 | options(att_syntax) 61 | } 62 | }; 63 | } 64 | 65 | #[cfg(target_os = "linux")] 66 | asm_func!(0xec); 67 | #[cfg(target_os = "android")] 68 | asm_func!(0x74); 69 | -------------------------------------------------------------------------------- /minidumper/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | mod errors; 4 | 5 | pub use errors::Error; 6 | use std::{fs::File, path::PathBuf}; 7 | 8 | mod ipc; 9 | pub use ipc::{Client, Server}; 10 | 11 | /// The result of a successful minidump generation. 12 | pub struct MinidumpBinary { 13 | /// The file the minidump was written to, as provided by [`ServerHandler::create_minidump_file`] 14 | pub file: File, 15 | /// The path to the file as provided by [`ServerHandler::create_minidump_file`]. 16 | pub path: PathBuf, 17 | /// The in-memory contents of the minidump, if available 18 | pub contents: Option>, 19 | } 20 | 21 | /// Actions for the [`Server`] message loop to take after a [`ServerHandler`] 22 | /// method is invoked 23 | #[derive(Copy, Clone, PartialEq, Eq)] 24 | pub enum LoopAction { 25 | /// Exits the message loop, stops processing messages, and disconnects all 26 | /// connected clients 27 | Exit, 28 | /// Continues running the message loop as normal 29 | Continue, 30 | } 31 | 32 | /// Allows user code to hook into the server to avoid hardcoding too many details 33 | pub trait ServerHandler: Send + Sync { 34 | /// Called when a crash request has been received and a backing file needs 35 | /// to be created to store it. 36 | fn create_minidump_file(&self) -> Result<(File, PathBuf), std::io::Error>; 37 | /// Called when a crash has been fully written as a minidump to the provided 38 | /// file. Also returns the full heap buffer as well. 39 | /// 40 | /// A return value of true indicates that the message loop should exit and 41 | /// stop processing messages. 42 | fn on_minidump_created(&self, result: Result) -> LoopAction; 43 | /// Called when the client sends a user message sent from the client with 44 | /// `send_message` 45 | fn on_message(&self, kind: u32, buffer: Vec); 46 | /// Optional allocation function for the buffer used to store a message. 47 | /// 48 | /// Defaults to creating a new vec. 49 | fn message_alloc(&self) -> Vec { 50 | Vec::new() 51 | } 52 | /// Called when a new client connection has been established with the Server, 53 | /// with the number of currently active client connections. 54 | fn on_client_connected(&self, _num_clients: usize) -> LoopAction { 55 | LoopAction::Continue 56 | } 57 | /// Called when a client has disconnected from the Server, with the number 58 | /// of currently active client connections. 59 | fn on_client_disconnected(&self, _num_clients: usize) -> LoopAction { 60 | LoopAction::Continue 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crash-context/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | # `🔥 crash-context` 10 | 11 | **Provides portable types containing target specific contextual information at the time of a crash** 12 | 13 | [![Embark](https://img.shields.io/badge/embark-open%20source-blueviolet.svg)](https://embark.dev) 14 | [![Embark](https://img.shields.io/badge/discord-ark-%237289da.svg?logo=discord)](https://discord.gg/dAuKfZS) 15 | [![Crates.io](https://img.shields.io/crates/v/crash-context.svg)](https://crates.io/crates/crash-context) 16 | [![Docs](https://docs.rs/crash-context/badge.svg)](https://docs.rs/crash-context) 17 | [![dependency status](https://deps.rs/repo/github/EmbarkStudios/crash-handling/status.svg)](https://deps.rs/repo/github/EmbarkStudios/crash-handling) 18 | [![Build status](https://github.com/EmbarkStudios/crash-handling/workflows/CI/badge.svg)](https://github.com/EmbarkStudios/crash-handling/actions) 19 | 20 |
21 | 22 | ## Supported targets 23 | 24 | - `aarch64-android-linux` 25 | - `aarch64-apple-darwin` 26 | - `aarch64-unknown-linux-gnu` 27 | - `aarch64-unknown-linux-musl` 28 | - `arm-linux-androideabi` 29 | - `arm-unknown-linux-gnueabi` 30 | - `arm-unknown-linux-musleabi` 31 | - `i686-linux-android` 32 | - `i686-unknown-linux-gnu` 33 | - `i686-unknown-linux-musl` 34 | - `x86_64-apple-darwin` 35 | - `x86_64-linux-android` 36 | - `x86_64-pc-windows-msvc` 37 | - `x86_64-unknown-linux-gnu` 38 | - `x86_64-unknown-linux-musl` 39 | 40 | ## Contribution 41 | 42 | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4-ff69b4.svg)](../CODE_OF_CONDUCT.md) 43 | 44 | We welcome community contributions to this project. 45 | 46 | Please read our [Contributor Guide](../CONTRIBUTING.md) for more information on how to get started. 47 | Please also read our [Contributor Terms](../CONTRIBUTING.md#contributor-terms) before you make any contributions. 48 | 49 | Any contribution intentionally submitted for inclusion in an Embark Studios project, shall comply with the Rust standard licensing model (MIT OR Apache 2.0) and therefore be dual licensed as described below, without any additional terms or conditions: 50 | 51 | ### License 52 | 53 | This contribution is dual licensed under EITHER OF 54 | 55 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 56 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 57 | 58 | at your option. 59 | 60 | For clarity, "your" refers to Embark or any other licensee/user of the contribution. 61 | -------------------------------------------------------------------------------- /crash-handler/src/windows.rs: -------------------------------------------------------------------------------- 1 | pub mod jmp; 2 | mod signal; 3 | mod state; 4 | 5 | use crate::Error; 6 | 7 | /// Possible exception codes values for the the `exception_code` field 8 | /// in the crash context. 9 | /// 10 | /// This is mainly for testing purposes, and is not exhaustive nor really accurate, 11 | /// as eg. a distinction is made between a divide by zero between integers and 12 | /// floats. 13 | #[derive(Copy, Clone)] 14 | #[repr(i32)] 15 | #[allow(overflowing_literals)] 16 | pub enum ExceptionCode { 17 | Abort = 0x40000015, // STATUS_FATAL_APP_EXIT 18 | Fpe = -1073741676, // EXCEPTION_INT_DIVIDE_BY_ZERO 19 | Illegal = -1073741795, // EXCEPTION_ILLEGAL_INSTRUCTION 20 | Segv = -1073741819, // EXCEPTION_ACCESS_VIOLATION 21 | StackOverflow = -1073741571, // EXCEPTION_STACK_OVERFLOW 22 | Trap = -2147483645, // EXCEPTION_BREAKPOINT 23 | InvalidParameter = -1073741811, // STATUS_INVALID_PARAMETER 24 | Purecall = -1073741787, // STATUS_NONCONTINUABLE_EXCEPTION 25 | User = 0xcca11ed, // https://github.com/chromium/crashpad/blob/fca8871ca3fb721d3afab370ca790122f9333bfd/util/win/exception_codes.h#L32 26 | HeapCorruption = 0xc0000374, // STATUS_HEAP_CORRUPTION 27 | } 28 | 29 | /// A Windows exception handler 30 | pub struct CrashHandler; 31 | 32 | #[allow(clippy::unused_self)] 33 | impl CrashHandler { 34 | /// Attaches the crash handler. 35 | /// 36 | /// The provided callback will be invoked if an exception is caught, 37 | /// providing a [`crate::CrashContext`] with the details of the thread where 38 | /// the exception was thrown. 39 | pub fn attach(on_crash: Box) -> Result { 40 | state::attach(on_crash)?; 41 | Ok(Self) 42 | } 43 | 44 | /// Detaches this handler, removing it from the handler stack. 45 | /// 46 | /// This is done automatically when this [`CrashHandler`] is dropped. 47 | #[inline] 48 | pub fn detach(self) { 49 | state::detach(); 50 | } 51 | 52 | /// Creates an exception with the specified exception code that is passed 53 | /// through the user provided callback. 54 | pub fn simulate_exception(&self, exception_code: Option) -> crate::CrashEventResult { 55 | // Normally this would be an unsafe function, since this unsafe encompasses 56 | // the entirety of the body, however the user is really not required to 57 | // uphold any guarantees on their end, so no real need to declare the 58 | // function itself unsafe. 59 | unsafe { state::simulate_exception(exception_code) } 60 | } 61 | } 62 | 63 | impl Drop for CrashHandler { 64 | fn drop(&mut self) { 65 | state::detach(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crash-handler/src/mac.rs: -------------------------------------------------------------------------------- 1 | mod ffi; 2 | mod signal; 3 | mod state; 4 | 5 | /// High level exception types 6 | /// 7 | /// `exception_types.h` 8 | #[repr(i32)] 9 | pub enum ExceptionType { 10 | /// Could not access memory. (SIGSEGV/SIGBUS) 11 | /// 12 | /// Code field contains `kern_return_t` describing error. 13 | /// Subcode field contains bad memory address. 14 | BadAccess = 1, 15 | /// Instruction failed. (SIGILL) 16 | /// 17 | /// Illegal or undfined instruction or operand. 18 | BadInstruction = 2, 19 | /// Arithmetic exception (SIGFPE) 20 | /// 21 | /// Exact nature of the exception is in code field. 22 | Arithmetic = 3, 23 | /// Emulation instruction 24 | /// 25 | /// Emulation support instruction encountered 26 | /// Details in code and subcode fields. 27 | Emulation = 4, 28 | /// Software generated exception 29 | /// 30 | /// Exaction exception is in the code field. 31 | /// Codes 0 - 0xffff reserved to hardware. 32 | /// Codes 0x10000 - 0x1ffff reserved for OS emulation (Unix) 33 | Software = 5, 34 | /// Trace, breakpoint, etc 35 | /// 36 | /// Details in the code field 37 | Breakpoint = 6, 38 | /// System calls 39 | SysCall = 7, 40 | /// Mach system calls 41 | MachSysCall = 8, 42 | /// RPC alert 43 | RpcAlert = 9, 44 | /// Abnormal process exit 45 | Crash = 10, 46 | /// Hit resource consumption limit 47 | /// 48 | /// Exact resource is in the code field. 49 | Resource = 11, 50 | /// Violated guarded resource protections 51 | Guard = 12, 52 | /// Abnormal process exited to corpse state 53 | CorpseNotify = 13, 54 | } 55 | 56 | /// A Macos exception handler 57 | pub struct CrashHandler; 58 | 59 | #[allow(clippy::unused_self)] 60 | impl CrashHandler { 61 | /// Attaches the exception handler. 62 | /// 63 | /// The provided callback will be invoked if an exception is caught, 64 | /// providing a [`crate::CrashContext`] with the details of the thread where 65 | /// the exception was thrown. 66 | pub fn attach(on_crash: Box) -> Result { 67 | state::attach(on_crash)?; 68 | Ok(Self) 69 | } 70 | 71 | /// Detaches the handler. 72 | /// 73 | /// This is done automatically when [`CrashHandler`] is dropped. 74 | #[inline] 75 | pub fn detach(self) { 76 | state::detach(false); 77 | } 78 | 79 | // Raises the specified user exception 80 | #[inline] 81 | pub fn simulate_exception(&self, exception_info: Option) -> bool { 82 | state::simulate_exception(exception_info) 83 | } 84 | } 85 | 86 | impl Drop for CrashHandler { 87 | fn drop(&mut self) { 88 | state::detach(false); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crash-context/src/linux/getcontext/riscv64.rs: -------------------------------------------------------------------------------- 1 | // UCONTEXT_SIGMASK_OFFSET = 40 2 | // MCONTEXT_GREGS_OFFSET = 168 3 | // MCONTEXT_GREGS_SIZE = 8 4 | 5 | std::arch::global_asm! { 6 | ".text", 7 | ".attribute arch, \"rv64gc\"", 8 | ".globl crash_context_getcontext", 9 | ".hidden crash_context_getcontext", 10 | ".type crash_context_getcontext, @function", 11 | ".align 0", 12 | ".cfi_startproc", 13 | "crash_context_getcontext:", 14 | 15 | // Save general registers 16 | "sd ra, 0xa8(a0)", 17 | "sd ra, 0xb0(a0)", 18 | "sd sp, 0xb8(a0)", 19 | "sd gp, 0xc0(a0)", 20 | "sd tp, 0xc8(a0)", 21 | "sd t0, 0xd0(a0)", 22 | "sd t1, 0xd8(a0)", 23 | "sd t2, 0xe0(a0)", 24 | "sd s0, 0xe8(a0)", 25 | "sd s1, 0xf0(a0)", 26 | "sd a0, 0xf8(a0)", 27 | "sd a1, 0x100(a0)", 28 | "sd a2, 0x108(a0)", 29 | "sd a3, 0x110(a0)", 30 | "sd a4, 0x118(a0)", 31 | "sd a5, 0x120(a0)", 32 | "sd a6, 0x128(a0)", 33 | "sd a7, 0x130(a0)", 34 | "sd s2, 0x138(a0)", 35 | "sd s3, 0x140(a0)", 36 | "sd s4, 0x148(a0)", 37 | "sd s5, 0x150(a0)", 38 | "sd s6, 0x158(a0)", 39 | "sd s7, 0x160(a0)", 40 | "sd s8, 0x168(a0)", 41 | "sd s9, 0x170(a0)", 42 | "sd s10, 0x178(a0)", 43 | "sd s11, 0x180(a0)", 44 | "sd t3, 0x188(a0)", 45 | "sd t4, 0x190(a0)", 46 | "sd t5, 0x198(a0)", 47 | "sd t6 , 0x1a0(a0)", 48 | 49 | // Save floating point registers 50 | "frsr a1", // Save CSR 51 | "fsd ft0, 0x1a8(a0)", 52 | "fsd ft1, 0x1b0(a0)", 53 | "fsd ft2, 0x1b8(a0)", 54 | "fsd ft3, 0x1c0(a0)", 55 | "fsd ft4, 0x1c8(a0)", 56 | "fsd ft5, 0x1d0(a0)", 57 | "fsd ft6, 0x1d8(a0)", 58 | "fsd ft7, 0x1e0(a0)", 59 | "fsd fs0, 0x1e8(a0)", 60 | "fsd fs1, 0x1f0(a0)", 61 | "fsd fa0, 0x1f8(a0)", 62 | "fsd fa1, 0x200(a0)", 63 | "fsd fa2, 0x208(a0)", 64 | "fsd fa3, 0x210(a0)", 65 | "fsd fa4, 0x218(a0)", 66 | "fsd fa5, 0x220(a0)", 67 | "fsd fa6, 0x228(a0)", 68 | "fsd fa7, 0x230(a0)", 69 | "fsd fs2, 0x238(a0)", 70 | "fsd fs3, 0x240(a0)", 71 | "fsd fs4, 0x248(a0)", 72 | "fsd fs5, 0x250(a0)", 73 | "fsd fs6, 0x258(a0)", 74 | "fsd fs7, 0x260(a0)", 75 | "fsd fs8, 0x268(a0)", 76 | "fsd fs9, 0x270(a0)", 77 | "fsd fs10, 0x278(a0)", 78 | "fsd fs11, 0x280(a0)", 79 | "fsd ft8, 0x288(a0)", 80 | "fsd ft9, 0x290(a0)", 81 | "fsd ft10, 0x298(a0)", 82 | "fsd ft11, 0x2a0(a0)", 83 | "sw a1, 0x2a8(a0)", 84 | 85 | "mv a1, zero", 86 | "add a2, a0, 40", // UCONTEXT_SIGMASK_OFFSET 87 | "li a3, 8", // _NSIG8 88 | "mv a0, zero", 89 | "li a7, 135", // __NR_rt_sigprocmask 90 | "ecall", 91 | 92 | "mv a0, zero", 93 | "ret", 94 | 95 | ".cfi_endproc", 96 | ".size crash_context_getcontext, . - crash_context_getcontext", 97 | } 98 | -------------------------------------------------------------------------------- /minidumper/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | # `🔥 minidumper` 10 | 11 | **IPC implementation for creating a minidump for a crashed process** 12 | 13 | [![Embark](https://img.shields.io/badge/embark-open%20source-blueviolet.svg)](https://embark.dev) 14 | [![Embark](https://img.shields.io/badge/discord-ark-%237289da.svg?logo=discord)](https://discord.gg/dAuKfZS) 15 | [![Crates.io](https://img.shields.io/crates/v/minidumper.svg)](https://crates.io/crates/minidumper) 16 | [![Docs](https://docs.rs/minidumper/badge.svg)](https://docs.rs/minidumper) 17 | [![dependency status](https://deps.rs/repo/github/EmbarkStudios/minidumper/status.svg)](https://deps.rs/repo/github/EmbarkStudios/minidumper) 18 | [![Build status](https://github.com/EmbarkStudios/crash-handling/workflows/CI/badge.svg)](https://github.com/EmbarkStudios/crash-handling/actions) 19 | 20 |
21 | 22 | This crate supplies a client and server IPC implementation for communicating between a process that _may_ crash (client) and a monitor (server) process. 23 | 24 | The client can communicate application-specific state via [`Client::send_message`], and, if a crash occurs, can use [`Client::request_dump`] to request a minidump be created. The [`Server`] uses a user implemented [`ServerHandler`] to handle the messages sent by the client, and provides a way to create the minidump file where a requested crash can be written to, as well as a callback when a minidump is finished writing (both on failure and success) to perform whatever additional steps make sense for the application, such as transmission of the minidump to an external HTTP service for processing or the like. 25 | 26 | ## Contribution 27 | 28 | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4-ff69b4.svg)](../CODE_OF_CONDUCT.md) 29 | 30 | We welcome community contributions to this project. 31 | 32 | Please read our [Contributor Guide](../CONTRIBUTING.md) for more information on how to get started. 33 | Please also read our [Contributor Terms](../CONTRIBUTING.md#contributor-terms) before you make any contributions. 34 | 35 | Any contribution intentionally submitted for inclusion in an Embark Studios project, shall comply with the Rust standard licensing model (MIT OR Apache 2.0) and therefore be dual licensed as described below, without any additional terms or conditions: 36 | 37 | ### License 38 | 39 | This contribution is dual licensed under EITHER OF 40 | 41 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 42 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 43 | 44 | at your option. 45 | 46 | For clarity, "your" refers to Embark or any other licensee/user of the contribution. 47 | -------------------------------------------------------------------------------- /crash-handler/src/windows/jmp.rs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright © 2016 Franklin "Snaipe" Mathieu 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | //! Provides an implementation of [`setjmp`] and [`longjmp`], as unfortunately the 24 | //! implementation in MSVCRT actually unwinds the stack 25 | 26 | #![cfg(target_arch = "x86_64")] 27 | 28 | std::arch::global_asm! { 29 | ".text", 30 | ".global ehsetjmp", 31 | ".align 4", 32 | ".cfi_startproc", 33 | "ehsetjmp:", 34 | "mov %rbx, 8(%rcx)", 35 | "mov %rsp, 16(%rcx)", 36 | "mov %rbp, 24(%rcx)", 37 | "mov %rsi, 32(%rcx)", 38 | "mov %rdi, 40(%rcx)", 39 | "mov %r12, 48(%rcx)", 40 | "mov %r13, 56(%rcx)", 41 | "mov %r14, 64(%rcx)", 42 | "mov %r15, 72(%rcx)", 43 | "pop 80(%rcx)", // rip 44 | "push 80(%rcx)", 45 | 46 | "xor %rax, %rax", 47 | "ret", 48 | ".cfi_endproc", 49 | options(att_syntax) 50 | } 51 | 52 | std::arch::global_asm! { 53 | ".text", 54 | ".global ehlongjmp", 55 | ".align 4", 56 | ".cfi_startproc", 57 | "ehlongjmp:", 58 | "mov 8(%rcx), %rbx", 59 | "mov 16(%rcx), %rsp", 60 | "mov 24(%rcx), %rbp", 61 | "mov 32(%rcx), %rsi", 62 | "mov 40(%rcx), %rdi", 63 | "mov 48(%rcx), %r12", 64 | "mov 56(%rcx), %r13", 65 | "mov 64(%rcx), %r14", 66 | "mov 72(%rcx), %r15", 67 | "pop %rax", 68 | "push 80(%rcx)", 69 | 70 | "mov %rdx, %rax", // return value 71 | "ret", 72 | ".cfi_endproc", 73 | options(att_syntax) 74 | } 75 | 76 | #[repr(C)] 77 | pub struct JmpBuf { 78 | __jmp_buf: [u128; 16], 79 | } 80 | 81 | #[allow(improper_ctypes)] // u128 is actually ok on x86_64 :) 82 | unsafe extern "C" { 83 | #[link_name = "ehsetjmp"] 84 | pub fn setjmp(jb: *mut JmpBuf) -> i32; 85 | #[link_name = "ehlongjmp"] 86 | pub fn longjmp(jb: *mut JmpBuf, val: i32) -> !; 87 | } 88 | -------------------------------------------------------------------------------- /crash-context/src/mac/guard.rs: -------------------------------------------------------------------------------- 1 | //! Contains types and helpers for dealing with `EXC_GUARD` exceptions. 2 | //! 3 | //! `EXC_GUARD` exceptions embed details about the guarded resource in the `code` 4 | //! and `subcode` fields of the exception 5 | //! 6 | //! See 7 | //! for the top level types that this module wraps. 8 | 9 | use mach2::exception_types::EXC_GUARD; 10 | 11 | /// The set of possible guard kinds 12 | #[derive(Copy, Clone, Debug)] 13 | #[repr(u8)] 14 | pub enum GuardKind { 15 | /// Null variant 16 | None = 0, 17 | /// A `mach_port_t` 18 | MachPort = 1, 19 | /// File descriptor 20 | Fd = 2, 21 | /// Userland assertion 22 | User = 3, 23 | /// Vnode 24 | Vnode = 4, 25 | /// Virtual memory operation 26 | VirtualMemory = 5, 27 | /// Rejected system call trap 28 | RejectedSyscall = 6, 29 | } 30 | 31 | #[inline] 32 | pub fn extract_guard_kind(code: u64) -> u8 { 33 | ((code >> 61) & 0x7) as u8 34 | } 35 | 36 | #[inline] 37 | pub fn extract_guard_flavor(code: u64) -> u32 { 38 | ((code >> 32) & 0x1fffffff) as u32 39 | } 40 | 41 | #[inline] 42 | pub fn extract_guard_target(code: u64) -> u32 { 43 | code as u32 44 | } 45 | 46 | /// The extracted details of an `EXC_GUARD` exception 47 | pub struct GuardException { 48 | /// One of [`GuardKind`] 49 | pub kind: u8, 50 | /// The specific guard flavor that was violated, specific to each `kind` 51 | pub flavor: u32, 52 | /// The resource that was guarded 53 | pub target: u32, 54 | /// Target specific guard information 55 | pub identifier: u64, 56 | } 57 | 58 | /// Extracts the guard details from an exceptions code and subcode 59 | /// 60 | /// code: 61 | /// +-------------------+----------------+--------------+ 62 | /// |[63:61] guard type | [60:32] flavor | [31:0] target| 63 | /// +-------------------+----------------+--------------+ 64 | /// 65 | /// subcode: 66 | /// +---------------------------------------------------+ 67 | /// |[63:0] guard identifier | 68 | /// +---------------------------------------------------+ 69 | #[inline] 70 | pub fn extract_guard_exception(code: u64, subcode: u64) -> GuardException { 71 | GuardException { 72 | kind: extract_guard_kind(code), 73 | flavor: extract_guard_flavor(code), 74 | target: extract_guard_target(code), 75 | identifier: subcode, 76 | } 77 | } 78 | 79 | impl super::ExceptionInfo { 80 | /// If this is an `EXC_GUARD` exception, retrieves the exception metadata 81 | /// from the code, otherwise returns `None` 82 | pub fn guard_exception(&self) -> Option { 83 | if self.kind != EXC_GUARD { 84 | return None; 85 | } 86 | 87 | Some(extract_guard_exception( 88 | self.code, 89 | self.subcode.unwrap_or_default(), 90 | )) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crash-handler/src/linux/jmp.rs: -------------------------------------------------------------------------------- 1 | //! FFI bindings for non-local goto 2 | //! 3 | //! ``` 4 | //! use crash_handler::jmp; 5 | //! 6 | //! unsafe { 7 | //! let mut jmp_buf = std::mem::MaybeUninit::uninit(); 8 | //! 9 | //! let val = jmp::sigsetjmp(jmp_buf.as_mut_ptr(), 1); 10 | //! 11 | //! if val == 0 { 12 | //! jmp::siglongjmp(jmp_buf.as_mut_ptr(), 22); 13 | //! } else { 14 | //! assert_eq!(val, 22); 15 | //! } 16 | //! } 17 | //! ``` 18 | 19 | cfg_if::cfg_if! { 20 | if #[cfg(target_arch = "x86_64")] { 21 | #[repr(C)] 22 | #[doc(hidden)] 23 | #[allow(non_camel_case_types)] 24 | pub struct __jmp_buf([u64; 8]); 25 | } else if #[cfg(target_arch = "x86")] { 26 | #[repr(C)] 27 | #[doc(hidden)] 28 | #[allow(non_camel_case_types)] 29 | pub struct __jmp_buf([u32; 6]); 30 | } else if #[cfg(target_arch = "arm")] { 31 | #[repr(C)] 32 | #[doc(hidden)] 33 | #[allow(non_camel_case_types)] 34 | pub struct __jmp_buf([u64; 32]); 35 | } else if #[cfg(target_arch = "aarch64")] { 36 | #[repr(C)] 37 | #[doc(hidden)] 38 | #[allow(non_camel_case_types)] 39 | pub struct __jmp_buf([u64; 22]); 40 | } else if #[cfg(target_arch = "riscv64")] { 41 | #[repr(C)] 42 | #[doc(hidden)] 43 | #[allow(non_camel_case_types)] 44 | pub struct __jmp_buf { 45 | __pc: u64, 46 | __regs: [u64; 12], 47 | __sp: u64, 48 | __fpregs: [f64; 12], 49 | } 50 | } 51 | } 52 | 53 | /// A jump buffer. 54 | /// 55 | /// This is essentially the register state of a point in execution at the time 56 | /// of a [`sigsetjmp`] call that can be returned to by passing this buffer to 57 | /// [`siglongjmp`]. 58 | #[repr(C)] 59 | pub struct JmpBuf { 60 | /// CPU context 61 | __jmp_buf: __jmp_buf, 62 | /// Whether the signal mask was saved 63 | __fl: u32, 64 | /// Saved signal mask 65 | __ss: [u32; 32], 66 | } 67 | 68 | unsafe extern "C" { 69 | /// Set jump point for a non-local goto. 70 | /// 71 | /// The return value will be 0 if this is a direct invocation (ie the "first 72 | /// time" `sigsetjmp` is executed), and will be the value passed to `siglongjmp` 73 | /// otherwise. 74 | /// 75 | /// See [sigsetjmp](https://man7.org/linux/man-pages/man3/sigsetjmp.3p.html) 76 | /// for more information. 77 | #[cfg_attr(target_env = "gnu", link_name = "__sigsetjmp")] 78 | pub fn sigsetjmp(jb: *mut JmpBuf, save_mask: i32) -> i32; 79 | /// Non-local goto with signal handling 80 | /// 81 | /// The value passed here will be returned by `sigsetjmp` when returning 82 | /// to that callsite. Note that passing a value of 0 here will be changed 83 | /// to a 1. 84 | /// 85 | /// See [siglongjmp](https://man7.org/linux/man-pages/man3/siglongjmp.3p.html) 86 | /// for more information. 87 | pub fn siglongjmp(jb: *mut JmpBuf, val: i32) -> !; 88 | } 89 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # add the below section to `.cargo/config.toml` 2 | # [target.x86_64-pc-windows-msvc] 3 | # linker = "lld-link" 4 | 5 | [target.'cfg(all())'] 6 | rustflags = [ 7 | # BEGIN - Embark standard lints v6 for Rust 1.55+ 8 | # do not change or add/remove here, but one can add exceptions after this section 9 | # for more info see: 10 | "-Dunsafe_code", 11 | "-Wclippy::all", 12 | "-Wclippy::await_holding_lock", 13 | "-Wclippy::char_lit_as_u8", 14 | "-Wclippy::checked_conversions", 15 | "-Wclippy::dbg_macro", 16 | "-Wclippy::debug_assert_with_mut_call", 17 | "-Wclippy::doc_markdown", 18 | "-Wclippy::empty_enum", 19 | "-Wclippy::enum_glob_use", 20 | "-Wclippy::exit", 21 | "-Wclippy::expl_impl_clone_on_copy", 22 | "-Wclippy::explicit_deref_methods", 23 | "-Wclippy::explicit_into_iter_loop", 24 | "-Wclippy::fallible_impl_from", 25 | "-Wclippy::filter_map_next", 26 | "-Wclippy::flat_map_option", 27 | "-Wclippy::float_cmp_const", 28 | "-Wclippy::fn_params_excessive_bools", 29 | "-Wclippy::from_iter_instead_of_collect", 30 | "-Wclippy::if_let_mutex", 31 | "-Wclippy::implicit_clone", 32 | "-Wclippy::imprecise_flops", 33 | "-Wclippy::inefficient_to_string", 34 | "-Wclippy::invalid_upcast_comparisons", 35 | "-Wclippy::large_digit_groups", 36 | "-Wclippy::large_stack_arrays", 37 | "-Wclippy::large_types_passed_by_value", 38 | "-Wclippy::let_unit_value", 39 | "-Wclippy::linkedlist", 40 | "-Wclippy::lossy_float_literal", 41 | "-Wclippy::macro_use_imports", 42 | "-Wclippy::manual_ok_or", 43 | "-Wclippy::map_err_ignore", 44 | "-Wclippy::map_flatten", 45 | "-Wclippy::map_unwrap_or", 46 | "-Wclippy::match_same_arms", 47 | "-Wclippy::match_wild_err_arm", 48 | "-Wclippy::match_wildcard_for_single_variants", 49 | "-Wclippy::mem_forget", 50 | "-Wclippy::missing_enforced_import_renames", 51 | "-Wclippy::mut_mut", 52 | "-Wclippy::mutex_integer", 53 | "-Wclippy::needless_borrow", 54 | "-Wclippy::needless_continue", 55 | "-Wclippy::needless_for_each", 56 | "-Wclippy::option_option", 57 | "-Wclippy::path_buf_push_overwrite", 58 | "-Wclippy::ptr_as_ptr", 59 | "-Wclippy::rc_mutex", 60 | "-Wclippy::ref_option_ref", 61 | "-Wclippy::rest_pat_in_fully_bound_structs", 62 | "-Wclippy::same_functions_in_if_condition", 63 | "-Wclippy::semicolon_if_nothing_returned", 64 | "-Wclippy::single_match_else", 65 | "-Wclippy::string_add_assign", 66 | "-Wclippy::string_add", 67 | "-Wclippy::string_lit_as_bytes", 68 | "-Wclippy::string_to_string", 69 | "-Wclippy::todo", 70 | "-Wclippy::trait_duplication_in_bounds", 71 | "-Wclippy::unimplemented", 72 | "-Wclippy::unnested_or_patterns", 73 | "-Wclippy::unused_self", 74 | "-Wclippy::useless_transmute", 75 | "-Wclippy::verbose_file_reads", 76 | "-Wclippy::zero_sized_map_values", 77 | "-Wfuture_incompatible", 78 | "-Wnonstandard_style", 79 | "-Wrust_2018_idioms", 80 | "-Wunexpected_cfgs", 81 | # END - Embark standard lints v6 for Rust 1.55+ 82 | ] 83 | -------------------------------------------------------------------------------- /crash-context/src/linux/getcontext/aarch64.rs: -------------------------------------------------------------------------------- 1 | // GREGS_OFFSET = 184 2 | // REGISTER_SIZE = 8 3 | // SIMD_REGISTER_SIZE = 16 4 | 5 | std::arch::global_asm! { 6 | ".text", 7 | ".global crash_context_getcontext", 8 | ".hidden crash_context_getcontext", 9 | ".type crash_context_getcontext, #function", 10 | ".align 4", 11 | ".cfi_startproc", 12 | "crash_context_getcontext:", 13 | 14 | // The saved context will return to the getcontext() call point 15 | // with a return value of 0 16 | "str xzr, [x0, 184]", // GREGS_OFFSET 17 | 18 | "stp x18, x19, [x0, 328]", // GREGS_OFFSET + 18 * REGISTER_SIZE 19 | "stp x20, x21, [x0, 344]", // GREGS_OFFSET + 20 * REGISTER_SIZE 20 | "stp x22, x23, [x0, 360]", // GREGS_OFFSET + 22 * REGISTER_SIZE 21 | "stp x24, x25, [x0, 376]", // GREGS_OFFSET + 24 * REGISTER_SIZE 22 | "stp x26, x27, [x0, 392]", // GREGS_OFFSET + 26 * REGISTER_SIZE 23 | "stp x28, x29, [x0, 408]", // GREGS_OFFSET + 28 * REGISTER_SIZE 24 | "str x30, [x0, 424]", // GREGS_OFFSET + 30 * REGISTER_SIZE 25 | 26 | // Place LR into the saved PC, this will ensure that when switching to this 27 | // saved context with setcontext() control will pass back to the caller of 28 | // getcontext(), we have already arranged to return the appropriate return 29 | // value in x0 above. 30 | "str x30, [x0, 440]", 31 | 32 | // Save the current SP 33 | "mov x2, sp", 34 | "str x2, [x0, 432]", 35 | 36 | // Initialize the pstate. 37 | "str xzr, [x0, 448]", 38 | 39 | // Figure out where to place the first context extension block. 40 | "add x2, x0, #464", 41 | 42 | // Write the context extension fpsimd header. 43 | // FPSIMD_MAGIC = 0x46508001 44 | "mov w3, #(0x46508001 & 0xffff)", 45 | "movk w3, #(0x46508001 >> 16), lsl #16", 46 | "str w3, [x2, #0]", // FPSIMD_CONTEXT_MAGIC_OFFSET 47 | "mov w3, #528", // FPSIMD_CONTEXT_SIZE 48 | "str w3, [x2, #4]", // FPSIMD_CONTEXT_SIZE_OFFSET 49 | 50 | // Fill in the FP SIMD context. 51 | "add x3, x2, #144", // VREGS_OFFSET + 8 * SIMD_REGISTER_SIZE 52 | "stp d8, d9, [x3], #32", 53 | "stp d10, d11, [x3], #32", 54 | "stp d12, d13, [x3], #32", 55 | "stp d14, d15, [x3], #32", 56 | 57 | "add x3, x2, 8", // FPSR_OFFSET 58 | 59 | "mrs x4, fpsr", 60 | "str w4, [x3]", 61 | 62 | "mrs x4, fpcr", 63 | "str w4, [x3, 4]", // FPCR_OFFSET - FPSR_OFFSET 64 | 65 | // Write the termination context extension header. 66 | "add x2, x2, #528", // FPSIMD_CONTEXT_SIZE 67 | 68 | "str xzr, [x2, #0]", // FPSIMD_CONTEXT_MAGIC_OFFSET 69 | "str xzr, [x2, #4]", // FPSIMD_CONTEXT_SIZE_OFFSET 70 | 71 | // Grab the signal mask 72 | // rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) 73 | "add x2, x0, #40", // UCONTEXT_SIGMASK_OFFSET 74 | "mov x0, #0", // SIG_BLOCK 75 | "mov x1, #0", // NULL 76 | "mov x3, #(64 / 8)", // _NSIG / 8 77 | "mov x8, #135", // __NR_rt_sigprocmask 78 | "svc 0", 79 | 80 | // Return x0 for success 81 | "mov x0, 0", 82 | "ret", 83 | 84 | ".cfi_endproc", 85 | ".size crash_context_getcontext, . - crash_context_getcontext", 86 | } 87 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@embark-studios.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/workflows/rust-ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | tags: 6 | - "*" 7 | pull_request: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | name: CI 14 | jobs: 15 | lint: 16 | name: Lint 17 | strategy: 18 | matrix: 19 | os: 20 | - ubuntu-24.04 21 | - windows-2022 22 | - macos-14 23 | - ubuntu-24.04-arm 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: dtolnay/rust-toolchain@stable 28 | with: 29 | components: "clippy, rustfmt" 30 | - uses: Swatinem/rust-cache@v2 31 | 32 | # make sure all code has been formatted with rustfmt 33 | - name: check rustfmt 34 | run: cargo fmt -- --check --color always 35 | 36 | # run clippy to verify we have no warnings 37 | - run: cargo fetch 38 | - name: cargo clippy 39 | run: | 40 | cargo clippy --all-targets --all-features -- -D warnings 41 | 42 | test: 43 | name: Test 44 | strategy: 45 | matrix: 46 | os: 47 | - ubuntu-24.04 48 | - windows-2022 49 | - macos-14 50 | - ubuntu-24.04-arm 51 | runs-on: ${{ matrix.os }} 52 | steps: 53 | - uses: actions/checkout@v4 54 | - uses: dtolnay/rust-toolchain@stable 55 | - uses: Swatinem/rust-cache@v2 56 | - run: cargo fetch 57 | - name: cargo test build 58 | run: cargo build --tests --all-features 59 | - name: cargo test 60 | run: cargo test --all-features 61 | 62 | build-android: 63 | name: Build sources 64 | runs-on: ubuntu-24.04 65 | strategy: 66 | matrix: 67 | job: 68 | - { target: aarch64-linux-android, toolchain: stable } 69 | - { target: arm-unknown-linux-gnueabihf, toolchain: stable } 70 | steps: 71 | - uses: actions/checkout@v4 72 | - name: Install Rust 73 | uses: dtolnay/rust-toolchain@master 74 | with: 75 | toolchain: ${{ matrix.job.toolchain }} 76 | target: ${{ matrix.job.target }} 77 | # - uses: Swatinem/rust-cache@v2 78 | # with: 79 | # key: "1.86.0" 80 | - name: Build 81 | # We need to install cross from git, because https://github.com/cross-rs/cross/issues/1222 82 | # is still unreleased (it's been almost a year since the last release) 83 | # and we can't use 1.67.0 any longer because some dependencies (sigh, 84 | # home, really?) have a higher MSRV...so... 85 | run: | 86 | cargo install cross --git https://github.com/cross-rs/cross --rev 185398b 87 | cross build --release --target ${{ matrix.job.target }} --verbose --all-targets 88 | 89 | deny-check: 90 | name: cargo-deny 91 | runs-on: ubuntu-24.04 92 | steps: 93 | - uses: actions/checkout@v4 94 | - uses: EmbarkStudios/cargo-deny-action@v2 95 | 96 | publish-check: 97 | name: Publish Check 98 | runs-on: ubuntu-24.04 99 | steps: 100 | - uses: actions/checkout@v4 101 | - uses: dtolnay/rust-toolchain@stable 102 | - uses: Swatinem/rust-cache@v2 103 | - run: cargo fetch 104 | - name: cargo publish check 105 | run: | 106 | cargo publish --dry-run --manifest-path crash-context/Cargo.toml 107 | cargo publish --dry-run --manifest-path crash-handler/Cargo.toml 108 | cargo publish --dry-run --manifest-path minidumper/Cargo.toml 109 | cargo publish --dry-run --manifest-path sadness-generator/Cargo.toml 110 | 111 | all: 112 | runs-on: ubuntu-24.04 113 | needs: [lint, test, build-android, deny-check, publish-check] 114 | steps: 115 | - run: echo "All test jobs passed" 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | # `🔥 crash-handling` 10 | 11 | **Set of utility crates for catching and handling crashes** 12 | 13 | [![Embark](https://img.shields.io/badge/embark-open%20source-blueviolet.svg)](https://embark.dev) 14 | [![Embark](https://img.shields.io/badge/discord-ark-%237289da.svg?logo=discord)](https://discord.gg/dAuKfZS) 15 | [![Build status](https://github.com/EmbarkStudios/crash-handling/workflows/CI/badge.svg)](https://github.com/EmbarkStudios/crash-handling/actions) 16 | 17 |
18 | 19 | ## Crates 20 | 21 | Name | Description | crates.io | docs.rs 22 | --- | --- | --- | --- 23 | [`crash-context`](crash-context) | Provides portable types containing target specific contextual information at the time of a crash | [![Crates.io](https://img.shields.io/crates/v/crash-context.svg)](https://crates.io/crates/crash-context) | [![Docs](https://docs.rs/crash-context/badge.svg)](https://docs.rs/crash-context) 24 | [`sadness-generator`](sadness-generator) | Provides various ways to make your program sad | [![Crates.io](https://img.shields.io/crates/v/sadness-generator.svg)](https://crates.io/crates/sadness-generator) | [![Docs](https://docs.rs/sadness-generator/badge.svg)](https://docs.rs/sadness-generator) 25 | [`crash-handler`](crash-handler) | Provides a crash handler to invoke a user supplied callback with the contextual information of a crash | [![Crates.io](https://img.shields.io/crates/v/crash-handler.svg)](https://crates.io/crates/crash-handler) | [![Docs](https://docs.rs/crash-handler/badge.svg)](https://docs.rs/crash-handler) 26 | [`minidumper`](minidumper) | Provides an IPC client and server for creating minidumps for an external process | [![Crates.io](https://img.shields.io/crates/v/minidumper.svg)](https://crates.io/crates/minidumper) | [![Docs](https://docs.rs/minidumper/badge.svg)](https://docs.rs/minidumper) 27 | 28 | ## Notable external crate 29 | 30 | [`minidump-writer`](https://github.com/rust-minidump/minidump-writer) does the heavy lifting of inspecting a crashed process and writing a [minidump](https://github.com/rust-minidump/rust-minidump) for it. This is used by the [`minidumper::Service`](https://docs.rs/minidumper/latest/minidumper/struct.Server.html) to write a minidump to the user specified location when a [`minidumper::Client`](https://docs.rs/minidumper/latest/minidumper/struct.Client.html) detects a crash and requests a dump be created. 31 | 32 | ## Supported targets 33 | 34 | | Arch | unknown-linux-gnu | unknown-linux-musl | linux-android | pc-windows-msvc | apple-darwin 35 | --- | --- | --- | --- | --- | --- 36 | `x86_64` | ✅ | ✅ | ❌ | ✅ | ✅ 37 | `i686` | ✅ | ✅ | ❌ | ❌ | ❌ | 38 | `arm` | ✅ | ✅ | ✅ | ❌ | ❌ 39 | `aarch64` | ✅ | ✅ | ✅ | ❌ | ✅ 40 | 41 | ## Contribution 42 | 43 | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4-ff69b4.svg)](CODE_OF_CONDUCT.md) 44 | 45 | We welcome community contributions to this project. 46 | 47 | Please read our [Contributor Guide](CONTRIBUTING.md) for more information on how to get started. 48 | Please also read our [Contributor Terms](CONTRIBUTING.md#contributor-terms) before you make any contributions. 49 | 50 | Any contribution intentionally submitted for inclusion in an Embark Studios project, shall comply with the Rust standard licensing model (MIT OR Apache 2.0) and therefore be dual licensed as described below, without any additional terms or conditions: 51 | 52 | ### License 53 | 54 | This contribution is dual licensed under EITHER OF 55 | 56 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 57 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 58 | 59 | at your option. 60 | 61 | For clarity, "your" refers to Embark or any other licensee/user of the contribution. 62 | -------------------------------------------------------------------------------- /sadness-generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Changelog 4 | 5 | All notable changes to this project will be documented in this file. 6 | 7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 9 | 10 | 11 | ## [Unreleased] - ReleaseDate 12 | ## [0.6.0] - 2024-06-08 13 | ### Changed 14 | - Update MSRV to 1.62.0 15 | 16 | ## [0.5.0] - 2022-11-17 17 | ### Changed 18 | - [PR#63](https://github.com/EmbarkStudios/crash-handling/pull/63) added `SadnessFlavor::Abort` to Windows, and `raise_abort` now uses `libc::abort` instead of `std::process::abort` to have consistent behavior between all targets. 19 | 20 | ### Fixed 21 | - [PR#63](https://github.com/EmbarkStudios/crash-handling/pull/63) fixed compilation for `aarch64-pc-windows-msvc`. 22 | 23 | ## [0.4.2] - 2022-09-02 24 | ### Changed 25 | - [PR#52](https://github.com/EmbarkStudios/crash-handling/pull/52) changed the address used to produce a `sigsegv` to be a canonical rather than a non-canonical address on 64-bit architectures. We will eventually add back support for non-canonical addresses. 26 | 27 | ## [0.4.1] - 2022-07-21 28 | ### Added 29 | - [PR#47](https://github.com/EmbarkStudios/crash-handling/pull/47) added support for raising `EXC_GUARD` exceptions on MacOS. 30 | 31 | ## [0.4.0] - 2022-07-19 32 | ### Changed 33 | - [PR#39](https://github.com/EmbarkStudios/crash-handling/pull/39) resolved [#24](https://github.com/EmbarkStudios/crash-handling/issues/24) and [#37](https://github.com/EmbarkStudios/crash-handling/issues/37) by changing stack overflow to use recursion instead of a single large stack allocated buffer, and changed all crash functions to be `-> !`. 34 | 35 | ## [0.3.1] - 2022-05-25 36 | ### Changed 37 | - Updated to `minidump-writer` 0.2.1 which includes support for MacOS thread names, and aligns on crash-context 0.3.0. 38 | 39 | ## [0.3.0] - 2022-05-23 40 | ### Added 41 | - First usable release of `crash-context`, `crash-handler`, `sadness-generator`, and `minidumper` crates. 42 | 43 | ## [crash-handler-v0.1.0] - 2022-04-29 44 | ### Added 45 | - Initial publish of crash-handler with Linux, Windows, and MacOS support 46 | 47 | ## [sadness-generator-v0.1.0] - 2022-04-29 48 | ### Added 49 | - Initial published of sadness-generator, can generated crashes on Linux, Windows, and MacOS 50 | 51 | ## [crash-context-v0.2.0] - 2022-04-29 52 | ### Added 53 | - Add Windows and MacOS support 54 | 55 | ## [crash-context-v0.1.0] - 2022-04-21 56 | ### Added 57 | - Initial pass of crash-context, Linux only 58 | 59 | 60 | [Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/sadness-generator-0.6.0...HEAD 61 | [0.6.0]: https://github.com/EmbarkStudios/crash-handling/compare/sadness-generator-0.5.0...sadness-generator-0.6.0 62 | [0.5.0]: https://github.com/EmbarkStudios/crash-handling/compare/sadness-generator-0.4.2...sadness-generator-0.5.0 63 | [0.4.2]: https://github.com/EmbarkStudios/crash-handling/compare/sadness-generator-0.4.1...sadness-generator-0.4.2 64 | [0.4.1]: https://github.com/EmbarkStudios/crash-handling/compare/sadness-generator-0.4.0...sadness-generator-0.4.1 65 | [0.4.0]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.1...sadness-generator-0.4.0 66 | [0.3.1]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.1...0.3.1 67 | [0.3.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-v0.1.0...0.3.0 68 | [crash-handler-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-handler-v0.1.0 69 | [sadness-generator-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/sadness-generator-v0.1.0 70 | [crash-context-v0.2.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.2.0 71 | [crash-context-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.1.0 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Embark Contributor Guidelines 2 | 3 | Welcome! This project is created by the team at [Embark Studios](https://embark.games). We're glad you're interested in contributing! We welcome contributions from people of all backgrounds who are interested in making great software with us. 4 | 5 | At Embark, we aspire to empower everyone to create interactive experiences. To do this, we're exploring and pushing the boundaries of new technologies, and sharing our learnings with the open source community. 6 | 7 | If you have ideas for collaboration, email us at opensource@embark-studios.com. 8 | 9 | We're also hiring full-time engineers to work with us in Stockholm! Check out our current job postings [here](https://www.embark-studios.com/jobs). 10 | 11 | ## Issues 12 | 13 | ### Feature Requests 14 | 15 | If you have ideas or how to improve our projects, you can suggest features by opening a GitHub issue. Make sure to include details about the feature or change, and describe any uses cases it would enable. 16 | 17 | Feature requests will be tagged as `enhancement` and their status will be updated in the comments of the issue. 18 | 19 | ### Bugs 20 | 21 | When reporting a bug or unexpected behaviour in a project, make sure your issue describes steps to reproduce the behaviour, including the platform you were using, what steps you took, and any error messages. 22 | 23 | Reproducible bugs will be tagged as `bug` and their status will be updated in the comments of the issue. 24 | 25 | ### Wontfix 26 | 27 | Issues will be closed and tagged as `wontfix` if we decide that we do not wish to implement it, usually due to being misaligned with the project vision or out of scope. We will comment on the issue with more detailed reasoning. 28 | 29 | ## Contribution Workflow 30 | 31 | ### Open Issues 32 | 33 | If you're ready to contribute, start by looking at our open issues tagged as [`help wanted`](../../issues?q=is%3Aopen+is%3Aissue+label%3A"help+wanted") or [`good first issue`](../../issues?q=is%3Aopen+is%3Aissue+label%3A"good+first+issue"). 34 | 35 | You can comment on the issue to let others know you're interested in working on it or to ask questions. 36 | 37 | ### Making Changes 38 | 39 | 1. Fork the repository. 40 | 41 | 2. Create a new feature branch. 42 | 43 | 3. Make your changes. Ensure that there are no build errors by running the project with your changes locally. 44 | 45 | 4. Open a pull request with a name and description of what you did. You can read more about working with pull requests on GitHub [here](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork). 46 | 47 | 5. A maintainer will review your pull request and may ask you to make changes. 48 | 49 | ## Code Guidelines 50 | 51 | ### Rust 52 | 53 | You can read about our standards and recommendations for working with Rust [here](https://github.com/EmbarkStudios/rust-ecosystem/blob/main/guidelines.md). 54 | 55 | ### Python 56 | 57 | We recommend following [PEP8 conventions](https://www.python.org/dev/peps/pep-0008/) when working with Python modules. 58 | 59 | ### JavaScript & TypeScript 60 | 61 | We use [Prettier](https://prettier.io/) with the default settings to auto-format our JavaScript and TypeScript code. 62 | 63 | ## Licensing 64 | 65 | Unless otherwise specified, all Embark open source projects shall comply with the Rust standard licensing model (MIT + Apache 2.0) and are thereby licensed under a dual license, allowing licensees to choose either MIT OR Apache-2.0 at their option. 66 | 67 | ## Contributor Terms 68 | 69 | Thank you for your interest in Embark Studios’ open source project. By providing a contribution (new or modified code, other input, feedback or suggestions etc.) you agree to these Contributor Terms. 70 | 71 | You confirm that each of your contributions has been created by you and that you are the copyright owner. You also confirm that you have the right to provide the contribution to us and that you do it under the Rust dual licence model (MIT + Apache 2.0). 72 | 73 | If you want to contribute something that is not your original creation, you may submit it to Embark Studios separately from any contribution, including details of its source and of any license or other restriction (such as related patents, trademarks, agreements etc.) 74 | 75 | Please also note that our projects are released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md) to ensure that they are welcoming places for everyone to contribute. By participating in any Embark Studios open source project, you agree to keep to the Contributor Code of Conduct. 76 | -------------------------------------------------------------------------------- /crash-handler/src/linux.rs: -------------------------------------------------------------------------------- 1 | pub mod jmp; 2 | mod state; 3 | 4 | use crate::Error; 5 | 6 | /// The signals that we support catching and raising 7 | #[derive(Copy, Clone, PartialEq, Eq)] 8 | #[repr(i32)] 9 | pub enum Signal { 10 | Abort = libc::SIGABRT, 11 | Bus = libc::SIGBUS, 12 | Fpe = libc::SIGFPE, 13 | Illegal = libc::SIGILL, 14 | Segv = libc::SIGSEGV, 15 | Trap = libc::SIGTRAP, 16 | } 17 | 18 | impl Signal { 19 | #[inline] 20 | pub fn ignore(self) { 21 | unsafe { 22 | state::ignore_signal(self); 23 | } 24 | } 25 | } 26 | 27 | /// A Linux/Android signal handler 28 | pub struct CrashHandler; 29 | 30 | #[allow(clippy::unused_self)] 31 | impl CrashHandler { 32 | /// Attaches the signal handler. 33 | /// 34 | /// The provided callback will be invoked if a signal is caught, providing a 35 | /// [`crate::CrashContext`] with the details of the thread where the 36 | /// signal was raised. 37 | /// 38 | /// The callback runs in a compromised context, so it is highly recommended 39 | /// to not perform actions that may fail due to corrupted state that caused 40 | /// or is a symptom of the original signal. This includes doing heap 41 | /// allocations from the same allocator as the crashing code. 42 | pub fn attach(on_crash: Box) -> Result { 43 | state::attach(on_crash)?; 44 | Ok(Self) 45 | } 46 | 47 | /// Detaches the handler. 48 | /// 49 | /// This is done automatically when this [`CrashHandler`] is dropped. 50 | #[inline] 51 | pub fn detach(self) { 52 | state::detach(); 53 | } 54 | 55 | /// Set the process that is allowed to perform `ptrace` operations on the 56 | /// current process. 57 | /// 58 | /// If you want to write a minidump from a child/external process when 59 | /// a crash occurs in this process, you can use this method to set that 60 | /// process as the only process allowed to use ptrace on this process. 61 | /// 62 | /// The process set by this method will be used by calling 63 | /// `prctl(PR_SET_PTRACER, , ...)` 64 | /// before handing off control to your user callback, presumably to trigger 65 | /// dumping of your process via the specified process. By default if this 66 | /// method is not called, `PR_SET_PTRACER_ANY` is used to allow any process 67 | /// to dump the current process. 68 | /// 69 | /// Note that this is only needed if `/proc/sys/kernel/yama/ptrace_scope` 70 | /// is 1 "restricted ptrace", but there is no harm in setting this if it is 71 | /// in another mode. 72 | /// 73 | /// See for 74 | /// the full documentation. 75 | #[inline] 76 | pub fn set_ptracer(&self, pid: Option) { 77 | let mut lock = state::HANDLER.lock(); 78 | 79 | if let Some(handler) = &mut *lock { 80 | handler.dump_process = pid; 81 | } 82 | } 83 | 84 | /// Sends the specified user signal. 85 | pub fn simulate_signal(&self, signal: u32) -> crate::CrashEventResult { 86 | // Normally this would be an unsafe function, since this unsafe encompasses 87 | // the entirety of the body, however the user is really not required to 88 | // uphold any guarantees on their end, so no real need to declare the 89 | // function itself unsafe. 90 | unsafe { 91 | let mut siginfo: libc::signalfd_siginfo = std::mem::zeroed(); 92 | siginfo.ssi_signo = signal; 93 | siginfo.ssi_code = state::SI_USER; 94 | siginfo.ssi_pid = std::process::id(); 95 | 96 | let mut context = std::mem::zeroed(); 97 | crash_context::crash_context_getcontext(&mut context); 98 | 99 | let lock = state::HANDLER.lock(); 100 | if let Some(handler) = &*lock { 101 | handler.handle_signal( 102 | &mut *(&mut siginfo as *mut libc::signalfd_siginfo).cast::(), 103 | &mut *(&mut context as *mut crash_context::ucontext_t).cast::(), 104 | ) 105 | } else { 106 | crate::CrashEventResult::Handled(false) 107 | } 108 | } 109 | } 110 | } 111 | 112 | impl Drop for CrashHandler { 113 | fn drop(&mut self) { 114 | state::detach(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /crash-context/src/linux/getcontext/x86_64.rs: -------------------------------------------------------------------------------- 1 | /* The x64 implementation of breakpad_getcontext was derived in part 2 | from the implementation of libunwind which requires the following 3 | notice. */ 4 | /* libunwind - a platform-independent unwind library 5 | Copyright (C) 2008 Google, Inc 6 | Contributed by Paul Pluzhnikov 7 | Copyright (C) 2010 Konstantin Belousov 8 | 9 | This file is part of libunwind. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining 12 | a copy of this software and associated documentation files (the 13 | "Software"), to deal in the Software without restriction, including 14 | without limitation the rights to use, copy, modify, merge, publish, 15 | distribute, sublicense, and/or sell copies of the Software, and to 16 | permit persons to whom the Software is furnished to do so, subject to 17 | the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | 30 | // Oof, unfortunately android libc and normal linux libc don't have the same 31 | // fpregs offset in ucontext_t, but asm! in Rust is...not convenient for only 32 | // adding certain lines/blocks of asm based using cfg https://github.com/rust-lang/rust/issues/15701 33 | // and they're not really inputs, just literals, so...yah 34 | 35 | #![cfg(any(target_os = "linux", target_os = "android"))] 36 | 37 | // Unfortunately, the asm! macro has a few really annoying limitations at the 38 | // moment 39 | // 40 | // 1. const operands are unstable 41 | // 2. cfg attributes can't be used inside the asm macro at all 42 | // 43 | // and the worst part is we need it for literally only 1 thing, using a different 44 | // offset to the fpstate in ucontext depending on whether we are targeting android 45 | // or not :( 46 | macro_rules! asm_func { 47 | ($offset:expr) => { 48 | std::arch::global_asm! { 49 | ".text", 50 | ".global crash_context_getcontext", 51 | ".hidden crash_context_getcontext", 52 | ".align 4", 53 | ".type crash_context_getcontext, @function", 54 | "crash_context_getcontext:", 55 | ".cfi_startproc", 56 | // Callee saved: RBX, RBP, R12-R15 57 | "movq %r12, 0x48(%rdi)", 58 | "movq %r13, 0x50(%rdi)", 59 | "movq %r14, 0x58(%rdi)", 60 | "movq %r15, 0x60(%rdi)", 61 | "movq %rbp, 0x78(%rdi)", 62 | "movq %rbx, 0x80(%rdi)", 63 | 64 | // Save argument registers 65 | "movq %r8, 0x28(%rdi)", 66 | "movq %r9, 0x30(%rdi)", 67 | "movq %rdi, 0x68(%rdi)", 68 | "movq %rsi, 0x70(%rdi)", 69 | "movq %rdx, 0x88(%rdi)", 70 | "movq %rax, 0x90(%rdi)", 71 | "movq %rcx, 0x98(%rdi)", 72 | 73 | // Save fp state 74 | stringify!(leaq $offset(%rdi),%r8), 75 | "movq %r8, 0xe0(%rdi)", 76 | "fnstenv (%r8)", 77 | "stmxcsr 0x18(%r8)", 78 | 79 | // Exclude this call 80 | "leaq 8(%rsp), %rax", 81 | "movq %rax, 0xa0(%rdi)", 82 | 83 | "movq 0(%rsp), %rax", 84 | "movq %rax, 0xa8(%rdi)", 85 | 86 | // Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) 87 | "leaq 0x128(%rdi), %rdx", // arg3 88 | "xorq %rsi, %rsi", // arg2 NULL 89 | "xorq %rdi, %rdi", // arg1 SIGBLOCK == 0 90 | "call sigprocmask@PLT", 91 | 92 | // Always return 0 for success, even if sigprocmask failed. 93 | "xorl %eax, %eax", 94 | "ret", 95 | ".cfi_endproc", 96 | ".size crash_context_getcontext, . - crash_context_getcontext", 97 | options(att_syntax) 98 | } 99 | }; 100 | } 101 | 102 | #[cfg(target_os = "linux")] 103 | asm_func!(0x1a8); 104 | #[cfg(target_os = "android")] 105 | asm_func!(0x130); 106 | -------------------------------------------------------------------------------- /minidumper/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Changelog 4 | 5 | All notable changes to this project will be documented in this file. 6 | 7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 9 | 10 | 11 | ## [Unreleased] - ReleaseDate 12 | ## [0.8.3] - 2024-06-08 13 | ## [0.8.2] - 2024-02-15 14 | ### Changed 15 | - [PR#83](https://github.com/EmbarkStudios/crash-handling/pull/83) updated `scroll` to 0.12. 16 | 17 | ## [0.8.1] - 2024-01-29 18 | ### Changed 19 | - [PR#81](https://github.com/EmbarkStudios/crash-handling/pull/81) resolved [#80](https://github.com/EmbarkStudios/crash-handling/issues/80) by updating `polling` to 0.3. 20 | 21 | ## [0.8.0] - 2023-04-03 22 | ### Changed 23 | - [PR#70](https://github.com/EmbarkStudios/crash-handling/pull/70) removed `winapi` in favor of embedded bindings. 24 | 25 | ## [0.7.0] - 2022-11-17 26 | ### Fixed 27 | - [PR#63](https://github.com/EmbarkStudios/crash-handling/pull/63) replaced `windows-sys` with `winapi`, resolving [#61](https://github.com/EmbarkStudios/crash-handling/issues/61). Eventually `winapi` will be removed as well. 28 | 29 | ## [0.6.0] - 2022-10-21 30 | ### Changed 31 | - [PR#60](https://github.com/EmbarkStudios/crash-handling/pull/60) updated `minidump-writer -> 0.5` and `windows-sys -> 0.42`. 32 | 33 | ## [0.5.1] - 2022-09-02 34 | ### Fixed 35 | - [PR#58](https://github.com/EmbarkStudios/crash-handling/pull/58) fixed an issue on Linux where the socket poll would erroneously return an error if it was interrupted. This is now handled gracefully. Thanks [@MarcusGrass](https://github.com/MarcusGrass)! 36 | 37 | ## [0.5.0] - 2022-07-21 38 | ### Changed 39 | - [PR#50](https://github.com/EmbarkStudios/crash-handling/pull/50) updated `minidump-writer` to take advantage of improvements in writing macos minidumps. 40 | 41 | ## [0.4.0] - 2022-07-19 42 | ### Changed 43 | - [PR#41](https://github.com/EmbarkStudios/crash-handling/pull/41) added support for detecting stale client connections for cases where the OS might not efficiently close the client end of the connection so that the server notices and removes the client from the event loop. 44 | - [PR#44](https://github.com/EmbarkStudios/crash-handling/pull/44) updated `minidump-writer` to 0.3, which includes improved support for MacOS 45 | 46 | ## [0.3.1] - 2022-05-25 47 | ### Changed 48 | - Updated to `minidump-writer` 0.2.1 which includes support for MacOS thread names, and aligns on crash-context 0.3.0. 49 | 50 | ## [0.3.0] - 2022-05-23 51 | ### Added 52 | - First usable release of `crash-context`, `crash-handler`, `sadness-generator`, and `minidumper` crates. 53 | 54 | ## [crash-handler-v0.1.0] - 2022-04-29 55 | ### Added 56 | - Initial publish of crash-handler with Linux, Windows, and MacOS support 57 | 58 | ## [sadness-generator-v0.1.0] - 2022-04-29 59 | ### Added 60 | - Initial published of sadness-generator, can generated crashes on Linux, Windows, and MacOS 61 | 62 | ## [crash-context-v0.2.0] - 2022-04-29 63 | ### Added 64 | - Add Windows and MacOS support 65 | 66 | ## [crash-context-v0.1.0] - 2022-04-21 67 | ### Added 68 | - Initial pass of crash-context, Linux only 69 | 70 | 71 | [Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.8.3...HEAD 72 | [0.8.3]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.8.2...minidumper-0.8.3 73 | [0.8.2]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.8.1...minidumper-0.8.2 74 | [0.8.1]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.8.0...minidumper-0.8.1 75 | [0.8.0]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.7.0...minidumper-0.8.0 76 | [0.7.0]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.6.0...minidumper-0.7.0 77 | [0.6.0]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.5.1...minidumper-0.6.0 78 | [0.5.1]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.5.0...minidumper-0.5.1 79 | [0.5.0]: https://github.com/EmbarkStudios/crash-handling/compare/minidumper-0.4.0...minidumper-0.5.0 80 | [0.4.0]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.1...minidumper-0.4.0 81 | [0.3.1]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.0...0.3.1 82 | [0.3.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-v0.1.0...0.3.0 83 | [crash-handler-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-handler-v0.1.0 84 | [sadness-generator-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/sadness-generator-v0.1.0 85 | [crash-context-v0.2.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.2.0 86 | [crash-context-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.1.0 87 | -------------------------------------------------------------------------------- /minidumper/examples/diskwrite.rs: -------------------------------------------------------------------------------- 1 | const SOCKET_NAME: &str = "minidumper-disk-example"; 2 | 3 | use minidumper::{Client, Server}; 4 | 5 | fn main() { 6 | pretty_env_logger::init(); 7 | if std::env::args().any(|a| a == "--server") { 8 | let mut server = Server::with_name(SOCKET_NAME).expect("failed to create server"); 9 | 10 | let ab = std::sync::atomic::AtomicBool::new(false); 11 | 12 | struct Handler; 13 | 14 | impl minidumper::ServerHandler for Handler { 15 | /// Called when a crash has been received and a backing file needs to be 16 | /// created to store it. 17 | fn create_minidump_file( 18 | &self, 19 | ) -> Result<(std::fs::File, std::path::PathBuf), std::io::Error> { 20 | let uuid = uuid::Uuid::new_v4(); 21 | 22 | let dump_path = std::path::PathBuf::from(format!("dumps/{uuid}.dmp")); 23 | 24 | if let Some(dir) = dump_path.parent() 25 | && !dir.try_exists()? 26 | { 27 | std::fs::create_dir_all(dir)?; 28 | } 29 | let file = std::fs::File::create(&dump_path)?; 30 | Ok((file, dump_path)) 31 | } 32 | 33 | /// Called when a crash has been fully written as a minidump to the provided 34 | /// file. Also returns the full heap buffer as well. 35 | fn on_minidump_created( 36 | &self, 37 | result: Result, 38 | ) -> minidumper::LoopAction { 39 | match result { 40 | Ok(mut md_bin) => { 41 | use std::io::Write; 42 | let _ = md_bin.file.flush(); 43 | log::info!("wrote minidump to disk"); 44 | } 45 | Err(e) => { 46 | log::error!("failed to write minidump: {:#}", e); 47 | } 48 | } 49 | 50 | // Tells the server to exit, which will in turn exit the process 51 | minidumper::LoopAction::Exit 52 | } 53 | 54 | fn on_message(&self, kind: u32, buffer: Vec) { 55 | log::info!( 56 | "kind: {kind}, message: {}", 57 | String::from_utf8(buffer).unwrap() 58 | ); 59 | } 60 | } 61 | 62 | server 63 | .run(Box::new(Handler), &ab, None) 64 | .expect("failed to run server"); 65 | 66 | return; 67 | } 68 | 69 | let mut server = None; 70 | 71 | // Attempt to connect to the server 72 | let (client, _server) = loop { 73 | if let Ok(client) = Client::with_name(SOCKET_NAME) { 74 | break (client, server.unwrap()); 75 | } 76 | 77 | let exe = std::env::current_exe().expect("unable to find ourselves"); 78 | 79 | server = Some( 80 | std::process::Command::new(exe) 81 | .arg("--server") 82 | .spawn() 83 | .expect("unable to spawn server process"), 84 | ); 85 | 86 | // Give it time to start 87 | std::thread::sleep(std::time::Duration::from_millis(100)); 88 | }; 89 | 90 | // Register our exception handler 91 | client.send_message(1, "mistakes will be made").unwrap(); 92 | 93 | #[allow(unsafe_code)] 94 | let handler = crash_handler::CrashHandler::attach(unsafe { 95 | crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| { 96 | // Before we request the crash, send a message to the server 97 | client.send_message(2, "mistakes were made").unwrap(); 98 | 99 | // Send a ping to the server, this ensures that all messages that have been sent 100 | // are "flushed" before the crash event is sent. This is only really useful 101 | // on macos where messages and crash events are sent via different, unsynchronized, 102 | // methods which can result in the crash event closing the server before 103 | // the non-crash messages are received/processed 104 | client.ping().unwrap(); 105 | 106 | crash_handler::CrashEventResult::Handled(client.request_dump(crash_context).is_ok()) 107 | }) 108 | }) 109 | .expect("failed to attach signal handler"); 110 | 111 | // On linux we can explicitly allow only the server process to inspect the 112 | // process we are monitoring (this one) for crashes 113 | #[cfg(any(target_os = "linux", target_os = "android"))] 114 | { 115 | handler.set_ptracer(Some(_server.id())); 116 | } 117 | 118 | cfg_if::cfg_if! { 119 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 120 | handler.simulate_signal(libc::SIGALRM as _); 121 | } else if #[cfg(windows)] { 122 | handler.simulate_exception(None); 123 | } else if #[cfg(target_os = "macos")] { 124 | handler.simulate_exception(None); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /crash-handler/tests/shared.rs: -------------------------------------------------------------------------------- 1 | #![allow(unsafe_code)] 2 | 3 | use crash_handler as ch; 4 | 5 | pub use sadness_generator::SadnessFlavor; 6 | 7 | pub fn handles_crash(flavor: SadnessFlavor) { 8 | let mut _handler = None; 9 | 10 | unsafe { 11 | _handler = Some( 12 | ch::CrashHandler::attach(ch::make_crash_event(move |cc: &ch::CrashContext| { 13 | cfg_if::cfg_if! { 14 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 15 | use ch::Signal; 16 | 17 | assert_eq!( 18 | cc.siginfo.ssi_signo, 19 | match flavor { 20 | SadnessFlavor::Abort => Signal::Abort, 21 | SadnessFlavor::Bus => Signal::Bus, 22 | SadnessFlavor::DivideByZero => Signal::Fpe, 23 | SadnessFlavor::Illegal => Signal::Illegal, 24 | SadnessFlavor::Segfault | SadnessFlavor::StackOverflow { .. } => { 25 | Signal::Segv 26 | } 27 | SadnessFlavor::Trap => Signal::Trap, 28 | } as u32, 29 | ); 30 | 31 | //assert_eq!(cc.tid, tid); 32 | 33 | // At least on linux these...aren't set. Which is weird 34 | //assert_eq!(cc.siginfo.ssi_pid, std::process::id()); 35 | //assert_eq!(cc.siginfo.ssi_tid, tid as u32); 36 | } else if #[cfg(target_os = "macos")] { 37 | use ch::ExceptionType; 38 | 39 | let exc = cc.exception.expect("we should have an exception"); 40 | 41 | let expected = match flavor { 42 | SadnessFlavor::Abort => { 43 | assert_eq!(exc.code, 0x10003); // EXC_SOFT_SIGNAL 44 | assert_eq!(exc.subcode.unwrap(), libc::SIGABRT as _); 45 | 46 | ExceptionType::Software 47 | } 48 | SadnessFlavor::Bus 49 | | SadnessFlavor::Segfault 50 | | SadnessFlavor::StackOverflow { .. } => { 51 | if flavor == SadnessFlavor::Segfault { 52 | // For EXC_BAD_ACCESS exceptions, the subcode will be the 53 | // bad address we tried to access 54 | assert_eq!(cc.exception.unwrap().subcode.unwrap(), sadness_generator::SEGFAULT_ADDRESS as _); 55 | } 56 | 57 | ExceptionType::BadAccess 58 | }, 59 | SadnessFlavor::DivideByZero => ExceptionType::Arithmetic, 60 | SadnessFlavor::Illegal => ExceptionType::BadInstruction, 61 | SadnessFlavor::Trap => ExceptionType::Breakpoint, 62 | SadnessFlavor::Guard => ExceptionType::Guard, 63 | }; 64 | 65 | assert_eq!(exc.kind, expected as _); 66 | } else if #[cfg(target_os = "windows")] { 67 | use ch::ExceptionCode; 68 | 69 | let ec = match flavor { 70 | SadnessFlavor::Abort => ExceptionCode::Abort, 71 | SadnessFlavor::DivideByZero => ExceptionCode::Fpe, 72 | SadnessFlavor::Illegal => ExceptionCode::Illegal, 73 | SadnessFlavor::InvalidParameter => ExceptionCode::InvalidParameter, 74 | SadnessFlavor::Purecall => ExceptionCode::Purecall, 75 | SadnessFlavor::Segfault => ExceptionCode::Segv, 76 | SadnessFlavor::StackOverflow { .. }=> ExceptionCode::StackOverflow, 77 | SadnessFlavor::Trap => ExceptionCode::Trap, 78 | SadnessFlavor::HeapCorruption => ExceptionCode::HeapCorruption, 79 | }; 80 | 81 | assert_eq!( 82 | cc.exception_code, ec as i32, 83 | "0x{:x} != 0x{:x}", 84 | cc.exception_code, ec as i32 85 | ); 86 | } 87 | } 88 | 89 | // Once we've verified we've received the exception we expected, 90 | // we exit with a success code to satisfy cargo test. While 91 | // we _could_ jump back on non-mac platforms (Mac can't since 92 | // exceptions are handled on a different thread) this is a 93 | // simpler approach that is consistent across platforms since 94 | // we only have 1 test per binary 95 | #[allow(clippy::exit)] 96 | std::process::exit(0); 97 | })) 98 | .unwrap(), 99 | ); 100 | 101 | #[inline(never)] 102 | unsafe fn indirect(flavor: SadnessFlavor) { 103 | unsafe { flavor.make_sad() }; 104 | } 105 | 106 | indirect(flavor); 107 | 108 | panic!("this should be impossible"); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /minidumper-test/crash-client/src/main.rs: -------------------------------------------------------------------------------- 1 | use minidumper_test::Signal; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Parser)] 6 | struct Command { 7 | /// The unique identifier for the socket connection and the minidump file 8 | /// that should be produced when this client crashes 9 | #[clap(long)] 10 | id: String, 11 | /// The signal/exception to raise 12 | #[clap(long)] 13 | signal: Signal, 14 | /// Raises the signal on a separate thread rather than the main thread 15 | #[clap(long)] 16 | use_thread: bool, 17 | /// Waits on a debugger to attach 18 | #[clap(long)] 19 | wait_on_debugger: bool, 20 | } 21 | 22 | #[allow(unsafe_code)] 23 | fn real_main() -> anyhow::Result<()> { 24 | let cmd = Command::parse(); 25 | 26 | println!("pid: {}", std::process::id()); 27 | 28 | let start = std::time::Instant::now(); 29 | let connect_timeout = std::time::Duration::from_secs(2); 30 | 31 | if cmd.wait_on_debugger { 32 | println!("waiting on debugger"); 33 | // notify_rust has some crazy deps, can be turned on manually if needed... 34 | // notify_rust::Notification::new() 35 | // .summary("Waiting on debugger") 36 | // .body(&format!("{} - {}", cmd.id, std::process::id())) 37 | // .timeout(0) 38 | // .show() 39 | // .expect("failed to post notification") 40 | // .wait_for_action(|_| { 41 | // println!("continuing"); 42 | // }); 43 | } 44 | 45 | let md_client = loop { 46 | match minidumper::Client::with_name(&cmd.id) { 47 | Ok(md_client) => break md_client, 48 | Err(e) => { 49 | if std::time::Instant::now() - start > connect_timeout { 50 | anyhow::bail!("timed out trying to connect to server process: {:#}", e); 51 | } 52 | } 53 | } 54 | }; 55 | 56 | let _handler = crash_handler::CrashHandler::attach(unsafe { 57 | crash_handler::make_crash_event(move |cc: &crash_handler::CrashContext| { 58 | let handled = md_client.request_dump(cc).is_ok(); 59 | crash_handler::CrashEventResult::Handled(handled) 60 | }) 61 | }); 62 | 63 | let signal = cmd.signal; 64 | 65 | let raise_signal = move || { 66 | // SAFETY: we're about to intentionally crash ourselves via shenanigans, 67 | // none of this is safe 68 | unsafe { 69 | match signal { 70 | Signal::Illegal => { 71 | sadness_generator::raise_illegal_instruction(); 72 | } 73 | Signal::Trap => { 74 | sadness_generator::raise_trap(); 75 | } 76 | Signal::Abort => { 77 | sadness_generator::raise_abort(); 78 | } 79 | #[cfg(unix)] 80 | Signal::Bus => { 81 | sadness_generator::raise_bus(); 82 | } 83 | Signal::Fpe => { 84 | sadness_generator::raise_floating_point_exception(); 85 | } 86 | Signal::Segv => { 87 | sadness_generator::raise_segfault(); 88 | } 89 | Signal::StackOverflow => { 90 | sadness_generator::raise_stack_overflow(); 91 | } 92 | Signal::StackOverflowCThread => { 93 | #[cfg(unix)] 94 | { 95 | sadness_generator::raise_stack_overflow_in_non_rust_thread_normal(); 96 | } 97 | #[cfg(windows)] 98 | { 99 | unreachable!(); 100 | } 101 | } 102 | #[cfg(windows)] 103 | Signal::Purecall => { 104 | sadness_generator::raise_purecall(); 105 | } 106 | #[cfg(windows)] 107 | Signal::InvalidParameter => { 108 | sadness_generator::raise_invalid_parameter(); 109 | } 110 | #[cfg(windows)] 111 | Signal::HeapCorruption => { 112 | sadness_generator::raise_heap_corruption(); 113 | } 114 | #[cfg(target_os = "macos")] 115 | Signal::Guard => { 116 | sadness_generator::raise_guard_exception(); 117 | } 118 | } 119 | } 120 | }; 121 | 122 | let mut threads = Vec::new(); 123 | 124 | for _ in 0..10 { 125 | threads.push(std::thread::spawn(move || { 126 | std::thread::sleep(std::time::Duration::MAX); 127 | })); 128 | } 129 | 130 | if cmd.use_thread { 131 | std::thread::spawn(raise_signal) 132 | .join() 133 | .expect("failed to join thread"); 134 | } else { 135 | raise_signal(); 136 | } 137 | 138 | anyhow::bail!("we should have raised a signal and exited"); 139 | } 140 | 141 | fn main() { 142 | // We want this program to crash and have a minidump written, it _shouldn't_ 143 | // have errors that prevent that from happening, so emit an error code if we 144 | // do encounter an error so that we can fail the test 145 | if let Err(e) = real_main() { 146 | eprintln!("error: {:#}", e); 147 | 148 | // When exiting due to a crash, the exit code will be 128 + the integer 149 | // signal number, at least on unixes 150 | #[allow(clippy::exit)] 151 | std::process::exit(222); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /crash-context/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Changelog 4 | 5 | All notable changes to this project will be documented in this file. 6 | 7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 9 | 10 | 11 | ## [Unreleased] - ReleaseDate 12 | ## [0.6.3] - 2024-07-25 13 | ### Fixed 14 | - [PR#89](https://github.com/EmbarkStudios/crash-handling/pull/89) fixed compilation for `arm-unknown-linux-gnueabihf`...again. 15 | 16 | ## [0.6.2] - 2024-06-08 17 | ### Changed 18 | - Update MSRV to 1.62.0 19 | 20 | ## [0.6.1] - 2023-06-19 21 | ### Added 22 | - [PR#76](https://github.com/EmbarkStudios/crash-handling/pull/76) added support for `i686-linux-android` and `x86_64-linux-android`. Thanks [@gabrielesvelto](https://github.com/gabrielesvelto)! 23 | 24 | ## [0.6.0] - 2023-04-03 25 | ### Changed 26 | - [PR#70](https://github.com/EmbarkStudios/crash-handling/pull/70) removed the `winapi` dependency in favor of embedded bindings to avoid dependencies. 27 | - [PR#70](https://github.com/EmbarkStudios/crash-handling/pull/70) removed the asm implementations for Windows CPU context retrieval in favor of using `RtlCaptureContext`. This means that floating state is not captured, but is otherwise and improvement. 28 | 29 | ### Added 30 | - [PR#68](https://github.com/EmbarkStudios/crash-handling/pull/68) added capture context support for x86 Windows, but this change was supplanted in [PR#70](https://github.com/EmbarkStudios/crash-handling/pull/70) to use `RtlCaptureContext` instead. 31 | 32 | ### Fixed 33 | - [PR#71](https://github.com/EmbarkStudios/crash-handling/pull/71) fixed the definition of `mcontext_t` for `i686-unknow-linux`. Thanks [@afranchuk](https://github.com/afranchuk)! 34 | 35 | ## [0.5.1] - 2022-11-17 36 | ### Fixed 37 | - [PR#66](https://github.com/EmbarkStudios/crash-handling/pull/66) (apparently) resolved [#65](https://github.com/EmbarkStudios/crash-handling/issues/65) by...changing from AT&T to Intel syntax. This shouldn't have changed anything, but it did, and I'm too tired and have other things to work on, so here we are. 38 | 39 | ## [0.5.0] - 2022-11-17 40 | ### Added 41 | - [PR#62](https://github.com/EmbarkStudios/crash-handling/pull/62) added a replacement implementation of `RtlCaptureContext`, and defines its own `CONTEXT` structure. This was needed due to both `winapi` and `windows-sys` incorrectly defining the `CONTEXT` and related structures on both x86_64 and aarch64 to not be correctly aligned, as well as `RtlCaptureContext` not actually capturing the floating point and vector state. 42 | 43 | ## [0.4.0] - 2022-07-21 44 | ### Added 45 | - [PR#46](https://github.com/EmbarkStudios/crash-handling/pull/46) added support for unpacking `EXC_RESOURCE` exceptions on MacOS. 46 | - [PR#47](https://github.com/EmbarkStudios/crash-handling/pull/47) added support for unpacking `EXC_GUARD` exceptions on MacOS. 47 | 48 | ### Changed 49 | - [PR#47](https://github.com/EmbarkStudios/crash-handling/pull/47) changed `ExceptionInfo` to use unsigned types for all of its fields. While these are declared as signed, in practice all usage of them is as unsigned integers. 50 | 51 | ### Fixed 52 | - [PR#47](https://github.com/EmbarkStudios/crash-handling/pull/47) fixed a potential issue with the IPC exception passing due to the structure's not being `#[repr(C, packed(4))]`, and the receiving side not (properly) accounting for the trailer that is added by the kernel to every mach msg. 53 | 54 | ## [0.3.1] - 2022-05-25 55 | ### Changed 56 | - Updated to `minidump-writer` 0.2.1 which includes support for MacOS thread names, and aligns on crash-context 0.3.0. 57 | 58 | ## [0.3.0] - 2022-05-23 59 | ### Added 60 | - First usable release of `crash-context`, `crash-handler`, `sadness-generator`, and `minidumper` crates. 61 | 62 | ## [crash-handler-v0.1.0] - 2022-04-29 63 | ### Added 64 | - Initial publish of crash-handler with Linux, Windows, and MacOS support 65 | 66 | ## [sadness-generator-v0.1.0] - 2022-04-29 67 | ### Added 68 | - Initial published of sadness-generator, can generated crashes on Linux, Windows, and MacOS 69 | 70 | ## [crash-context-v0.2.0] - 2022-04-29 71 | ### Added 72 | - Add Windows and MacOS support 73 | 74 | ## [crash-context-v0.1.0] - 2022-04-21 75 | ### Added 76 | - Initial pass of crash-context, Linux only 77 | 78 | 79 | [Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/{{tag_name}}...HEAD 80 | [0.6.3]: https://github.com/EmbarkStudios/crash-handling/compare/crash-context-0.6.2...{{tag_name}} 81 | [0.6.2]: https://github.com/EmbarkStudios/crash-handling/compare/crash-context-0.6.1...crash-context-0.6.2 82 | [0.6.1]: https://github.com/EmbarkStudios/crash-handling/compare/crash-context-0.6.0...crash-context-0.6.1 83 | [0.6.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-context-0.5.1...crash-context-0.6.0 84 | [0.5.1]: https://github.com/EmbarkStudios/crash-handling/compare/crash-context-0.5.0...crash-context-0.5.1 85 | [0.5.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-context-0.4.0...crash-context-0.5.0 86 | [0.4.0]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.1...crash-context-0.4.0 87 | [0.3.1]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.0...0.3.1 88 | [0.3.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-v0.1.0...0.3.0 89 | [crash-handler-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-handler-v0.1.0 90 | [sadness-generator-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/sadness-generator-v0.1.0 91 | [crash-context-v0.2.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.2.0 92 | [crash-context-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.1.0 93 | -------------------------------------------------------------------------------- /crash-handler/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | # `🔥 crash-handler` 10 | 11 | **Runs user-specified code when a crash occurs** 12 | 13 | [![Embark](https://img.shields.io/badge/embark-open%20source-blueviolet.svg)](https://embark.dev) 14 | [![Embark](https://img.shields.io/badge/discord-ark-%237289da.svg?logo=discord)](https://discord.gg/dAuKfZS) 15 | [![Crates.io](https://img.shields.io/crates/v/crash-handler.svg)](https://crates.io/crates/crash-handler) 16 | [![Docs](https://docs.rs/crash-handler/badge.svg)](https://docs.rs/crash-handler) 17 | [![dependency status](https://deps.rs/repo/github/EmbarkStudios/crash-handling/status.svg)](https://deps.rs/repo/github/EmbarkStudios/crash-handling) 18 | [![Build status](https://github.com/EmbarkStudios/crash-handling/workflows/CI/badge.svg)](https://github.com/EmbarkStudios/crash-handling/actions) 19 | 20 |
21 | 22 | ## Linux/Android 23 | 24 | On Linux this is done by handling [signals](https://man7.org/linux/man-pages/man7/signal.7.html), namely the following. 25 | 26 | One important detail of the Linux signal handling is that this crate hooks [`pthread_create`](https://man7.org/linux/man-pages/man3/pthread_create.3.html) so that an [alternate signal stack](https://man7.org/linux/man-pages/man2/sigaltstack.2.html) is always installed on every thread. [`std::thread::Thread`] already does this, however hooking `pthread_create` allows us to ensure this occurs for threads created from eg. C/C++ code as well. An alternate stack is necessary to reliably handle a [`SIGSEGV`](#sigsegv) caused by a [stack overflow](https://en.wikipedia.org/wiki/Stack_buffer_overflow), as signals are otherwise handled on the same stack that raised the signal. 27 | 28 | ### `SIGABRT` 29 | 30 | Signal sent to a process to tell it to abort, i.e. to terminate. The signal is usually initiated by the process itself when it calls `std::process::abort` or `libc::abort`, but it can be sent to the process from outside itself like any other signal. 31 | 32 | ### `SIGBUS` 33 | 34 | Signal sent to a process when it causes a [bus error](https://en.wikipedia.org/wiki/Bus_error). 35 | 36 | ### `SIGFPE` 37 | 38 | Signal sent to a process when it executes an erroneous arithmetic operation. Though it stands for **f**loating **p**oint **e**xception this signal covers integer operations as well. 39 | 40 | ### `SIGILL` 41 | 42 | Signal sent to a process when it attempts to execute an **illegal**, malformed, unknown, or privileged, instruction. 43 | 44 | ### `SIGSEGV` 45 | 46 | Signal sent to a process when it makes an invalid virtual memory reference, a [segmentation fault](https://en.wikipedia.org/wiki/Segmentation_fault). This covers infamous `null` pointer access, out of bounds access, use after free, stack overflows, etc. 47 | 48 | ### `SIGTRAP` 49 | 50 | Signal sent to a process when a trap is raised, eg. a breakpoint or debug assertion. 51 | 52 | ## Windows 53 | 54 | On Windows we catch [exceptions](https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling), which cover a wide range of crash reasons, as well as [invalid parameters](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/set-invalid-parameter-handler-set-thread-local-invalid-parameter-handler?view=msvc-170) and [purecall](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-purecall-handler-set-purecall-handler?view=msvc-170) 55 | 56 | ## `MacOS` 57 | 58 | On Macos we use [exception ports](https://flylib.com/books/en/3.126.1.109/1/). Exception ports are the first layer that exceptions are filtered, from a thread level, to a process (task) level, and finally to a host level. 59 | 60 | If no user ports have been registered, the default Macos implementation is to convert the Mach exception into an equivalent Unix signal and deliver it to any registered signal handlers before performing the default action for the exception/signal (ie process termination). This means that if you use this crate in conjunction with signal handling on `MacOS`, **you will not get the results you expect** as the exception port used by this crate will take precedence over the signal handler. See [this issue](https://github.com/bytecodealliance/wasmtime/issues/2456) for a concrete example. 61 | 62 | Note that there is one exception to the above, which is that `SIGABRT` is handled by a signal handler, as there is no equivalent Mach exception for it. 63 | 64 | ### `EXC_BAD_ACCESS` 65 | 66 | Covers similar crashes as [`SIGSEGV`](#sigsegv) and [`SIGBUS`](#sigbus) 67 | 68 | ### `EXC_BAD_INSTRUCTION` 69 | 70 | Covers similar crashes as [`SIGILL`](#sigill) 71 | 72 | ### `EXC_ARITHMETIC` 73 | 74 | Covers similar crashes as [`SIGFPE`](#sigfpe) 75 | 76 | ### `EXC_BREAKPOINT` 77 | 78 | Covers similar crashes as [`SIGTRAP`](#sigtrap) 79 | 80 | ## Contribution 81 | 82 | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4-ff69b4.svg)](../CODE_OF_CONDUCT.md) 83 | 84 | We welcome community contributions to this project. 85 | 86 | Please read our [Contributor Guide](../CONTRIBUTING.md) for more information on how to get started. 87 | Please also read our [Contributor Terms](../CONTRIBUTING.md#contributor-terms) before you make any contributions. 88 | 89 | Any contribution intentionally submitted for inclusion in an Embark Studios project, shall comply with the Rust standard licensing model (MIT OR Apache 2.0) and therefore be dual licensed as described below, without any additional terms or conditions: 90 | 91 | ### License 92 | 93 | This contribution is dual licensed under EITHER OF 94 | 95 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 96 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 97 | 98 | at your option. 99 | 100 | For clarity, "your" refers to Embark or any other licensee/user of the contribution. 101 | -------------------------------------------------------------------------------- /crash-handler/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![allow(unsafe_code)] 3 | 4 | mod error; 5 | 6 | pub use error::Error; 7 | 8 | #[cfg(feature = "debug-print")] 9 | #[macro_export] 10 | macro_rules! debug_print { 11 | ($s:literal) => { 12 | let cstr = concat!(file!(), ":", line!(), " ", $s, "\n"); 13 | $crate::write_stderr(cstr); 14 | }; 15 | } 16 | 17 | #[cfg(not(feature = "debug-print"))] 18 | #[macro_export] 19 | macro_rules! debug_print { 20 | ($s:literal) => {}; 21 | } 22 | 23 | /// Writes the specified string directly to stderr. 24 | /// 25 | /// This is safe to be called from within a compromised context. 26 | #[inline] 27 | pub fn write_stderr(s: &'static str) { 28 | unsafe { 29 | #[cfg(target_os = "windows")] 30 | libc::write(2, s.as_ptr().cast(), s.len() as u32); 31 | 32 | #[cfg(not(target_os = "windows"))] 33 | libc::write(2, s.as_ptr().cast(), s.len()); 34 | } 35 | } 36 | 37 | cfg_if::cfg_if! { 38 | if #[cfg(all(unix, not(target_os = "macos")))] { 39 | /// The sole purpose of the unix module is to hook `pthread_create` to ensure 40 | /// an alternate stack is installed for every native thread in case of a 41 | /// stack overflow. This doesn't apply to `MacOS` as it uses exception ports, 42 | /// which are always delivered to a specific thread owned by the exception 43 | /// handler 44 | pub mod unix; 45 | } 46 | } 47 | 48 | pub use crash_context::CrashContext; 49 | 50 | /// The result of the user code executed during a crash event 51 | pub enum CrashEventResult { 52 | /// The event was handled in some way 53 | Handled(bool), 54 | #[cfg(any( 55 | target_os = "linux", 56 | target_os = "android", 57 | all(target_os = "windows", target_arch = "x86_64"), 58 | ))] 59 | /// The handler wishes to jump somewhere else, presumably to return 60 | /// execution and skip the code that caused the exception 61 | Jump { 62 | /// The location to jump back to, retrieved via sig/setjmp 63 | jmp_buf: *mut jmp::JmpBuf, 64 | /// The value that will be returned from the sig/setjmp call that we 65 | /// jump to. Note that if the value is 0 it will be corrected to 1 66 | value: i32, 67 | }, 68 | } 69 | 70 | impl From for CrashEventResult { 71 | fn from(b: bool) -> Self { 72 | Self::Handled(b) 73 | } 74 | } 75 | 76 | /// User implemented trait for handling a crash event that has ocurred. 77 | /// 78 | /// # Safety 79 | /// 80 | /// This trait is marked unsafe as care needs to be taken when implementing it 81 | /// due to the [`Self::on_crash`] method being run in a compromised context. In 82 | /// general, it is advised to do as _little_ as possible when handling a 83 | /// crash, with more complicated or dangerous (in a compromised context) code 84 | /// being intialized before the [`CrashHandler`] is installed, or hoisted out to 85 | /// another process entirely. 86 | /// 87 | /// ## Linux 88 | /// 89 | /// Notably, only a small subset of libc functions are 90 | /// [async signal safe](https://man7.org/linux/man-pages/man7/signal-safety.7.html) 91 | /// and calling non-safe ones can have undefined behavior, including such common 92 | /// ones as `malloc` (especially if using a multi-threaded allocator). 93 | /// 94 | /// ## Windows 95 | /// 96 | /// Windows [structured exceptions](https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling) 97 | /// don't have the a notion similar to signal safety, but it is again recommended 98 | /// to do as little work as possible in response to an exception. 99 | /// 100 | /// ## Macos 101 | /// 102 | /// Mac uses exception ports (sorry, can't give a good link here since Apple 103 | /// documentation is terrible) which are handled by a thread owned by the 104 | /// exception handler which makes them slightly safer to handle than UNIX signals, 105 | /// but it is again recommended to do as little work as possible. 106 | pub unsafe trait CrashEvent: Send + Sync { 107 | /// Method invoked when a crash occurs. 108 | /// 109 | /// Returning true indicates your handler has processed the crash and that 110 | /// no further handlers should run. 111 | fn on_crash(&self, context: &CrashContext) -> CrashEventResult; 112 | } 113 | 114 | /// Creates a [`CrashEvent`] using the supplied closure as the implementation. 115 | /// 116 | /// The supplied closure will be called for both real crash events as well as 117 | /// those simulated by calling `simulate_signal/exception`, which is why it is 118 | /// not `FnOnce` 119 | /// 120 | /// # Safety 121 | /// 122 | /// See the [`CrashEvent`] Safety section for information on why this is `unsafe`. 123 | #[inline] 124 | pub unsafe fn make_crash_event(closure: F) -> Box 125 | where 126 | F: Send + Sync + Fn(&CrashContext) -> CrashEventResult + 'static, 127 | { 128 | struct Wrapper { 129 | inner: F, 130 | } 131 | 132 | unsafe impl CrashEvent for Wrapper 133 | where 134 | F: Send + Sync + Fn(&CrashContext) -> CrashEventResult, 135 | { 136 | fn on_crash(&self, context: &CrashContext) -> CrashEventResult { 137 | (self.inner)(context) 138 | } 139 | } 140 | 141 | Box::new(Wrapper { inner: closure }) 142 | } 143 | 144 | /// Creates a [`CrashEvent`] using the supplied closure as the implementation. 145 | /// 146 | /// This uses an `FnOnce` closure instead of `Fn` like `[make_crash_event]`, but 147 | /// means this closure can only be used for the first crash, and cannot be used 148 | /// in a situation where user-triggered crashes via the `simulate_signal/exception` 149 | /// methods are used. 150 | /// 151 | /// # Safety 152 | /// 153 | /// See the [`CrashEvent`] Safety section for information on why this is `unsafe`. 154 | #[inline] 155 | pub unsafe fn make_single_crash_event(closure: F) -> Box 156 | where 157 | F: Send + Sync + FnOnce(&CrashContext) -> CrashEventResult + 'static, 158 | { 159 | struct Wrapper { 160 | // technically mutexes are not async signal safe on linux, but this is 161 | // an internal-only detail that will be safe _unless_ the callback invoked 162 | // by the user also crashes, but if that occurs...that's on them 163 | inner: parking_lot::Mutex>, 164 | } 165 | 166 | unsafe impl CrashEvent for Wrapper 167 | where 168 | F: Send + Sync + FnOnce(&CrashContext) -> CrashEventResult, 169 | { 170 | fn on_crash(&self, context: &CrashContext) -> CrashEventResult { 171 | if let Some(inner) = self.inner.lock().take() { 172 | (inner)(context) 173 | } else { 174 | false.into() 175 | } 176 | } 177 | } 178 | 179 | Box::new(Wrapper { 180 | inner: parking_lot::Mutex::new(Some(closure)), 181 | }) 182 | } 183 | 184 | cfg_if::cfg_if! { 185 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 186 | mod linux; 187 | 188 | pub use linux::{CrashHandler, Signal, jmp}; 189 | } else if #[cfg(target_os = "windows")] { 190 | mod windows; 191 | 192 | #[cfg(target_arch = "x86_64")] 193 | pub use windows::jmp; 194 | 195 | pub use windows::{CrashHandler, ExceptionCode}; 196 | } else if #[cfg(target_os = "macos")] { 197 | mod mac; 198 | 199 | pub use mac::{CrashHandler, ExceptionType}; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /crash-handler/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Changelog 4 | 5 | All notable changes to this project will be documented in this file. 6 | 7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 9 | 10 | 11 | ## [Unreleased] - ReleaseDate 12 | ## [0.6.3] - 2025-05-13 13 | ### Fixed 14 | - [PR#94](https://github.com/EmbarkStudios/crash-handling/pull/94) resolved a case of UB, resolving [#93](https://github.com/EmbarkStudios/crash-handling/issues/93). Thanks [@Chain-Fox](https://github.com/Chain-Fox)! 15 | - [PR#96](https://github.com/EmbarkStudios/crash-handling/pull/96) resolved [#92](https://github.com/EmbarkStudios/crash-handling/issues/92) by avoid creating a copy of the crash context on the stack, which on `aarch64-linux` would result in a stack overflow in the signal handler. 16 | 17 | ### Added 18 | - [PR#96](https://github.com/EmbarkStudios/crash-handling/pull/96) added testing of `aarch64-linux` to CI. 19 | 20 | ### Changed 21 | - [PR#95](https://github.com/EmbarkStudios/crash-handling/pull/95) updated crates, and changed the edition to 2024. 22 | 23 | ## [0.6.2] - 2024-06-08 24 | ### Added 25 | - [PR#86](https://github.com/EmbarkStudios/crash-handling/pull/86) (carrying on from [PR#85](https://github.com/EmbarkStudios/crash-handling/pull/85)) added support for [vectored exception handlers](https://learn.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling) on Windows, which can catch heap corruption exceptions that the vanilla exception handler cannot catch. Thanks [Tom!](https://github.com/h3r2tic)! 26 | 27 | ## [0.6.1] - 2024-01-29 28 | ### Added 29 | - [PR#81](https://github.com/EmbarkStudios/crash-handling/pull/81) resolved [#79](https://github.com/EmbarkStudios/crash-handling/issues/79) by adding `make_single_crash_event`. 30 | 31 | ## [0.6.0] - 2023-04-03 32 | ### Changed 33 | - [PR#70](https://github.com/EmbarkStudios/crash-handling/pull/70) removed `winapi` in favor of embedded bindings. 34 | 35 | ## [0.5.1] - 2022-11-17 36 | ### Fixed 37 | - [PR#64](https://github.com/EmbarkStudios/crash-handling/pull/64) fixed compilation for `aarch64-linux-android`. Additional targets were added to CI so they get caught before release. 38 | 39 | ## [0.5.0] - 2022-11-17 40 | ### Added 41 | - [PR#62](https://github.com/EmbarkStudios/crash-handling/pull/62) and [PR#63](https://github.com/EmbarkStudios/crash-handling/pull/63) added support for handling `SIGABRT` on Windows. 42 | 43 | ### Fixed 44 | - [PR#62](https://github.com/EmbarkStudios/crash-handling/pull/62) changed from using `RtlCaptureContext` on Windows to using `crash_context::capture_context`. This implementation fixes a crash issue due to `winapi` and `windows-sys` having [improper bindings](https://github.com/microsoft/win32metadata/issues/1044). 45 | 46 | ### Changed 47 | - [PR#62](https://github.com/EmbarkStudios/crash-handling/pull/62) changed from using `RtlCaptureContext` on Windows to using `crash_context::capture_context`. This implementation additionally captures floating point and vector state on `x86_64` unlike `RtlCaptureContext`. 48 | 49 | ## [0.4.0] - 2022-10-21 50 | ### Added 51 | - [PR#60](https://github.com/EmbarkStudios/crash-handling/pull/60) resolved [#59](https://github.com/EmbarkStudios/crash-handling/issues/59) by adding support for `PR_SET_PTRACER` before invoking the user callback, ensuring that an external process has permissions to perform `ptrace` operations on the crashing process, even if `/proc/sys/kernel/yama/ptrace_scope` is set to restricted (1), as this is the default for most newer distributions. 52 | 53 | ### Changed 54 | - [PR#60](https://github.com/EmbarkStudios/crash-handling/pull/60) bumped `windows-sys` to 0.42. 55 | 56 | ## [0.3.3] - 2022-07-21 57 | ### Added 58 | - [PR#46](https://github.com/EmbarkStudios/crash-handling/pull/46) resolved [#33](https://github.com/EmbarkStudios/crash-handling/issues/33) by adding support for `EXC_RESOURCE` exceptions. Since not all resource exceptions are fatal, they are checked and only reported to the user callback if they are indeed fatal. 59 | - [PR#47](https://github.com/EmbarkStudios/crash-handling/pull/47) resolved [#34](https://github.com/EmbarkStudios/crash-handling/issues/34) by adding support for `EXC_GUARD` exceptions. 60 | 61 | ## [0.3.2] - 2022-07-19 62 | ### Fixed 63 | - [PR#38](https://github.com/EmbarkStudios/crash-handling/pull/38) resolved [#31](https://github.com/EmbarkStudios/crash-handling/issues/31) and [#35](https://github.com/EmbarkStudios/crash-handling/issues/35) by adding support for 64-bit codes in the mach exception information, as well as now handling `EXC_CRASH` exceptions. 64 | - [PR#43](https://github.com/EmbarkStudios/crash-handling/pull/42) resolved [#42](https://github.com/EmbarkStudios/crash-handling/issues/42) by fixing a bug on `aarch64-linux`. Thanks [@sfackler](https://github.com/sfackler)! 65 | 66 | ## [0.3.1] - 2022-05-25 67 | ### Changed 68 | - Updated to `minidump-writer` 0.2.1 which includes support for MacOS thread names, and aligns on crash-context 0.3.0. 69 | 70 | ## [0.3.0] - 2022-05-23 71 | ### Added 72 | - First usable release of `crash-context`, `crash-handler`, `sadness-generator`, and `minidumper` crates. 73 | 74 | ## [crash-handler-v0.1.0] - 2022-04-29 75 | ### Added 76 | - Initial publish of crash-handler with Linux, Windows, and MacOS support 77 | 78 | ## [sadness-generator-v0.1.0] - 2022-04-29 79 | ### Added 80 | - Initial published of sadness-generator, can generated crashes on Linux, Windows, and MacOS 81 | 82 | ## [crash-context-v0.2.0] - 2022-04-29 83 | ### Added 84 | - Add Windows and MacOS support 85 | 86 | ## [crash-context-v0.1.0] - 2022-04-21 87 | ### Added 88 | - Initial pass of crash-context, Linux only 89 | 90 | 91 | [Unreleased]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.6.3...HEAD 92 | [0.6.3]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.6.2...crash-handler-0.6.3 93 | [0.6.2]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.6.1...crash-handler-0.6.2 94 | [0.6.1]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.6.0...crash-handler-0.6.1 95 | [0.6.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.5.1...crash-handler-0.6.0 96 | [0.5.1]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.5.0...crash-handler-0.5.1 97 | [0.5.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.4.0...crash-handler-0.5.0 98 | [0.4.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.3.3...crash-handler-0.4.0 99 | [0.3.3]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-0.3.2...crash-handler-0.3.3 100 | [0.3.2]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.1...crash-handler-0.3.2 101 | [0.3.1]: https://github.com/EmbarkStudios/crash-handling/compare/0.3.1...0.3.1 102 | [0.3.0]: https://github.com/EmbarkStudios/crash-handling/compare/crash-handler-v0.1.0...0.3.0 103 | [crash-handler-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-handler-v0.1.0 104 | [sadness-generator-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/sadness-generator-v0.1.0 105 | [crash-context-v0.2.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.2.0 106 | [crash-context-v0.1.0]: https://github.com/EmbarkStudios/crash-handling/releases/tag/crash-context-v0.1.0 107 | -------------------------------------------------------------------------------- /minidumper/tests/ipc.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, atomic}; 2 | 3 | /// Tests that the user can send and receive their own messages over IPC 4 | #[test] 5 | fn ipc_messages() { 6 | let name = "ipc_messages"; 7 | 8 | let mut server = minidumper::Server::with_name(name).unwrap(); 9 | 10 | struct Message { 11 | kind: u32, 12 | msg: String, 13 | } 14 | 15 | struct Server { 16 | messages: Arc>>, 17 | } 18 | 19 | impl minidumper::ServerHandler for Server { 20 | fn create_minidump_file( 21 | &self, 22 | ) -> Result<(std::fs::File, std::path::PathBuf), std::io::Error> { 23 | panic!("should not be called"); 24 | } 25 | 26 | fn on_minidump_created( 27 | &self, 28 | _result: Result, 29 | ) -> minidumper::LoopAction { 30 | panic!("should not be called"); 31 | } 32 | 33 | fn on_message(&self, kind: u32, buffer: Vec) { 34 | self.messages.lock().push(Message { 35 | kind, 36 | msg: String::from_utf8(buffer).unwrap(), 37 | }); 38 | } 39 | } 40 | 41 | let messages = Arc::new(parking_lot::Mutex::new(Vec::new())); 42 | 43 | let server_handler = Server { 44 | messages: messages.clone(), 45 | }; 46 | 47 | let shutdown = Arc::new(atomic::AtomicBool::new(false)); 48 | let is_shutdown = shutdown.clone(); 49 | let server_loop = 50 | std::thread::spawn(move || server.run(Box::new(server_handler), &is_shutdown, None)); 51 | 52 | let client = minidumper::Client::with_name(name).unwrap(); 53 | 54 | for i in 0..1000 { 55 | assert!(client.send_message(i, format!("msg #{i}")).is_ok(), "{i}"); 56 | } 57 | 58 | shutdown.store(true, atomic::Ordering::Relaxed); 59 | server_loop.join().unwrap().unwrap(); 60 | 61 | let messages = messages.lock(); 62 | for (i, msg) in (0..1000).zip(messages.iter()) { 63 | assert_eq!(i, msg.kind); 64 | assert_eq!(format!("msg #{i}"), msg.msg); 65 | } 66 | } 67 | 68 | /// Tests that the server reaps inactive clients 69 | #[test] 70 | fn inactive_reap() { 71 | if std::env::var("CI").is_ok() { 72 | println!("not potato compatible"); 73 | return; 74 | } 75 | 76 | let name = "inactive_reap"; 77 | 78 | let mut server = minidumper::Server::with_name(name).unwrap(); 79 | 80 | struct Message { 81 | msg: String, 82 | } 83 | 84 | struct Server { 85 | messages: Arc>>, 86 | } 87 | 88 | impl minidumper::ServerHandler for Server { 89 | fn create_minidump_file( 90 | &self, 91 | ) -> Result<(std::fs::File, std::path::PathBuf), std::io::Error> { 92 | panic!("should not be called"); 93 | } 94 | 95 | fn on_minidump_created( 96 | &self, 97 | _result: Result, 98 | ) -> minidumper::LoopAction { 99 | panic!("should not be called"); 100 | } 101 | 102 | fn on_message(&self, _kind: u32, buffer: Vec) { 103 | self.messages.lock().push(Message { 104 | msg: String::from_utf8(buffer).unwrap(), 105 | }); 106 | } 107 | 108 | fn on_client_disconnected(&self, num_clients: usize) -> minidumper::LoopAction { 109 | self.messages.lock().push(Message { 110 | msg: format!("num_clients = {num_clients}"), 111 | }); 112 | 113 | if num_clients == 0 { 114 | minidumper::LoopAction::Exit 115 | } else { 116 | minidumper::LoopAction::Continue 117 | } 118 | } 119 | } 120 | 121 | let messages = Arc::new(parking_lot::Mutex::new(Vec::new())); 122 | 123 | let server_handler = Server { 124 | messages: messages.clone(), 125 | }; 126 | 127 | let shutdown = Arc::new(atomic::AtomicBool::new(false)); 128 | let server_loop = std::thread::spawn(move || { 129 | server.run( 130 | Box::new(server_handler), 131 | &shutdown, 132 | Some(std::time::Duration::from_millis(20)), 133 | ) 134 | }); 135 | 136 | let client_one = minidumper::Client::with_name(name).unwrap(); 137 | let client_two = minidumper::Client::with_name(name).unwrap(); 138 | 139 | client_one.send_message(1, "msg #1").expect("1"); 140 | client_two.send_message(2, "msg #2").expect("2"); 141 | 142 | std::thread::sleep(std::time::Duration::from_millis(12)); 143 | 144 | client_one.ping().expect("ping"); 145 | 146 | std::thread::sleep(std::time::Duration::from_millis(12)); 147 | 148 | client_one.send_message(1, "msg #3").expect("3"); 149 | 150 | server_loop.join().unwrap().unwrap(); 151 | 152 | let messages = messages.lock(); 153 | 154 | assert_eq!(messages.len(), 5); 155 | assert_eq!(messages[0].msg, "msg #1"); 156 | assert_eq!(messages[1].msg, "msg #2"); 157 | assert_eq!(messages[2].msg, "num_clients = 1"); 158 | assert_eq!(messages[3].msg, "msg #3"); 159 | assert_eq!(messages[4].msg, "num_clients = 0"); 160 | } 161 | 162 | #[test] 163 | fn ping() { 164 | if std::env::var("CI").is_ok() { 165 | println!("not potato compatible"); 166 | return; 167 | } 168 | 169 | let name = "ping"; 170 | 171 | let mut server = minidumper::Server::with_name(name).unwrap(); 172 | 173 | struct Server; 174 | 175 | impl minidumper::ServerHandler for Server { 176 | fn create_minidump_file( 177 | &self, 178 | ) -> Result<(std::fs::File, std::path::PathBuf), std::io::Error> { 179 | panic!("should not be called"); 180 | } 181 | 182 | fn on_minidump_created( 183 | &self, 184 | _result: Result, 185 | ) -> minidumper::LoopAction { 186 | panic!("should not be called"); 187 | } 188 | 189 | fn on_message(&self, _kind: u32, _buffer: Vec) { 190 | panic!("should not be called"); 191 | } 192 | 193 | fn on_client_disconnected(&self, num_clients: usize) -> minidumper::LoopAction { 194 | if num_clients == 0 { 195 | minidumper::LoopAction::Exit 196 | } else { 197 | minidumper::LoopAction::Continue 198 | } 199 | } 200 | } 201 | 202 | let server_handler = Server; 203 | 204 | let shutdown = Arc::new(atomic::AtomicBool::new(false)); 205 | let server_loop = std::thread::spawn(move || { 206 | server.run( 207 | Box::new(server_handler), 208 | &shutdown, 209 | Some(std::time::Duration::from_millis(20)), 210 | ) 211 | }); 212 | 213 | let client = minidumper::Client::with_name(name).unwrap(); 214 | 215 | let start = std::time::Instant::now(); 216 | for i in 0..3 { 217 | std::thread::sleep(std::time::Duration::from_millis(10)); 218 | let res = client.ping(); 219 | assert!(res.is_ok(), "ping {i} {res:?}"); 220 | } 221 | 222 | server_loop.join().unwrap().unwrap(); 223 | 224 | #[allow(clippy::dbg_macro)] 225 | let elapsed = dbg!(start.elapsed()); 226 | 227 | assert!( 228 | elapsed < std::time::Duration::from_millis(60 + 30 /* account for potato computers */) 229 | && elapsed > std::time::Duration::from_millis(45) 230 | ); 231 | 232 | assert!(client.ping().is_err(), "server should be gone"); 233 | } 234 | -------------------------------------------------------------------------------- /crash-handler/src/mac/ffi.rs: -------------------------------------------------------------------------------- 1 | //! Additional bindings not (or incorrectly) exposed by the [`mach2`] crate. 2 | //! These are lifted from 3 | 4 | pub use mach2::{ 5 | exception_types as et, 6 | kern_return::{KERN_SUCCESS, kern_return_t}, 7 | mach_init::mach_thread_self, 8 | mach_port as mp, mach_types as mt, message as msg, 9 | port::{self, MACH_PORT_NULL, mach_port_t}, 10 | task, thread_status as ts, 11 | traps::mach_task_self, 12 | }; 13 | 14 | /// Number of top level exception types 15 | /// 16 | /// 17 | /// 18 | pub const EXC_TYPES_COUNT: usize = 14; 19 | /// For `EXC_SOFTWARE` exceptions, this indicates the exception was due to a Unix signal 20 | /// 21 | /// The actual Unix signal is stored in the subcode of the exception 22 | /// 23 | /// 24 | pub const EXC_SOFT_SIGNAL: u32 = 0x10003; 25 | 26 | cfg_if::cfg_if! { 27 | if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { 28 | /// 29 | pub const THREAD_STATE_NONE: ts::thread_state_flavor_t = 13; 30 | } else if #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] { 31 | /// 32 | pub const THREAD_STATE_NONE: ts::thread_state_flavor_t = 5; 33 | } 34 | } 35 | 36 | /// Network Data Representation Record 37 | /// 38 | /// 39 | #[repr(C)] 40 | #[derive(Copy, Clone)] 41 | pub struct NdrRecord { 42 | pub mig_vers: u8, 43 | pub if_vers: u8, 44 | pub reserved1: u8, 45 | pub mig_encoding: u8, 46 | pub int_rep: u8, 47 | pub char_rep: u8, 48 | pub float_rep: u8, 49 | pub reserved2: u8, 50 | } 51 | 52 | /// 53 | #[repr(C, packed(4))] 54 | pub struct MachMsgPortDescriptor { 55 | pub name: u32, 56 | __pad1: u32, 57 | __pad2: u16, 58 | __disposition: u8, 59 | __type: u8, 60 | } 61 | 62 | #[repr(C, packed(4))] 63 | pub struct MachMsgBody { 64 | pub descriptor_count: u32, 65 | } 66 | 67 | /// 68 | #[repr(C, packed(4))] 69 | pub struct MachMsgHeader { 70 | pub bits: u32, 71 | pub size: u32, 72 | pub remote_port: u32, 73 | pub local_port: u32, 74 | pub voucher_port: u32, 75 | pub id: u32, 76 | } 77 | 78 | /// 79 | #[repr(C, packed(4))] 80 | pub struct MachMsgTrailer { 81 | pub kind: u32, 82 | pub size: u32, 83 | } 84 | 85 | /// This structure can be obtained by running `mig /usr/include/mach_exc.defs` 86 | #[repr(C, packed(4))] 87 | pub struct ExceptionMessage { 88 | pub header: MachMsgHeader, 89 | /* start of the kernel processed data */ 90 | pub body: MachMsgBody, 91 | pub thread: MachMsgPortDescriptor, 92 | pub task: MachMsgPortDescriptor, 93 | /* end of the kernel processed data */ 94 | _ndr: NdrRecord, 95 | pub exception: u32, 96 | pub code_count: u32, 97 | pub code: [u64; 2], 98 | _trailer: MachMsgTrailer, 99 | } 100 | 101 | /// Whenever MIG detects an error, it sends back a generic `mig_reply_error_t` 102 | /// format message. Clients must accept these in addition to the expected reply 103 | /// message format. 104 | /// 105 | /// This structure can be obtained by running `mig /usr/include/mach_exc.defs` 106 | #[repr(C, packed(4))] 107 | pub struct ExceptionRaiseReply { 108 | pub header: MachMsgHeader, 109 | pub ndr: NdrRecord, 110 | pub ret_code: kern_return_t, 111 | } 112 | 113 | unsafe extern "C" { 114 | /// Set an exception handler for a thread on one or more exception types. 115 | /// At the same time, return the previously defined exception handlers for 116 | /// those types. 117 | /// 118 | /// Atomically (I assume) swaps the currently registered exception ports 119 | /// with a new one, returning the previously registered ports so that 120 | /// they can be restored later. 121 | /// 122 | /// Given the order of arguments I'm assuming this function has evolved 123 | /// over time, but basically (at least, according to how it is used in 124 | /// Breakpad), the output of this function will be 4 distinct arrays, 125 | /// which are basically a structure of arrays 126 | /// 127 | /// 128 | pub fn task_swap_exception_ports( 129 | task: mt::task_t, // The task we want to swap the ports for 130 | exception_mask: et::exception_mask_t, // The mask of exceptions, will only swaps ports that match an exception in the mask 131 | new_port: mach_port_t, // The new exception port we want to use 132 | behavior: et::exception_behavior_t, // The exception behavior when sending to the port 133 | new_flavor: ts::thread_state_flavor_t, // What CPU context info to retrieve 134 | masks: *mut et::exception_mask_t, // Output array of each exception mask that has a registered port 135 | masks_count: *mut u32, // The length of the masks array, as well as the following arrays 136 | old_handlers: *mut mach_port_t, // Output array of ports that are registered 137 | old_behaviors: *mut et::exception_behavior_t, // Output array of behaviors 138 | old_flavors: *mut ts::thread_state_flavor_t, // Output array of thread flavors 139 | ) -> kern_return_t; 140 | 141 | /// Set an exception handler for a task on one or more exception types. 142 | /// These handlers are invoked for all threads in the task if there are 143 | /// no thread-specific exception handlers or those handlers returned an 144 | /// error. 145 | /// 146 | /// 147 | pub fn task_set_exception_ports( 148 | task: mt::task_t, // The task we want to set the port for 149 | exception_mask: et::exception_mask_t, // The exception we want to set the port for 150 | new_port: mach_port_t, // The new port to receive exceptions on 151 | behavior: et::exception_behavior_t, // The exception behavior when send to the port 152 | new_flavor: ts::thread_state_flavor_t, // What CPU context info to send with the exception 153 | ) -> kern_return_t; 154 | 155 | /// The host? NDR 156 | /// 157 | /// 158 | /// 159 | pub static NDR_record: NdrRecord; 160 | } 161 | -------------------------------------------------------------------------------- /minidumper/src/ipc.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 3 | use std::os::{ 4 | unix::{prelude::{RawFd, BorrowedFd}, io::AsRawFd}, 5 | fd::AsFd, 6 | }; 7 | 8 | type Stream = uds::UnixSeqpacketConn; 9 | 10 | struct Connection(uds::nonblocking::UnixSeqpacketConn); 11 | 12 | impl polling::AsRawSource for Connection { 13 | fn raw(&self) -> RawFd { 14 | self.0.as_raw_fd() 15 | } 16 | } 17 | 18 | impl AsRawFd for Connection { 19 | fn as_raw_fd(&self) -> RawFd { 20 | self.0.as_raw_fd() 21 | } 22 | } 23 | 24 | impl AsFd for Connection { 25 | fn as_fd(&self) -> BorrowedFd<'_> { 26 | #[allow(unsafe_code)] 27 | unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } 28 | } 29 | } 30 | 31 | impl Connection { 32 | #[inline] 33 | fn send(&self, buf: &[u8]) -> Result { 34 | self.0.send(buf) 35 | } 36 | 37 | #[inline] 38 | fn recv(&self, buf: &mut [u8]) -> Result { 39 | self.0.recv(buf) 40 | } 41 | 42 | #[inline] 43 | fn recv_vectored(&self, buf: &mut [std::io::IoSliceMut<'_>]) -> Result<(usize, bool), std::io::Error> { 44 | self.0.recv_vectored(buf) 45 | } 46 | } 47 | 48 | struct Listener(uds::nonblocking::UnixSeqpacketListener); 49 | 50 | impl polling::AsRawSource for Listener { 51 | fn raw(&self) -> RawFd { 52 | self.0.as_raw_fd() 53 | } 54 | } 55 | 56 | impl AsRawFd for Listener { 57 | fn as_raw_fd(&self) -> RawFd { 58 | self.0.as_raw_fd() 59 | } 60 | } 61 | 62 | impl AsFd for Listener { 63 | fn as_fd(&self) -> BorrowedFd<'_> { 64 | #[allow(unsafe_code)] 65 | unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } 66 | } 67 | } 68 | 69 | impl Listener { 70 | fn accept_unix_addr(&self) -> Result<(Connection, uds::UnixSocketAddr), std::io::Error> { 71 | self.0.accept_unix_addr().map(|(conn, addr)| (Connection(conn), addr)) 72 | } 73 | } 74 | } else if #[cfg(target_os = "windows")] { 75 | mod windows; 76 | 77 | type Stream = windows::UnixStream; 78 | 79 | type Listener = windows::UnixListener; 80 | type Connection = windows::UnixStream; 81 | 82 | // This will of course break if the client and server are built for different 83 | // arches, but that is the fault of the user in that case 84 | cfg_if::cfg_if! { 85 | if #[cfg(target_pointer_width = "32")] { 86 | type ProtoPointer = u32; 87 | } else if #[cfg(target_pointer_width = "64")] { 88 | type ProtoPointer = u64; 89 | } 90 | } 91 | 92 | #[derive(scroll::Pwrite, scroll::Pread, scroll::SizeWith)] 93 | struct DumpRequest { 94 | /// The address of an `EXCEPTION_POINTERS` in the client's memory 95 | exception_pointers: ProtoPointer, 96 | /// The process id of the client process 97 | process_id: u32, 98 | /// The id of the thread in the client process in which the crash originated 99 | thread_id: u32, 100 | /// The top level exception code, also found in the `EXCEPTION_POINTERS.ExceptionRecord.ExceptionCode` 101 | exception_code: i32, 102 | } 103 | } else if #[cfg(target_os = "macos")] { 104 | mod mac; 105 | 106 | type Stream = mac::UnixStream; 107 | 108 | type Listener = mac::UnixListener; 109 | type Connection = mac::UnixStream; 110 | 111 | #[derive(scroll::Pwrite, scroll::Pread, scroll::SizeWith)] 112 | struct DumpRequest { 113 | /// The exception code 114 | code: i64, 115 | /// Optional subcode, typically only present for `EXC_BAD_ACCESS` exceptions 116 | subcode: i64, 117 | /// The process which crashed 118 | task: u32, 119 | /// The thread in the process that crashed 120 | thread: u32, 121 | /// The thread that handled the exception. This may be useful to ignore. 122 | handler_thread: u32, 123 | /// The exception kind 124 | kind: i32, 125 | /// Boolean to indicate if there is exception information or not 126 | has_exception: u8, 127 | /// Boolean to indicate if there is a subcode 128 | has_subcode: u8, 129 | } 130 | 131 | } 132 | } 133 | 134 | mod client; 135 | mod server; 136 | 137 | pub use client::Client; 138 | pub use server::Server; 139 | 140 | const CRASH: u32 = 0; 141 | #[cfg_attr(target_os = "macos", allow(dead_code))] 142 | const CRASH_ACK: u32 = 1; 143 | const PING: u32 = 2; 144 | const PONG: u32 = 3; 145 | const USER: u32 = 4; 146 | 147 | /// A socket name. 148 | /// 149 | /// Linux, Windows, and Macos can all use a file path as the name for the socket. 150 | /// 151 | /// Additionally, Linux can use a plain string that will be used as an abstract 152 | /// name. See [here](https://man7.org/linux/man-pages/man7/unix.7.html) for 153 | /// more details on abstract namespace sockets. 154 | /// 155 | /// Note that on Macos, this name is _also_ used as the name for a mach port. 156 | /// Apple doesn't have good/any documentation for mach port service names, but 157 | /// they are allowed to be longer than the path for a socket name. We also 158 | /// require that the path be utf-8. 159 | pub enum SocketName<'scope> { 160 | Path(&'scope std::path::Path), 161 | #[cfg(any(target_os = "linux", target_os = "android"))] 162 | Abstract(&'scope str), 163 | } 164 | 165 | impl<'scope> From<&'scope std::path::Path> for SocketName<'scope> { 166 | fn from(s: &'scope std::path::Path) -> Self { 167 | Self::Path(s) 168 | } 169 | } 170 | 171 | impl<'scope> From<&'scope str> for SocketName<'scope> { 172 | fn from(s: &'scope str) -> Self { 173 | cfg_if::cfg_if! { 174 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 175 | Self::Abstract(s) 176 | } else { 177 | Self::Path(std::path::Path::new(s)) 178 | } 179 | } 180 | } 181 | } 182 | 183 | impl<'scope> From<&'scope String> for SocketName<'scope> { 184 | fn from(s: &'scope String) -> Self { 185 | Self::from(s.as_str()) 186 | } 187 | } 188 | 189 | #[derive(Copy, Clone)] 190 | #[cfg_attr(test, derive(PartialEq, Eq, Debug))] 191 | #[repr(C)] 192 | pub struct Header { 193 | kind: u32, 194 | size: u32, 195 | } 196 | 197 | impl Header { 198 | fn as_bytes(&self) -> &[u8] { 199 | #[allow(unsafe_code)] 200 | unsafe { 201 | let size = std::mem::size_of::(); 202 | let ptr = (self as *const Self).cast(); 203 | std::slice::from_raw_parts(ptr, size) 204 | } 205 | } 206 | 207 | fn from_bytes(buf: &[u8]) -> Option { 208 | if buf.len() != std::mem::size_of::() { 209 | return None; 210 | } 211 | 212 | #[allow(unsafe_code)] 213 | unsafe { 214 | Some(*buf.as_ptr().cast::()) 215 | } 216 | } 217 | } 218 | 219 | #[cfg(test)] 220 | mod test { 221 | use super::Header; 222 | 223 | #[test] 224 | fn header_bytes() { 225 | let expected = Header { 226 | kind: 20, 227 | size: 8 * 1024, 228 | }; 229 | let exp_bytes = expected.as_bytes(); 230 | 231 | let actual = Header::from_bytes(exp_bytes).unwrap(); 232 | 233 | assert_eq!(expected, actual); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /crash-context/src/windows.rs: -------------------------------------------------------------------------------- 1 | /// Full Windows crash context 2 | pub struct CrashContext { 3 | /// The information on the exception. 4 | /// 5 | /// Note that this is a pointer into the actual memory of the crashed process, 6 | /// and is a pointer to an [EXCEPTION_POINTERS](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_pointers) 7 | pub exception_pointers: *const EXCEPTION_POINTERS, 8 | /// The top level exception code from the `exception_pointers`. This is provided 9 | /// so that external processes don't need to use `ReadProcessMemory` to inspect 10 | /// the exception code 11 | pub exception_code: i32, 12 | /// The pid of the process that crashed 13 | pub process_id: u32, 14 | /// The thread id on which the exception occurred 15 | pub thread_id: u32, 16 | } 17 | 18 | #[link(name = "kernel32")] 19 | unsafe extern "system" { 20 | #[link_name = "RtlCaptureContext"] 21 | pub fn capture_context(ctx_rec: *mut CONTEXT); 22 | } 23 | 24 | cfg_if::cfg_if! { 25 | if #[cfg(target_arch = "x86_64")] { 26 | #[repr(C, align(16))] 27 | pub struct M128A { 28 | pub Low: u64, 29 | pub High: i64, 30 | } 31 | 32 | #[repr(C)] 33 | pub struct CONTEXT_0_0 { 34 | pub Header: [M128A; 2], 35 | pub Legacy: [M128A; 8], 36 | pub Xmm0: M128A, 37 | pub Xmm1: M128A, 38 | pub Xmm2: M128A, 39 | pub Xmm3: M128A, 40 | pub Xmm4: M128A, 41 | pub Xmm5: M128A, 42 | pub Xmm6: M128A, 43 | pub Xmm7: M128A, 44 | pub Xmm8: M128A, 45 | pub Xmm9: M128A, 46 | pub Xmm10: M128A, 47 | pub Xmm11: M128A, 48 | pub Xmm12: M128A, 49 | pub Xmm13: M128A, 50 | pub Xmm14: M128A, 51 | pub Xmm15: M128A, 52 | } 53 | 54 | #[repr(C, align(16))] 55 | pub struct XSAVE_FORMAT { 56 | pub ControlWord: u16, 57 | pub StatusWord: u16, 58 | pub TagWord: u8, 59 | pub Reserved1: u8, 60 | pub ErrorOpcode: u16, 61 | pub ErrorOffset: u32, 62 | pub ErrorSelector: u16, 63 | pub Reserved2: u16, 64 | pub DataOffset: u32, 65 | pub DataSelector: u16, 66 | pub Reserved3: u16, 67 | pub MxCsr: u32, 68 | pub MxCsr_Mask: u32, 69 | pub FloatRegisters: [M128A; 8], 70 | pub XmmRegisters: [M128A; 16], 71 | pub Reserved4: [u8; 96], 72 | } 73 | 74 | #[repr(C)] 75 | pub union CONTEXT_0 { 76 | pub FltSave: std::mem::ManuallyDrop, 77 | pub Anonymous: std::mem::ManuallyDrop, 78 | } 79 | 80 | #[repr(C, align(16))] 81 | pub struct CONTEXT { 82 | pub P1Home: u64, 83 | pub P2Home: u64, 84 | pub P3Home: u64, 85 | pub P4Home: u64, 86 | pub P5Home: u64, 87 | pub P6Home: u64, 88 | pub ContextFlags: u32, 89 | pub MxCsr: u32, 90 | pub SegCs: u16, 91 | pub SegDs: u16, 92 | pub SegEs: u16, 93 | pub SegFs: u16, 94 | pub SegGs: u16, 95 | pub SegSs: u16, 96 | pub EFlags: u32, 97 | pub Dr0: u64, 98 | pub Dr1: u64, 99 | pub Dr2: u64, 100 | pub Dr3: u64, 101 | pub Dr6: u64, 102 | pub Dr7: u64, 103 | pub Rax: u64, 104 | pub Rcx: u64, 105 | pub Rdx: u64, 106 | pub Rbx: u64, 107 | pub Rsp: u64, 108 | pub Rbp: u64, 109 | pub Rsi: u64, 110 | pub Rdi: u64, 111 | pub R8: u64, 112 | pub R9: u64, 113 | pub R10: u64, 114 | pub R11: u64, 115 | pub R12: u64, 116 | pub R13: u64, 117 | pub R14: u64, 118 | pub R15: u64, 119 | pub Rip: u64, 120 | pub Anonymous: CONTEXT_0, 121 | pub VectorRegister: [M128A; 26], 122 | pub VectorControl: u64, 123 | pub DebugControl: u64, 124 | pub LastBranchToRip: u64, 125 | pub LastBranchFromRip: u64, 126 | pub LastExceptionToRip: u64, 127 | pub LastExceptionFromRip: u64, 128 | } 129 | } else if #[cfg(target_arch = "x86")] { 130 | #[repr(C)] 131 | pub struct FLOATING_SAVE_AREA { 132 | pub ControlWord: u32, 133 | pub StatusWord: u32, 134 | pub TagWord: u32, 135 | pub ErrorOffset: u32, 136 | pub ErrorSelector: u32, 137 | pub DataOffset: u32, 138 | pub DataSelector: u32, 139 | pub RegisterArea: [u8; 80], 140 | pub Spare0: u32, 141 | } 142 | 143 | #[repr(C, packed(4))] 144 | pub struct CONTEXT { 145 | pub ContextFlags: u32, 146 | pub Dr0: u32, 147 | pub Dr1: u32, 148 | pub Dr2: u32, 149 | pub Dr3: u32, 150 | pub Dr6: u32, 151 | pub Dr7: u32, 152 | pub FloatSave: FLOATING_SAVE_AREA, 153 | pub SegGs: u32, 154 | pub SegFs: u32, 155 | pub SegEs: u32, 156 | pub SegDs: u32, 157 | pub Edi: u32, 158 | pub Esi: u32, 159 | pub Ebx: u32, 160 | pub Edx: u32, 161 | pub Ecx: u32, 162 | pub Eax: u32, 163 | pub Ebp: u32, 164 | pub Eip: u32, 165 | pub SegCs: u32, 166 | pub EFlags: u32, 167 | pub Esp: u32, 168 | pub SegSs: u32, 169 | pub ExtendedRegisters: [u8; 512], 170 | } 171 | } else if #[cfg(target_arch = "aarch64")] { 172 | #[repr(C)] 173 | pub struct ARM64_NT_NEON128_0 { 174 | pub Low: u64, 175 | pub High: i64, 176 | } 177 | #[repr(C)] 178 | pub union ARM64_NT_NEON128 { 179 | pub Anonymous: std::mem::ManuallyDrop, 180 | pub D: [f64; 2], 181 | pub S: [f32; 4], 182 | pub H: [u16; 8], 183 | pub B: [u8; 16], 184 | } 185 | 186 | #[repr(C)] 187 | pub struct CONTEXT_0_0 { 188 | pub X0: u64, 189 | pub X1: u64, 190 | pub X2: u64, 191 | pub X3: u64, 192 | pub X4: u64, 193 | pub X5: u64, 194 | pub X6: u64, 195 | pub X7: u64, 196 | pub X8: u64, 197 | pub X9: u64, 198 | pub X10: u64, 199 | pub X11: u64, 200 | pub X12: u64, 201 | pub X13: u64, 202 | pub X14: u64, 203 | pub X15: u64, 204 | pub X16: u64, 205 | pub X17: u64, 206 | pub X18: u64, 207 | pub X19: u64, 208 | pub X20: u64, 209 | pub X21: u64, 210 | pub X22: u64, 211 | pub X23: u64, 212 | pub X24: u64, 213 | pub X25: u64, 214 | pub X26: u64, 215 | pub X27: u64, 216 | pub X28: u64, 217 | pub Fp: u64, 218 | pub Lr: u64, 219 | } 220 | 221 | #[repr(C)] 222 | pub union CONTEXT_0 { 223 | pub Anonymous: std::mem::ManuallyDrop, 224 | pub X: [u64; 31], 225 | } 226 | 227 | #[repr(C, align(16))] 228 | pub struct CONTEXT { 229 | pub ContextFlags: u32, 230 | pub Cpsr: u32, 231 | pub Anonymous: CONTEXT_0, 232 | pub Sp: u64, 233 | pub Pc: u64, 234 | pub V: [ARM64_NT_NEON128; 32], 235 | pub Fpcr: u32, 236 | pub Fpsr: u32, 237 | pub Bcr: [u32; 8], 238 | pub Bvr: [u64; 8], 239 | pub Wcr: [u32; 2], 240 | pub Wvr: [u64; 2], 241 | } 242 | } 243 | } 244 | 245 | pub type NTSTATUS = i32; 246 | pub type BOOL = i32; 247 | 248 | #[repr(C)] 249 | pub struct EXCEPTION_RECORD { 250 | pub ExceptionCode: NTSTATUS, 251 | pub ExceptionFlags: u32, 252 | pub ExceptionRecord: *mut EXCEPTION_RECORD, 253 | pub ExceptionAddress: *mut std::ffi::c_void, 254 | pub NumberParameters: u32, 255 | pub ExceptionInformation: [usize; 15], 256 | } 257 | 258 | #[repr(C)] 259 | pub struct EXCEPTION_POINTERS { 260 | pub ExceptionRecord: *mut EXCEPTION_RECORD, 261 | pub ContextRecord: *mut CONTEXT, 262 | } 263 | -------------------------------------------------------------------------------- /crash-handler/src/unix/pthread_interpose.rs: -------------------------------------------------------------------------------- 1 | //! Interposes calls to `pthread_create` so that we always install an alternate 2 | //! signal stack. 3 | //! 4 | //! Original code from 5 | 6 | #![allow(non_camel_case_types)] 7 | 8 | use libc::c_void; 9 | use std::ptr; 10 | 11 | pub type pthread_main_t = unsafe extern "C" fn(_: *mut c_void) -> *mut c_void; 12 | 13 | #[cfg(not(miri))] 14 | type pthread_create_t = unsafe extern "C" fn( 15 | thread: *mut libc::pthread_t, 16 | attr: *const libc::pthread_attr_t, 17 | f: pthread_main_t, 18 | arg: *mut c_void, 19 | ) -> i32; 20 | 21 | struct PthreadCreateParams { 22 | main: pthread_main_t, 23 | arg: *mut c_void, 24 | } 25 | 26 | /// Key created at first thread creation so that we can set the thread specific 27 | /// alternate stack memory as per-thread data that is uninstalled and unmapped 28 | /// in the `pthread_key` destructor 29 | static mut THREAD_DESTRUCTOR_KEY: libc::pthread_key_t = 0; 30 | 31 | #[cfg(all(target_env = "musl", not(miri)))] 32 | unsafe extern "C" { 33 | /// This is the weak alias for `pthread_create`. We declare this so we can 34 | /// use its address when targeting musl, as we can't lookup the actual 35 | /// `pthread_create` symbol at runtime since we've interposed it. 36 | pub fn __pthread_create( 37 | thread: *mut libc::pthread_t, 38 | attr: *const libc::pthread_attr_t, 39 | main: pthread_main_t, 40 | arg: *mut c_void, 41 | ) -> i32; 42 | } 43 | 44 | /// This interposer replaces `pthread_create` so that we can inject an 45 | /// alternate signal stack in every new thread, regardless of whether the 46 | /// thread is created directly in Rust's std library or not 47 | /// 48 | /// # Errors 49 | /// 50 | /// This will fail if we're unable to retrieve the address of the actual 51 | /// libc `pthread_create`, or if we do find the address but it's actually the 52 | /// address of this interpose function which would result in infinte recursion 53 | #[cfg(not(miri))] 54 | #[unsafe(no_mangle)] 55 | pub extern "C" fn pthread_create( 56 | thread: *mut libc::pthread_t, 57 | attr: *const libc::pthread_attr_t, 58 | main: pthread_main_t, 59 | arg: *mut c_void, 60 | ) -> i32 { 61 | /// Get the address of the _real_ `pthread_create` 62 | static mut REAL_PTHREAD_CREATE: Option = None; 63 | static INIT: parking_lot::Once = parking_lot::Once::new(); 64 | 65 | // Finds the real pthread_create and specifies the pthread_key that is 66 | // used to uninstall and unmap the alternate stack 67 | INIT.call_once(|| unsafe { 68 | cfg_if::cfg_if! { 69 | if #[cfg(target_env = "musl")] { 70 | let ptr = __pthread_create as *mut c_void; 71 | } else { 72 | const RTLD_NEXT: *mut c_void = -1isize as *mut c_void; 73 | let ptr = libc::dlsym(RTLD_NEXT, c"pthread_create".as_ptr().cast()); 74 | } 75 | } 76 | 77 | if !ptr.is_null() { 78 | REAL_PTHREAD_CREATE = Some(std::mem::transmute::<*mut libc::c_void, pthread_create_t>( 79 | ptr, 80 | )); 81 | } 82 | 83 | libc::pthread_key_create( 84 | std::ptr::addr_of_mut!(THREAD_DESTRUCTOR_KEY), 85 | Some(uninstall_sig_alt_stack), 86 | ); 87 | }); 88 | 89 | #[allow(static_mut_refs)] 90 | let real_pthread_create = unsafe { 91 | let real_pthread_create = REAL_PTHREAD_CREATE.as_ref().expect("pthread_create() intercept failed but the intercept function is still being called, this won't work"); 92 | assert!( 93 | !std::ptr::fn_addr_eq(*real_pthread_create, pthread_create as pthread_create_t), 94 | "We could not obtain the real pthread_create(). Calling the symbol we got would make us enter an infinte loop so stop here instead." 95 | ); 96 | real_pthread_create 97 | }; 98 | 99 | let create_params = Box::new(PthreadCreateParams { main, arg }); 100 | let create_params = Box::into_raw(create_params); 101 | 102 | let result = unsafe { 103 | real_pthread_create( 104 | thread, 105 | attr, 106 | set_alt_signal_stack_and_start, 107 | create_params.cast(), 108 | ) 109 | }; 110 | 111 | if result != 0 { 112 | // Only deallocate if the thread fails to spawn, if it succeeds it 113 | // will deallocate the box itself 114 | unsafe { 115 | drop(Box::from_raw(create_params)); 116 | } 117 | } 118 | 119 | result 120 | } 121 | 122 | // std::cmp::max is not const :( 123 | const fn get_stack_size() -> usize { 124 | if libc::SIGSTKSZ > 16 * 1024 { 125 | libc::SIGSTKSZ 126 | } else { 127 | 16 * 1024 128 | } 129 | } 130 | 131 | /// The size of the alternate stack that is mapped for every thread. 132 | /// 133 | /// This has a minimum size of 16k, which might seem a bit large, but this 134 | /// memory will only ever be committed in case we actually get a stack overflow, 135 | /// which is (hopefully) exceedingly rare 136 | const SIG_STACK_SIZE: usize = get_stack_size(); 137 | 138 | /// This is the replacment function for the user's thread entry, it installs 139 | /// the alternate stack before invoking the original thread entry, then cleans 140 | /// it up after the user's thread entry exits. 141 | #[unsafe(no_mangle)] 142 | unsafe extern "C" fn set_alt_signal_stack_and_start(params: *mut c_void) -> *mut libc::c_void { 143 | let (user_main, user_arg) = { 144 | let params = unsafe { Box::from_raw(params.cast::()) }; 145 | 146 | (params.main, params.arg) 147 | }; 148 | 149 | let alt_stack_mem = unsafe { install_sig_alt_stack() }; 150 | 151 | // The original code was using pthread_cleanup_push/pop, however those are 152 | // macros in glibc/musl, so we instead use pthread_key_create as it works 153 | // functionally the same and can call a cleanup function/destructor on both 154 | // thread exit and cancel 155 | unsafe { 156 | libc::pthread_setspecific(THREAD_DESTRUCTOR_KEY, alt_stack_mem); 157 | user_main(user_arg) 158 | } 159 | } 160 | 161 | /// Install the alternate signal stack 162 | /// 163 | /// Returns a pointer to the memory area we mapped to store the stack only if it 164 | /// was installed successfully, otherwise returns `null`. 165 | /// 166 | /// # Errors 167 | /// 168 | /// If we're able to map memory, but unable to install the alternate stack, we 169 | /// expect that we can unmap the memory 170 | unsafe fn install_sig_alt_stack() -> *mut libc::c_void { 171 | let alt_stack_mem = unsafe { 172 | libc::mmap( 173 | ptr::null_mut(), 174 | SIG_STACK_SIZE, 175 | libc::PROT_READ | libc::PROT_WRITE, 176 | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, 177 | -1, 178 | 0, 179 | ) 180 | }; 181 | 182 | // Check that we successfully mapped some memory 183 | if alt_stack_mem.is_null() { 184 | return alt_stack_mem; 185 | } 186 | 187 | let alt_stack = libc::stack_t { 188 | ss_sp: alt_stack_mem, 189 | ss_flags: 0, 190 | ss_size: SIG_STACK_SIZE, 191 | }; 192 | 193 | // Attempt to install the alternate stack 194 | let rv = unsafe { libc::sigaltstack(&alt_stack, ptr::null_mut()) }; 195 | 196 | // Attempt to cleanup the mapping if we failed to install the alternate stack 197 | if rv != 0 { 198 | assert_eq!( 199 | unsafe { libc::munmap(alt_stack_mem, SIG_STACK_SIZE) }, 200 | 0, 201 | "failed to install an alternate signal stack, and failed to unmap the alternate stack memory" 202 | ); 203 | ptr::null_mut() 204 | } else { 205 | alt_stack_mem 206 | } 207 | } 208 | 209 | /// Uninstall the alternate signal stack and unmaps the memory. 210 | /// 211 | /// # Errors 212 | /// 213 | /// If the alternate stack is not `null`, it is expected that uninstalling and 214 | /// unmapping will not error 215 | #[unsafe(no_mangle)] 216 | unsafe extern "C" fn uninstall_sig_alt_stack(alt_stack_mem: *mut libc::c_void) { 217 | if alt_stack_mem.is_null() { 218 | return; 219 | } 220 | 221 | let disable_stack = libc::stack_t { 222 | ss_sp: ptr::null_mut(), 223 | ss_flags: libc::SS_DISABLE, 224 | ss_size: 0, 225 | }; 226 | 227 | // Attempt to uninstall the alternate stack 228 | assert_eq!( 229 | unsafe { libc::sigaltstack(&disable_stack, ptr::null_mut()) }, 230 | 0, 231 | "failed to uninstall alternate signal stack" 232 | ); 233 | assert_eq!( 234 | unsafe { libc::munmap(alt_stack_mem, SIG_STACK_SIZE) }, 235 | 0, 236 | "failed to unmap alternate stack memory" 237 | ); 238 | } 239 | -------------------------------------------------------------------------------- /minidumper/src/ipc/client.rs: -------------------------------------------------------------------------------- 1 | use super::{Header, SocketName, Stream}; 2 | use crate::Error; 3 | use std::io::IoSlice; 4 | 5 | /// Client side of the connection, which runs in the process that may (or has) 6 | /// crashed to communicate with an external monitor process. 7 | pub struct Client { 8 | socket: Stream, 9 | /// On Macos we need this additional mach port based client to send crash 10 | /// contexts, as, unfortunately, it's the best (though hopefully not only?) 11 | /// way to get the real info needed by the minidump writer to write the 12 | /// minidump 13 | #[cfg(target_os = "macos")] 14 | port: crash_context::ipc::Client, 15 | } 16 | 17 | impl Client { 18 | /// Creates a new client with the given name. 19 | /// 20 | /// # Errors 21 | /// 22 | /// The specified socket name is invalid, or a connection cannot be made 23 | /// with a server 24 | pub fn with_name<'scope>(name: impl Into>) -> Result { 25 | let sn = name.into(); 26 | 27 | cfg_if::cfg_if! { 28 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 29 | let socket_addr = match sn { 30 | SocketName::Path(path) => { 31 | uds::UnixSocketAddr::from_path(path).map_err(|_err| Error::InvalidName)? 32 | } 33 | SocketName::Abstract(name) => { 34 | uds::UnixSocketAddr::from_abstract(name).map_err(|_err| Error::InvalidName)? 35 | } 36 | }; 37 | 38 | let socket = Stream::connect_unix_addr(&socket_addr)?; 39 | } else if #[cfg(target_os = "windows")] { 40 | let SocketName::Path(path) = sn; 41 | let socket = Stream::connect(path)?; 42 | } else if #[cfg(target_os = "macos")] { 43 | let SocketName::Path(path) = sn; 44 | let socket = Stream::connect(path)?; 45 | 46 | // Note that sun_path is limited to 108 characters including null, 47 | // while a mach port name is limited to 128 including null, so 48 | // the length is already effectively checked here 49 | let port_name = std::ffi::CString::new(path.to_str().ok_or(Error::InvalidPortName)?).map_err(|_err| Error::InvalidPortName)?; 50 | let port = crash_context::ipc::Client::create(&port_name)?; 51 | } else { 52 | compile_error!("unimplemented target platform"); 53 | } 54 | } 55 | 56 | let s = Self { 57 | socket, 58 | #[cfg(target_os = "macos")] 59 | port, 60 | }; 61 | 62 | #[cfg(target_os = "macos")] 63 | { 64 | // Since we aren't sending crash requests as id 0 like for other 65 | // platforms, we instead abuse it to send the pid of this process 66 | // so that the server can pair the port and the socket together 67 | let id_buf = std::process::id().to_ne_bytes(); 68 | s.send_message_impl(0, &id_buf)?; 69 | let mut ack = [0u8; 1]; 70 | s.socket.recv(&mut ack)?; 71 | } 72 | 73 | Ok(s) 74 | } 75 | 76 | /// Requests that the server generate a minidump for the specified crash 77 | /// context. This blocks until the server has finished writing the minidump. 78 | /// 79 | /// # Linux 80 | /// 81 | /// This uses a [`crash_context::CrashContext`] by reference as the size of 82 | /// it can be larger than one would want in an alternate stack handler, the 83 | /// use of a reference allows the context to be stored outside of the stack 84 | /// and heap to avoid that complication, though you may of course generate 85 | /// one however you like. 86 | /// 87 | /// # Windows 88 | /// 89 | /// This uses a [`crash_context::CrashContext`] by reference, as 90 | /// the crash context internally contains pointers into this process' 91 | /// memory that need to stay valid for the duration of the mindump creation. 92 | /// 93 | /// # Macos 94 | /// 95 | /// It is _highly_ recommended that you suspend all threads in the current 96 | /// process (other than the thread that executes this method) via 97 | /// [`thread_suspend`](https://developer.apple.com/documentation/kernel/1418833-thread_suspend) 98 | /// (apologies for the terrible documentation, blame Apple) before calling 99 | /// this method 100 | pub fn request_dump(&self, crash_context: &crash_context::CrashContext) -> Result<(), Error> { 101 | cfg_if::cfg_if! { 102 | if #[cfg(any(target_os = "linux", target_os = "android"))] { 103 | let crash_ctx_buffer = crash_context.as_bytes(); 104 | } else if #[cfg(target_os = "windows")] { 105 | use scroll::Pwrite; 106 | let mut buf = [0u8; 24]; 107 | let written = buf.pwrite( 108 | super::DumpRequest { 109 | exception_pointers: crash_context.exception_pointers as _, 110 | process_id: crash_context.process_id, 111 | thread_id: crash_context.thread_id, 112 | exception_code: crash_context.exception_code, 113 | }, 114 | 0, 115 | )?; 116 | 117 | let crash_ctx_buffer = &buf[..written]; 118 | } else if #[cfg(target_os = "macos")] { 119 | self.port.send_crash_context( 120 | crash_context, 121 | Some(std::time::Duration::from_secs(2)), 122 | Some(std::time::Duration::from_secs(5)) 123 | )?; 124 | Ok(()) 125 | } 126 | } 127 | 128 | #[cfg(not(target_os = "macos"))] 129 | { 130 | self.send_message_impl(0, crash_ctx_buffer)?; 131 | 132 | // Wait for the server to send back an ack that it has finished 133 | // with the crash context 134 | let mut ack = [0u8; std::mem::size_of::
()]; 135 | self.socket.recv(&mut ack)?; 136 | 137 | let header = Header::from_bytes(&ack); 138 | 139 | if header.filter(|hdr| hdr.kind == super::CRASH_ACK).is_none() { 140 | return Err(Error::ProtocolError("received invalid response to crash")); 141 | } 142 | 143 | Ok(()) 144 | } 145 | } 146 | 147 | /// Sends a message to the server. 148 | /// 149 | /// This method is provided so that users can send their own application 150 | /// specific messages to the monitor process. 151 | /// 152 | /// There are no limits imposed by this method itself, but it is recommended 153 | /// to keep the message reasonably sized, eg. below 64KiB, as different 154 | /// targets will have different limits for the maximum payload that can be 155 | /// delivered. 156 | /// 157 | /// It is also important to note that this method can be called from multiple 158 | /// threads if you so choose. Each message is sent vectored and thus won't 159 | /// be split, but if you care about ordering you will need to handle that 160 | /// yourself. 161 | /// 162 | /// # Errors 163 | /// 164 | /// The send to the server fails 165 | #[inline] 166 | pub fn send_message(&self, kind: u32, buf: impl AsRef<[u8]>) -> Result<(), Error> { 167 | debug_assert!(kind < u32::MAX - super::USER); 168 | 169 | self.send_message_impl(kind + super::USER, buf.as_ref()) 170 | 171 | // TODO: should we have an ACK? IPC is a (relatively) reliable communication 172 | // method, and reserving receives from the server for the exclusive 173 | // use of crash dumping, the main thing that users will care about, means 174 | // we reduce complication 175 | // let mut ack = [0u8; 1]; 176 | // self.socket.recv(&mut ack)?; 177 | } 178 | 179 | /// Sends a ping to the server, to keep it from reaping connections that haven't 180 | /// sent a message within its keep alive window 181 | /// 182 | /// # Errors 183 | /// 184 | /// The send to the server fails 185 | #[inline] 186 | pub fn ping(&self) -> Result<(), Error> { 187 | self.send_message_impl(super::PING, &[])?; 188 | 189 | let mut pong = [0u8; std::mem::size_of::
()]; 190 | self.socket.recv(&mut pong)?; 191 | 192 | let header = Header::from_bytes(&pong); 193 | 194 | if header.filter(|hdr| hdr.kind == super::PONG).is_none() { 195 | Err(Error::ProtocolError("received invalid response to ping")) 196 | } else { 197 | Ok(()) 198 | } 199 | } 200 | 201 | fn send_message_impl(&self, kind: u32, buf: &[u8]) -> Result<(), Error> { 202 | let header = Header { 203 | kind, 204 | size: buf.len() as u32, 205 | }; 206 | 207 | let io_bufs = [IoSlice::new(header.as_bytes()), IoSlice::new(buf)]; 208 | 209 | self.socket.send_vectored(&io_bufs)?; 210 | Ok(()) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /minidumper/src/ipc/mac.rs: -------------------------------------------------------------------------------- 1 | //! Implements support for Unix domain sockets for Macos. We don't use std since 2 | //! it `peek` is nightly only and it only implements the [`std::io::Read`] and 3 | //! [`std::io::Write`] traits, which doesn't fit with the model we started with 4 | //! with `uds`, which is that the sockets only do vectored reads/writes so 5 | //! exclusive access is not desired 6 | 7 | #![allow(clippy::mem_forget, unsafe_code)] 8 | 9 | use std::{ 10 | io, 11 | os::{ 12 | fd::AsFd, 13 | unix::{ 14 | ffi::OsStrExt, 15 | io::{AsRawFd, IntoRawFd, RawFd}, 16 | prelude::BorrowedFd, 17 | }, 18 | }, 19 | }; 20 | 21 | #[inline] 22 | fn sun_path_offset(addr: &libc::sockaddr_un) -> usize { 23 | // Work with an actual instance of the type since using a null pointer is UB 24 | let base = addr as *const _ as usize; 25 | let path = &addr.sun_path as *const _ as usize; 26 | path - base 27 | } 28 | 29 | pub struct UnixSocketAddr { 30 | pub(super) addr: libc::sockaddr_un, 31 | pub(super) len: libc::socklen_t, 32 | } 33 | 34 | impl UnixSocketAddr { 35 | pub(super) fn new(path: &std::path::Path) -> io::Result { 36 | // SAFETY: All zeros is a valid representation for `sockaddr_un`. 37 | let mut addr: libc::sockaddr_un = unsafe { std::mem::zeroed() }; 38 | addr.sun_family = libc::AF_UNIX as _; 39 | 40 | let bytes = path.as_os_str().as_bytes(); 41 | 42 | if bytes.contains(&0) { 43 | return Err(io::Error::new( 44 | io::ErrorKind::InvalidInput, 45 | "paths must not contain interior null bytes", 46 | )); 47 | } 48 | 49 | if bytes.len() >= addr.sun_path.len() { 50 | return Err(io::Error::new( 51 | io::ErrorKind::InvalidInput, 52 | "path must be shorter than SUN_LEN", 53 | )); 54 | } 55 | 56 | // SAFETY: `bytes` and `addr.sun_path` are not overlapping and 57 | // both point to valid memory. 58 | unsafe { 59 | std::ptr::copy_nonoverlapping( 60 | bytes.as_ptr(), 61 | addr.sun_path.as_mut_ptr().cast(), 62 | bytes.len(), 63 | ); 64 | } 65 | 66 | let mut len = sun_path_offset(&addr) + bytes.len(); 67 | match bytes.first() { 68 | Some(&0) | None => {} 69 | Some(_) => len += 1, // + null terminator 70 | } 71 | 72 | Ok(Self { 73 | addr, 74 | len: len as libc::socklen_t, 75 | }) 76 | } 77 | 78 | pub(super) fn from_parts( 79 | addr: libc::sockaddr_un, 80 | mut len: libc::socklen_t, 81 | ) -> io::Result { 82 | if len == 0 { 83 | // When there is a datagram from unnamed unix socket 84 | // linux returns zero bytes of address 85 | len = sun_path_offset(&addr) as libc::socklen_t; // i.e., zero-length address 86 | } else if addr.sun_family != libc::AF_UNIX as libc::sa_family_t { 87 | return Err(io::Error::new( 88 | io::ErrorKind::InvalidInput, 89 | "file descriptor did not correspond to a Unix socket", 90 | )); 91 | } 92 | 93 | Ok(Self { addr, len }) 94 | } 95 | } 96 | 97 | struct Uds(RawFd); 98 | 99 | impl Uds { 100 | pub fn new() -> io::Result { 101 | // SAFETY: syscalls 102 | unsafe { 103 | let fd = libc::socket(libc::AF_UNIX, libc::SOCK_STREAM, 0); 104 | 105 | if fd == -1 { 106 | return Err(io::Error::last_os_error()); 107 | } 108 | 109 | let s = Self(fd); 110 | 111 | if libc::ioctl(s.0, libc::FIOCLEX) != 0 { 112 | return Err(io::Error::last_os_error()); 113 | } 114 | 115 | if libc::setsockopt( 116 | fd, 117 | libc::SOL_SOCKET, 118 | libc::SO_NOSIGPIPE, 119 | (&1 as *const i32).cast(), 120 | std::mem::size_of::() as _, 121 | ) != 0 122 | { 123 | return Err(io::Error::last_os_error()); 124 | } 125 | 126 | Ok(s) 127 | } 128 | } 129 | 130 | fn accept(&self, storage: *mut libc::sockaddr, len: &mut libc::socklen_t) -> io::Result { 131 | // SAFETY: syscalls 132 | unsafe { 133 | let fd = libc::accept(self.0, storage, len); 134 | if fd == -1 { 135 | return Err(io::Error::last_os_error()); 136 | } 137 | 138 | let s = Self(fd); 139 | 140 | if libc::ioctl(s.0, libc::FIOCLEX) != 0 { 141 | return Err(io::Error::last_os_error()); 142 | } 143 | 144 | Ok(s) 145 | } 146 | } 147 | 148 | fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { 149 | let mut nonblocking = nonblocking as i32; 150 | // SAFETY: syscall 151 | if unsafe { libc::ioctl(self.0, libc::FIONBIO, &mut nonblocking) } != 0 { 152 | Err(io::Error::last_os_error()) 153 | } else { 154 | Ok(()) 155 | } 156 | } 157 | 158 | fn recv_with_flags(&self, buf: &mut [u8], flags: i32) -> io::Result { 159 | // SAFETY: syscall 160 | let read = unsafe { libc::recv(self.0, buf.as_mut_ptr().cast(), buf.len(), flags) }; 161 | 162 | if read == -1 { 163 | Err(io::Error::last_os_error()) 164 | } else { 165 | Ok(read as usize) 166 | } 167 | } 168 | 169 | fn recv_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { 170 | // SAFETY: syscall 171 | let read = unsafe { 172 | libc::readv( 173 | self.0, 174 | bufs.as_ptr().cast(), 175 | std::cmp::min(bufs.len(), libc::IOV_MAX as usize) as _, 176 | ) 177 | }; 178 | 179 | if read == -1 { 180 | Err(io::Error::last_os_error()) 181 | } else { 182 | Ok(read as usize) 183 | } 184 | } 185 | 186 | fn send_vectored(&self, bufs: &[io::IoSlice<'_>]) -> io::Result { 187 | // SAFETY: syscall 188 | let sent = unsafe { 189 | libc::writev( 190 | self.0, 191 | bufs.as_ptr().cast(), 192 | std::cmp::min(bufs.len(), libc::IOV_MAX as usize) as _, 193 | ) 194 | }; 195 | 196 | if sent == -1 { 197 | Err(io::Error::last_os_error()) 198 | } else { 199 | Ok(sent as usize) 200 | } 201 | } 202 | } 203 | 204 | impl AsRawFd for Uds { 205 | fn as_raw_fd(&self) -> RawFd { 206 | self.0 207 | } 208 | } 209 | 210 | impl AsFd for Uds { 211 | fn as_fd(&self) -> BorrowedFd<'_> { 212 | #[allow(unsafe_code)] 213 | unsafe { 214 | BorrowedFd::borrow_raw(self.as_raw_fd()) 215 | } 216 | } 217 | } 218 | 219 | impl Drop for Uds { 220 | fn drop(&mut self) { 221 | // SAFETY: syscall 222 | let _ = unsafe { libc::close(self.0) }; 223 | } 224 | } 225 | 226 | /// A Unix domain socket server 227 | pub(crate) struct UnixListener(Uds); 228 | 229 | impl UnixListener { 230 | pub(crate) fn bind(path: impl AsRef) -> io::Result { 231 | let listener = std::os::unix::net::UnixListener::bind(path)?; 232 | Ok(Self(Uds(listener.into_raw_fd()))) 233 | } 234 | 235 | pub(crate) fn accept_unix_addr(&self) -> io::Result<(UnixStream, UnixSocketAddr)> { 236 | let mut sock_addr = std::mem::MaybeUninit::::uninit(); 237 | let mut len = std::mem::size_of::() as _; 238 | 239 | let sock = self.0.accept(sock_addr.as_mut_ptr().cast(), &mut len)?; 240 | // SAFETY: should have been initialized if accept succeeded 241 | let addr = UnixSocketAddr::from_parts(unsafe { sock_addr.assume_init() }, len)?; 242 | 243 | Ok((UnixStream(sock), addr)) 244 | } 245 | 246 | #[inline] 247 | pub(crate) fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { 248 | self.0.set_nonblocking(nonblocking) 249 | } 250 | } 251 | 252 | impl AsRawFd for UnixListener { 253 | fn as_raw_fd(&self) -> RawFd { 254 | self.0.as_raw_fd() 255 | } 256 | } 257 | 258 | impl AsFd for UnixListener { 259 | fn as_fd(&self) -> BorrowedFd<'_> { 260 | #[allow(unsafe_code)] 261 | unsafe { 262 | BorrowedFd::borrow_raw(self.as_raw_fd()) 263 | } 264 | } 265 | } 266 | 267 | /// A Unix doman socket stream 268 | pub(crate) struct UnixStream(Uds); 269 | 270 | impl UnixStream { 271 | pub(crate) fn connect(path: impl AsRef) -> io::Result { 272 | // SAFETY: syscalls 273 | unsafe { 274 | let inner = Uds::new()?; 275 | let addr = UnixSocketAddr::new(path.as_ref())?; 276 | 277 | if libc::connect( 278 | inner.0, 279 | (&addr.addr as *const libc::sockaddr_un).cast(), 280 | addr.len, 281 | ) != 0 282 | { 283 | Err(std::io::Error::last_os_error()) 284 | } else { 285 | Ok(Self(inner)) 286 | } 287 | } 288 | } 289 | 290 | #[inline] 291 | pub(crate) fn peek(&self, buf: &mut [u8]) -> io::Result { 292 | self.0.recv_with_flags(buf, libc::MSG_PEEK) 293 | } 294 | 295 | #[inline] 296 | pub(crate) fn recv(&self, buf: &mut [u8]) -> io::Result { 297 | self.recv_vectored(&mut [io::IoSliceMut::new(buf)]) 298 | } 299 | 300 | #[inline] 301 | pub(crate) fn recv_vectored(&self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { 302 | self.0.recv_vectored(bufs) 303 | } 304 | 305 | #[inline] 306 | pub(crate) fn send(&self, buf: &[u8]) -> io::Result { 307 | self.send_vectored(&[io::IoSlice::new(buf)]) 308 | } 309 | 310 | #[inline] 311 | pub(crate) fn send_vectored(&self, bufs: &[io::IoSlice<'_>]) -> io::Result { 312 | self.0.send_vectored(bufs) 313 | } 314 | } 315 | 316 | impl AsRawFd for UnixStream { 317 | fn as_raw_fd(&self) -> RawFd { 318 | self.0.as_raw_fd() 319 | } 320 | } 321 | 322 | impl AsFd for UnixStream { 323 | fn as_fd(&self) -> BorrowedFd<'_> { 324 | #[allow(unsafe_code)] 325 | unsafe { 326 | BorrowedFd::borrow_raw(self.as_raw_fd()) 327 | } 328 | } 329 | } 330 | --------------------------------------------------------------------------------