├── .github ├── buildomat │ ├── config.toml │ └── jobs │ │ ├── build-and-test.sh │ │ └── test-no-op-implementation.sh ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs └── tracing-tokio.md ├── dof ├── .gitignore ├── Cargo.toml ├── README.md └── src │ ├── des.rs │ ├── dof.rs │ ├── dof_bindings.rs │ ├── fmt.rs │ ├── lib.rs │ └── ser.rs ├── dtrace-parser ├── .gitignore ├── Cargo.toml ├── src │ ├── dtrace.pest │ └── lib.rs └── test-data │ └── foo.d ├── dusty ├── Cargo.toml ├── release.toml └── src │ └── main.rs ├── probe-test-attr ├── Cargo.toml ├── build.rs ├── release.toml └── src │ └── main.rs ├── probe-test-build ├── Cargo.toml ├── build.rs ├── release.toml ├── src │ └── main.rs └── test.d ├── probe-test-macro ├── Cargo.toml ├── build.rs ├── release.toml ├── src │ └── main.rs └── test.d ├── rust-toolchain.toml ├── tests ├── argument-types │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ └── src │ │ └── main.rs ├── common-build.rs ├── compile-errors │ ├── Cargo.toml │ ├── build.rs │ ├── providers │ │ ├── type-mismatch.d │ │ └── unsupported-type.d │ ├── release.toml │ └── src │ │ ├── different-serializable-type.rs │ │ ├── different-serializable-type.stderr │ │ ├── lib.rs │ │ ├── no-closure.rs │ │ ├── no-closure.stderr │ │ ├── no-provider-file.rs │ │ ├── no-provider-file.stderr │ │ ├── relative-import.rs │ │ ├── relative-import.stderr │ │ ├── type-mismatch.rs │ │ ├── type-mismatch.stderr │ │ ├── unsupported-type.rs │ │ ├── unsupported-type.stderr │ │ ├── zero-arg-probe-type-check.rs │ │ └── zero-arg-probe-type-check.stderr ├── does-it-work │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ ├── src │ │ └── main.rs │ └── test.d ├── empty │ ├── Cargo.toml │ ├── build.rs │ ├── provider.d │ ├── release.toml │ └── src │ │ └── main.rs ├── fake-cmd │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ └── src │ │ └── main.rs ├── fake-lib │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ ├── src │ │ └── lib.rs │ └── test.d ├── modules │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ └── src │ │ ├── inner.rs │ │ └── main.rs ├── rename-builder │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ ├── src │ │ └── main.rs │ └── test.d ├── rename │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ └── src │ │ └── main.rs ├── test-json │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ └── src │ │ └── main.rs ├── test-unique-id │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ └── src │ │ └── main.rs ├── usize │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ └── src │ │ └── main.rs └── zero-arg-probe │ ├── Cargo.toml │ ├── build.rs │ ├── release.toml │ ├── src │ └── main.rs │ └── test.d ├── usdt-attr-macro ├── Cargo.toml └── src │ └── lib.rs ├── usdt-impl ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── common.rs │ ├── empty.rs │ ├── lib.rs │ ├── linker.rs │ ├── no-linker.rs │ └── record.rs ├── usdt-macro ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── usdt-tests-common ├── Cargo.toml ├── release.toml └── src │ └── lib.rs └── usdt ├── Cargo.toml ├── README.md └── src └── lib.rs /.github/buildomat/config.toml: -------------------------------------------------------------------------------- 1 | # 2 | # This file, with this flag, must be present in the default branch in order for 3 | # the buildomat integration to create check suites. 4 | # 5 | enable = true 6 | allow_users = [ 7 | "dependabot[bot]" 8 | ] 9 | -------------------------------------------------------------------------------- /.github/buildomat/jobs/build-and-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #: 3 | #: name = "illumos / build-and-test" 4 | #: variety = "basic" 5 | #: target = "helios" 6 | #: rust_toolchain = "1.85" 7 | #: output_rules = [] 8 | #: 9 | 10 | set -o errexit 11 | set -o pipefail 12 | set -o xtrace 13 | 14 | rustup show active-toolchain || rustup toolchain install 15 | cargo --version 16 | rustc --version 17 | 18 | export RUST_BACKTRACE=1 19 | 20 | banner clippy 21 | ptime -m cargo clippy \ 22 | --workspace \ 23 | -- -D warnings -A clippy::style 24 | 25 | banner test 26 | ptime -m cargo test \ 27 | --release \ 28 | --no-fail-fast \ 29 | --verbose \ 30 | --workspace 31 | -------------------------------------------------------------------------------- /.github/buildomat/jobs/test-no-op-implementation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #: 3 | #: name = "illumos / test-no-op-implementation" 4 | #: variety = "basic" 5 | #: target = "helios" 6 | #: rust_toolchain = "stable" 7 | #: output_rules = [] 8 | #: 9 | 10 | set -o errexit 11 | set -o pipefail 12 | set -o xtrace 13 | 14 | rustup show active-toolchain || rustup toolchain install 15 | cargo --version 16 | rustc --version 17 | 18 | export RUST_BACKTRACE=1 19 | 20 | banner test 21 | ptime -m cargo test \ 22 | --release \ 23 | --verbose \ 24 | --no-default-features \ 25 | --package empty 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Dependabot configuration file 3 | # 4 | 5 | version: 2 6 | updates: 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | RUST_BACKTRACE: 1 11 | 12 | jobs: 13 | style-check: 14 | name: Check Rust style 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: dtolnay/rust-toolchain@master 19 | with: 20 | toolchain: 1.85.0 21 | components: rustfmt 22 | - run: cargo fmt -- --check 23 | 24 | stable-test: 25 | name: Run most tests 26 | runs-on: ${{ matrix.os }} 27 | strategy: 28 | matrix: 29 | os: [ "macos-15" ] 30 | # Test on MSRV and stable. 31 | toolchain: [ "1.85.0", "stable" ] 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: dtolnay/rust-toolchain@master 35 | with: 36 | toolchain: ${{ matrix.toolchain }} 37 | - run: > 38 | cargo +${{ matrix.toolchain }} test 39 | --release 40 | --verbose 41 | --workspace 42 | --exclude compile-errors 43 | 44 | trybuild-test: 45 | name: Run trybuild tests 46 | runs-on: ${{ matrix.os }} 47 | strategy: 48 | matrix: 49 | os: [ "macos-15", "ubuntu-latest" ] 50 | # Test on the pinned rust-toolchain version. 51 | toolchain: [ "1.85.0" ] 52 | steps: 53 | - uses: actions/checkout@v4 54 | - uses: dtolnay/rust-toolchain@master 55 | with: 56 | toolchain: ${{ matrix.toolchain }} 57 | - run: > 58 | cargo +${{ matrix.toolchain }} test 59 | --release 60 | --verbose 61 | --package compile-errors 62 | 63 | stable-test-no-support: 64 | name: Test on DTrace-less systems 65 | runs-on: ${{ matrix.os }} 66 | strategy: 67 | matrix: 68 | os: [ "ubuntu-latest", "windows-latest" ] 69 | # Test on MSRV and stable. 70 | toolchain: [ "1.85.0", "stable" ] 71 | steps: 72 | - uses: actions/checkout@v4 73 | - uses: dtolnay/rust-toolchain@master 74 | with: 75 | toolchain: ${{ matrix.toolchain }} 76 | - run: > 77 | cargo +${{ matrix.toolchain }} test 78 | --release 79 | --verbose 80 | --workspace 81 | --exclude compile-errors 82 | --exclude does-it-work 83 | --exclude test-json 84 | --exclude test-unique-id 85 | 86 | stable-test-no-op: 87 | name: Test with probes disabled 88 | runs-on: ${{ matrix.os }} 89 | strategy: 90 | matrix: 91 | os: [ "macos-15" ] 92 | steps: 93 | - uses: actions/checkout@v4 94 | - uses: dtolnay/rust-toolchain@master 95 | with: 96 | toolchain: stable 97 | - run: > 98 | cargo +stable test 99 | --release 100 | --verbose 101 | --no-default-features 102 | --workspace 103 | --exclude does-it-work 104 | --exclude test-json 105 | --exclude test-unique-id 106 | --exclude compile-errors 107 | 108 | recent-nightly: 109 | name: Run tests on a recent nightly 110 | runs-on: ${{ matrix.os }} 111 | strategy: 112 | matrix: 113 | os: [ "macos-15" ] 114 | steps: 115 | - uses: actions/checkout@v4 116 | - uses: dtolnay/rust-toolchain@master 117 | with: 118 | toolchain: nightly-2025-01-01 119 | - run: > 120 | cargo +nightly-2025-01-01 test 121 | --release 122 | --verbose 123 | --workspace 124 | --exclude compile-errors 125 | --exclude dusty 126 | 127 | recent-nightly-no-support: 128 | name: Run tests on a recent nightly 129 | runs-on: ${{ matrix.os }} 130 | strategy: 131 | matrix: 132 | os: [ "ubuntu-latest", "windows-latest" ] 133 | steps: 134 | - uses: actions/checkout@v4 135 | - uses: dtolnay/rust-toolchain@master 136 | with: 137 | toolchain: nightly-2025-01-01 138 | - run: > 139 | cargo +nightly-2025-01-01 test 140 | --release 141 | --verbose 142 | --workspace 143 | --exclude compile-errors 144 | --exclude does-it-work 145 | --exclude test-json 146 | --exclude test-unique-id 147 | --exclude dusty 148 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "dof", 4 | "dtrace-parser", 5 | "dusty", 6 | "probe-test-build", 7 | "probe-test-macro", 8 | "probe-test-attr", 9 | "tests/argument-types", 10 | "tests/compile-errors", 11 | "tests/does-it-work", 12 | "tests/empty", 13 | "tests/fake-cmd", 14 | "tests/fake-lib", 15 | "tests/modules", 16 | "tests/rename", 17 | "tests/rename-builder", 18 | "tests/test-json", 19 | "tests/test-unique-id", 20 | "tests/usize", 21 | "tests/zero-arg-probe", 22 | "usdt", 23 | "usdt-attr-macro", 24 | "usdt-impl", 25 | "usdt-macro", 26 | "usdt-tests-common", 27 | ] 28 | 29 | resolver = "2" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `usdt` 2 | 3 | Dust your Rust with USDT probes. 4 | 5 | ## Overview 6 | 7 | `usdt` exposes statically-defined [DTrace probes][1] to Rust code. Users write a _provider_ 8 | definition, in either the D language or directly in Rust code. The _probes_ of the provider 9 | can then be compiled into Rust code that fire the probes. These are visible via the `dtrace` 10 | command-line tool. 11 | 12 | There are three mechanisms for converting the D probe definitions into Rust. 13 | 14 | 1. A `build.rs` script 15 | 2. A function-like procedural macro, `usdt::dtrace_provider`. 16 | 3. An attribute macro, `usdt::provider`. 17 | 18 | The generated code is the same in all cases, though the third provides a bit more flexibility 19 | than the first two. See [below](#serializable-types) for more details, but briefly, the third 20 | form supports probe arguments of any type that implement [`serde::Seralize`][2]. These different 21 | versions are shown in the crates `probe-test-{build,macro,attr}` respectively. 22 | 23 | > Note: This crate uses inline assembly to work its magic. See the [notes](#notes) for a discussion 24 | of its use prior to Rust 1.59 and prior to 1.66 on macOS. 25 | 26 | ## Example 27 | 28 | The `probe-test-build` binary crate in this package implements a complete example, using the 29 | build-time code generation. 30 | 31 | The starting point is a D script, called `"test.d"`. It looks like: 32 | 33 | ```d 34 | provider my_provider { 35 | probe start_work(uint8_t); 36 | probe stop_work(char*, uint8_t); 37 | }; 38 | ``` 39 | 40 | This script defines a single provider, `test`, with two probes, `start` and `stop`, 41 | with a different set of arguments. (Integral primitive types, pointers to 42 | integral types, and `&str`s are currently supported. Note that `char*` is used 43 | to indicate Rust-style UTF-8 strings. If you'd like a byte array, use `uint8_t*` 44 | or `int8_t*`.) 45 | 46 | This provider definition must be converted into Rust code, which can be done in a simple 47 | build script: 48 | 49 | 50 | ```rust 51 | use usdt::Builder; 52 | 53 | fn main() { 54 | Builder::new("test.d").build().unwrap(); 55 | } 56 | ``` 57 | 58 | This generates a file in the directory `OUT_DIR` which contains the generated Rust macros 59 | that fire the probes. Unless it is changed, this file is named the same as the provider 60 | definition file, so `test.rs` in this case. 61 | 62 | Using the probes in Rust code looks like the following, which is in `probe-test-build/src/main.rs`. 63 | 64 | ```rust 65 | //! An example using the `usdt` crate, generating the probes via a build script. 66 | 67 | use std::thread::sleep; 68 | use std::time::Duration; 69 | 70 | use usdt::register_probes; 71 | 72 | // Include the Rust implementation generated by the build script. 73 | include!(concat!(env!("OUT_DIR"), "/test.rs")); 74 | 75 | fn main() { 76 | let duration = Duration::from_secs(1); 77 | let mut counter: u8 = 0; 78 | 79 | // NOTE: One _must_ call this function in order to actually register the probes with DTrace. 80 | // Without this, it won't be possible to list, enable, or see the probes via `dtrace(1)`. 81 | register_probes().unwrap(); 82 | 83 | loop { 84 | // Call the "start_work" probe which accepts a u8. 85 | my_provider::start_work!(|| (counter)); 86 | 87 | // Do some work. 88 | sleep(duration); 89 | 90 | // Call the "stop_work" probe, which accepts a &str and a u8. 91 | my_provider::stop_work!(|| ("the probe has fired", counter)); 92 | 93 | counter = counter.wrapping_add(1); 94 | } 95 | } 96 | ``` 97 | 98 | > Note: Prior to 1.59 (and prior to 1.66 on macOS) nightly features are required. See the 99 | [notes](#notes) for a discussion. 100 | 101 | One can also see that the Rust code is included directly using the `include!` macro. The probe 102 | definitions are converted into Rust macros, in a module named by the provider, and with macro 103 | named by the probe. In our case, the the first probe is converted into a macro 104 | `my_provider::start_work!`. 105 | 106 | > IMPORTANT: It's important to note that the application _must_ call `usdt::register_probes()` 107 | in order to actually register the probe points with DTrace. Failing to do this will not impact 108 | the application's functionality, but it will be impossible to list, enable, or otherwise see the 109 | probes with the `dtrace(1)` tool without this. 110 | 111 | We can see that this is hooked up with DTrace by running the example and listing the expected 112 | probes by name. 113 | 114 | ```bash 115 | $ cargo run 116 | ``` 117 | 118 | And in another terminal, list the matching probes with: 119 | 120 | ```bash 121 | $ sudo dtrace -l -n my_provider*::: 122 | ID PROVIDER MODULE FUNCTION NAME 123 | 2865 test14314 probe-test-build _ZN16probe_test_build4main17h906db832bb52ab01E [probe_test_build::main::h906db832bb52ab01] start_work 124 | 2866 test14314 probe-test-build _ZN16probe_test_build4main17h906db832bb52ab01E [probe_test_build::main::h906db832bb52ab01] stop_work 125 | ``` 126 | 127 | ## Probe arguments 128 | 129 | One can see that the probe macros are called with closures, rather than with the probe 130 | arguments directly. This has two purposes. 131 | 132 | First, it indicates that the probe arguments may not be evaluated. DTrace generates 133 | "is-enabled" probes for defined probe, which is a simple way to check if the probe has 134 | currently been enabled. The arguments are only unpacked if the probe is enabled, and 135 | so users _must not_ rely on side-effects. The closure helps indicate this. 136 | 137 | The second point of this is efficiency. Again, the arguments are not evaluated if the 138 | probe is not enabled. The closure is only evaluated internally _after_ the probe is 139 | verified to be enabled, which avoid the unnecessary work of argument marshalling if 140 | the probe is disabled. 141 | 142 | ## Procedural macro version 143 | 144 | The procedural macro version of this crate can be seen in the `probe-test-macro` example, 145 | which is nearly identical to the above example. However, there is no build.rs script, 146 | so in place of the `include!` macro, one finds the procedural macro: 147 | 148 | ```rust 149 | dtrace_provider!("test.d"); 150 | ``` 151 | 152 | This macro generates the same macros as seen above, but does at the time the source 153 | itself is compiled. This may be easier for some use cases, as there is no build script. 154 | However, procedural macros have downsides. It can be difficult to understand their 155 | internals, especially when things fail. Additionally, the macro is run on every compile, 156 | even if the provider definition is unchanged. This may be negligible for small provider 157 | definitions, but users may see a noticeable increase in compile times when many probes 158 | are defined. 159 | 160 | ## Serializable types 161 | 162 | As described above, the three forms of defining a provider a _nearly_ equivalent. The 163 | only distinction is in the support of types implementing [`serde::Serialize`][2]. This uses 164 | DTrace's [JSON functionality][3] -- Any serializable type is serialized to JSON with 165 | [`serde_json::to_string()`][4], and the string may be unpacked and inspected in DTrace 166 | scripts with the `json` function. For example, imagine we have the type: 167 | 168 | ```rust 169 | #[derive(serde::Serialize)] 170 | pub struct Arg { 171 | val: u8, 172 | data: Vec, 173 | } 174 | ``` 175 | 176 | and a probe definition: 177 | 178 | ```rust 179 | #[usdt::provider] 180 | mod my_provider { 181 | use crate::Arg; 182 | fn my_probe(_: &Arg) {} 183 | } 184 | ``` 185 | 186 | Values of type `Arg` may be used in the generated probe macros. In a DTrace script, one can 187 | look at the data in the argument like: 188 | 189 | ``` 190 | dtrace -n 'my_probe* { printf("%s", json(copyinstr(arg0), "ok.val")); }' # prints `Arg::val`. 191 | ``` 192 | 193 | The `json` function also supports nested objects and array indexing, so one could also do: 194 | 195 | ``` 196 | dtrace -n 'my_probe* { printf("%s", json(copyinstr(arg0), "ok.data[0]")); }' # prints `Arg::data[0]`. 197 | ``` 198 | 199 | See the `probe-test-attr` example for more details and usage. 200 | 201 | ### Serialization is fallible 202 | 203 | Note that in the above examples, the first key of the JSON blob being accessed is `"ok"`. This 204 | is because the `serde_json::to_string` function is fallible, returning a `Result`. This is mapped 205 | into JSON in a natural way: 206 | 207 | - `Ok(_) => {"ok": _}` 208 | - `Err(_) => {"err": _}` 209 | 210 | In the error case, the [`Error`][serde-json-error] returned is formatted using its `Display` 211 | implementation. This isn't an academic concern. It's quite easy to build types that successfully 212 | compile, and yet fail to serialize at runtime, even with types that `#[derive(Serialize)]`. See 213 | [this issue][serde-runtime-fail] for details. 214 | 215 | ## A note about registration 216 | 217 | Note that the `usdt::register_probes()` function is called at the top of main in the above 218 | example. This method is required to actually register the probes with the DTrace kernel 219 | module. This presents a quandary for library developers who wish to instrument their 220 | code, as consumers of their library may forget to (or choose not to) call this function. 221 | There are potential workarounds to this problem (init-sections, other magic), but each 222 | comes with significant tradeoffs. As such the current recommendation is: 223 | 224 | > Library developers are encouraged to re-export the `usdt::register_probes` (or a 225 | function calling it), and document to their users that this function should be called to 226 | guarantee that probes are registered. 227 | 228 | ## References 229 | 230 | [1]: https://illumos.org/books/dtrace/chp-usdt.html#chp-usdt 231 | [2]: https://docs.rs/serde/1.0.130/serde/trait.Serialize.html 232 | [3]: https://sysmgr.org/blog/2012/11/29/dtrace_and_json_together_at_last/ 233 | [4]: https://docs.rs/serde_json/1.0.68/serde_json/fn.to_string.html 234 | [serde-json-error]: https://docs.serde.rs/serde_json/error/struct.Error.html 235 | [serde-runtime-fail]: https://github.com/serde-rs/serde/issues/1307 236 | -------------------------------------------------------------------------------- /docs/tracing-tokio.md: -------------------------------------------------------------------------------- 1 | # Tracing `tokio` 2 | 3 | Many DTrace examples you'll find make extensive use of thread-local variables, prefixed with 4 | `self->`. For example, you'll often see scripts like this: 5 | 6 | ```dtrace 7 | // !!! Don't do this with tokio!!! 8 | 9 | pid$target::my_async_func:entry 10 | { 11 | self->arg = arg0; 12 | } 13 | 14 | pid$target::my_async_func:return 15 | /self->arg/ 16 | { 17 | // Do something with self->arg 18 | self->arg = 0; 19 | } 20 | ``` 21 | 22 | This uses the system concept of a thread, and--while `async` and `tokio` docs go to great lengths to 23 | liken a "task" to a "thread"--these are fundamentally different entities! In particular, an `async` 24 | Rust executor (such as `tokio`) will often multiplex tasks over a thread pool. This means that the 25 | system's notion of a thread (as in the example above) is meaningless with regard to tracing `async`. 26 | 27 | We can think of DTrace's thread-local variables as shorthand for a global array indexed by the 28 | current thread. So `self->foo` is (conceptually) equivalent to `foo[curthread]`. Since a task may be 29 | run on various threads, the current thread isn't a useful key. 30 | **In order to correlate activies across calls in an `async` function, you need some other unique 31 | ID**. 32 | 33 | ## Aside: tokio tasks 34 | 35 | It might seem tempting to use some unique task ID as the stand-in for a thread ID. While Tokio 36 | certainly *could* provide some task-unique identifier, it doesn't... and it wouldn't be that useful 37 | if it did! Threads--it turns out--have an extremely important characteristic for understanding their 38 | execution: a given thread is only in one place at one time. You can interrogate a running process 39 | (or even a dead one in the form of a core file!) and ask where each thread is. This may seem 40 | vacuous, but that *very* useful property does not hold for tasks in `async` Rust! 41 | 42 | A task is not really representable by a simple, linear stack, but rather requires a tree: a task can 43 | be in multiple places at once! How? The simplest form is via the `join!` macro which explicitly 44 | executes multiple `Future`s in parallel, each of which may make independent progress. So, where is a 45 | task? It may effectively be in several places at once which means that the task doesn't present a 46 | useful way to correlate events. 47 | 48 | ## Unique IDs 49 | 50 | Rather than correlating events by thread, we instead need some other unique ID. Your USDT probes may 51 | already have a unique ID (e.g. a transaction ID), but if it doesn't, `usdt` provides a lightweight 52 | mechanism for adding a unique ID to correlate events. See the [docs for 53 | `usdt::UniqueId`](https://docs.rs/usdt/0.3.5/usdt/struct.UniqueId.html). 54 | 55 | If we have a collection of USDT probes whose first argument is a unique ID, we can use it like this 56 | in D: 57 | 58 | ```dtrace 59 | my_prov$target:::event-start 60 | { 61 | event[arg0] = 1; 62 | } 63 | 64 | my_prov$target:::event-done 65 | /event[arg0]/ 66 | { 67 | // Trace stuff of interest 68 | event[arg0] = 0; 69 | } 70 | ``` 71 | 72 | Note that this D uses a **global** associative array whose key is the unique ID. We do **not** use 73 | thread-local variables because (as noted exhaustively) the task may run on other threads and other 74 | tasks may run on this thread. (Even in a `async` executor that's single-threaded, use of 75 | thread-local variables could be confused across tasks!) -------------------------------------------------------------------------------- /dof/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /dof/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dof" 3 | version = "0.3.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Tools to read and write the DTrace Object Format (DOF)" 7 | repository = "https://github.com/oxidecomputer/usdt.git" 8 | 9 | [dependencies] 10 | goblin = { version = "0.10", optional = true, features = ["elf64", "mach64"] } 11 | pretty-hex = { version = "0.4", optional = true } 12 | thiserror = "2" 13 | zerocopy = { version = "0.8.25", features = [ "derive" ] } 14 | serde = { version = "1", features = [ "derive" ] } 15 | serde_json = "1" 16 | 17 | [features] 18 | des = ["pretty-hex", "goblin"] 19 | -------------------------------------------------------------------------------- /dof/README.md: -------------------------------------------------------------------------------- 1 | `dof` 2 | ---- 3 | 4 | Prototype crate for parsing and ultimately generating DTrace Object Format. 5 | -------------------------------------------------------------------------------- /dof/src/des.rs: -------------------------------------------------------------------------------- 1 | //! Functions to deserialize crate types from DOF. 2 | 3 | // Copyright 2021 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use std::convert::{TryFrom, TryInto}; 18 | use std::mem::size_of; 19 | use std::path::Path; 20 | 21 | use goblin::Object; 22 | use zerocopy::Ref; 23 | 24 | use crate::dof::DOF_MAGIC; 25 | use crate::dof_bindings::*; 26 | use crate::{Error, Ident, Probe, Provider, Section}; 27 | 28 | // Extract one or more null-terminated strings from the given byte slice. 29 | fn extract_strings(buf: &[u8], count: Option) -> Vec { 30 | let chunks = buf.split(|&x| x == 0); 31 | if let Some(count) = count { 32 | chunks 33 | .take(count) 34 | .map(|chunk| String::from_utf8(chunk.to_vec()).unwrap()) 35 | .collect() 36 | } else { 37 | chunks 38 | .map(|chunk| String::from_utf8(chunk.to_vec()).unwrap()) 39 | .collect() 40 | } 41 | } 42 | 43 | // Parse a section of probes. The buffer must already be guaranteed to come from a DOF_SECT_PROBES 44 | // section, and be the correct length. 45 | fn parse_probe_section( 46 | buf: &[u8], 47 | strtab: &[u8], 48 | offsets: &[u32], 49 | enabled_offsets: &[u32], 50 | _argument_indices: &[u8], 51 | ) -> Vec { 52 | let parse_probe = |buf| { 53 | let probe = *Ref::<_, dof_probe>::from_bytes(buf).unwrap(); 54 | let offset_index = probe.dofpr_offidx as usize; 55 | let offs = (offset_index..offset_index + probe.dofpr_noffs as usize) 56 | .map(|index| offsets[index]) 57 | .collect(); 58 | let enabled_offset_index = probe.dofpr_enoffidx as usize; 59 | let enabled_offs = (enabled_offset_index 60 | ..enabled_offset_index + probe.dofpr_nenoffs as usize) 61 | .map(|index| enabled_offsets[index]) 62 | .collect(); 63 | let arg_base = probe.dofpr_nargv as usize; 64 | let arguments = extract_strings(&strtab[arg_base..], Some(probe.dofpr_nargc as _)); 65 | Probe { 66 | name: extract_strings(&strtab[probe.dofpr_name as _..], Some(1))[0].clone(), 67 | function: extract_strings(&strtab[probe.dofpr_func as _..], Some(1))[0].clone(), 68 | address: probe.dofpr_addr, 69 | offsets: offs, 70 | enabled_offsets: enabled_offs, 71 | arguments, 72 | } 73 | }; 74 | buf.chunks(size_of::()) 75 | .map(parse_probe) 76 | .collect() 77 | } 78 | 79 | // Extract the bytes of a section by index 80 | fn extract_section<'a>(sections: &[dof_sec], index: usize, buf: &'a [u8]) -> &'a [u8] { 81 | let offset = sections[index].dofs_offset as usize; 82 | let size = sections[index].dofs_size as usize; 83 | &buf[offset..offset + size] 84 | } 85 | 86 | // Parse all provider sections 87 | fn parse_providers(sections: &[dof_sec], buf: &[u8]) -> Vec { 88 | let provider_sections = sections 89 | .iter() 90 | .filter(|sec| sec.dofs_type == DOF_SECT_PROVIDER); 91 | let mut providers = Vec::new(); 92 | for section_header in provider_sections { 93 | let section_start = section_header.dofs_offset as usize; 94 | let section_size = section_header.dofs_size as usize; 95 | let provider = 96 | *Ref::<_, dof_provider>::from_bytes(&buf[section_start..section_start + section_size]) 97 | .unwrap(); 98 | 99 | let strtab = extract_section(sections, provider.dofpv_strtab as _, buf); 100 | let name = extract_strings(&strtab[provider.dofpv_name as _..], Some(1))[0].clone(); 101 | 102 | // Extract the offset/index sections as vectors of a specific type 103 | let offsets: Vec<_> = extract_section(sections, provider.dofpv_proffs as _, buf) 104 | .chunks(size_of::()) 105 | .map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap())) 106 | .collect(); 107 | let enabled_offsets: Vec<_> = extract_section(sections, provider.dofpv_prenoffs as _, buf) 108 | .chunks(size_of::()) 109 | .map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap())) 110 | .collect(); 111 | let arguments = extract_section(sections, provider.dofpv_prargs as _, buf).to_vec(); 112 | let probes_list = parse_probe_section( 113 | extract_section(sections, provider.dofpv_probes as _, buf), 114 | strtab, 115 | &offsets, 116 | &enabled_offsets, 117 | &arguments, 118 | ); 119 | 120 | let probes = probes_list 121 | .into_iter() 122 | .map(|probe| (probe.name.clone(), probe)) 123 | .collect(); 124 | 125 | providers.push(Provider { name, probes }); 126 | } 127 | providers 128 | } 129 | 130 | fn deserialize_raw_headers(buf: &[u8]) -> Result<(dof_hdr, Vec), Error> { 131 | let file_header = *Ref::<_, dof_hdr>::from_bytes(&buf[..size_of::()]) 132 | .map_err(|_| Error::ParseError)?; 133 | let n_sections: usize = file_header.dofh_secnum as _; 134 | let mut section_headers = Vec::with_capacity(n_sections); 135 | for i in 0..n_sections { 136 | let start = file_header.dofh_secoff as usize + file_header.dofh_secsize as usize * i; 137 | let end = start + file_header.dofh_secsize as usize; 138 | section_headers 139 | .push(*Ref::<_, dof_sec>::from_bytes(&buf[start..end]).map_err(|_| Error::ParseError)?); 140 | } 141 | Ok((file_header, section_headers)) 142 | } 143 | 144 | /// Simple container for raw DOF data, including header and sections. 145 | #[derive(Clone, Debug)] 146 | pub struct RawSections { 147 | /// The DOF header. 148 | pub header: dof_hdr, 149 | /// A list of each section, along with its raw bytes. 150 | pub sections: Vec<(dof_sec, Vec)>, 151 | } 152 | 153 | /// Deserialize the raw C-structs for the file header and each section header, along with the byte 154 | /// array for those sections. 155 | pub fn deserialize_raw_sections(buf: &[u8]) -> Result { 156 | let (file_headers, section_headers) = deserialize_raw_headers(buf)?; 157 | let sections = section_headers 158 | .into_iter() 159 | .map(|header| { 160 | let start = header.dofs_offset as usize; 161 | let end = start + header.dofs_size as usize; 162 | (header, buf[start..end].to_vec()) 163 | }) 164 | .collect(); 165 | Ok(RawSections { 166 | header: file_headers, 167 | sections, 168 | }) 169 | } 170 | 171 | /// Deserialize a `Section` from a slice of DOF bytes 172 | pub fn deserialize_section(buf: &[u8]) -> Result { 173 | let (file_header, section_headers) = deserialize_raw_headers(buf)?; 174 | let ident = Ident::try_from(&file_header.dofh_ident[..])?; 175 | let providers_list = parse_providers(§ion_headers, buf); 176 | let providers = providers_list 177 | .into_iter() 178 | .map(|provider| (provider.name.clone(), provider)) 179 | .collect(); 180 | Ok(Section { ident, providers }) 181 | } 182 | 183 | /// Return true if the given byte slice is a DOF section of an object file. 184 | pub fn is_dof_section(buf: &[u8]) -> bool { 185 | buf.len() >= DOF_MAGIC.len() && buf.starts_with(&DOF_MAGIC) 186 | } 187 | 188 | /// Return the raw byte blobs for each DOF section in the given object file 189 | pub fn collect_dof_sections>(path: P) -> Result>, Error> { 190 | let data = std::fs::read(path)?; 191 | match Object::parse(&data)? { 192 | Object::Elf(elf) => Ok(elf 193 | .section_headers 194 | .iter() 195 | .filter_map(|section| { 196 | let start = section.sh_offset as usize; 197 | let end = start + section.sh_size as usize; 198 | if is_dof_section(&data[start..end]) { 199 | Some(data[start..end].to_vec()) 200 | } else { 201 | None 202 | } 203 | }) 204 | .collect()), 205 | Object::Mach(goblin::mach::Mach::Binary(mach)) => Ok(mach 206 | .segments 207 | .sections() 208 | .flatten() 209 | .filter_map(|item| { 210 | if let Ok((_, section_data)) = item { 211 | if is_dof_section(section_data) { 212 | Some(section_data.to_vec()) 213 | } else { 214 | None 215 | } 216 | } else { 217 | None 218 | } 219 | }) 220 | .collect()), 221 | _ => Err(Error::UnsupportedObjectFile), 222 | } 223 | } 224 | 225 | /// Extract DOF sections from the given object file (ELF or Mach-O) 226 | pub fn extract_dof_sections>(path: P) -> Result, Error> { 227 | collect_dof_sections(path)? 228 | .into_iter() 229 | .map(|sect| Section::from_bytes(§)) 230 | .collect() 231 | } 232 | -------------------------------------------------------------------------------- /dof/src/dof.rs: -------------------------------------------------------------------------------- 1 | //! Types representing DTrace Object Format data structures. 2 | //! 3 | //! The [`Section`] struct is used to represent a complete DTrace Object Format section as 4 | //! contained in an object file. It contains one or more [`Provider`]s, each with one or more 5 | //! [`Probe`]s. The `Probe` type contains all the information required to locate a probe callsite 6 | //! within an object file. 7 | 8 | // Copyright 2021 Oxide Computer Company 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | 22 | use std::mem::size_of; 23 | use std::{ 24 | collections::BTreeMap, 25 | convert::{TryFrom, TryInto}, 26 | }; 27 | 28 | use serde::Serialize; 29 | use thiserror::Error; 30 | 31 | // Magic bytes for a DOF section 32 | pub(crate) const DOF_MAGIC: [u8; 4] = [0x7F, b'D', b'O', b'F']; 33 | 34 | /// Errors related to building or manipulating the DOF format 35 | #[derive(Error, Debug)] 36 | pub enum Error { 37 | /// The DOF identifier is invalid, such as invalid magic bytes 38 | #[error("invalid DOF identifier (magic bytes, endianness, or version)")] 39 | InvalidIdentifier, 40 | 41 | /// An error occurred parsing a type from an underlying byte slice 42 | #[error("data does not match expected struct layout or is misaligned")] 43 | ParseError, 44 | 45 | /// Attempt to read from an unsupported object file format 46 | #[error("unsupported object file format")] 47 | UnsupportedObjectFile, 48 | 49 | /// An error related to parsing the object file 50 | #[cfg(feature = "des")] 51 | #[error(transparent)] 52 | ObjectError(#[from] goblin::error::Error), 53 | 54 | /// An error during IO 55 | #[error(transparent)] 56 | IO(#[from] std::io::Error), 57 | } 58 | 59 | /// Represents the DTrace data model, e.g. the pointer width of the platform 60 | #[derive(Debug, Clone, Copy, Serialize)] 61 | #[repr(u8)] 62 | pub enum DataModel { 63 | None = 0, 64 | ILP32 = 1, 65 | LP64 = 2, 66 | } 67 | 68 | impl Default for DataModel { 69 | fn default() -> Self { 70 | if cfg!(target_pointer_width = "64") { 71 | DataModel::LP64 72 | } else { 73 | DataModel::ILP32 74 | } 75 | } 76 | } 77 | 78 | impl TryFrom for DataModel { 79 | type Error = Error; 80 | fn try_from(x: u8) -> Result { 81 | match x { 82 | 0 => Ok(DataModel::None), 83 | 1 => Ok(DataModel::ILP32), 84 | 2 => Ok(DataModel::LP64), 85 | _ => Err(Error::InvalidIdentifier), 86 | } 87 | } 88 | } 89 | 90 | /// Represents the endianness of the platform 91 | #[derive(Debug, Clone, Copy, Serialize)] 92 | #[repr(u8)] 93 | pub enum DataEncoding { 94 | None = 0, 95 | LittleEndian = 1, 96 | BigEndian = 2, 97 | } 98 | 99 | impl Default for DataEncoding { 100 | fn default() -> Self { 101 | if cfg!(target_endian = "big") { 102 | DataEncoding::BigEndian 103 | } else { 104 | DataEncoding::LittleEndian 105 | } 106 | } 107 | } 108 | 109 | impl TryFrom for DataEncoding { 110 | type Error = Error; 111 | fn try_from(x: u8) -> Result { 112 | match x { 113 | 0 => Ok(DataEncoding::None), 114 | 1 => Ok(DataEncoding::LittleEndian), 115 | 2 => Ok(DataEncoding::BigEndian), 116 | _ => Err(Error::InvalidIdentifier), 117 | } 118 | } 119 | } 120 | 121 | /// Static identifying information about a DOF section (such as version numbers) 122 | #[derive(Debug, Clone, Copy, Serialize)] 123 | pub struct Ident { 124 | pub magic: [u8; 4], 125 | pub model: DataModel, 126 | pub encoding: DataEncoding, 127 | pub version: u8, 128 | pub dif_vers: u8, 129 | pub dif_ireg: u8, 130 | pub dif_treg: u8, 131 | } 132 | 133 | impl<'a> TryFrom<&'a [u8]> for Ident { 134 | type Error = Error; 135 | fn try_from(buf: &'a [u8]) -> Result { 136 | if buf.len() < size_of::() { 137 | return Err(Error::ParseError); 138 | } 139 | let magic = &buf[..DOF_MAGIC.len()]; 140 | if magic != DOF_MAGIC { 141 | return Err(Error::InvalidIdentifier); 142 | } 143 | let model = DataModel::try_from(buf[crate::dof_bindings::DOF_ID_MODEL as usize])?; 144 | let encoding = DataEncoding::try_from(buf[crate::dof_bindings::DOF_ID_ENCODING as usize])?; 145 | let version = buf[crate::dof_bindings::DOF_ID_VERSION as usize]; 146 | let dif_vers = buf[crate::dof_bindings::DOF_ID_DIFVERS as usize]; 147 | let dif_ireg = buf[crate::dof_bindings::DOF_ID_DIFIREG as usize]; 148 | let dif_treg = buf[crate::dof_bindings::DOF_ID_DIFTREG as usize]; 149 | Ok(Ident { 150 | // Unwrap is safe if the above check against DOF_MAGIC passes 151 | magic: magic.try_into().unwrap(), 152 | model, 153 | encoding, 154 | version, 155 | dif_vers, 156 | dif_ireg, 157 | dif_treg, 158 | }) 159 | } 160 | } 161 | 162 | impl Ident { 163 | pub fn as_bytes(&self) -> [u8; 16] { 164 | let mut out = [0; 16]; 165 | let start = self.magic.len(); 166 | out[..start].copy_from_slice(&self.magic[..]); 167 | out[start] = self.model as _; 168 | out[start + 1] = self.encoding as _; 169 | out[start + 2] = self.version; 170 | out[start + 3] = self.dif_vers; 171 | out[start + 4] = self.dif_ireg; 172 | out[start + 5] = self.dif_treg; 173 | out 174 | } 175 | } 176 | 177 | /// Representation of a DOF section of an object file 178 | #[derive(Debug, Clone, Serialize)] 179 | pub struct Section { 180 | /// The identifying bytes of this section 181 | pub ident: Ident, 182 | /// The list of providers defined in this section 183 | pub providers: BTreeMap, 184 | } 185 | 186 | impl Section { 187 | /// Construct a section from a DOF byte array. 188 | #[cfg(feature = "des")] 189 | pub fn from_bytes(buf: &[u8]) -> Result { 190 | crate::des::deserialize_section(buf) 191 | } 192 | 193 | /// Serialize a section into DOF object file section. 194 | pub fn as_bytes(&self) -> Vec { 195 | crate::ser::serialize_section(self) 196 | } 197 | 198 | /// Serialize a section into a JSON representation of the DOF object file section. 199 | pub fn to_json(&self) -> String { 200 | serde_json::to_string_pretty(self).unwrap() 201 | } 202 | } 203 | 204 | impl Default for Section { 205 | fn default() -> Self { 206 | Self { 207 | ident: Ident { 208 | magic: DOF_MAGIC, 209 | model: DataModel::LP64, 210 | encoding: DataEncoding::LittleEndian, 211 | version: crate::dof_bindings::DOF_VERSION as u8, 212 | dif_vers: crate::dof_bindings::DIF_VERSION as u8, 213 | dif_ireg: crate::dof_bindings::DIF_DIR_NREGS as u8, 214 | dif_treg: crate::dof_bindings::DIF_DTR_NREGS as u8, 215 | }, 216 | providers: BTreeMap::new(), 217 | } 218 | } 219 | } 220 | 221 | /// Information about a single DTrace probe 222 | #[derive(Debug, Clone, Serialize)] 223 | pub struct Probe { 224 | /// Name of this probe 225 | pub name: String, 226 | /// Name of the function containing this probe 227 | pub function: String, 228 | /// Address or offset in the resulting object code 229 | pub address: u64, 230 | /// Offsets in containing function at which this probe occurs. 231 | pub offsets: Vec, 232 | /// Offsets in the containing function at which this probe's is-enabled functions occur. 233 | pub enabled_offsets: Vec, 234 | /// Type information for each argument 235 | pub arguments: Vec, 236 | } 237 | 238 | /// Information about a single provider 239 | #[derive(Debug, Clone, Serialize)] 240 | pub struct Provider { 241 | /// Name of the provider 242 | pub name: String, 243 | /// List of probes this provider exports 244 | pub probes: BTreeMap, 245 | } 246 | -------------------------------------------------------------------------------- /dof/src/dof_bindings.rs: -------------------------------------------------------------------------------- 1 | //! Auto-generated bindings to the DOF-related types in `dtrace.h` 2 | /* automatically generated by rust-bindgen 0.57.0 */ 3 | 4 | #![allow(non_camel_case_types)] 5 | 6 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; 7 | 8 | pub const DIF_VERSION_1: u32 = 1; 9 | pub const DIF_VERSION_2: u32 = 2; 10 | pub const DIF_VERSION: u32 = 2; 11 | pub const DIF_DIR_NREGS: u32 = 8; 12 | pub const DIF_DTR_NREGS: u32 = 8; 13 | pub const DIF_OP_OR: u32 = 1; 14 | pub const DIF_OP_XOR: u32 = 2; 15 | pub const DIF_OP_AND: u32 = 3; 16 | pub const DIF_OP_SLL: u32 = 4; 17 | pub const DIF_OP_SRL: u32 = 5; 18 | pub const DIF_OP_SUB: u32 = 6; 19 | pub const DIF_OP_ADD: u32 = 7; 20 | pub const DIF_OP_MUL: u32 = 8; 21 | pub const DIF_OP_SDIV: u32 = 9; 22 | pub const DIF_OP_UDIV: u32 = 10; 23 | pub const DIF_OP_SREM: u32 = 11; 24 | pub const DIF_OP_UREM: u32 = 12; 25 | pub const DIF_OP_NOT: u32 = 13; 26 | pub const DIF_OP_MOV: u32 = 14; 27 | pub const DIF_OP_CMP: u32 = 15; 28 | pub const DIF_OP_TST: u32 = 16; 29 | pub const DIF_OP_BA: u32 = 17; 30 | pub const DIF_OP_BE: u32 = 18; 31 | pub const DIF_OP_BNE: u32 = 19; 32 | pub const DIF_OP_BG: u32 = 20; 33 | pub const DIF_OP_BGU: u32 = 21; 34 | pub const DIF_OP_BGE: u32 = 22; 35 | pub const DIF_OP_BGEU: u32 = 23; 36 | pub const DIF_OP_BL: u32 = 24; 37 | pub const DIF_OP_BLU: u32 = 25; 38 | pub const DIF_OP_BLE: u32 = 26; 39 | pub const DIF_OP_BLEU: u32 = 27; 40 | pub const DIF_OP_LDSB: u32 = 28; 41 | pub const DIF_OP_LDSH: u32 = 29; 42 | pub const DIF_OP_LDSW: u32 = 30; 43 | pub const DIF_OP_LDUB: u32 = 31; 44 | pub const DIF_OP_LDUH: u32 = 32; 45 | pub const DIF_OP_LDUW: u32 = 33; 46 | pub const DIF_OP_LDX: u32 = 34; 47 | pub const DIF_OP_RET: u32 = 35; 48 | pub const DIF_OP_NOP: u32 = 36; 49 | pub const DIF_OP_SETX: u32 = 37; 50 | pub const DIF_OP_SETS: u32 = 38; 51 | pub const DIF_OP_SCMP: u32 = 39; 52 | pub const DIF_OP_LDGA: u32 = 40; 53 | pub const DIF_OP_LDGS: u32 = 41; 54 | pub const DIF_OP_STGS: u32 = 42; 55 | pub const DIF_OP_LDTA: u32 = 43; 56 | pub const DIF_OP_LDTS: u32 = 44; 57 | pub const DIF_OP_STTS: u32 = 45; 58 | pub const DIF_OP_SRA: u32 = 46; 59 | pub const DIF_OP_CALL: u32 = 47; 60 | pub const DIF_OP_PUSHTR: u32 = 48; 61 | pub const DIF_OP_PUSHTV: u32 = 49; 62 | pub const DIF_OP_POPTS: u32 = 50; 63 | pub const DIF_OP_FLUSHTS: u32 = 51; 64 | pub const DIF_OP_LDGAA: u32 = 52; 65 | pub const DIF_OP_LDTAA: u32 = 53; 66 | pub const DIF_OP_STGAA: u32 = 54; 67 | pub const DIF_OP_STTAA: u32 = 55; 68 | pub const DIF_OP_LDLS: u32 = 56; 69 | pub const DIF_OP_STLS: u32 = 57; 70 | pub const DIF_OP_ALLOCS: u32 = 58; 71 | pub const DIF_OP_COPYS: u32 = 59; 72 | pub const DIF_OP_STB: u32 = 60; 73 | pub const DIF_OP_STH: u32 = 61; 74 | pub const DIF_OP_STW: u32 = 62; 75 | pub const DIF_OP_STX: u32 = 63; 76 | pub const DIF_OP_ULDSB: u32 = 64; 77 | pub const DIF_OP_ULDSH: u32 = 65; 78 | pub const DIF_OP_ULDSW: u32 = 66; 79 | pub const DIF_OP_ULDUB: u32 = 67; 80 | pub const DIF_OP_ULDUH: u32 = 68; 81 | pub const DIF_OP_ULDUW: u32 = 69; 82 | pub const DIF_OP_ULDX: u32 = 70; 83 | pub const DIF_OP_RLDSB: u32 = 71; 84 | pub const DIF_OP_RLDSH: u32 = 72; 85 | pub const DIF_OP_RLDSW: u32 = 73; 86 | pub const DIF_OP_RLDUB: u32 = 74; 87 | pub const DIF_OP_RLDUH: u32 = 75; 88 | pub const DIF_OP_RLDUW: u32 = 76; 89 | pub const DIF_OP_RLDX: u32 = 77; 90 | pub const DIF_OP_XLATE: u32 = 78; 91 | pub const DIF_OP_XLARG: u32 = 79; 92 | pub const DIF_OP_STRIP: u32 = 80; 93 | pub const DIF_INTOFF_MAX: u32 = 65535; 94 | pub const DIF_STROFF_MAX: u32 = 65535; 95 | pub const DIF_REGISTER_MAX: u32 = 255; 96 | pub const DIF_VARIABLE_MAX: u32 = 65535; 97 | pub const DIF_SUBROUTINE_MAX: u32 = 65535; 98 | pub const DIF_VAR_ARRAY_MIN: u32 = 0; 99 | pub const DIF_VAR_ARRAY_UBASE: u32 = 128; 100 | pub const DIF_VAR_ARRAY_MAX: u32 = 255; 101 | pub const DIF_VAR_OTHER_MIN: u32 = 256; 102 | pub const DIF_VAR_OTHER_UBASE: u32 = 1280; 103 | pub const DIF_VAR_OTHER_MAX: u32 = 65535; 104 | pub const DIF_VAR_ARGS: u32 = 0; 105 | pub const DIF_VAR_REGS: u32 = 1; 106 | pub const DIF_VAR_UREGS: u32 = 2; 107 | pub const DIF_VAR_CURTHREAD: u32 = 256; 108 | pub const DIF_VAR_TIMESTAMP: u32 = 257; 109 | pub const DIF_VAR_VTIMESTAMP: u32 = 258; 110 | pub const DIF_VAR_IPL: u32 = 259; 111 | pub const DIF_VAR_EPID: u32 = 260; 112 | pub const DIF_VAR_ID: u32 = 261; 113 | pub const DIF_VAR_ARG0: u32 = 262; 114 | pub const DIF_VAR_ARG1: u32 = 263; 115 | pub const DIF_VAR_ARG2: u32 = 264; 116 | pub const DIF_VAR_ARG3: u32 = 265; 117 | pub const DIF_VAR_ARG4: u32 = 266; 118 | pub const DIF_VAR_ARG5: u32 = 267; 119 | pub const DIF_VAR_ARG6: u32 = 268; 120 | pub const DIF_VAR_ARG7: u32 = 269; 121 | pub const DIF_VAR_ARG8: u32 = 270; 122 | pub const DIF_VAR_ARG9: u32 = 271; 123 | pub const DIF_VAR_STACKDEPTH: u32 = 272; 124 | pub const DIF_VAR_CALLER: u32 = 273; 125 | pub const DIF_VAR_PROBEPROV: u32 = 274; 126 | pub const DIF_VAR_PROBEMOD: u32 = 275; 127 | pub const DIF_VAR_PROBEFUNC: u32 = 276; 128 | pub const DIF_VAR_PROBENAME: u32 = 277; 129 | pub const DIF_VAR_PID: u32 = 278; 130 | pub const DIF_VAR_TID: u32 = 279; 131 | pub const DIF_VAR_EXECNAME: u32 = 280; 132 | pub const DIF_VAR_ZONENAME: u32 = 281; 133 | pub const DIF_VAR_WALLTIMESTAMP: u32 = 282; 134 | pub const DIF_VAR_USTACKDEPTH: u32 = 283; 135 | pub const DIF_VAR_UCALLER: u32 = 284; 136 | pub const DIF_VAR_PPID: u32 = 285; 137 | pub const DIF_VAR_UID: u32 = 286; 138 | pub const DIF_VAR_GID: u32 = 287; 139 | pub const DIF_VAR_ERRNO: u32 = 288; 140 | pub const DIF_VAR_PTHREAD_SELF: u32 = 512; 141 | pub const DIF_VAR_DISPATCHQADDR: u32 = 513; 142 | pub const DIF_VAR_MACHTIMESTAMP: u32 = 514; 143 | pub const DIF_VAR_CPU: u32 = 515; 144 | pub const DIF_VAR_CPUINSTRS: u32 = 516; 145 | pub const DIF_VAR_CPUCYCLES: u32 = 517; 146 | pub const DIF_VAR_VINSTRS: u32 = 518; 147 | pub const DIF_VAR_VCYCLES: u32 = 519; 148 | pub const DIF_SUBR_RAND: u32 = 0; 149 | pub const DIF_SUBR_MUTEX_OWNED: u32 = 1; 150 | pub const DIF_SUBR_MUTEX_OWNER: u32 = 2; 151 | pub const DIF_SUBR_MUTEX_TYPE_ADAPTIVE: u32 = 3; 152 | pub const DIF_SUBR_MUTEX_TYPE_SPIN: u32 = 4; 153 | pub const DIF_SUBR_RW_READ_HELD: u32 = 5; 154 | pub const DIF_SUBR_RW_WRITE_HELD: u32 = 6; 155 | pub const DIF_SUBR_RW_ISWRITER: u32 = 7; 156 | pub const DIF_SUBR_COPYIN: u32 = 8; 157 | pub const DIF_SUBR_COPYINSTR: u32 = 9; 158 | pub const DIF_SUBR_SPECULATION: u32 = 10; 159 | pub const DIF_SUBR_PROGENYOF: u32 = 11; 160 | pub const DIF_SUBR_STRLEN: u32 = 12; 161 | pub const DIF_SUBR_COPYOUT: u32 = 13; 162 | pub const DIF_SUBR_COPYOUTSTR: u32 = 14; 163 | pub const DIF_SUBR_ALLOCA: u32 = 15; 164 | pub const DIF_SUBR_BCOPY: u32 = 16; 165 | pub const DIF_SUBR_COPYINTO: u32 = 17; 166 | pub const DIF_SUBR_MSGDSIZE: u32 = 18; 167 | pub const DIF_SUBR_MSGSIZE: u32 = 19; 168 | pub const DIF_SUBR_GETMAJOR: u32 = 20; 169 | pub const DIF_SUBR_GETMINOR: u32 = 21; 170 | pub const DIF_SUBR_DDI_PATHNAME: u32 = 22; 171 | pub const DIF_SUBR_STRJOIN: u32 = 23; 172 | pub const DIF_SUBR_LLTOSTR: u32 = 24; 173 | pub const DIF_SUBR_BASENAME: u32 = 25; 174 | pub const DIF_SUBR_DIRNAME: u32 = 26; 175 | pub const DIF_SUBR_CLEANPATH: u32 = 27; 176 | pub const DIF_SUBR_STRCHR: u32 = 28; 177 | pub const DIF_SUBR_STRRCHR: u32 = 29; 178 | pub const DIF_SUBR_STRSTR: u32 = 30; 179 | pub const DIF_SUBR_STRTOK: u32 = 31; 180 | pub const DIF_SUBR_SUBSTR: u32 = 32; 181 | pub const DIF_SUBR_INDEX: u32 = 33; 182 | pub const DIF_SUBR_RINDEX: u32 = 34; 183 | pub const DIF_SUBR_HTONS: u32 = 35; 184 | pub const DIF_SUBR_HTONL: u32 = 36; 185 | pub const DIF_SUBR_HTONLL: u32 = 37; 186 | pub const DIF_SUBR_NTOHS: u32 = 38; 187 | pub const DIF_SUBR_NTOHL: u32 = 39; 188 | pub const DIF_SUBR_NTOHLL: u32 = 40; 189 | pub const DIF_SUBR_INET_NTOP: u32 = 41; 190 | pub const DIF_SUBR_INET_NTOA: u32 = 42; 191 | pub const DIF_SUBR_INET_NTOA6: u32 = 43; 192 | pub const DIF_SUBR_TOUPPER: u32 = 44; 193 | pub const DIF_SUBR_TOLOWER: u32 = 45; 194 | pub const DIF_SUBR_JSON: u32 = 46; 195 | pub const DIF_SUBR_STRTOLL: u32 = 47; 196 | pub const DIF_SUBR_STRIP: u32 = 48; 197 | pub const DIF_SUBR_MAX: u32 = 48; 198 | pub const DIF_SUBR_APPLE_MIN: u32 = 200; 199 | pub const DIF_SUBR_VM_KERNEL_ADDRPERM: u32 = 200; 200 | pub const DIF_SUBR_KDEBUG_TRACE: u32 = 201; 201 | pub const DIF_SUBR_KDEBUG_TRACE_STRING: u32 = 202; 202 | pub const DIF_SUBR_APPLE_MAX: u32 = 202; 203 | pub const DIF_INSTR_NOP: u32 = 603979776; 204 | pub const DIF_INSTR_POPTS: u32 = 838860800; 205 | pub const DIF_INSTR_FLUSHTS: u32 = 855638016; 206 | pub const DIF_REG_R0: u32 = 0; 207 | pub const DIF_TYPE_CTF: u32 = 0; 208 | pub const DIF_TYPE_STRING: u32 = 1; 209 | pub const DIF_TF_BYREF: u32 = 1; 210 | pub const DIF_TF_BYUREF: u32 = 2; 211 | pub const DOF_ID_SIZE: u32 = 16; 212 | pub const DOF_ID_MAG0: u32 = 0; 213 | pub const DOF_ID_MAG1: u32 = 1; 214 | pub const DOF_ID_MAG2: u32 = 2; 215 | pub const DOF_ID_MAG3: u32 = 3; 216 | pub const DOF_ID_MODEL: u32 = 4; 217 | pub const DOF_ID_ENCODING: u32 = 5; 218 | pub const DOF_ID_VERSION: u32 = 6; 219 | pub const DOF_ID_DIFVERS: u32 = 7; 220 | pub const DOF_ID_DIFIREG: u32 = 8; 221 | pub const DOF_ID_DIFTREG: u32 = 9; 222 | pub const DOF_ID_PAD: u32 = 10; 223 | pub const DOF_MAG_MAG0: u32 = 127; 224 | pub const DOF_MAG_MAG1: u8 = 68u8; 225 | pub const DOF_MAG_MAG2: u8 = 79u8; 226 | pub const DOF_MAG_MAG3: u8 = 70u8; 227 | pub const DOF_MAG_STRING: &[u8; 5usize] = b"\x7FDOF\0"; 228 | pub const DOF_MAG_STRLEN: u32 = 4; 229 | pub const DOF_MODEL_NONE: u32 = 0; 230 | pub const DOF_MODEL_ILP32: u32 = 1; 231 | pub const DOF_MODEL_LP64: u32 = 2; 232 | pub const DOF_MODEL_NATIVE: u32 = 2; 233 | pub const DOF_ENCODE_NONE: u32 = 0; 234 | pub const DOF_ENCODE_LSB: u32 = 1; 235 | pub const DOF_ENCODE_MSB: u32 = 2; 236 | pub const DOF_ENCODE_NATIVE: u32 = 1; 237 | 238 | pub const DOF_VERSION_1: u32 = 1; 239 | pub const DOF_VERSION_2: u32 = 2; 240 | pub const DOF_VERSION_3: u32 = 3; 241 | #[cfg(target_os = "macos")] 242 | pub const DOF_VERSION: u32 = 3; 243 | #[cfg(not(target_os = "macos"))] 244 | pub const DOF_VERSION: u32 = 2; 245 | 246 | pub const DOF_FL_VALID: u32 = 0; 247 | pub const DOF_SECIDX_NONE: i32 = -1; 248 | pub const DOF_STRIDX_NONE: i32 = -1; 249 | pub const DOF_SECT_NONE: u32 = 0; 250 | pub const DOF_SECT_COMMENTS: u32 = 1; 251 | pub const DOF_SECT_SOURCE: u32 = 2; 252 | pub const DOF_SECT_ECBDESC: u32 = 3; 253 | pub const DOF_SECT_PROBEDESC: u32 = 4; 254 | pub const DOF_SECT_ACTDESC: u32 = 5; 255 | pub const DOF_SECT_DIFOHDR: u32 = 6; 256 | pub const DOF_SECT_DIF: u32 = 7; 257 | pub const DOF_SECT_STRTAB: u32 = 8; 258 | pub const DOF_SECT_VARTAB: u32 = 9; 259 | pub const DOF_SECT_RELTAB: u32 = 10; 260 | pub const DOF_SECT_TYPTAB: u32 = 11; 261 | pub const DOF_SECT_URELHDR: u32 = 12; 262 | pub const DOF_SECT_KRELHDR: u32 = 13; 263 | pub const DOF_SECT_OPTDESC: u32 = 14; 264 | pub const DOF_SECT_PROVIDER: u32 = 15; 265 | pub const DOF_SECT_PROBES: u32 = 16; 266 | pub const DOF_SECT_PRARGS: u32 = 17; 267 | pub const DOF_SECT_PROFFS: u32 = 18; 268 | pub const DOF_SECT_INTTAB: u32 = 19; 269 | pub const DOF_SECT_UTSNAME: u32 = 20; 270 | pub const DOF_SECT_XLTAB: u32 = 21; 271 | pub const DOF_SECT_XLMEMBERS: u32 = 22; 272 | pub const DOF_SECT_XLIMPORT: u32 = 23; 273 | pub const DOF_SECT_XLEXPORT: u32 = 24; 274 | pub const DOF_SECT_PREXPORT: u32 = 25; 275 | pub const DOF_SECT_PRENOFFS: u32 = 26; 276 | pub const DOF_SECF_LOAD: u32 = 1; 277 | pub const DOF_RELO_NONE: u32 = 0; 278 | pub const DOF_RELO_SETX: u32 = 1; 279 | #[repr(C)] 280 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 281 | pub struct dtrace_diftype { 282 | pub dtdt_kind: u8, 283 | pub dtdt_ckind: u8, 284 | pub dtdt_flags: u8, 285 | pub dtdt_pad: u8, 286 | pub dtdt_size: u32, 287 | } 288 | pub type dtrace_diftype_t = dtrace_diftype; 289 | #[repr(C)] 290 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 291 | pub struct dof_hdr { 292 | pub dofh_ident: [u8; 16usize], 293 | pub dofh_flags: u32, 294 | pub dofh_hdrsize: u32, 295 | pub dofh_secsize: u32, 296 | pub dofh_secnum: u32, 297 | pub dofh_secoff: u64, 298 | pub dofh_loadsz: u64, 299 | pub dofh_filesz: u64, 300 | pub dofh_pad: u64, 301 | } 302 | pub type dof_hdr_t = dof_hdr; 303 | pub type dof_secidx_t = u32; 304 | pub type dof_stridx_t = u32; 305 | #[repr(C)] 306 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 307 | pub struct dof_sec { 308 | pub dofs_type: u32, 309 | pub dofs_align: u32, 310 | pub dofs_flags: u32, 311 | pub dofs_entsize: u32, 312 | pub dofs_offset: u64, 313 | pub dofs_size: u64, 314 | } 315 | pub type dof_sec_t = dof_sec; 316 | #[repr(C)] 317 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 318 | pub struct dof_ecbdesc { 319 | pub dofe_probes: dof_secidx_t, 320 | pub dofe_pred: dof_secidx_t, 321 | pub dofe_actions: dof_secidx_t, 322 | pub dofe_pad: u32, 323 | pub dofe_uarg: u64, 324 | } 325 | pub type dof_ecbdesc_t = dof_ecbdesc; 326 | #[repr(C)] 327 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 328 | pub struct dof_probedesc { 329 | pub dofp_strtab: dof_secidx_t, 330 | pub dofp_provider: dof_stridx_t, 331 | pub dofp_mod: dof_stridx_t, 332 | pub dofp_func: dof_stridx_t, 333 | pub dofp_name: dof_stridx_t, 334 | pub dofp_id: u32, 335 | } 336 | pub type dof_probedesc_t = dof_probedesc; 337 | #[repr(C)] 338 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 339 | pub struct dof_actdesc { 340 | pub dofa_difo: dof_secidx_t, 341 | pub dofa_strtab: dof_secidx_t, 342 | pub dofa_kind: u32, 343 | pub dofa_ntuple: u32, 344 | pub dofa_arg: u64, 345 | pub dofa_uarg: u64, 346 | } 347 | pub type dof_actdesc_t = dof_actdesc; 348 | #[repr(C)] 349 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 350 | pub struct dof_difohdr { 351 | pub dofd_rtype: dtrace_diftype_t, 352 | pub dofd_links: [dof_secidx_t; 1usize], 353 | } 354 | pub type dof_difohdr_t = dof_difohdr; 355 | #[repr(C)] 356 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 357 | pub struct dof_relohdr { 358 | pub dofr_strtab: dof_secidx_t, 359 | pub dofr_relsec: dof_secidx_t, 360 | pub dofr_tgtsec: dof_secidx_t, 361 | } 362 | pub type dof_relohdr_t = dof_relohdr; 363 | #[repr(C)] 364 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 365 | pub struct dof_relodesc { 366 | pub dofr_name: dof_stridx_t, 367 | pub dofr_type: u32, 368 | pub dofr_offset: u64, 369 | pub dofr_data: u64, 370 | } 371 | pub type dof_relodesc_t = dof_relodesc; 372 | #[repr(C)] 373 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 374 | pub struct dof_optdesc { 375 | pub dofo_option: u32, 376 | pub dofo_strtab: dof_secidx_t, 377 | pub dofo_value: u64, 378 | } 379 | pub type dof_optdesc_t = dof_optdesc; 380 | pub type dof_attr_t = u32; 381 | #[repr(C)] 382 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 383 | pub struct dof_provider { 384 | pub dofpv_strtab: dof_secidx_t, 385 | pub dofpv_probes: dof_secidx_t, 386 | pub dofpv_prargs: dof_secidx_t, 387 | pub dofpv_proffs: dof_secidx_t, 388 | pub dofpv_name: dof_stridx_t, 389 | pub dofpv_provattr: dof_attr_t, 390 | pub dofpv_modattr: dof_attr_t, 391 | pub dofpv_funcattr: dof_attr_t, 392 | pub dofpv_nameattr: dof_attr_t, 393 | pub dofpv_argsattr: dof_attr_t, 394 | pub dofpv_prenoffs: dof_secidx_t, 395 | } 396 | pub type dof_provider_t = dof_provider; 397 | #[repr(C)] 398 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 399 | pub struct dof_probe { 400 | pub dofpr_addr: u64, 401 | pub dofpr_func: dof_stridx_t, 402 | pub dofpr_name: dof_stridx_t, 403 | pub dofpr_nargv: dof_stridx_t, 404 | pub dofpr_xargv: dof_stridx_t, 405 | pub dofpr_argidx: u32, 406 | pub dofpr_offidx: u32, 407 | pub dofpr_nargc: u8, 408 | pub dofpr_xargc: u8, 409 | pub dofpr_noffs: u16, 410 | pub dofpr_enoffidx: u32, 411 | pub dofpr_nenoffs: u16, 412 | pub dofpr_pad1: u16, 413 | pub dofpr_pad2: u32, 414 | } 415 | pub type dof_probe_t = dof_probe; 416 | #[repr(C)] 417 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 418 | pub struct dof_xlator { 419 | pub dofxl_members: dof_secidx_t, 420 | pub dofxl_strtab: dof_secidx_t, 421 | pub dofxl_argv: dof_stridx_t, 422 | pub dofxl_argc: u32, 423 | pub dofxl_type: dof_stridx_t, 424 | pub dofxl_attr: dof_attr_t, 425 | } 426 | pub type dof_xlator_t = dof_xlator; 427 | #[repr(C)] 428 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 429 | pub struct dof_xlmember { 430 | pub dofxm_difo: dof_secidx_t, 431 | pub dofxm_name: dof_stridx_t, 432 | pub dofxm_type: dtrace_diftype_t, 433 | } 434 | pub type dof_xlmember_t = dof_xlmember; 435 | #[repr(C)] 436 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 437 | pub struct dof_xlref { 438 | pub dofxr_xlator: dof_secidx_t, 439 | pub dofxr_member: u32, 440 | pub dofxr_argn: u32, 441 | } 442 | pub type dof_xlref_t = dof_xlref; 443 | #[repr(C)] 444 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Copy, Clone)] 445 | pub struct dof_helper { 446 | pub dofhp_mod: [::std::os::raw::c_char; 64usize], 447 | pub dofhp_addr: u64, 448 | pub dofhp_dof: u64, 449 | } 450 | impl Default for dof_helper { 451 | fn default() -> Self { 452 | unsafe { ::std::mem::zeroed() } 453 | } 454 | } 455 | pub type dof_helper_t = dof_helper; 456 | #[repr(C)] 457 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Copy, Clone)] 458 | pub struct dof_ioctl_data { 459 | pub dofiod_count: u64, 460 | pub dofiod_helpers: [dof_helper_t; 1usize], 461 | } 462 | impl Default for dof_ioctl_data { 463 | fn default() -> Self { 464 | unsafe { ::std::mem::zeroed() } 465 | } 466 | } 467 | pub type dof_ioctl_data_t = dof_ioctl_data; 468 | #[repr(C)] 469 | #[derive(Immutable, KnownLayout, IntoBytes, FromBytes, Debug, Default, Copy, Clone)] 470 | pub struct dt_node { 471 | pub _address: u8, 472 | } 473 | -------------------------------------------------------------------------------- /dof/src/fmt.rs: -------------------------------------------------------------------------------- 1 | //! Functions to format types from DOF. 2 | 3 | // Copyright 2021 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use crate::{des::RawSections, Section}; 18 | use crate::{dof_bindings::*, Error}; 19 | use pretty_hex::PrettyHex; 20 | use std::{fmt::Debug, mem::size_of}; 21 | use zerocopy::{FromBytes, Immutable, KnownLayout, Ref}; 22 | 23 | /// Format a DOF section into a pretty-printable string. 24 | pub fn fmt_dof_sec(sec: &dof_sec, index: usize) -> String { 25 | let mut ret = String::new(); 26 | 27 | ret.push_str(format!("DOF section {} ({:#x})\n", index, index).as_str()); 28 | ret.push_str( 29 | format!( 30 | " dofs_type: {} {}\n", 31 | sec.dofs_type, 32 | match sec.dofs_type { 33 | DOF_SECT_NONE => "(DOF_SECT_NONE)", 34 | DOF_SECT_COMMENTS => "(DOF_SECT_COMMENTS)", 35 | DOF_SECT_SOURCE => "(DOF_SECT_SOURCE)", 36 | DOF_SECT_ECBDESC => "(DOF_SECT_ECBDESC)", 37 | DOF_SECT_PROBEDESC => "(DOF_SECT_PROBEDESC)", 38 | DOF_SECT_ACTDESC => "(DOF_SECT_ACTDESC)", 39 | DOF_SECT_DIFOHDR => "(DOF_SECT_DIFOHDR)", 40 | DOF_SECT_DIF => "(DOF_SECT_DIF)", 41 | DOF_SECT_STRTAB => "(DOF_SECT_STRTAB)", 42 | DOF_SECT_VARTAB => "(DOF_SECT_VARTAB)", 43 | DOF_SECT_RELTAB => "(DOF_SECT_RELTAB)", 44 | DOF_SECT_TYPTAB => "(DOF_SECT_TYPTAB)", 45 | DOF_SECT_URELHDR => "(DOF_SECT_URELHDR)", 46 | DOF_SECT_KRELHDR => "(DOF_SECT_KRELHDR)", 47 | DOF_SECT_OPTDESC => "(DOF_SECT_OPTDESC)", 48 | DOF_SECT_PROVIDER => "(DOF_SECT_PROVIDER)", 49 | DOF_SECT_PROBES => "(DOF_SECT_PROBES)", 50 | DOF_SECT_PRARGS => "(DOF_SECT_PRARGS)", 51 | DOF_SECT_PROFFS => "(DOF_SECT_PROFFS)", 52 | DOF_SECT_INTTAB => "(DOF_SECT_INTTAB)", 53 | DOF_SECT_UTSNAME => "(DOF_SECT_UTSNAME)", 54 | DOF_SECT_XLTAB => "(DOF_SECT_XLTAB)", 55 | DOF_SECT_XLMEMBERS => "(DOF_SECT_XLMEMBERS)", 56 | DOF_SECT_XLIMPORT => "(DOF_SECT_XLIMPORT)", 57 | DOF_SECT_XLEXPORT => "(DOF_SECT_XLEXPORT)", 58 | DOF_SECT_PREXPORT => "(DOF_SECT_PREXPORT)", 59 | DOF_SECT_PRENOFFS => "(DOF_SECT_PRENOFFS)", 60 | _ => "(unknown)", 61 | } 62 | ) 63 | .as_str(), 64 | ); 65 | ret.push_str(format!(" dofs_align: {}\n", sec.dofs_align).as_str()); 66 | ret.push_str(format!(" dofs_flags: {}\n", sec.dofs_flags).as_str()); 67 | ret.push_str(format!(" dofs_entsize: {}\n", sec.dofs_entsize).as_str()); 68 | ret.push_str(format!(" dofs_offset: {}\n", sec.dofs_offset).as_str()); 69 | ret.push_str(format!(" dofs_size: {}\n", sec.dofs_size).as_str()); 70 | 71 | ret 72 | } 73 | 74 | /// Format the binary data from a DOF section into a pretty-printable hex string. 75 | pub fn fmt_dof_sec_data(sec: &dof_sec, data: &Vec) -> String { 76 | match sec.dofs_type { 77 | DOF_SECT_PROBES => fmt_dof_sec_type::(data), 78 | DOF_SECT_RELTAB => fmt_dof_sec_type::(data), 79 | DOF_SECT_URELHDR => fmt_dof_sec_type::(data), 80 | DOF_SECT_PROVIDER => fmt_dof_sec_type::(data), 81 | _ => format!("{:?}", data.hex_dump()), 82 | } 83 | } 84 | 85 | fn fmt_dof_sec_type(data: &[u8]) -> String { 86 | data.chunks(size_of::()) 87 | .map(|chunk| { 88 | let item = *Ref::<_, T>::from_bytes(chunk).unwrap(); 89 | format!("{:#x?}", item) 90 | }) 91 | .collect::>() 92 | .join("\n") 93 | } 94 | 95 | /// Controls how DOF data is formatted 96 | #[derive(Clone, Copy)] 97 | pub enum FormatMode { 98 | // Emit Rust types used by the usdt crate 99 | Pretty, 100 | // Emit Rust types as json for parsing 101 | Json, 102 | /// Emit underlying DOF C types 103 | Raw { 104 | /// If true, the DOF section data is included, along with the section headers. 105 | /// If false, only the section headers are printed. 106 | include_sections: bool, 107 | }, 108 | } 109 | 110 | /// Format all DOF data in a collection of DOF sections into a pretty-printable string. 111 | /// 112 | /// Uses the `FormatMode` to determine how the data is formatted. 113 | pub fn fmt_dof(sections: Vec
, format: FormatMode) -> Result, Error> { 114 | let mut out = String::new(); 115 | match format { 116 | FormatMode::Raw { include_sections } => { 117 | for section in sections.iter() { 118 | let RawSections { header, sections } = 119 | crate::des::deserialize_raw_sections(section.as_bytes().as_slice())?; 120 | out.push_str(&format!("{:#?}\n", header)); 121 | for (index, (section_header, data)) in sections.into_iter().enumerate() { 122 | out.push_str(&format!("{}\n", fmt_dof_sec(§ion_header, index))); 123 | if include_sections { 124 | out.push_str(&format!("{}\n", fmt_dof_sec_data(§ion_header, &data))); 125 | } 126 | } 127 | } 128 | } 129 | FormatMode::Json => { 130 | for section in sections.iter() { 131 | out.push_str(section.to_json().as_str()); 132 | } 133 | } 134 | FormatMode::Pretty => { 135 | for section in sections.iter() { 136 | out.push_str(&format!("{:#?}\n", section)); 137 | } 138 | } 139 | } 140 | 141 | if out.is_empty() { 142 | Ok(None) 143 | } else { 144 | Ok(Some(out)) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /dof/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Tools for extracting and parsing data in DTrace Object Format (DOF). 2 | //! 3 | //! The `dof` crate provides types and functions for reading and writing the DTrace Object Format, 4 | //! an ELF-like serialization format used to store information about DTrace providers and probes in 5 | //! object files. DOF sections are generated from source code at compile time, and used to 6 | //! communicate information to the in-kernel portions of DTrace about the providers and probes 7 | //! defined in the source. 8 | //! 9 | //! Low-level bindings to the DOF C structures are contained in the [`dof_bindings`] module, 10 | //! however most client code with interact with the more convenient stuctures defined in the crate 11 | //! root. The [`Section`] type describes a complete DOF section as contained in an object file. It 12 | //! contains one or more [`Provider`]s, each of which contains one or more [`Probe`]s. 13 | //! 14 | //! A [`Probe`] describes the names of the related components, such as the function in which it is 15 | //! called, the provider to which it belongs, and the probe name itself. It also contains 16 | //! information about the location of the probe callsite in the object file itself. This is used by 17 | //! DTrace to enable and disable the probe dynamically. 18 | //! 19 | //! Users of the crate will most likely be interested in deserializing existing DOF data from an 20 | //! object file. The function [`extract_dof_sections`] may be used to pull all sections (and all 21 | //! providers and probes) from either an ELF or Mach-O object file. 22 | //! 23 | //! The [`Section::from_bytes`] and [`Section::as_bytes`] methods can be used for ser/des of a 24 | //! section directly to DOF itself, i.e., ignoring the larger object file format. 25 | //! 26 | //! Most useful methods and types are exported in the crate root. However, the lower-level Rust 27 | //! bindings to the raw C-structs are also exposed in the [`dof_bindings`] module, and may be 28 | //! extracted from a DOF byte slice with the [`des::deserialize_raw_sections`] function. 29 | 30 | // Copyright 2021 Oxide Computer Company 31 | // 32 | // Licensed under the Apache License, Version 2.0 (the "License"); 33 | // you may not use this file except in compliance with the License. 34 | // You may obtain a copy of the License at 35 | // 36 | // http://www.apache.org/licenses/LICENSE-2.0 37 | // 38 | // Unless required by applicable law or agreed to in writing, software 39 | // distributed under the License is distributed on an "AS IS" BASIS, 40 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 41 | // See the License for the specific language governing permissions and 42 | // limitations under the License. 43 | 44 | #[cfg(feature = "des")] 45 | pub mod des; 46 | pub mod dof; 47 | pub mod dof_bindings; 48 | #[cfg(feature = "des")] 49 | pub mod fmt; 50 | pub mod ser; 51 | 52 | #[cfg(feature = "des")] 53 | pub use crate::des::{ 54 | collect_dof_sections, deserialize_section, extract_dof_sections, is_dof_section, 55 | }; 56 | pub use crate::dof::*; 57 | pub use crate::ser::serialize_section; 58 | -------------------------------------------------------------------------------- /dof/src/ser.rs: -------------------------------------------------------------------------------- 1 | //! Functions to serialize crate types into DOF. 2 | 3 | // Copyright 2021 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use std::mem::size_of; 18 | 19 | use zerocopy::IntoBytes; 20 | 21 | use crate::dof_bindings::*; 22 | use crate::Section; 23 | 24 | // Build the binary data for each section of a serialized Section object, as a vector of 25 | // (section_type, section_data) tuples. 26 | fn build_section_data(section: &Section) -> Vec<(u32, Vec)> { 27 | let mut probe_sections = Vec::new(); 28 | let mut provider_sections = Vec::new(); 29 | let mut strings = Vec::::new(); 30 | strings.push(0); // starts with a NULL byte 31 | let mut arguments = Vec::::new(); 32 | let mut offsets = Vec::new(); 33 | let mut enabled_offsets = Vec::new(); 34 | 35 | for (i, provider) in section.providers.values().enumerate() { 36 | let mut provider_section = dof_provider { 37 | dofpv_name: strings.len() as _, 38 | ..Default::default() 39 | }; 40 | strings.extend_from_slice(provider.name.as_bytes()); 41 | strings.push(0); 42 | 43 | // Links to the constituent sections for this provider. Note that the probes are all placed 44 | // first, with one section (array of probes) for each provider. 45 | provider_section.dofpv_strtab = 0; 46 | provider_section.dofpv_prargs = 1; 47 | provider_section.dofpv_proffs = 2; 48 | provider_section.dofpv_prenoffs = 3; 49 | provider_section.dofpv_probes = (4 + i) as _; 50 | 51 | let mut probe_section = Vec::with_capacity(provider.probes.len() * size_of::()); 52 | for probe in provider.probes.values() { 53 | let mut probe_t = dof_probe { 54 | dofpr_addr: probe.address, 55 | ..Default::default() 56 | }; 57 | 58 | // Insert function name and store strtab index 59 | probe_t.dofpr_func = strings.len() as _; 60 | strings.extend_from_slice(probe.function.as_bytes()); 61 | strings.push(0); 62 | 63 | // Insert probe name and store strtab index 64 | probe_t.dofpr_name = strings.len() as _; 65 | strings.extend_from_slice(probe.name.as_bytes()); 66 | strings.push(0); 67 | 68 | // Insert argument strings and store strtab indices 69 | probe_t.dofpr_argidx = arguments.len() as _; 70 | let argv = strings.len() as u32; 71 | for (i, arg) in probe.arguments.iter().enumerate() { 72 | strings.extend_from_slice(arg.as_bytes()); 73 | strings.push(0); 74 | arguments.push(i as _); 75 | } 76 | probe_t.dofpr_nargv = argv; 77 | probe_t.dofpr_nargc = probe.arguments.len() as _; 78 | probe_t.dofpr_xargv = argv; 79 | probe_t.dofpr_xargc = probe.arguments.len() as _; 80 | 81 | // Insert probe offsets and store indices 82 | probe_t.dofpr_offidx = offsets.len() as _; 83 | offsets.extend_from_slice(&probe.offsets); 84 | probe_t.dofpr_noffs = probe.offsets.len() as _; 85 | 86 | // Insert is-enabled offset and store indices 87 | probe_t.dofpr_enoffidx = enabled_offsets.len() as _; 88 | enabled_offsets.extend_from_slice(&probe.enabled_offsets); 89 | probe_t.dofpr_nenoffs = probe.enabled_offsets.len() as _; 90 | 91 | probe_section.extend_from_slice(probe_t.as_bytes()); 92 | } 93 | probe_sections.push(probe_section); 94 | provider_sections.push(provider_section.as_bytes().to_vec()); 95 | } 96 | 97 | // Construct the string table. 98 | let mut section_data = Vec::with_capacity(4 + 2 * probe_sections.len()); 99 | section_data.push((DOF_SECT_STRTAB, strings)); 100 | 101 | // Construct the argument mappings table 102 | if arguments.is_empty() { 103 | arguments.push(0); 104 | } 105 | section_data.push((DOF_SECT_PRARGS, arguments)); 106 | 107 | // Construct the offset table 108 | let offset_section = offsets 109 | .iter() 110 | .flat_map(|offset| offset.to_ne_bytes().to_vec()) 111 | .collect::>(); 112 | section_data.push((DOF_SECT_PROFFS, offset_section)); 113 | 114 | // Construct enabled offset table 115 | let enabled_offset_section = enabled_offsets 116 | .iter() 117 | .flat_map(|offset| offset.to_ne_bytes().to_vec()) 118 | .collect::>(); 119 | section_data.push((DOF_SECT_PRENOFFS, enabled_offset_section)); 120 | 121 | // Push remaining probe and provider data. They must be done in this order so the indices to 122 | // the probe section for each provider is accurate. 123 | for probe_section in probe_sections.into_iter() { 124 | section_data.push((DOF_SECT_PROBES, probe_section)); 125 | } 126 | for provider_section in provider_sections.into_iter() { 127 | section_data.push((DOF_SECT_PROVIDER, provider_section)); 128 | } 129 | 130 | section_data 131 | } 132 | 133 | fn build_section_headers( 134 | sections: Vec<(u32, Vec)>, 135 | mut offset: usize, 136 | ) -> (Vec, Vec>, usize) { 137 | let mut section_headers = Vec::with_capacity(sections.len()); 138 | let mut section_data = Vec::>::with_capacity(sections.len()); 139 | 140 | for (sec_type, data) in sections.into_iter() { 141 | // Different sections expect different alignment and entry sizes. 142 | let (alignment, entry_size) = match sec_type { 143 | DOF_SECT_STRTAB | DOF_SECT_PRARGS => (1, 1), 144 | DOF_SECT_PROFFS | DOF_SECT_PRENOFFS => (size_of::(), size_of::()), 145 | DOF_SECT_PROVIDER => (size_of::(), 0), 146 | DOF_SECT_PROBES => (size_of::(), size_of::()), 147 | _ => unimplemented!(), 148 | }; 149 | 150 | // Pad the data of the *previous* section as needed. Note that this space 151 | // is not accounted for by the dofs_size field of any section, but it 152 | // is--of course--part of the total dofh_filesz. 153 | if offset % alignment > 0 { 154 | let padding = alignment - offset % alignment; 155 | section_data.last_mut().unwrap().extend(vec![0; padding]); 156 | offset += padding; 157 | } 158 | 159 | let header = dof_sec { 160 | dofs_type: sec_type, 161 | dofs_align: alignment as u32, 162 | dofs_flags: DOF_SECF_LOAD, 163 | dofs_entsize: entry_size as u32, 164 | dofs_offset: offset as u64, 165 | dofs_size: data.len() as u64, 166 | }; 167 | 168 | offset += data.len(); 169 | section_headers.push(header); 170 | section_data.push(data); 171 | } 172 | 173 | (section_headers, section_data, offset) 174 | } 175 | 176 | /// Serialize a Section into a vector of DOF bytes 177 | pub fn serialize_section(section: &Section) -> Vec { 178 | let sections = build_section_data(section); 179 | let hdr_size = size_of::() + sections.len() * size_of::(); 180 | let (section_headers, section_data, size) = build_section_headers(sections, hdr_size); 181 | 182 | let header = dof_hdr { 183 | dofh_ident: section.ident.as_bytes(), 184 | dofh_flags: 0, 185 | dofh_hdrsize: size_of::() as _, 186 | dofh_secsize: size_of::() as _, 187 | dofh_secnum: section_headers.len() as _, 188 | dofh_secoff: size_of::() as _, 189 | dofh_loadsz: size as _, 190 | dofh_filesz: size as _, 191 | dofh_pad: 0, 192 | }; 193 | 194 | let mut file_data = Vec::with_capacity(header.dofh_filesz as _); 195 | file_data.extend(header.as_bytes()); 196 | for header in section_headers.into_iter() { 197 | file_data.extend(header.as_bytes()); 198 | } 199 | for data in section_data.into_iter() { 200 | file_data.extend(data); 201 | } 202 | file_data 203 | } 204 | 205 | #[cfg(test)] 206 | mod test { 207 | use super::build_section_headers; 208 | use crate::dof_bindings::*; 209 | #[test] 210 | fn test_padding() { 211 | let sections = vec![ 212 | (DOF_SECT_STRTAB, vec![96_u8]), 213 | (DOF_SECT_PROFFS, vec![0x11_u8, 0x22_u8, 0x33_u8, 0x44_u8]), 214 | ]; 215 | 216 | assert_eq!(sections[0].1.len(), 1); 217 | 218 | let (_, section_data, size) = build_section_headers(sections, 0); 219 | 220 | assert_eq!(section_data[0].len(), 4); 221 | assert_eq!(size, 8); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /dtrace-parser/.gitignore: -------------------------------------------------------------------------------- 1 | *.d 2 | -------------------------------------------------------------------------------- /dtrace-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dtrace-parser" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Parse DTrace provider definitions into Rust" 7 | repository = "https://github.com/oxidecomputer/usdt.git" 8 | 9 | [dependencies] 10 | pest = "2.8.0" 11 | pest_derive = "2.8.0" 12 | thiserror = "2.0.12" 13 | 14 | [dev-dependencies] 15 | rstest = "0.22.0" 16 | -------------------------------------------------------------------------------- /dtrace-parser/src/dtrace.pest: -------------------------------------------------------------------------------- 1 | // A definition of the basic grammar of DTrace provider definitions. 2 | // Copyright 2022 Oxide Computer Company 3 | 4 | // Some basic tokens 5 | PROBE_KEY = @{ "probe" } 6 | PROVIDER_KEY = @{ "provider" } 7 | SEMICOLON = @{ ";" } 8 | LEFT_PAREN = @{ "(" } 9 | RIGHT_PAREN = @{ ")" } 10 | LEFT_BRACE = @{ "{" } 11 | RIGHT_BRACE = @{ "}" } 12 | 13 | // A valid identifier for a provider or probe 14 | IDENTIFIER = @{ ASCII_ALPHA+ ~ (ASCII_ALPHANUMERIC | "_")* } 15 | 16 | // Data types 17 | BIT_WIDTH = @{ "8" | "16" | "32" | "64" } 18 | PTR_T = @{ "ptr" } 19 | SIGNED_INT = ${ "int" ~ (BIT_WIDTH | PTR_T) ~ "_t" } 20 | UNSIGNED_INT = ${ "uint" ~ (BIT_WIDTH | PTR_T) ~ "_t" } 21 | INTEGER = ${ (SIGNED_INT | UNSIGNED_INT) } 22 | STAR = ${ "*" } 23 | INTEGER_POINTER = ${ INTEGER ~ STAR } 24 | STRING = { "char" ~ STAR } 25 | DATA_TYPE = { INTEGER_POINTER | INTEGER | STRING } 26 | 27 | // A list of probe arguments, which are just data types 28 | ARGUMENT_LIST = { ( DATA_TYPE ~ ("," ~ DATA_TYPE)* )* } 29 | 30 | // Definition of a probe 31 | PROBE = { 32 | PROBE_KEY 33 | ~ IDENTIFIER 34 | ~ LEFT_PAREN 35 | ~ ARGUMENT_LIST 36 | ~ RIGHT_PAREN 37 | ~ SEMICOLON 38 | } 39 | 40 | // Definition of a provider 41 | PROVIDER = { 42 | PROVIDER_KEY 43 | ~ IDENTIFIER 44 | ~ LEFT_BRACE 45 | ~ (PROBE)+ 46 | ~ RIGHT_BRACE 47 | ~ SEMICOLON 48 | } 49 | 50 | PRAGMA = ${ 51 | "#pragma" 52 | ~ SPACE+ 53 | ~ (!("\n") ~ ANY)* 54 | ~ SPACE* 55 | ~ "\n" 56 | } 57 | 58 | SPACE = _{ " " | "\t" } 59 | 60 | // Files consist of providers and pragmas 61 | FILE = { 62 | SOI 63 | ~( 64 | PROVIDER 65 | | PRAGMA 66 | )* 67 | ~EOI 68 | } 69 | 70 | WHITESPACE = _{ " " | "\t" | "\r" | "\n" } 71 | COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" } 72 | -------------------------------------------------------------------------------- /dtrace-parser/test-data/foo.d: -------------------------------------------------------------------------------- 1 | provider foo { 2 | probe baz(char*, uint16_t, uint8_t); 3 | }; 4 | -------------------------------------------------------------------------------- /dusty/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dusty" 3 | version = "0.2.0" 4 | edition = "2021" 5 | description = "Tool to inspect USDT probe records in object files" 6 | license = "Apache-2.0" 7 | rust-version = "1.85.0" 8 | publish = false 9 | 10 | [dependencies] 11 | clap = { version = "4.5.38", features = ["derive"] } 12 | dof = { path = "../dof", features = ["des"] } 13 | usdt = { path = "../usdt" } 14 | usdt-impl = { path = "../usdt-impl", features = ["des"] } 15 | -------------------------------------------------------------------------------- /dusty/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /dusty/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Tool to inspect the representation of USDT probes in object files. 2 | 3 | // Copyright 2021 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use clap::Parser; 18 | use std::path::PathBuf; 19 | use usdt::probe_records; 20 | use usdt_impl::Error as UsdtError; 21 | 22 | /// Inspect data related to USDT probes in object files. 23 | #[derive(Debug, Parser)] 24 | struct Cmd { 25 | /// The object file to inspect 26 | file: PathBuf, 27 | 28 | /// Operate more verbosely, printing all available information 29 | #[arg(short, long)] 30 | verbose: bool, 31 | 32 | /// Print raw binary data along with summaries or headers 33 | #[arg(short, long, conflicts_with = "json")] 34 | raw: bool, 35 | 36 | /// Format output as JSON 37 | #[arg(short, long)] 38 | json: bool, 39 | } 40 | 41 | fn main() { 42 | let cmd = Cmd::parse(); 43 | let format_mode = if cmd.raw { 44 | dof::fmt::FormatMode::Raw { 45 | include_sections: cmd.verbose, 46 | } 47 | } else if cmd.json { 48 | dof::fmt::FormatMode::Json 49 | } else { 50 | dof::fmt::FormatMode::Pretty 51 | }; 52 | 53 | match probe_records(&cmd.file) { 54 | Ok(data) => match dof::fmt::fmt_dof(data, format_mode) { 55 | Ok(Some(dof)) => println!("{}", dof), 56 | Ok(None) => println!("No probe information found"), 57 | Err(e) => println!("Failed to format probe information, {:?}", e), 58 | }, 59 | Err(UsdtError::InvalidFile) => { 60 | println!("No probe information found"); 61 | } 62 | Err(e) => { 63 | println!("Failed to parse probe information, {:?}", e); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /probe-test-attr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "probe-test-attr" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../usdt", default-features = false } 9 | serde = "1" 10 | -------------------------------------------------------------------------------- /probe-test-attr/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /probe-test-attr/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /probe-test-attr/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Example using the `usdt` crate, defining probes inline in Rust code which accept any 2 | //! serializable data type. 3 | 4 | // Copyright 2022 Oxide Computer Company 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use serde::Serialize; 19 | 20 | /// By deriving the `serde::Serialize` trait, the `Arg` struct can be used as an argument to a 21 | /// DTrace probe. DTrace provides the `json` function, which accepts a JSON-encoded string and a 22 | /// (possibly-nested) key, and prints the corresponding value of the JSON object. For example, in 23 | /// this case one could use the DTrace snippet: 24 | /// 25 | /// ```bash 26 | /// $ dtrace -n 'stop { printf("arg.x = %s", json(copyinstr(arg1), "ok.x")); }' 27 | /// ``` 28 | /// 29 | /// to print the value of the `x` field. 30 | #[derive(Debug, Clone, Serialize)] 31 | pub struct Arg { 32 | x: u8, 33 | buffer: Vec, 34 | } 35 | 36 | /// Note that not all types are JSON serializable. The most common case is internally-tagged 37 | /// enums with a newtype variant, such as this type. Note that this will not break your program, 38 | /// but an error message will be transmitted to DTrace rather than a successfully-converted value. 39 | #[derive(Debug, Clone, Serialize)] 40 | #[serde(tag = "type")] 41 | pub enum Whoops { 42 | NoBueno(u8), 43 | } 44 | 45 | /// Providers may be defined directly in Rust code using the `usdt::provider` attribute. 46 | /// 47 | /// The attribute should be attached to a module, whose name becomes the provider name. The module 48 | /// can contain `use` statements to import any required items. Probe are defined as `fn`s, whose 49 | /// bodies must be empty. The types of the function are the types of the DTrace probe, assuming 50 | /// they are supported. 51 | /// 52 | /// Note that in most cases, writing the provider in Rust or a D script is equivalent. The main 53 | /// difference is in the support of serializable types. This can't be conveniently expressed in D, 54 | /// as data there is simply a string. So if you want to provide a probe with a more complex Rust 55 | /// type as an argument, it must be defined using this macro. 56 | #[usdt::provider] 57 | mod test { 58 | /// The `Arg` type needs to be imported here, just like in any other module. Note that you 59 | /// _must_ use an absolute import, such as `crate::Arg` or `::std::net::IpAddr`. Relative 60 | /// imports will generate a compiler error. The generated probe macros may be called from 61 | /// anywhere, meaning that those relative imports generally can't be resolved in the same way 62 | /// at the macro invocation site. 63 | use crate::Arg; 64 | 65 | /// Parameters may be given names, but these are only for documentation purposes. 66 | fn start_work(x: u8) {} 67 | 68 | /// Parameters need not have names, and may be taken by reference... 69 | fn stop_work(_: String, arg: &Arg) {} 70 | 71 | /// ... or by value 72 | fn stop_work_by_value(_: String, _: Arg) {} 73 | 74 | /// Probes usually contain standard path types, such as `u8` or `std::net::IpAddr`. However, 75 | /// they may also contain slices, arrays, tuples, and references. In these cases, as in the 76 | /// case of any non-native D type, the value will be JSON serialized when sending to DTrace. 77 | fn arg_as_tuple(_: (u8, &[i32])) {} 78 | 79 | /// Some types aren't JSON serializable. These will not break the program, but an error message 80 | /// will be seen in DTrace. 81 | fn not_json_serializable(_: crate::Whoops) {} 82 | 83 | /// Constant pointers to integer types are also supported 84 | fn work_with_pointer(_buffer: *const u8, _: u64) {} 85 | } 86 | 87 | fn main() { 88 | usdt::register_probes().unwrap(); 89 | let mut arg = Arg { 90 | x: 0, 91 | buffer: vec![1; 12], 92 | }; 93 | let buffer = [2; 4]; 94 | loop { 95 | test::start_work!(|| arg.x); 96 | std::thread::sleep(std::time::Duration::from_secs(1)); 97 | arg.x = arg.x.wrapping_add(1); 98 | test::stop_work!(|| { (format!("the probe has fired {}", arg.x), &arg) }); 99 | test::stop_work_by_value!(|| { 100 | let new_arg = Arg { 101 | x: arg.x, 102 | buffer: vec![arg.x.into()], 103 | }; 104 | (format!("the probe has fired {}", arg.x), new_arg) 105 | }); 106 | test::arg_as_tuple!(|| (arg.x, &arg.buffer[..])); 107 | test::not_json_serializable!(|| Whoops::NoBueno(0)); 108 | test::work_with_pointer!(|| (buffer.as_ptr(), buffer.len() as u64)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /probe-test-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "probe-test-build" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../usdt", default-features = false } 9 | 10 | [build-dependencies] 11 | usdt = { path = "../usdt" } 12 | -------------------------------------------------------------------------------- /probe-test-build/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use usdt::Builder; 16 | 17 | fn main() { 18 | println!("cargo:rerun-if-changed=build.rs"); 19 | 20 | println!("cargo:rerun-if-changed=test.d"); 21 | Builder::new("test.d").build().unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /probe-test-build/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /probe-test-build/src/main.rs: -------------------------------------------------------------------------------- 1 | //! An example using the `usdt` crate, generating the probes via a build script. 2 | 3 | // Copyright 2022 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use std::thread::sleep; 18 | use std::time::Duration; 19 | 20 | use usdt::register_probes; 21 | 22 | // Include the Rust implementation generated by the build script. 23 | include!(concat!(env!("OUT_DIR"), "/test.rs")); 24 | 25 | fn main() { 26 | let duration = Duration::from_secs(1); 27 | let mut counter: u8 = 0; 28 | 29 | // NOTE: One _must_ call this function in order to actually register the probes with DTrace. 30 | // Without this, it won't be possible to list, enable, or see the probes via `dtrace(1)`. 31 | register_probes().unwrap(); 32 | 33 | loop { 34 | // Call the "start_work" probe which accepts a u8. 35 | test::start_work!(|| (counter)); 36 | 37 | // Do some work. 38 | sleep(duration); 39 | 40 | // Call the "stop_work" probe, which accepts a string, u8, and string. 41 | test::stop_work!(|| ( 42 | format!("the probe has fired {}", counter), 43 | counter, 44 | format!("{:x}", counter) 45 | )); 46 | 47 | counter = counter.wrapping_add(1); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /probe-test-build/test.d: -------------------------------------------------------------------------------- 1 | provider test { 2 | probe start_work(uint8_t); 3 | probe stop_work(char*, uint8_t, char*); 4 | }; 5 | -------------------------------------------------------------------------------- /probe-test-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "probe-test-macro" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../usdt", default-features = false } 9 | -------------------------------------------------------------------------------- /probe-test-macro/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /probe-test-macro/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /probe-test-macro/src/main.rs: -------------------------------------------------------------------------------- 1 | //! An example using the `usdt` crate, generating the probes via a procedural macro 2 | 3 | // Copyright 2022 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use std::thread::sleep; 18 | use std::time::Duration; 19 | 20 | // Import the `dtrace_provider` procedural macro, which generates Rust code to call the probes 21 | // defined in the given provider file. 22 | use usdt::{dtrace_provider, register_probes}; 23 | 24 | // Call the macro, which generates a Rust macro for each probe in the provider. 25 | dtrace_provider!("test.d"); 26 | 27 | fn main() { 28 | let duration = Duration::from_secs(1); 29 | let mut counter: u8 = 0; 30 | 31 | // NOTE: One _must_ call this function in order to actually register the probes with DTrace. 32 | // Without this, it won't be possible to list, enable, or see the probes via `dtrace(1)`. 33 | register_probes().unwrap(); 34 | 35 | loop { 36 | // Call the "start_work" probe which accepts a u8. 37 | test::start_work!(|| (counter)); 38 | 39 | // Do some work. 40 | sleep(duration); 41 | 42 | // Call the "stop-work" probe, which accepts a string, u8, and string. 43 | test::stop_work!(|| ( 44 | format!("the probe has fired {}", counter), 45 | counter, 46 | format!("{:x}", counter) 47 | )); 48 | 49 | counter = counter.wrapping_add(1); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /probe-test-macro/test.d: -------------------------------------------------------------------------------- 1 | provider test { 2 | probe start_work(uint8_t); 3 | probe stop_work(char*, uint8_t, char*); 4 | }; 5 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # The Rust version is pinned because the repo has a number of trybuild tests, 3 | # and rustc's human-readable output is unstable across Rust versions. This 4 | # doesn't have to be the MSRV. 5 | # 6 | # NOTE: This toolchain is also specified within .github/workflows/rust.yml 7 | # If you update it here, update that file too. 8 | channel = "1.85.0" 9 | profile = "default" 10 | -------------------------------------------------------------------------------- /tests/argument-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "argument-types" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | serde = "1" 10 | -------------------------------------------------------------------------------- /tests/argument-types/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/argument-types/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/argument-types/src/main.rs: -------------------------------------------------------------------------------- 1 | //! An example and compile-test showing the various ways in which probe arguments may be specified, 2 | //! both in the parameter list and when passing values in the probe argument closure. 3 | 4 | // Copyright 2022 Oxide Computer Company 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | use serde::Serialize; 19 | 20 | /// Most struct or tuple types implementing serde::Serialize may be used in probes. 21 | #[derive(Default, Clone, Serialize)] 22 | struct Arg { 23 | x: Vec, 24 | } 25 | 26 | /// Types with references are not supported. 27 | #[derive(Serialize)] 28 | #[allow(dead_code)] 29 | struct NotSupported<'a> { 30 | x: &'a [i32], 31 | } 32 | 33 | #[usdt::provider] 34 | mod refs { 35 | /// Simple types such as integers may be taken by value ... 36 | fn u8_as_value(_: u8) {} 37 | 38 | /// ... or by reference 39 | fn u8_as_reference(_: &u8) {} 40 | 41 | /// Same with strings 42 | fn string_as_value(_: String) {} 43 | fn string_as_reference(_: &String) {} 44 | 45 | /// Slices are supported 46 | fn slice(_: &[u8]) {} 47 | 48 | /// As are arrays. 49 | fn array(_: [u8; 4]) {} 50 | 51 | /// And tuples. 52 | fn tuple(_: (u8, &[u8])) {} 53 | 54 | /// Tuples cannot be passed by reference, so this won't work. This would require naming the 55 | /// lifetime of the inner shared slice, which isn't currently supported. 56 | // fn tuple_by_reference(_: &(u8, &[u8])) {} 57 | 58 | /// Serializable types may also be taken by value or reference. 59 | fn serializable_as_value(_: crate::Arg) {} 60 | fn serializable_as_reference(_: &crate::Arg) {} 61 | } 62 | 63 | fn main() { 64 | usdt::register_probes().unwrap(); 65 | 66 | // Probe macros internally take a _reference_ to the data whenever possible. This means probes 67 | // that accept a type by value... 68 | refs::u8_as_value!(|| 0); 69 | 70 | // ... may also take that type by reference. 71 | refs::u8_as_value!(|| &0); 72 | 73 | // And vice-versa: a probe accepting a parameter by reference may take it by value as well. 74 | refs::u8_as_reference!(|| 0); 75 | refs::u8_as_reference!(|| &0); 76 | 77 | // This is true for string types as well. Probes accepting a string type may be called with 78 | // anything that implements `AsRef`, which includes `&str`, owned `String`s, and 79 | // `&String` as well. 80 | refs::string_as_value!(|| "&'static str"); 81 | refs::string_as_value!(|| String::from("owned")); 82 | refs::string_as_reference!(|| "&'static str"); 83 | refs::string_as_reference!(|| String::from("owned")); 84 | 85 | // Vectors are supported as well. In this case, the probe argument behaves the way it might in 86 | // a "normal" function -- with a signature like `fn foo(_: Vec)`, one can pass a `Vec`. 87 | // (In this case a reference would also work, i.e., `&Vec`.) However, with a _slice_ as the 88 | // argument, `fn foo(_: &[T])`, one may pass anything that implements `AsRef<[T]>`, which 89 | // includes slices and `Vec`. 90 | let x = vec![0, 1, 2]; 91 | 92 | // Call with an actual slice ... 93 | refs::slice!(|| &x[..]); 94 | 95 | // .. Or the vector itself, just like any function `fn(&[T])`. 96 | refs::slice!(|| &x); 97 | 98 | // Arrays may also be passed to something expecting a slice. 99 | let arr: [u8; 4] = [0, 1, 2, 3]; 100 | refs::slice!(|| &arr[..2]); 101 | refs::array!(|| arr); 102 | refs::array!(|| &arr); 103 | 104 | // Tuples may be passed in by value. 105 | refs::tuple!(|| (0, &x[..])); 106 | 107 | // Serializable types may be passed by value or reference, to a probe expecting either a value 108 | // or a reference. Note, however, that the normal lifetime rules apply: you can't return a 109 | // reference from an argument closure to data constructed _inside_ the closure. I.e., this will 110 | // _not_ work: 111 | // 112 | // ``` 113 | // refs::serializable_as_reference!(|| &crate::Arg::default()); 114 | // ``` 115 | let arg = crate::Arg::default(); 116 | refs::serializable_as_value!(crate::Arg::default); 117 | refs::serializable_as_value!(|| &arg); 118 | refs::serializable_as_reference!(crate::Arg::default); 119 | refs::serializable_as_reference!(|| &arg); 120 | 121 | // It's also possible to capture and return local variables by value in the probe argument 122 | // closure. This behaves just like any other captured variable, and so `arg` cannot be used 123 | // again, unless it implements Copy. 124 | refs::serializable_as_reference!(|| arg); 125 | 126 | // This line will fail to compile, indicating that `arg` is borrowed after it's been moved. 127 | // println!("{:#?}", arg.x); 128 | } 129 | -------------------------------------------------------------------------------- /tests/common-build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/compile-errors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compile-errors" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | serde = "*" 10 | serde_json = "*" 11 | 12 | [dev-dependencies] 13 | trybuild = "1.0.105" 14 | -------------------------------------------------------------------------------- /tests/compile-errors/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/compile-errors/providers/type-mismatch.d: -------------------------------------------------------------------------------- 1 | provider mismatch { 2 | probe bad(uint8_t); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/compile-errors/providers/unsupported-type.d: -------------------------------------------------------------------------------- 1 | provider unsupported { 2 | probe bad(float); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/compile-errors/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/compile-errors/src/different-serializable-type.rs: -------------------------------------------------------------------------------- 1 | //! Test that passing a type that is serializable, but not the same concrete type as the probe 2 | //! signature, fails compilation. 3 | 4 | // Copyright 2022 Oxide Computer Company 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #[derive(serde::Serialize)] 19 | struct Expected { 20 | x: u8, 21 | } 22 | 23 | #[derive(serde::Serialize)] 24 | struct Different { 25 | x: u8, 26 | } 27 | 28 | #[usdt::provider] 29 | mod my_provider { 30 | use crate::Expected; 31 | fn my_probe(_: Expected) {} 32 | } 33 | 34 | fn main() { 35 | my_provider::my_probe!(|| Different { x: 0 }); 36 | } 37 | -------------------------------------------------------------------------------- /tests/compile-errors/src/different-serializable-type.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `Different: Borrow` is not satisfied 2 | --> src/different-serializable-type.rs:28:1 3 | | 4 | 28 | #[usdt::provider] 5 | | ^^^^^^^^^^^^^^^^^ the trait `Borrow` is not implemented for `Different` 6 | ... 7 | 35 | my_provider::my_probe!(|| Different { x: 0 }); 8 | | --------------------------------------------- in this macro invocation 9 | | 10 | note: required by a bound in `__usdt_private_my_provider_my_probe_type_check` 11 | --> src/different-serializable-type.rs:28:1 12 | | 13 | 28 | #[usdt::provider] 14 | | ^^^^^^^^^^^^^^^^^ required by this bound in `__usdt_private_my_provider_my_probe_type_check` 15 | ... 16 | 35 | my_provider::my_probe!(|| Different { x: 0 }); 17 | | --------------------------------------------- in this macro invocation 18 | = note: this error originates in the macro `my_provider::my_probe` (in Nightly builds, run with -Z macro-backtrace for more info) 19 | -------------------------------------------------------------------------------- /tests/compile-errors/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![deny(warnings)] 16 | 17 | // These tests are only valid when running on the Rust version specified 18 | // in rust-toolchain.toml 19 | #[cfg(test)] 20 | mod tests { 21 | #[test] 22 | fn test_compile_errors() { 23 | let t = trybuild::TestCases::new(); 24 | t.compile_fail("src/type-mismatch.rs"); 25 | t.compile_fail("src/unsupported-type.rs"); 26 | t.compile_fail("src/no-closure.rs"); 27 | t.compile_fail("src/no-provider-file.rs"); 28 | t.compile_fail("src/zero-arg-probe-type-check.rs"); 29 | t.compile_fail("src/different-serializable-type.rs"); 30 | t.compile_fail("src/relative-import.rs"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/compile-errors/src/no-closure.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | usdt::dtrace_provider!("../../../tests/compile-errors/providers/type-mismatch.d"); 16 | 17 | fn main() { 18 | let arg: u8 = 0; 19 | mismatch::bad!(arg); 20 | } 21 | -------------------------------------------------------------------------------- /tests/compile-errors/src/no-closure.stderr: -------------------------------------------------------------------------------- 1 | error: proc macro panicked 2 | --> src/no-closure.rs:15:1 3 | | 4 | 15 | usdt::dtrace_provider!("../../../tests/compile-errors/providers/type-mismatch.d"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: Could not read D source file "../../../tests/compile-errors/providers/type-mismatch.d" in "$WORKSPACE/target/tests/trybuild/compile-errors" 8 | 9 | error[E0433]: failed to resolve: use of undeclared crate or module `mismatch` 10 | --> src/no-closure.rs:19:5 11 | | 12 | 19 | mismatch::bad!(arg); 13 | | ^^^^^^^^ use of undeclared crate or module `mismatch` 14 | -------------------------------------------------------------------------------- /tests/compile-errors/src/no-provider-file.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | usdt::dtrace_provider!("non-existent.d"); 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/compile-errors/src/no-provider-file.stderr: -------------------------------------------------------------------------------- 1 | error: proc macro panicked 2 | --> src/no-provider-file.rs:15:1 3 | | 4 | 15 | usdt::dtrace_provider!("non-existent.d"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: Could not read D source file "non-existent.d" in "$WORKSPACE/target/tests/trybuild/compile-errors" 8 | -------------------------------------------------------------------------------- /tests/compile-errors/src/relative-import.rs: -------------------------------------------------------------------------------- 1 | //! Test that we can't name types into the provider module using a relative import 2 | 3 | // Copyright 2021 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | #[derive(serde::Serialize)] 18 | struct Expected { 19 | x: u8, 20 | } 21 | 22 | #[usdt::provider] 23 | mod my_provider { 24 | use super::Expected; 25 | fn my_probe(_: Expected) {} 26 | } 27 | 28 | fn main() { 29 | my_provider::my_probe!(|| Different { x: 0 }); 30 | } 31 | -------------------------------------------------------------------------------- /tests/compile-errors/src/relative-import.stderr: -------------------------------------------------------------------------------- 1 | error: Use-statements in USDT macros cannot contain relative imports (`super`), because the generated macros may be called from anywhere in a crate. Consider using `crate` instead. 2 | --> $DIR/relative-import.rs:24:9 3 | | 4 | 24 | use super::Expected; 5 | | ^^^^^ 6 | 7 | error[E0433]: failed to resolve: use of undeclared crate or module `my_provider` 8 | --> $DIR/relative-import.rs:29:5 9 | | 10 | 29 | my_provider::my_probe!(|| Different { x: 0 }); 11 | | ^^^^^^^^^^^ use of undeclared crate or module `my_provider` 12 | -------------------------------------------------------------------------------- /tests/compile-errors/src/type-mismatch.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | usdt::dtrace_provider!("../../../tests/compile-errors/providers/type-mismatch.d"); 16 | 17 | fn main() { 18 | let bad: f32 = 0.0; 19 | mismatch::bad!(|| (bad)); 20 | } 21 | -------------------------------------------------------------------------------- /tests/compile-errors/src/type-mismatch.stderr: -------------------------------------------------------------------------------- 1 | error: proc macro panicked 2 | --> src/type-mismatch.rs:15:1 3 | | 4 | 15 | usdt::dtrace_provider!("../../../tests/compile-errors/providers/type-mismatch.d"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: Could not read D source file "../../../tests/compile-errors/providers/type-mismatch.d" in "$WORKSPACE/target/tests/trybuild/compile-errors" 8 | 9 | error[E0433]: failed to resolve: use of undeclared crate or module `mismatch` 10 | --> src/type-mismatch.rs:19:5 11 | | 12 | 19 | mismatch::bad!(|| (bad)); 13 | | ^^^^^^^^ use of undeclared crate or module `mismatch` 14 | -------------------------------------------------------------------------------- /tests/compile-errors/src/unsupported-type.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | usdt::dtrace_provider!("../../../tests/compile-errors/providers/unsupported-type.d"); 16 | 17 | fn main() { 18 | let bad: u8 = 0; 19 | unsupported::bad!(|| (bad)); 20 | } 21 | -------------------------------------------------------------------------------- /tests/compile-errors/src/unsupported-type.stderr: -------------------------------------------------------------------------------- 1 | error: proc macro panicked 2 | --> src/unsupported-type.rs:15:1 3 | | 4 | 15 | usdt::dtrace_provider!("../../../tests/compile-errors/providers/unsupported-type.d"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = help: message: Could not read D source file "../../../tests/compile-errors/providers/unsupported-type.d" in "$WORKSPACE/target/tests/trybuild/compile-errors" 8 | 9 | error[E0433]: failed to resolve: use of undeclared crate or module `unsupported` 10 | --> src/unsupported-type.rs:19:5 11 | | 12 | 19 | unsupported::bad!(|| (bad)); 13 | | ^^^^^^^^^^^ use of undeclared crate or module `unsupported` 14 | -------------------------------------------------------------------------------- /tests/compile-errors/src/zero-arg-probe-type-check.rs: -------------------------------------------------------------------------------- 1 | //! Test that a zero-argument probe is correctly type-checked 2 | 3 | // Copyright 2022 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | #[usdt::provider] 18 | mod my_provider { 19 | fn my_probe() {} 20 | } 21 | 22 | fn main() { 23 | my_provider::my_probe!(|| "This should fail"); 24 | } 25 | -------------------------------------------------------------------------------- /tests/compile-errors/src/zero-arg-probe-type-check.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> $DIR/zero-arg-probe-type-check.rs:17:1 3 | | 4 | 17 | #[usdt::provider] 5 | | ^^^^^^^^^^^^^^^^^ 6 | | | 7 | | expected `()`, found `&str` 8 | | expected due to this 9 | ... 10 | 23 | my_provider::my_probe!(|| "This should fail"); 11 | | --------------------------------------------- in this macro invocation 12 | | 13 | = note: this error originates in the macro `my_provider::my_probe` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/does-it-work/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "does-it-work" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | usdt-tests-common = { path = "../../usdt-tests-common" } 10 | 11 | [build-dependencies] 12 | usdt = { path = "../../usdt" } 13 | -------------------------------------------------------------------------------- /tests/does-it-work/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use usdt::Builder; 16 | 17 | fn main() { 18 | println!("cargo:rerun-if-changed=build.rs"); 19 | 20 | println!("cargo:rerun-if-changed=test.d"); 21 | Builder::new("test.d").build().unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/does-it-work/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/does-it-work/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Small example which tests that this whole thing works. Specifically, this constructs and 2 | //! registers a single probe with arguments, and then verifies that this probe is visible to the 3 | //! `dtrace(1)` command-line tool. 4 | 5 | // Copyright 2024 Oxide Computer Company 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | #![allow(non_snake_case)] 20 | 21 | use usdt::register_probes; 22 | 23 | include!(concat!(env!("OUT_DIR"), "/test.rs")); 24 | 25 | fn main() { 26 | does__it::work!(|| (0, "something")); 27 | } 28 | 29 | // Dissuade the compiler from inlining this, which would ruin the test for `probefunc`. 30 | #[inline(never)] 31 | #[allow(dead_code)] 32 | fn run_test(rx: std::sync::mpsc::Receiver<()>) { 33 | register_probes().unwrap(); 34 | does__it::work!(|| (0, "something")); 35 | let _ = rx.recv(); 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::run_test; 41 | use std::process::Stdio; 42 | use std::sync::mpsc::channel; 43 | use std::thread; 44 | use usdt_tests_common::root_command; 45 | 46 | #[test] 47 | fn test_does_it_work() { 48 | let (send, recv) = channel(); 49 | let thr = thread::spawn(move || run_test(recv)); 50 | let dtrace = std::process::Command::new(root_command()) 51 | .arg("dtrace") 52 | .arg("-l") 53 | .arg("-v") 54 | .arg("-n") 55 | .arg("does__it*:::") 56 | .stdin(Stdio::piped()) 57 | .stdout(Stdio::piped()) 58 | .spawn() 59 | .expect("Could not start DTrace"); 60 | let output = dtrace 61 | .wait_with_output() 62 | .expect("Failed to read DTrace stdout"); 63 | 64 | // Kill the test thread 65 | let _ = send.send(()); 66 | 67 | // Collect the actual output 68 | let output = String::from_utf8_lossy(&output.stdout); 69 | println!("{}", output); 70 | 71 | // Check the line giving the full description of the probe 72 | let mut lines = output.lines().skip_while(|line| !line.contains("does__it")); 73 | let line = lines 74 | .next() 75 | .expect("Expected a line containing the provider name"); 76 | let mut parts = line.split_whitespace(); 77 | let _ = parts.next().expect("Expected an ID"); 78 | 79 | let provider = parts.next().expect("Expected a provider name"); 80 | assert!( 81 | provider.starts_with("does__it"), 82 | "Provider name appears incorrect: {}", 83 | provider 84 | ); 85 | 86 | let module = parts.next().expect("Expected a module name"); 87 | assert!( 88 | module.starts_with("does_it_work"), 89 | "Module name appears incorrect: {}", 90 | module 91 | ); 92 | 93 | let mangled_function = parts.next().expect("Expected a mangled function name"); 94 | assert!( 95 | mangled_function.contains("does_it_work8run_test"), 96 | "Mangled function name appears incorrect: {}", 97 | mangled_function 98 | ); 99 | 100 | // Verify the argument types 101 | let mut lines = lines.skip_while(|line| !line.contains("args[0]")); 102 | let first = lines 103 | .next() 104 | .expect("Expected a line with the argument description") 105 | .trim(); 106 | assert_eq!( 107 | first, "args[0]: uint8_t", 108 | "Argument is incorrect: {}", 109 | first 110 | ); 111 | let second = lines 112 | .next() 113 | .expect("Expected a line with the argument description") 114 | .trim(); 115 | assert_eq!( 116 | second, "args[1]: char *", 117 | "Argument is incorrect: {}", 118 | second 119 | ); 120 | 121 | thr.join().expect("Failed to join test runner thread"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/does-it-work/test.d: -------------------------------------------------------------------------------- 1 | provider does__it { 2 | probe work(uint8_t, char*); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/empty/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "empty" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies.usdt] 8 | path = "../../usdt" 9 | default-features = false 10 | 11 | [build-dependencies.usdt] 12 | path = "../../usdt" 13 | default-features = false 14 | -------------------------------------------------------------------------------- /tests/empty/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use usdt::Builder; 16 | 17 | fn main() { 18 | println!("cargo:rerun-if-changed=build.rs"); 19 | 20 | println!("cargo:rerun-if-changed=provider.d"); 21 | Builder::new("provider.d") 22 | .build() 23 | .expect("Failed to build provider"); 24 | } 25 | -------------------------------------------------------------------------------- /tests/empty/provider.d: -------------------------------------------------------------------------------- 1 | provider stuff { 2 | probe start_work(uint8_t); 3 | probe stop_work(char*, uint8_t); 4 | probe noargs(); 5 | probe unused(); 6 | }; 7 | -------------------------------------------------------------------------------- /tests/empty/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/empty/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![deny(warnings)] 16 | 17 | use usdt::register_probes; 18 | 19 | include!(concat!(env!("OUT_DIR"), "/provider.rs")); 20 | 21 | fn main() { 22 | register_probes().unwrap(); 23 | 24 | let counter: u8 = 0; 25 | stuff::start_work!(|| (counter)); 26 | stuff::stop_work!(|| ("the probe has fired", counter)); 27 | stuff::noargs!(|| ()); 28 | stuff::noargs!(); 29 | } 30 | 31 | #[cfg(test)] 32 | mod test { 33 | // We just want to make sure that main builds and runs. 34 | #[test] 35 | fn test_main() { 36 | super::main(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/fake-cmd/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fake-cmd" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | fake-lib = { path = "../fake-lib" } 9 | -------------------------------------------------------------------------------- /tests/fake-cmd/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/fake-cmd/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/fake-cmd/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![deny(warnings)] 16 | 17 | fn main() { 18 | fake_lib::register_probes().unwrap(); 19 | fake_lib::dummy(); 20 | } 21 | 22 | #[cfg(test)] 23 | mod test { 24 | // We just want to make sure that main builds and runs. 25 | #[test] 26 | fn test_main() { 27 | super::main(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/fake-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fake-lib" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | 10 | [build-dependencies] 11 | usdt = { path = "../../usdt" } 12 | -------------------------------------------------------------------------------- /tests/fake-lib/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use usdt::Builder; 16 | 17 | fn main() { 18 | println!("cargo:rerun-if-changed=build.rs"); 19 | 20 | println!("cargo:rerun-if-changed=test.d"); 21 | Builder::new("test.d").build().unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/fake-lib/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/fake-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![deny(warnings)] 16 | 17 | pub use usdt::register_probes; 18 | 19 | include!(concat!(env!("OUT_DIR"), "/test.rs")); 20 | 21 | pub fn dummy() { 22 | test::here__i__am!(); 23 | test::here__i__am!(); 24 | } 25 | -------------------------------------------------------------------------------- /tests/fake-lib/test.d: -------------------------------------------------------------------------------- 1 | provider test { 2 | probe here__i__am(); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/modules/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "modules" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | serde = "1" 10 | -------------------------------------------------------------------------------- /tests/modules/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/modules/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/modules/src/inner.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #[usdt::provider(provider = "modules")] 16 | pub mod probes { 17 | fn am_i_visible() {} 18 | } 19 | -------------------------------------------------------------------------------- /tests/modules/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | mod inner; 16 | 17 | fn main() { 18 | usdt::register_probes().expect("Could not register probes"); 19 | // Verify that we can call the probe from its full path. 20 | inner::probes::am_i_visible!(|| ()); 21 | 22 | // This is an overly-cautious test for macOS. We define extern symbols inside each expanded 23 | // probe macro, with a link-name for a symbol that the macOS linker will generate for us. This 24 | // checks that there is no issue defining these locally-scoped extern symbols multiple times. 25 | inner::probes::am_i_visible!(|| ()); 26 | } 27 | -------------------------------------------------------------------------------- /tests/rename-builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rename-builder" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | serde = "1" 10 | 11 | [build-dependencies] 12 | usdt = { path = "../../usdt" } 13 | -------------------------------------------------------------------------------- /tests/rename-builder/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use usdt::Builder; 16 | 17 | fn main() { 18 | println!("cargo:rerun-if-changed=build.rs"); 19 | 20 | println!("cargo:rerun-if-changed=test.d"); 21 | Builder::new("test.d").module("still_test").build().unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/rename-builder/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/rename-builder/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Test verifying that renaming the provider/probes in various ways works when using a build 2 | //! script. 3 | 4 | // Copyright 2022 Oxide Computer Company 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | include!(concat!(env!("OUT_DIR"), "/test.rs")); 19 | 20 | fn main() { 21 | usdt::register_probes().unwrap(); 22 | 23 | // Renamed the module that the probes are generated to `still_test`. So naming them as 24 | // `test::start_work` will fail. 25 | still_test::start_work!(|| 0); 26 | } 27 | -------------------------------------------------------------------------------- /tests/rename-builder/test.d: -------------------------------------------------------------------------------- 1 | provider test { 2 | probe start_work(uint8_t); 3 | probe stop_work(char*, uint8_t, char*); 4 | }; 5 | -------------------------------------------------------------------------------- /tests/rename/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rename" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | serde = "1" 10 | -------------------------------------------------------------------------------- /tests/rename/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/rename/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/rename/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Integration test verifying that provider modules are renamed correctly. 2 | 3 | // Copyright 2022 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | #[usdt::provider(provider = "something", probe_format = "probe_{probe}")] 18 | mod probes { 19 | fn something() {} 20 | } 21 | 22 | fn main() { 23 | usdt::register_probes().unwrap(); 24 | probes::probe_something!(|| ()); 25 | } 26 | -------------------------------------------------------------------------------- /tests/test-json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-json" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | usdt-tests-common = { path = "../../usdt-tests-common" } 10 | serde = "*" 11 | serde_json = "*" 12 | 13 | [dev-dependencies] 14 | tokio = { version = "1.45.1", features = [ "full" ] } 15 | -------------------------------------------------------------------------------- /tests/test-json/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/test-json/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/test-json/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Integration test verifying JSON output, including when serialization fails. 2 | 3 | // Copyright 2024 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use serde::{Serialize, Serializer}; 18 | 19 | // Expected error message from serialization failure 20 | const SERIALIZATION_ERROR: &str = "nonono"; 21 | 22 | #[derive(Debug, Serialize)] 23 | pub struct ProbeArg { 24 | value: u8, 25 | buffer: Vec, 26 | } 27 | 28 | impl Default for ProbeArg { 29 | fn default() -> Self { 30 | ProbeArg { 31 | value: 1, 32 | buffer: vec![1, 2, 3], 33 | } 34 | } 35 | } 36 | 37 | // A type that intentionally fails serialization 38 | #[derive(Debug, Default)] 39 | pub struct NotJsonSerializable { 40 | _x: u8, 41 | } 42 | 43 | impl Serialize for NotJsonSerializable { 44 | fn serialize(&self, _: S) -> Result { 45 | Err(serde::ser::Error::custom(SERIALIZATION_ERROR)) 46 | } 47 | } 48 | 49 | #[usdt::provider] 50 | mod test_json { 51 | use crate::{NotJsonSerializable, ProbeArg as GoodArg}; 52 | fn good(_: &GoodArg) {} 53 | fn bad(_: &NotJsonSerializable) {} 54 | } 55 | 56 | fn main() { 57 | usdt::register_probes().unwrap(); 58 | let arg = ProbeArg::default(); 59 | test_json::good!(|| &arg); 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | use serde_json::Value; 66 | use std::process::Stdio; 67 | use std::time::Duration; 68 | use tokio::io::AsyncReadExt; 69 | use tokio::process::Child; 70 | use tokio::process::Command; 71 | use tokio::sync::mpsc::channel; 72 | use tokio::sync::mpsc::Receiver; 73 | use tokio::sync::mpsc::Sender; 74 | use tokio::time::Instant; 75 | use usdt_tests_common::root_command; 76 | 77 | // Maximum duration to wait for DTrace, controlling total test duration 78 | const MAX_WAIT: Duration = Duration::from_secs(30); 79 | 80 | // A sentinel printed by DTrace, so we know when it starts up successfully. 81 | const BEGIN_SENTINEL: &str = "BEGIN"; 82 | 83 | // Fire the test probes in sequence, when a notification is received on the channel. 84 | async fn fire_test_probes(mut recv: Receiver<()>) { 85 | usdt::register_probes().unwrap(); 86 | 87 | // Wait for notification from the main thread 88 | println!("Test runner waiting for first notification"); 89 | recv.recv().await.unwrap(); 90 | println!("Test runner firing first probe"); 91 | 92 | // Fire the good probe until the main thread signals us to continue. 93 | let data = ProbeArg::default(); 94 | test_json::good!(|| &data); 95 | println!("Test runner fired first probe"); 96 | println!("Test runner awaiting notification to continue"); 97 | recv.recv().await.unwrap(); 98 | println!("Test runner received notification to continue"); 99 | 100 | // Fire the bad probe. 101 | println!("Test runner firing second probe"); 102 | let data = NotJsonSerializable::default(); 103 | test_json::bad!(|| &data); 104 | println!("Test runner fired second probe"); 105 | } 106 | 107 | // Run DTrace as a subprocess, waiting for the JSON output of the provided probe. 108 | async fn run_dtrace_and_return_json(tx: &Sender<()>, probe_name: &str) -> Value { 109 | // Start the DTrace subprocess, and don't exit if the probe doesn't exist. 110 | let mut dtrace = Command::new(root_command()) 111 | .arg("dtrace") 112 | .arg("-Z") 113 | .arg("-q") 114 | .arg("-n") 115 | // The test probe we're interested in listening for. 116 | .arg(format!( 117 | "test_json{}:::{} {{ printf(\"%s\", copyinstr(arg0)); exit(0); }}", 118 | std::process::id(), 119 | probe_name 120 | )) 121 | .arg("-n") 122 | // An output printed by DTrace when it starts, to coordinate with the test thread 123 | // firing the probe itself. 124 | .arg(format!("BEGIN {{ trace(\"{}\"); }}", BEGIN_SENTINEL)) 125 | .stdout(Stdio::piped()) 126 | .stderr(Stdio::piped()) 127 | .spawn() 128 | .expect("Failed to spawn DTrace subprocess"); 129 | 130 | // Wait for DTrace to correctly start up before notifying the test thread to start 131 | // firing probes. 132 | let now = Instant::now(); 133 | wait_for_begin_sentinel(&mut dtrace, &now).await; 134 | 135 | // Instruct the task firing probes to continue. 136 | tx.send(()).await.unwrap(); 137 | 138 | // Wait for the process to finish, up to a pretty generous limit. 139 | let output = tokio::time::timeout_at(now + MAX_WAIT, dtrace.wait_with_output()) 140 | .await 141 | .expect(&format!("DTrace did not complete within {:?}", MAX_WAIT)) 142 | .expect("Failed to wait for DTrace subprocess"); 143 | assert!( 144 | output.status.success(), 145 | "DTrace process failed:\n{:?}", 146 | String::from_utf8_lossy(&output.stderr), 147 | ); 148 | let stdout = std::str::from_utf8(&output.stdout).expect("Non-UTF8 stdout"); 149 | println!("DTrace output\n{}\n", stdout); 150 | let json: Value = serde_json::from_str(&stdout).unwrap(); 151 | json 152 | } 153 | 154 | // Check DTrace subprocess stdout for the begin sentinel, telling us the program has spawned 155 | // successfully. 156 | async fn wait_for_begin_sentinel(dtrace: &mut Child, now: &Instant) { 157 | let mut output = String::new(); 158 | let stdout = dtrace.stdout.as_mut().expect("Expected piped stdout"); 159 | let max_time = *now + MAX_WAIT; 160 | 161 | // Try to read data from stdout, up to the maximum wait time. This may take multiple reads, 162 | // though it's pretty unlikely. 163 | while now.elapsed() < MAX_WAIT { 164 | let read_task = tokio::time::timeout_at(max_time, async { 165 | let mut bytes = vec![0; 128]; 166 | stdout.read(&mut bytes).await.map(|_| bytes) 167 | }); 168 | match read_task.await { 169 | Ok(read_result) => { 170 | let chunk = read_result.expect("Failed to read DTrace stdout"); 171 | output.push_str(std::str::from_utf8(&chunk).expect("Non-UTF8 stdout")); 172 | if output.contains(BEGIN_SENTINEL) { 173 | println!("DTrace started up successfully"); 174 | return; 175 | } 176 | } 177 | _ => {} 178 | } 179 | println!("DTrace not yet ready"); 180 | continue; 181 | } 182 | panic!("DTrace failed to startup within {:?}", MAX_WAIT); 183 | } 184 | 185 | #[tokio::test] 186 | async fn test_json_support() { 187 | let (tx, rx) = channel(4); 188 | let test_task = tokio::task::spawn(fire_test_probes(rx)); 189 | 190 | let json = run_dtrace_and_return_json(&tx, "good").await; 191 | assert!(json.get("ok").is_some()); 192 | assert!(json.get("err").is_none()); 193 | assert_eq!(json["ok"]["value"], Value::from(1)); 194 | assert_eq!(json["ok"]["buffer"], Value::from(vec![1, 2, 3])); 195 | 196 | // Tell the thread to continue with the bad probe 197 | let json = run_dtrace_and_return_json(&tx, "bad").await; 198 | assert!(json.get("ok").is_none()); 199 | assert!(json.get("err").is_some()); 200 | assert_eq!(json["err"], Value::from(SERIALIZATION_ERROR)); 201 | 202 | test_task.await.unwrap(); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /tests/test-unique-id/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-unique-id" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | 10 | [dev-dependencies] 11 | subprocess = "0.2" 12 | -------------------------------------------------------------------------------- /tests/test-unique-id/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/test-unique-id/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/test-unique-id/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Integration test for `usdt::UniqueId` 2 | 3 | // Copyright 2022 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | #[usdt::provider] 18 | mod with_ids { 19 | use usdt::UniqueId; 20 | fn start_work(_: &UniqueId) {} 21 | fn waypoint_from_thread(_: &UniqueId, message: &str) {} 22 | fn work_finished(_: &UniqueId, result: u64) {} 23 | } 24 | 25 | fn main() {} 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::with_ids; 30 | use std::thread; 31 | use std::time::Duration; 32 | use subprocess::Exec; 33 | use usdt::UniqueId; 34 | 35 | #[test] 36 | fn test_unique_ids() { 37 | usdt::register_probes().unwrap(); 38 | let id = UniqueId::new(); 39 | with_ids::start_work!(|| &id); 40 | let id2 = id.clone(); 41 | let thr = thread::spawn(move || { 42 | for _ in 0..10 { 43 | with_ids::waypoint_from_thread!(|| (&id2, "we're in a thread")); 44 | thread::sleep(Duration::from_millis(10)); 45 | } 46 | id2.as_u64() 47 | }); 48 | let result = thr.join().unwrap(); 49 | with_ids::work_finished!(|| (&id, result)); 50 | assert_eq!(result, id.as_u64()); 51 | 52 | // Actually verify that the same value is received by DTrace. 53 | let sudo = if cfg!(target_os = "illumos") { 54 | "pfexec" 55 | } else { 56 | "sudo" 57 | }; 58 | let mut dtrace = Exec::cmd(sudo) 59 | .arg("/usr/sbin/dtrace") 60 | .arg("-q") 61 | .arg("-n") 62 | .arg(r#"with_ids*:::waypoint_from_thread { printf("%d\n", arg0); exit(0); }"#) 63 | .stdin(subprocess::NullFile) 64 | .stderr(subprocess::Redirection::Pipe) 65 | .stdout(subprocess::Redirection::Pipe) 66 | .popen() 67 | .expect("Failed to run DTrace"); 68 | thread::sleep(Duration::from_millis(1000)); 69 | let id = UniqueId::new(); 70 | let id2 = id.clone(); 71 | let thr = thread::spawn(move || { 72 | with_ids::waypoint_from_thread!(|| (&id2, "we're in a thread")); 73 | }); 74 | thr.join().unwrap(); 75 | 76 | const TIMEOUT: Duration = Duration::from_secs(10); 77 | let mut comm = dtrace.communicate_start(None).limit_time(TIMEOUT); 78 | if dtrace 79 | .wait_timeout(TIMEOUT) 80 | .expect("DTrace command failed") 81 | .is_none() 82 | { 83 | std::process::Command::new(sudo) 84 | .arg("kill") 85 | .arg(format!("{}", dtrace.pid().unwrap())) 86 | .spawn() 87 | .expect("Failed to spawn kill") 88 | .wait() 89 | .expect("Failed to kill DTrace subprocess"); 90 | panic!("DTrace didn't exit within timeout of {:?}", TIMEOUT); 91 | } 92 | let (stdout, stderr) = comm.read_string().expect("Failed to read DTrace output"); 93 | let stdout = stdout.unwrap_or_else(|| String::from("")); 94 | let stderr = stderr.unwrap_or_else(|| String::from("")); 95 | let actual_id: u64 = stdout.trim().parse().unwrap_or_else(|_| { 96 | panic!( 97 | concat!( 98 | "Expected a u64\n", 99 | "stdout\n", 100 | "------\n", 101 | "{}\n", 102 | "stderr\n", 103 | "------\n", 104 | "{}" 105 | ), 106 | stdout, stderr 107 | ) 108 | }); 109 | 110 | assert_eq!(actual_id, id.as_u64()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/usize/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usize" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | serde = "*" 10 | serde_json = "*" 11 | 12 | [build-dependencies] 13 | usdt = { path = "../../usdt" } 14 | -------------------------------------------------------------------------------- /tests/usize/build.rs: -------------------------------------------------------------------------------- 1 | ../common-build.rs -------------------------------------------------------------------------------- /tests/usize/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/usize/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Integration test verifying that we correctly compile usize/isize probes. 2 | 3 | // Copyright 2023 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | #[usdt::provider] 18 | mod usize__test { 19 | fn emit_usize(_: usize) {} 20 | fn emit_isize(_: &isize) {} 21 | fn emit_u8(_: u8) {} 22 | } 23 | 24 | fn main() { 25 | usdt::register_probes().unwrap(); 26 | usize__test::emit_usize!(|| 1usize); 27 | usize__test::emit_isize!(|| &1isize); 28 | usize__test::emit_u8!(|| 1); 29 | } 30 | -------------------------------------------------------------------------------- /tests/zero-arg-probe/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zero-arg-probe" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | usdt = { path = "../../usdt" } 9 | 10 | [build-dependencies] 11 | usdt = { path = "../../usdt" } 12 | -------------------------------------------------------------------------------- /tests/zero-arg-probe/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use usdt::Builder; 16 | 17 | fn main() { 18 | println!("cargo:rerun-if-changed=build.rs"); 19 | 20 | println!("cargo:rerun-if-changed=test.d"); 21 | Builder::new("test.d").build().unwrap(); 22 | } 23 | -------------------------------------------------------------------------------- /tests/zero-arg-probe/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /tests/zero-arg-probe/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![deny(warnings)] 16 | 17 | use usdt::register_probes; 18 | 19 | include!(concat!(env!("OUT_DIR"), "/test.rs")); 20 | 21 | fn main() { 22 | register_probes().unwrap(); 23 | 24 | zero::here__i__am!(|| ()); 25 | zero::here__i__am!(); 26 | } 27 | 28 | #[cfg(test)] 29 | mod test { 30 | // We just want to make sure that main builds and runs. 31 | #[test] 32 | fn test_main() { 33 | super::main(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/zero-arg-probe/test.d: -------------------------------------------------------------------------------- 1 | provider zero { 2 | probe here__i__am(); 3 | }; 4 | -------------------------------------------------------------------------------- /usdt-attr-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usdt-attr-macro" 3 | version = "0.5.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Procedural macro for generating Rust macros for USDT probes" 7 | repository = "https://github.com/oxidecomputer/usdt.git" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | dtrace-parser = { path = "../dtrace-parser", version = "=0.2.0" } 14 | proc-macro2 = "1" 15 | serde_tokenstream = "0.2" 16 | syn = { version = "2", features = ["full"] } 17 | quote = "1" 18 | usdt-impl = { path = "../usdt-impl", default-features = false, version = "=0.5.0" } 19 | 20 | [dev-dependencies] 21 | rstest = "0.22.0" 22 | -------------------------------------------------------------------------------- /usdt-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usdt-impl" 3 | version = "0.5.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Main implementation crate for the USDT package" 7 | repository = "https://github.com/oxidecomputer/usdt.git" 8 | 9 | [dependencies] 10 | byteorder = "1" 11 | dtrace-parser = { path = "../dtrace-parser", version = "=0.2.0" } 12 | libc = "0.2" 13 | proc-macro2 = "1" 14 | quote = "1" 15 | serde = { version = "1", features = ["derive"] } 16 | serde_json = "1" 17 | syn = { version = "2", features = ["full", "extra-traits"] } 18 | thiserror = "2" 19 | thread-id = "4" 20 | 21 | [target.'cfg(target_os = "macos")'.dependencies] 22 | dof = { path = "../dof", optional = true, default-features = false, version = "=0.3.0" } 23 | 24 | [target.'cfg(not(target_os = "macos"))'.dependencies] 25 | dof = { path = "../dof", default-features = false, version = "=0.3.0" } 26 | 27 | [features] 28 | default = [] 29 | # The `des` feature enables `dof` and company to be able to deserialize special 30 | # sections emitted in the binary which describe the probes. Except on 31 | # platforms with linker integration for USDT probes (currently only MacOS), 32 | # that data is required in order to register the probes with the kernel. 33 | des = ["dof", "dof/des"] 34 | -------------------------------------------------------------------------------- /usdt-impl/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /usdt-impl/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use std::env; 17 | 18 | #[derive(Copy, Clone)] 19 | enum Backend { 20 | // Standard (read: illumos) probe registration 21 | Standard, 22 | // MacOS linker-aware probe registration 23 | Linker, 24 | // Provide probe macros, but probes are no-ops (dtrace-less OSes) 25 | NoOp, 26 | } 27 | 28 | fn main() { 29 | println!("cargo:rerun-if-changed=build.rs"); 30 | println!("cargo:rustc-check-cfg=cfg(usdt_backend_noop)"); 31 | println!("cargo:rustc-check-cfg=cfg(usdt_backend_linker)"); 32 | println!("cargo:rustc-check-cfg=cfg(usdt_backend_standard)"); 33 | 34 | let backend = match env::var("CARGO_CFG_TARGET_OS").ok().as_deref() { 35 | Some("macos") => Backend::Linker, 36 | Some("illumos") | Some("solaris") => Backend::Standard, 37 | _ => Backend::NoOp, 38 | }; 39 | 40 | match backend { 41 | Backend::NoOp => { 42 | println!("cargo:rustc-cfg=usdt_backend_noop"); 43 | } 44 | Backend::Linker => { 45 | println!("cargo:rustc-cfg=usdt_backend_linker"); 46 | } 47 | Backend::Standard => { 48 | println!("cargo:rustc-cfg=usdt_backend_standard"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /usdt-impl/src/common.rs: -------------------------------------------------------------------------------- 1 | //! Shared code used in both the linker and no-linker implementations of this crate. 2 | 3 | // Copyright 2024 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use crate::DataType; 18 | use proc_macro2::TokenStream; 19 | use quote::{format_ident, quote}; 20 | 21 | /// Construct a function to type-check the argument closure. 22 | /// 23 | /// This constructs a function that is never called, but is used to ensure that 24 | /// the closure provided to each probe macro returns arguments of the right 25 | /// type. 26 | pub fn construct_type_check( 27 | provider_name: &str, 28 | probe_name: &str, 29 | use_statements: &[syn::ItemUse], 30 | types: &[DataType], 31 | ) -> TokenStream { 32 | // If there are zero arguments, we need to make sure we can assign the 33 | // result of the closure to (). 34 | if types.is_empty() { 35 | return quote! { 36 | let _: () = ($args_lambda)(); 37 | }; 38 | } 39 | let type_check_params = types 40 | .iter() 41 | .map(|typ| match typ { 42 | DataType::Serializable(ty) => { 43 | match ty { 44 | syn::Type::Reference(reference) => { 45 | if let Some(elem) = shared_slice_elem_type(reference) { 46 | quote! { _: impl AsRef<[#elem]> } 47 | } else { 48 | let elem = &*reference.elem; 49 | quote! { _: impl ::std::borrow::Borrow<#elem> } 50 | } 51 | } 52 | syn::Type::Slice(slice) => { 53 | let elem = &*slice.elem; 54 | quote! { _: impl AsRef<[#elem]> } 55 | } 56 | syn::Type::Array(array) => { 57 | let elem = &*array.elem; 58 | quote! { _: impl AsRef<[#elem]> } 59 | } 60 | syn::Type::Path(_) => { 61 | quote! { _: impl ::std::borrow::Borrow<#ty> } 62 | } 63 | _ => { 64 | // Any other type must be specified exactly as given in the probe parameter 65 | quote! { _: #ty } 66 | } 67 | } 68 | } 69 | DataType::Native(dtrace_parser::DataType::String) => quote! { _: impl AsRef }, 70 | _ => { 71 | let arg = typ.to_rust_type(); 72 | quote! { _: impl ::std::borrow::Borrow<#arg> } 73 | } 74 | }) 75 | .collect::>(); 76 | 77 | // Create a list of arguments `arg.0`, `arg.1`, ... to pass to the check 78 | // function. 79 | let type_check_args = (0..types.len()) 80 | .map(|i| { 81 | let index = syn::Index::from(i); 82 | quote! { args.#index } 83 | }) 84 | .collect::>(); 85 | 86 | let type_check_fn = format_ident!("__usdt_private_{}_{}_type_check", provider_name, probe_name); 87 | quote! { 88 | #[allow(unused_imports)] 89 | #(#use_statements)* 90 | #[allow(non_snake_case)] 91 | fn #type_check_fn(#(#type_check_params),*) {} 92 | let _ = || { #type_check_fn(#(#type_check_args),*); }; 93 | } 94 | } 95 | 96 | fn shared_slice_elem_type(reference: &syn::TypeReference) -> Option<&syn::Type> { 97 | if let syn::Type::Slice(slice) = &*reference.elem { 98 | Some(&*slice.elem) 99 | } else { 100 | None 101 | } 102 | } 103 | 104 | // Return code to destructure a probe arguments into identifiers, and to pass those to ASM 105 | // registers. 106 | pub fn construct_probe_args(types: &[DataType]) -> (TokenStream, TokenStream) { 107 | // x86_64 passes the first 6 arguments in registers, with the rest on the stack. 108 | // We limit this to 6 arguments in all cases for now, as handling those stack 109 | // arguments would be challenging with the current `asm!` macro implementation. 110 | #[cfg(target_arch = "x86_64")] 111 | let abi_regs = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]; 112 | #[cfg(target_arch = "aarch64")] 113 | let abi_regs = ["x0", "x1", "x2", "x3", "x4", "x5"]; 114 | #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] 115 | compile_error!("USDT only supports x86_64 and ARM64 architectures"); 116 | 117 | assert!( 118 | types.len() <= abi_regs.len(), 119 | "Up to 6 probe arguments are currently supported" 120 | ); 121 | let (unpacked_args, in_regs): (Vec<_>, Vec<_>) = types 122 | .iter() 123 | .zip(&abi_regs) 124 | .enumerate() 125 | .map(|(i, (typ, reg))| { 126 | let arg = format_ident!("arg_{}", i); 127 | let index = syn::Index::from(i); 128 | let input = quote! { args.#index }; 129 | let (value, at_use) = asm_type_convert(typ, input); 130 | 131 | // These values must refer to the actual traced data and prevent it 132 | // from being dropped until after we've completed the probe 133 | // invocation. 134 | let destructured_arg = quote! { 135 | let #arg = #value; 136 | }; 137 | // Here, we convert the argument to store it within a register. 138 | let register_arg = quote! { in(#reg) (#arg #at_use) }; 139 | 140 | (destructured_arg, register_arg) 141 | }) 142 | .unzip(); 143 | let arg_lambda = call_argument_closure(types); 144 | let unpacked_args = quote! { 145 | #arg_lambda 146 | #(#unpacked_args)* 147 | }; 148 | let in_regs = quote! { #(#in_regs,)* }; 149 | (unpacked_args, in_regs) 150 | } 151 | 152 | /// Call the argument closure, assigning its output to `args`. 153 | pub fn call_argument_closure(types: &[DataType]) -> TokenStream { 154 | match types.len() { 155 | // Don't bother with any closure if there are no arguments. 156 | 0 => quote! {}, 157 | // Wrap a single argument in a tuple. 158 | 1 => quote! { let args = (($args_lambda)(),); }, 159 | // General case. 160 | _ => quote! { let args = ($args_lambda)(); }, 161 | } 162 | } 163 | 164 | // Convert a supported data type to 1. a type to store for the duration of the 165 | // probe invocation and 2. a transformation for compatibility with an asm 166 | // register. 167 | fn asm_type_convert(typ: &DataType, input: TokenStream) -> (TokenStream, TokenStream) { 168 | match typ { 169 | DataType::Serializable(_) => ( 170 | // Convert the input to JSON. This is a fallible operation, however, so we wrap the 171 | // data in a result-like JSON blob, mapping the `Result`'s variants to the keys "ok" 172 | // and "err". 173 | quote! { 174 | [ 175 | match ::usdt::to_json(&#input) { 176 | Ok(json) => format!("{{\"ok\":{}}}", json), 177 | Err(e) => format!("{{\"err\":\"{}\"}}", e.to_string()), 178 | }.as_bytes(), 179 | &[0_u8] 180 | ].concat() 181 | }, 182 | quote! { .as_ptr() as usize }, 183 | ), 184 | DataType::Native(dtrace_parser::DataType::String) => ( 185 | quote! { 186 | [(#input.as_ref() as &str).as_bytes(), &[0_u8]].concat() 187 | }, 188 | quote! { .as_ptr() as usize }, 189 | ), 190 | DataType::Native(_) => { 191 | let ty = typ.to_rust_type(); 192 | ( 193 | quote! { (*<_ as ::std::borrow::Borrow<#ty>>::borrow(&#input) as usize) }, 194 | quote! {}, 195 | ) 196 | } 197 | DataType::UniqueId => (quote! { #input.as_u64() as usize }, quote! {}), 198 | } 199 | } 200 | 201 | /// Create the top-level probe macro. 202 | /// 203 | /// This takes the implementation block constructed elsewhere, and builds out 204 | /// the actual macro users call in their code to fire the probe. 205 | pub(crate) fn build_probe_macro( 206 | config: &crate::CompileProvidersConfig, 207 | probe_name: &str, 208 | types: &[DataType], 209 | impl_block: TokenStream, 210 | ) -> TokenStream { 211 | let module = config.module_ident(); 212 | let macro_name = config.probe_ident(probe_name); 213 | let no_args_match = if types.is_empty() { 214 | quote! { () => { crate::#module::#macro_name!(|| ()) }; } 215 | } else { 216 | quote! {} 217 | }; 218 | quote! { 219 | #[allow(unused_macros)] 220 | macro_rules! #macro_name { 221 | #no_args_match 222 | ($tree:tt) => { 223 | compile_error!("USDT probe macros should be invoked with a closure returning the arguments"); 224 | }; 225 | ($args_lambda:expr) => { 226 | { 227 | #impl_block 228 | } 229 | }; 230 | } 231 | #[allow(unused_imports)] 232 | pub(crate) use #macro_name; 233 | } 234 | } 235 | 236 | #[cfg(test)] 237 | mod tests { 238 | 239 | use super::*; 240 | use dtrace_parser::BitWidth; 241 | use dtrace_parser::DataType as DType; 242 | use dtrace_parser::Integer; 243 | use dtrace_parser::Sign; 244 | 245 | #[test] 246 | fn test_construct_type_check_empty() { 247 | let expected = quote! { 248 | let _ : () = ($args_lambda)(); 249 | }; 250 | let block = construct_type_check("", "", &[], &[]); 251 | assert_eq!(block.to_string(), expected.to_string()); 252 | } 253 | 254 | #[test] 255 | fn test_construct_type_check_native() { 256 | let provider = "provider"; 257 | let probe = "probe"; 258 | let types = &[ 259 | DataType::Native(DType::Integer(Integer { 260 | sign: Sign::Unsigned, 261 | width: BitWidth::Bit8, 262 | })), 263 | DataType::Native(DType::Integer(Integer { 264 | sign: Sign::Signed, 265 | width: BitWidth::Bit64, 266 | })), 267 | ]; 268 | let expected = quote! { 269 | #[allow(unused_imports)] 270 | #[allow(non_snake_case)] 271 | fn __usdt_private_provider_probe_type_check( 272 | _: impl ::std::borrow::Borrow, 273 | _: impl ::std::borrow::Borrow 274 | ) { } 275 | let _ = || { 276 | __usdt_private_provider_probe_type_check(args.0, args.1); 277 | }; 278 | }; 279 | let block = construct_type_check(provider, probe, &[], types); 280 | assert_eq!(block.to_string(), expected.to_string()); 281 | } 282 | 283 | #[test] 284 | fn test_construct_type_check_with_string() { 285 | let provider = "provider"; 286 | let probe = "probe"; 287 | let types = &[DataType::Native(dtrace_parser::DataType::String)]; 288 | let use_statements = vec![]; 289 | let expected = quote! { 290 | #[allow(unused_imports)] 291 | #[allow(non_snake_case)] 292 | fn __usdt_private_provider_probe_type_check(_: impl AsRef) { } 293 | let _ = || { 294 | __usdt_private_provider_probe_type_check(args.0); 295 | }; 296 | }; 297 | let block = construct_type_check(provider, probe, &use_statements, types); 298 | assert_eq!(block.to_string(), expected.to_string()); 299 | } 300 | 301 | #[test] 302 | fn test_construct_type_check_with_shared_slice() { 303 | let provider = "provider"; 304 | let probe = "probe"; 305 | let types = &[DataType::Serializable(syn::parse_str("&[u8]").unwrap())]; 306 | let use_statements = vec![]; 307 | let expected = quote! { 308 | #[allow(unused_imports)] 309 | #[allow(non_snake_case)] 310 | fn __usdt_private_provider_probe_type_check(_: impl AsRef<[u8]>) { } 311 | let _ = || { 312 | __usdt_private_provider_probe_type_check(args.0); 313 | }; 314 | }; 315 | let block = construct_type_check(provider, probe, &use_statements, types); 316 | assert_eq!(block.to_string(), expected.to_string()); 317 | } 318 | 319 | #[test] 320 | fn test_construct_type_check_with_custom_type() { 321 | let provider = "provider"; 322 | let probe = "probe"; 323 | let types = &[DataType::Serializable(syn::parse_str("MyType").unwrap())]; 324 | let use_statements = vec![syn::parse2(quote! { use my_module::MyType; }).unwrap()]; 325 | let expected = quote! { 326 | #[allow(unused_imports)] 327 | use my_module::MyType; 328 | #[allow(non_snake_case)] 329 | fn __usdt_private_provider_probe_type_check(_: impl ::std::borrow::Borrow) { } 330 | let _ = || { 331 | __usdt_private_provider_probe_type_check(args.0); 332 | }; 333 | }; 334 | let block = construct_type_check(provider, probe, &use_statements, types); 335 | assert_eq!(block.to_string(), expected.to_string()); 336 | } 337 | 338 | #[test] 339 | fn test_construct_probe_args() { 340 | let types = &[ 341 | DataType::Native(DType::Pointer(Integer { 342 | sign: Sign::Unsigned, 343 | width: BitWidth::Bit8, 344 | })), 345 | DataType::Native(dtrace_parser::DataType::String), 346 | ]; 347 | #[cfg(target_arch = "x86_64")] 348 | let registers = ["rdi", "rsi"]; 349 | #[cfg(target_arch = "aarch64")] 350 | let registers = ["x0", "x1"]; 351 | let (args, regs) = construct_probe_args(types); 352 | let expected = quote! { 353 | let args = ($args_lambda)(); 354 | let arg_0 = (*<_ as ::std::borrow::Borrow<*const u8>>::borrow(&args.0) as usize); 355 | let arg_1 = [(args.1.as_ref() as &str).as_bytes(), &[0_u8]].concat(); 356 | }; 357 | assert_eq!(args.to_string(), expected.to_string()); 358 | 359 | for (i, (expected, actual)) in registers 360 | .iter() 361 | .zip(regs.to_string().split(',')) 362 | .enumerate() 363 | { 364 | let reg = actual.replace(' ', ""); 365 | let expected = format!("in(\"{}\")(arg_{}", expected, i); 366 | assert!( 367 | reg.starts_with(&expected), 368 | "reg: {}; expected {}", 369 | reg, 370 | expected, 371 | ); 372 | } 373 | } 374 | 375 | #[test] 376 | fn test_asm_type_convert() { 377 | use std::str::FromStr; 378 | let (out, post) = asm_type_convert( 379 | &DataType::Native(DType::Integer(Integer { 380 | sign: Sign::Unsigned, 381 | width: BitWidth::Bit8, 382 | })), 383 | TokenStream::from_str("foo").unwrap(), 384 | ); 385 | assert_eq!( 386 | out.to_string(), 387 | quote! {(*<_ as ::std::borrow::Borrow>::borrow(&foo) as usize)}.to_string() 388 | ); 389 | assert_eq!(post.to_string(), quote! {}.to_string()); 390 | 391 | let (out, post) = asm_type_convert( 392 | &DataType::Native(dtrace_parser::DataType::String), 393 | TokenStream::from_str("foo").unwrap(), 394 | ); 395 | assert_eq!( 396 | out.to_string(), 397 | quote! { [(foo.as_ref() as &str).as_bytes(), &[0_u8]].concat() }.to_string() 398 | ); 399 | assert_eq!(post.to_string(), quote! { .as_ptr() as usize }.to_string()); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /usdt-impl/src/empty.rs: -------------------------------------------------------------------------------- 1 | //! The empty implementation of the USDT crate. 2 | //! 3 | //! Used on platforms without DTrace. 4 | 5 | // Copyright 2024 Oxide Computer Company 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | use crate::common; 20 | use crate::{Probe, Provider}; 21 | use proc_macro2::TokenStream; 22 | use quote::quote; 23 | use std::convert::TryFrom; 24 | 25 | pub fn compile_provider_source( 26 | source: &str, 27 | config: &crate::CompileProvidersConfig, 28 | ) -> Result { 29 | let dfile = dtrace_parser::File::try_from(source)?; 30 | let providers = dfile 31 | .providers() 32 | .into_iter() 33 | .map(|provider| { 34 | let provider = Provider::from(provider); 35 | // Ensure that the name of the module in the config is set, either by the caller or 36 | // defaulting to the provider name. 37 | let config = crate::CompileProvidersConfig { 38 | provider: Some(provider.name.clone()), 39 | probe_format: config.probe_format.clone(), 40 | module: match &config.module { 41 | None => Some(provider.name.clone()), 42 | other => other.clone(), 43 | }, 44 | }; 45 | compile_provider(&provider, &config) 46 | }) 47 | .collect::>(); 48 | Ok(quote! { 49 | #(#providers)* 50 | }) 51 | } 52 | 53 | pub fn compile_provider_from_definition( 54 | provider: &Provider, 55 | config: &crate::CompileProvidersConfig, 56 | ) -> TokenStream { 57 | compile_provider(provider, config) 58 | } 59 | 60 | fn compile_provider(provider: &Provider, config: &crate::CompileProvidersConfig) -> TokenStream { 61 | let probe_impls = provider 62 | .probes 63 | .iter() 64 | .map(|probe| compile_probe(provider, probe, config)) 65 | .collect::>(); 66 | let module = config.module_ident(); 67 | quote! { 68 | pub(crate) mod #module { 69 | #(#probe_impls)* 70 | } 71 | } 72 | } 73 | 74 | fn compile_probe( 75 | provider: &Provider, 76 | probe: &Probe, 77 | config: &crate::CompileProvidersConfig, 78 | ) -> TokenStream { 79 | // We don't need to add any actual probe emission code, but we do still want 80 | // to perform type-checking of its arguments here. 81 | let args = common::call_argument_closure(&probe.types); 82 | let type_check_fn = common::construct_type_check( 83 | &provider.name, 84 | &probe.name, 85 | &provider.use_statements, 86 | &probe.types, 87 | ); 88 | let impl_block = quote! { 89 | #args 90 | #type_check_fn 91 | }; 92 | common::build_probe_macro(config, &probe.name, &probe.types, impl_block) 93 | } 94 | 95 | pub fn register_probes() -> Result<(), crate::Error> { 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /usdt-impl/src/linker.rs: -------------------------------------------------------------------------------- 1 | //! USDT implementation on platforms with linker support (macOS). 2 | //! 3 | //! On systems with linker support for the compile-time construction of DTrace 4 | //! USDT probes we can lean heavily on those mechanisms. Rather than interpreting 5 | //! the provider file ourselves, we invoke the system's `dtrace -h` to generate a C 6 | //! header file. That header file contains the linker directives that convey 7 | //! information from the provider definition such as types and stability. We parse 8 | //! that header file and generate code that effectively reproduces in Rust the 9 | //! equivalent of what we would see in C. 10 | //! 11 | //! For example, the header file might contain code like this: 12 | //! ```ignore 13 | //! #define FOO_STABILITY "___dtrace_stability$foo$v1$1_1_0_1_1_0_1_1_0_1_1_0_1_1_0" 14 | //! #define FOO_TYPEDEFS "___dtrace_typedefs$foo$v2" 15 | //! 16 | //! #if !defined(DTRACE_PROBES_DISABLED) || !DTRACE_PROBES_DISABLED 17 | //! 18 | //! #define FOO_BAR() \ 19 | //! do { \ 20 | //! __asm__ volatile(".reference " FOO_TYPEDEFS); \ 21 | //! __dtrace_probe$foo$bar$v1(); \ 22 | //! __asm__ volatile(".reference " FOO_STABILITY); \ 23 | //! } while (0) 24 | //! ``` 25 | //! 26 | //! In rust, we'll want the probe site to look something like this: 27 | //! ```ignore 28 | //! extern "C" { 29 | //! #[link_name = "__dtrace_stability$foo$v1$1_1_0_1_1_0_1_1_0_1_1_0_1_1_0"] 30 | //! fn stability(); 31 | //! #[link_name = "__dtrace_probe$foo$bar$v1"] 32 | //! fn probe(); 33 | //! #[link_name = "__dtrace_typedefs$foo$v2"] 34 | //! fn typedefs(); 35 | //! 36 | //! } 37 | //! unsafe { 38 | //! asm!(".reference {}", sym typedefs); 39 | //! probe(); 40 | //! asm!(".reference {}", sym stability); 41 | //! } 42 | //! ``` 43 | //! There are a few things to note above: 44 | //! 1. We cannot simply generate code with the symbol name embedded in the asm! 45 | //! block e.g. `asm!(".reference __dtrace_typedefs$foo$v2")`. The asm! macro 46 | //! removes '$' characters yielding the incorrect symbol. 47 | //! 2. The header file stability and typedefs contain three '_'s whereas the 48 | //! Rust code has just two. The `sym ` apparently prepends an 49 | //! extra underscore in this case. 50 | //! 3. The probe needs to be a function type (because we call it), but the types 51 | //! of the `stability` and `typedefs` symbols could be anything--we just need 52 | //! a symbol name we can reference for the asm! macro that won't get garbled. 53 | 54 | // Copyright 2024 Oxide Computer Company 55 | // 56 | // Licensed under the Apache License, Version 2.0 (the "License"); 57 | // you may not use this file except in compliance with the License. 58 | // You may obtain a copy of the License at 59 | // 60 | // http://www.apache.org/licenses/LICENSE-2.0 61 | // 62 | // Unless required by applicable law or agreed to in writing, software 63 | // distributed under the License is distributed on an "AS IS" BASIS, 64 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | // See the License for the specific language governing permissions and 66 | // limitations under the License. 67 | 68 | use crate::{common, DataType, Provider}; 69 | use proc_macro2::TokenStream; 70 | use quote::{format_ident, quote}; 71 | use std::{ 72 | collections::BTreeMap, 73 | convert::TryFrom, 74 | io::Write, 75 | process::{Command, Stdio}, 76 | }; 77 | 78 | /// Compile a DTrace provider definition into Rust tokens that implement its probes. 79 | pub fn compile_provider_source( 80 | source: &str, 81 | config: &crate::CompileProvidersConfig, 82 | ) -> Result { 83 | let dfile = dtrace_parser::File::try_from(source)?; 84 | let header = build_header_from_provider(&source)?; 85 | let provider_info = extract_providers(&header); 86 | let providers = dfile 87 | .providers() 88 | .into_iter() 89 | .map(|provider| { 90 | let provider = Provider::from(provider); 91 | // Ensure that the name of the module in the config is set, either by the caller or 92 | // defaulting to the provider name. 93 | let config = crate::CompileProvidersConfig { 94 | provider: Some(provider.name.clone()), 95 | probe_format: config.probe_format.clone(), 96 | module: match &config.module { 97 | None => Some(provider.name.clone()), 98 | other => other.clone(), 99 | }, 100 | }; 101 | compile_provider(&provider, &provider_info[&provider.name], &config) 102 | }) 103 | .collect::>(); 104 | Ok(quote! { 105 | #(#providers)* 106 | }) 107 | } 108 | 109 | pub fn compile_provider_from_definition( 110 | provider: &Provider, 111 | config: &crate::CompileProvidersConfig, 112 | ) -> TokenStream { 113 | // Unwrap safety: The type signature confirms that `provider` is valid. 114 | let header = build_header_from_provider(&provider.to_d_source()).unwrap(); 115 | let provider_info = extract_providers(&header); 116 | let provider_tokens = compile_provider(provider, &provider_info[&provider.name], config); 117 | quote! { 118 | #provider_tokens 119 | } 120 | } 121 | 122 | fn compile_provider( 123 | provider: &Provider, 124 | provider_info: &ProviderInfo, 125 | config: &crate::CompileProvidersConfig, 126 | ) -> TokenStream { 127 | let mut probe_impls = Vec::new(); 128 | for probe in provider.probes.iter() { 129 | probe_impls.push(compile_probe( 130 | provider, 131 | &probe.name, 132 | config, 133 | provider_info, 134 | &probe.types, 135 | )); 136 | } 137 | let module = config.module_ident(); 138 | quote! { 139 | pub(crate) mod #module { 140 | #(#probe_impls)* 141 | } 142 | } 143 | } 144 | 145 | fn compile_probe( 146 | provider: &Provider, 147 | probe_name: &str, 148 | config: &crate::CompileProvidersConfig, 149 | provider_info: &ProviderInfo, 150 | types: &[DataType], 151 | ) -> TokenStream { 152 | // Retrieve the string names and the Rust identifiers used for the extern functions. 153 | // These are provided by the macOS linker, but have invalid Rust identifier names, like 154 | // `foo$bar`. We name them with valid Rust idents, and specify their link name as that of the 155 | // function provided by the macOS linker. 156 | let stability = &provider_info.stability; 157 | let stability_fn = format_ident!("stability"); 158 | let typedefs = &provider_info.typedefs; 159 | let typedef_fn = format_ident!("typedefs"); 160 | let is_enabled = &provider_info.is_enabled[probe_name]; 161 | let is_enabled_fn = format_ident!("{}_{}_enabled", &provider.name, probe_name); 162 | 163 | // The probe function is a little different. We prefix it with `__` because otherwise it has 164 | // the same name as the macro itself, which leads to conflicts. 165 | let probe = &provider_info.probes[probe_name]; 166 | let extern_probe_fn = format_ident!("__{}", config.probe_ident(probe_name)); 167 | 168 | let ffi_param_list = types.iter().map(|typ| { 169 | let ty = typ.to_rust_ffi_type(); 170 | syn::parse2::(quote! { _: #ty }).unwrap() 171 | }); 172 | let (unpacked_args, in_regs) = common::construct_probe_args(types); 173 | let type_check_fn = 174 | common::construct_type_check(&provider.name, probe_name, &provider.use_statements, types); 175 | 176 | #[cfg(target_arch = "x86_64")] 177 | let call_instruction = quote! { "call {extern_probe_fn}" }; 178 | #[cfg(target_arch = "aarch64")] 179 | let call_instruction = quote! { "bl {extern_probe_fn}" }; 180 | #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] 181 | compile_error!("USDT only supports x86_64 and AArch64 architectures"); 182 | 183 | let impl_block = quote! { 184 | extern "C" { 185 | #[allow(unused)] 186 | #[link_name = #stability] 187 | fn stability(); 188 | 189 | #[allow(unused)] 190 | #[link_name = #typedefs] 191 | fn typedefs(); 192 | 193 | #[allow(unused)] 194 | #[link_name = #is_enabled] 195 | fn #is_enabled_fn() -> i32; 196 | 197 | #[allow(unused)] 198 | #[link_name = #probe] 199 | fn #extern_probe_fn(#(#ffi_param_list,)*); 200 | } 201 | unsafe { 202 | if #is_enabled_fn() != 0 { 203 | #unpacked_args 204 | #type_check_fn 205 | ::std::arch::asm!( 206 | ".reference {typedefs}", 207 | #call_instruction, 208 | ".reference {stability}", 209 | typedefs = sym #typedef_fn, 210 | extern_probe_fn = sym #extern_probe_fn, 211 | stability = sym #stability_fn, 212 | #in_regs 213 | options(nomem, nostack, preserves_flags) 214 | ); 215 | } 216 | } 217 | }; 218 | 219 | common::build_probe_macro(config, probe_name, types, impl_block) 220 | } 221 | 222 | #[derive(Debug, Default, Clone)] 223 | struct ProviderInfo { 224 | pub stability: String, 225 | pub typedefs: String, 226 | pub is_enabled: BTreeMap, 227 | pub probes: BTreeMap, 228 | } 229 | 230 | fn extract_providers(header: &str) -> BTreeMap { 231 | let mut providers = BTreeMap::new(); 232 | for line in header.lines() { 233 | if let Some((provider_name, stability)) = is_stability_line(&line) { 234 | let mut info = ProviderInfo::default(); 235 | info.stability = stability.to_string(); 236 | providers.insert(provider_name.to_string(), info); 237 | } 238 | if let Some((provider_name, typedefs)) = is_typedefs_line(&line) { 239 | providers.get_mut(provider_name).unwrap().typedefs = typedefs.to_string(); 240 | } 241 | if let Some((provider_name, probe_name, enabled)) = is_enabled_line(&line) { 242 | providers 243 | .get_mut(provider_name) 244 | .unwrap() 245 | .is_enabled 246 | .insert(probe_name.to_string(), enabled.to_string()); 247 | } 248 | if let Some((provider_name, probe_name, probe)) = is_probe_line(&line) { 249 | providers 250 | .get_mut(provider_name) 251 | .unwrap() 252 | .probes 253 | .insert(probe_name.to_string(), probe.to_string()); 254 | } 255 | } 256 | providers 257 | } 258 | 259 | // Return the (provider_name, stability) from a line, if it looks like the appropriate #define'd 260 | // line from the autogenerated header file. 261 | fn is_stability_line(line: &str) -> Option<(&str, &str)> { 262 | contains_needle(line, "___dtrace_stability$") 263 | } 264 | 265 | // Return the (provider_name, typedefs) from a line, if it looks like the appropriate #define'd 266 | // line from the autogenerated header file. 267 | fn is_typedefs_line(line: &str) -> Option<(&str, &str)> { 268 | contains_needle(line, "___dtrace_typedefs$") 269 | } 270 | 271 | fn contains_needle<'a>(line: &'a str, needle: &str) -> Option<(&'a str, &'a str)> { 272 | if let Some(index) = line.find(needle) { 273 | let rest = &line[index + needle.len()..]; 274 | let provider_end = rest.find("$").unwrap(); 275 | let provider_name = &rest[..provider_end]; 276 | // NOTE: The extra offset to the start index works as follows. The symbol name really needs 277 | // to be `___dtrace_stability$...`. But that symbol name will have a "_" prefixed to it 278 | // during compilation, so we remove the leading one here, knowing it will be added back. 279 | let needle = &line[index + 1..line.len() - 1]; 280 | Some((provider_name, needle)) 281 | } else { 282 | None 283 | } 284 | } 285 | 286 | // Return the (provider, probe, enabled) from a line, if it looks like the appropriate extern 287 | // function declaration from the autogenerated header file. 288 | fn is_enabled_line(line: &str) -> Option<(&str, &str, &str)> { 289 | contains_needle2(line, "extern int __dtrace_isenabled$") 290 | } 291 | 292 | // Return the (provider, probe, probe) from a line, if it looks like the appropriate extern 293 | // function declaration from the autogenerated header file. 294 | fn is_probe_line(line: &str) -> Option<(&str, &str, &str)> { 295 | contains_needle2(line, "extern void __dtrace_probe$") 296 | } 297 | 298 | fn contains_needle2<'a>(line: &'a str, needle: &str) -> Option<(&'a str, &'a str, &'a str)> { 299 | if let Some(index) = line.find(needle) { 300 | let rest = &line[index + needle.len()..]; 301 | let provider_end = rest.find("$").unwrap(); 302 | let provider_name = &rest[..provider_end]; 303 | 304 | let rest = &rest[provider_end + 1..]; 305 | let probe_end = rest.find("$").unwrap(); 306 | let probe_name = &rest[..probe_end]; 307 | 308 | let end = line.rfind("(").unwrap(); 309 | let start = line.find(line.split(" ").nth(2).unwrap()).unwrap(); 310 | let needle = &line[start..end]; 311 | Some((provider_name, probe_name, needle)) 312 | } else { 313 | None 314 | } 315 | } 316 | 317 | fn build_header_from_provider(source: &str) -> Result { 318 | let mut child = Command::new("dtrace") 319 | .arg("-h") 320 | .arg("-s") 321 | .arg("/dev/stdin") 322 | .arg("-o") 323 | .arg("/dev/stdout") 324 | .stdin(Stdio::piped()) 325 | .stdout(Stdio::piped()) 326 | .spawn()?; 327 | { 328 | let stdin = child.stdin.as_mut().ok_or(crate::Error::DTraceError)?; 329 | stdin 330 | .write_all(source.as_bytes()) 331 | .map_err(|_| crate::Error::DTraceError)?; 332 | } 333 | let output = child.wait_with_output()?; 334 | String::from_utf8(output.stdout).map_err(|_| crate::Error::DTraceError) 335 | } 336 | 337 | pub fn register_probes() -> Result<(), crate::Error> { 338 | // This function is a NOP, since we're using Apple's linker to create the DOF and call ioctl(2) 339 | // to send it to the driver. 340 | Ok(()) 341 | } 342 | 343 | #[cfg(test)] 344 | mod tests { 345 | use super::*; 346 | use crate::Probe; 347 | 348 | #[test] 349 | fn test_is_stability_line() { 350 | let line = "this line is ok \"___dtrace_stability$foo$bar\""; 351 | let result = is_stability_line(line); 352 | assert!(result.is_some()); 353 | assert_eq!(result.unwrap().0, "foo"); 354 | assert_eq!(result.unwrap().1, "__dtrace_stability$foo$bar"); 355 | assert!(is_stability_line("bad").is_none()); 356 | } 357 | 358 | #[test] 359 | fn test_is_typedefs_line() { 360 | let line = "this line is ok \"___dtrace_typedefs$foo$bar\""; 361 | let result = is_typedefs_line(line); 362 | assert!(result.is_some()); 363 | assert_eq!(result.unwrap().0, "foo"); 364 | assert_eq!(result.unwrap().1, "__dtrace_typedefs$foo$bar"); 365 | assert!(is_typedefs_line("bad").is_none()); 366 | } 367 | 368 | #[test] 369 | fn test_is_enabled_line() { 370 | let line = "extern int __dtrace_isenabled$foo$bar$xxx(void);"; 371 | let result = is_enabled_line(line); 372 | assert!(result.is_some()); 373 | assert_eq!(result.unwrap().0, "foo"); 374 | assert_eq!(result.unwrap().1, "bar"); 375 | assert_eq!(result.unwrap().2, "__dtrace_isenabled$foo$bar$xxx"); 376 | assert!(is_enabled_line("bad").is_none()); 377 | } 378 | 379 | #[test] 380 | fn test_is_probe_line() { 381 | let line = "extern void __dtrace_probe$foo$bar$xxx(whatever);"; 382 | let result = is_probe_line(line); 383 | assert!(result.is_some()); 384 | assert_eq!(result.unwrap().0, "foo"); 385 | assert_eq!(result.unwrap().1, "bar"); 386 | assert_eq!(result.unwrap().2, "__dtrace_probe$foo$bar$xxx"); 387 | assert!(is_enabled_line("bad").is_none()); 388 | } 389 | 390 | #[test] 391 | fn test_compile_probe() { 392 | let provider_name = "foo"; 393 | let probe_name = "bar"; 394 | let extern_probe_name = "__bar"; 395 | let is_enabled = "__dtrace_isenabled$foo$bar$xxx"; 396 | let probe = "__dtrace_probe$foo$bar$xxx"; 397 | let stability = "__dtrace_probe$foo$v1$1_1_1"; 398 | let typedefs = "__dtrace_typedefs$foo$v2"; 399 | let types = vec![]; 400 | let provider = Provider { 401 | name: provider_name.to_string(), 402 | probes: vec![Probe { 403 | name: probe_name.to_string(), 404 | types: types.clone(), 405 | }], 406 | use_statements: vec![], 407 | }; 408 | 409 | let mut is_enabled_map = BTreeMap::new(); 410 | is_enabled_map.insert(String::from(probe_name), String::from(is_enabled)); 411 | let mut probes_map = BTreeMap::new(); 412 | probes_map.insert(String::from(probe_name), String::from(probe)); 413 | let provider_info = ProviderInfo { 414 | stability: String::from(stability), 415 | typedefs: String::from(typedefs), 416 | is_enabled: is_enabled_map, 417 | probes: probes_map, 418 | }; 419 | 420 | let tokens = compile_probe( 421 | &provider, 422 | probe_name, 423 | &crate::CompileProvidersConfig { 424 | provider: Some(provider_name.to_string()), 425 | ..Default::default() 426 | }, 427 | &provider_info, 428 | &types, 429 | ); 430 | 431 | let output = tokens.to_string(); 432 | 433 | let needle = format!("link_name = \"{is_enabled}\"", is_enabled = is_enabled); 434 | assert!(output.contains(&needle)); 435 | 436 | let needle = format!("link_name = \"{probe}\"", probe = probe); 437 | assert!(output.contains(&needle)); 438 | 439 | let needle = format!( 440 | "fn {provider_name}_{probe_name}", 441 | provider_name = provider_name, 442 | probe_name = probe_name 443 | ); 444 | assert!(output.contains(&needle)); 445 | 446 | let needles = &[ 447 | "asm ! (\".reference {typedefs}\"", 448 | #[cfg(target_arch = "x86_64")] 449 | "call {extern_probe_fn}", 450 | #[cfg(target_arch = "aarch64")] 451 | "bl {extern_probe_fn}", 452 | "\".reference {stability}", 453 | "typedefs = sym typedefs", 454 | &format!( 455 | "probe_fn = sym {extern_probe_name}", 456 | extern_probe_name = extern_probe_name 457 | ), 458 | "stability = sym stability", 459 | ]; 460 | for needle in needles.iter() { 461 | assert!( 462 | output.contains(needle), 463 | "needle {} not found in haystack {}", 464 | needle, 465 | output, 466 | ); 467 | } 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /usdt-impl/src/no-linker.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of USDT functionality on platforms without runtime linker support. 2 | 3 | // Copyright 2022 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use std::convert::TryFrom; 18 | use std::fs::OpenOptions; 19 | use std::os::unix::io::AsRawFd; 20 | 21 | use crate::record::{emit_probe_record, process_section}; 22 | use crate::{common, Probe, Provider}; 23 | use dof::{serialize_section, Section}; 24 | use proc_macro2::TokenStream; 25 | use quote::quote; 26 | 27 | /// Compile a DTrace provider definition into Rust tokens that implement its probes. 28 | pub fn compile_provider_source( 29 | source: &str, 30 | config: &crate::CompileProvidersConfig, 31 | ) -> Result { 32 | let dfile = dtrace_parser::File::try_from(source)?; 33 | let providers = dfile 34 | .providers() 35 | .iter() 36 | .map(|provider| { 37 | let provider = Provider::from(provider); 38 | // Ensure that the name of the module in the config is set, either by the caller or 39 | // defaulting to the provider name. 40 | let config = crate::CompileProvidersConfig { 41 | provider: Some(provider.name.clone()), 42 | probe_format: config.probe_format.clone(), 43 | module: match &config.module { 44 | None => Some(provider.name.clone()), 45 | other => other.clone(), 46 | }, 47 | }; 48 | compile_provider(&provider, &config) 49 | }) 50 | .collect::>(); 51 | Ok(quote! { 52 | #(#providers)* 53 | }) 54 | } 55 | 56 | pub fn compile_provider_from_definition( 57 | provider: &Provider, 58 | config: &crate::CompileProvidersConfig, 59 | ) -> TokenStream { 60 | compile_provider(provider, config) 61 | } 62 | 63 | fn compile_provider(provider: &Provider, config: &crate::CompileProvidersConfig) -> TokenStream { 64 | let probe_impls = provider 65 | .probes 66 | .iter() 67 | .map(|probe| compile_probe(provider, probe, config)) 68 | .collect::>(); 69 | let module = config.module_ident(); 70 | quote! { 71 | pub(crate) mod #module { 72 | #(#probe_impls)* 73 | } 74 | } 75 | } 76 | 77 | fn compile_probe( 78 | provider: &Provider, 79 | probe: &Probe, 80 | config: &crate::CompileProvidersConfig, 81 | ) -> TokenStream { 82 | let (unpacked_args, in_regs) = common::construct_probe_args(&probe.types); 83 | let is_enabled_rec = emit_probe_record(&provider.name, &probe.name, None); 84 | let probe_rec = emit_probe_record(&provider.name, &probe.name, Some(&probe.types)); 85 | let type_check_fn = common::construct_type_check( 86 | &provider.name, 87 | &probe.name, 88 | &provider.use_statements, 89 | &probe.types, 90 | ); 91 | 92 | let impl_block = quote! { 93 | { 94 | let mut is_enabled: u64; 95 | unsafe { 96 | ::std::arch::asm!( 97 | "990: clr rax", 98 | #is_enabled_rec, 99 | out("rax") is_enabled, 100 | options(nomem, nostack) 101 | ); 102 | } 103 | 104 | if is_enabled != 0 { 105 | #unpacked_args 106 | #type_check_fn 107 | unsafe { 108 | ::std::arch::asm!( 109 | "990: nop", 110 | #probe_rec, 111 | #in_regs 112 | options(nomem, nostack, preserves_flags) 113 | ); 114 | } 115 | } 116 | } 117 | }; 118 | common::build_probe_macro(config, &probe.name, &probe.types, impl_block) 119 | } 120 | 121 | fn extract_probe_records_from_section() -> Result { 122 | extern "C" { 123 | #[link_name = "__start_set_dtrace_probes"] 124 | static dtrace_probes_start: usize; 125 | #[link_name = "__stop_set_dtrace_probes"] 126 | static dtrace_probes_stop: usize; 127 | } 128 | 129 | // Without this the illumos linker may decide to omit the symbols above that 130 | // denote the start and stop addresses for this section. Note that the variable 131 | // must be mutable, otherwise this will generate a read-only section with the 132 | // name `set_dtrace_probes`. The section containing the actual probe records is 133 | // writable (to implement one-time registration), so an immutable variable here 134 | // leads to _two_ sections, one writable and one read-only. A mutable variable 135 | // here ensures this ends up in a mutable section, the same as the probe records. 136 | #[cfg(target_os = "illumos")] 137 | #[link_section = "set_dtrace_probes"] 138 | #[used] 139 | static mut FORCE_LOAD: [u64; 0] = []; 140 | 141 | let data = unsafe { 142 | let start = (&dtrace_probes_start as *const usize) as usize; 143 | let stop = (&dtrace_probes_stop as *const usize) as usize; 144 | std::slice::from_raw_parts_mut(start as *mut u8, stop - start) 145 | }; 146 | process_section(data, /* register = */ true) 147 | } 148 | 149 | pub fn register_probes() -> Result<(), crate::Error> { 150 | let section = extract_probe_records_from_section()?; 151 | let module_name = section 152 | .providers 153 | .values() 154 | .next() 155 | .and_then(|provider| { 156 | provider.probes.values().next().and_then(|probe| { 157 | crate::record::addr_to_info(probe.address) 158 | .1 159 | .map(|path| path.rsplit('/').next().map(String::from).unwrap_or(path)) 160 | .or_else(|| Some(format!("?{:#x}", probe.address))) 161 | }) 162 | }) 163 | .unwrap_or_else(|| String::from("unknown-module")); 164 | let mut modname = [0; 64]; 165 | for (i, byte) in module_name.bytes().take(modname.len() - 1).enumerate() { 166 | modname[i] = byte as i8; 167 | } 168 | ioctl_section(&serialize_section(§ion), modname) 169 | } 170 | 171 | fn ioctl_section(buf: &[u8], modname: [std::os::raw::c_char; 64]) -> Result<(), crate::Error> { 172 | let helper = dof::dof_bindings::dof_helper { 173 | dofhp_mod: modname, 174 | dofhp_addr: buf.as_ptr() as u64, 175 | dofhp_dof: buf.as_ptr() as u64, 176 | }; 177 | let data = &helper as *const _; 178 | let cmd: i32 = 0x64746803; 179 | let file = OpenOptions::new() 180 | .read(true) 181 | .write(true) 182 | .open("/dev/dtrace/helper")?; 183 | if unsafe { libc::ioctl(file.as_raw_fd(), cmd, data) } < 0 { 184 | Err(crate::Error::IO(std::io::Error::last_os_error())) 185 | } else { 186 | Ok(()) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /usdt-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usdt-macro" 3 | version = "0.5.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Procedural macro for generating Rust macros for USDT probes" 7 | repository = "https://github.com/oxidecomputer/usdt.git" 8 | 9 | [dependencies] 10 | dtrace-parser = { path = "../dtrace-parser", version = "=0.2.0" } 11 | proc-macro2 = "1" 12 | serde_tokenstream = "0.2" 13 | syn = { version = "2", features = ["full"] } 14 | quote = "1" 15 | usdt-impl = { path = "../usdt-impl", default-features = false, version = "=0.5.0" } 16 | 17 | [lib] 18 | proc-macro = true 19 | -------------------------------------------------------------------------------- /usdt-macro/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /usdt-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Prototype proc-macro crate for parsing a DTrace provider definition into Rust code. 2 | 3 | // Copyright 2021 Oxide Computer Company 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | use std::iter::FromIterator; 18 | use std::{fs, path::Path}; 19 | 20 | use quote::quote; 21 | use syn::{parse_macro_input, Lit}; 22 | 23 | use usdt_impl::compile_provider_source; 24 | 25 | /// Generate DTrace probe macros from a provider definition file. 26 | /// 27 | /// This macro parses a DTrace provider.d file, given as a single literal string path. It then 28 | /// generates a Rust macro for each of the DTrace probe definitions. 29 | /// 30 | /// For example, let's say the file `"test.d"` has the following contents: 31 | /// 32 | /// ```ignore 33 | /// provider test { 34 | /// probe start(uint8_t); 35 | /// probe stop(char *, uint8_t); 36 | /// }; 37 | /// ``` 38 | /// 39 | /// In a Rust library or application, write: 40 | /// 41 | /// ```ignore 42 | /// dtrace_provider!("test.d"); 43 | /// ``` 44 | /// 45 | /// The macro looks for the file relative to the root of the package, so `"test.d"` 46 | /// in this case would be in the same directory as `"Cargo.toml"`. 47 | /// 48 | /// By default probe macros are named `{provider}_{probe}!`. Arguments are passed 49 | /// via a closure that returns a tuple. Note that the provided closure is only 50 | /// evaluated when the probe is enabled. One can then add points of instrumentation 51 | /// by invoking the macro: 52 | /// 53 | /// ```ignore 54 | /// fn do_stuff(count: u8, name: String) { 55 | /// // doing stuff 56 | /// test_stop!(|| (name, count)); 57 | /// } 58 | /// ``` 59 | /// 60 | /// The probe macro names can be customized by adding `, format = 61 | /// my_prefix_{provider}_{probe}` to the macro invocation where `{provider}` and 62 | /// `{probe}` are optional and will be substituted with the actual provider and 63 | /// probe names: 64 | /// 65 | /// ```ignore 66 | /// dtrace_provider!("test.d", format = "dtrace_{provider}_{probe}"); 67 | /// ``` 68 | /// 69 | /// Note 70 | /// ---- 71 | /// The only supported types are integers of specific bit-width (e.g., `uint16_t`), 72 | /// pointers to integers, and `char *`. 73 | #[proc_macro] 74 | pub fn dtrace_provider(item: proc_macro::TokenStream) -> proc_macro::TokenStream { 75 | let mut tokens = item.into_iter().collect::>(); 76 | 77 | let comma_index = tokens 78 | .iter() 79 | .enumerate() 80 | .find_map(|(i, token)| match token { 81 | proc_macro::TokenTree::Punct(p) if p.as_char() == ',' => Some(i), 82 | _ => None, 83 | }); 84 | 85 | // Split off the tokens after the comma if there is one. 86 | let rest = if let Some(index) = comma_index { 87 | let mut rest = tokens.split_off(index); 88 | let _ = rest.remove(0); 89 | rest 90 | } else { 91 | Vec::new() 92 | }; 93 | 94 | // Parse the config from the remaining tokens. 95 | let config: usdt_impl::CompileProvidersConfig = serde_tokenstream::from_tokenstream( 96 | &proc_macro2::TokenStream::from(proc_macro::TokenStream::from_iter(rest)), 97 | ) 98 | .unwrap(); 99 | 100 | let first_item = proc_macro::TokenStream::from_iter(tokens); 101 | let tok = parse_macro_input!(first_item as Lit); 102 | let filename = match tok { 103 | Lit::Str(f) => f.value(), 104 | _ => panic!("DTrace provider must be a single literal string filename"), 105 | }; 106 | let source = if filename.ends_with(".d") { 107 | let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else( 108 | |_| std::env::current_dir().unwrap(), 109 | |s| Path::new(&s).to_path_buf(), 110 | ); 111 | 112 | let path = dir.join(&filename); 113 | fs::read_to_string(path).unwrap_or_else(|_| { 114 | panic!( 115 | "Could not read D source file \"{}\" in {:?}", 116 | &filename, dir, 117 | ) 118 | }) 119 | } else { 120 | filename.clone() 121 | }; 122 | match compile_provider_source(&source, &config) { 123 | Ok(provider) => provider.into(), 124 | Err(e) => { 125 | let message = format!( 126 | "Error building provider definition in \"{}\"\n\n{}", 127 | filename, e 128 | ); 129 | let out = quote! { 130 | compile_error!(#message); 131 | }; 132 | out.into() 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /usdt-tests-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usdt-tests-common" 3 | version = "0.0.0" 4 | license = "Apache-2.0" 5 | description = "Common routines for integration tests in this repository" 6 | edition = "2021" 7 | publish = false 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /usdt-tests-common/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /usdt-tests-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Oxide Computer Company 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #[cfg(target_os = "illumos")] 16 | pub fn root_command() -> String { 17 | // On illumos systems, we prefer pfexec(1) but allow some other command to 18 | // be specified through the environment. 19 | std::env::var("PFEXEC").unwrap_or_else(|_| "/usr/bin/pfexec".to_string()) 20 | } 21 | 22 | #[cfg(not(target_os = "illumos"))] 23 | pub fn root_command() -> String { 24 | String::from("sudo") 25 | } 26 | -------------------------------------------------------------------------------- /usdt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usdt" 3 | version = "0.5.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "Dust your Rust with USDT probes" 7 | repository = "https://github.com/oxidecomputer/usdt.git" 8 | rust-version = "1.85.0" 9 | 10 | # NOTE: The use of path and version dependencies is deliberate and load-bearing. 11 | # 12 | # When building from source, this will select the version in the workspace, by 13 | # path. When publishing the specific version will be used. Combined with the 14 | # exact version requirements, this prevents issues like oxidecomputer/usdt#69, 15 | # where the public-facing `usdt` crate may pick up different versions of the 16 | # internal implementation crates. 17 | [dependencies] 18 | serde = "1" 19 | usdt-impl = { path = "../usdt-impl", default-features = false, version = "=0.5.0", features = [ 20 | "des", 21 | ] } 22 | usdt-macro = { path = "../usdt-macro", default-features = false, version = "=0.5.0" } 23 | usdt-attr-macro = { path = "../usdt-attr-macro", default-features = false, version = "=0.5.0" } 24 | dof = { path = "../dof", features = ["des"], version = "=0.3.0" } 25 | goblin = { version = "0.10", features = ["elf32", "elf64"] } 26 | memmap2 = { version = "0.9.5" } 27 | 28 | [features] 29 | default = ["asm"] 30 | # This feature used to be functional, but is now a no-op because inline `asm` is available on all 31 | # supported versions of Rust. It's kept around for BC reasons, but whenever there's a breaking 32 | # change to the `usdt` crate, this feature should be removed. 33 | # 34 | # There's also a comment about this in lib.rs -- remove it when this feature is removed. 35 | asm = [] 36 | -------------------------------------------------------------------------------- /usdt/README.md: -------------------------------------------------------------------------------- 1 | ../README.md --------------------------------------------------------------------------------