diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4d012be --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target = "./armv7a-vex-v5.json" + +[unstable] +build-std = ["core", "compiler_builtins", "alloc"] +build-std-features = ["compiler-builtins-mem"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..bfce5c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Something is not working as expected +title: '' +labels: type:bug, repro:required +assignees: '' + +--- + +## Bug Description + + +## Code to reproduce + + +## Expected vs. actual behavior + + +## Additional information + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..6578f37 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,38 @@ +--- +name: Feature request +about: Propose an idea or new feature +title: '' +labels: enhancement +assignees: '' + +--- + +## What's the motivation for this feature? + + +## Describe the solution you'd like + + +## Describe the drawbacks, if any + + +## Describe the alternative solutions, if any + + +## Additional context + diff --git a/.github/ISSUE_TEMPLATE/small_issue.md b/.github/ISSUE_TEMPLATE/small_issue.md new file mode 100644 index 0000000..e7240a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/small_issue.md @@ -0,0 +1,26 @@ +--- +name: Small issue +about: Report a small problem (e.g. typo) +title: '' +labels: type:bug, repro:needed +assignees: '' + +--- + +## Problem Description + + +## Screenshots + + +## Additional information + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f77dbcb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## Describe the changes this PR makes. Why should it be merged? + +## Additional Context + diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..7917e7e --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,61 @@ +name: Rust + +on: [push, pull_request] + +jobs: + # Check and lint are separated because linting doesn't seem to fail + # if there are errors are outside of the PR's changes. + check: + name: Check + runs-on: ubuntu-latest + steps: + - name: Setup | Checkout + uses: actions/checkout@v2 + + - name: Setup | Toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2024-02-07 + override: true + + - name: Check + uses: actions-rs/cargo@v1 + with: + command: check + args: --lib --bins --examples --all-features + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + with: + components: clippy + - uses: giraffate/clippy-action@v1 + with: + reporter: "github-pr-check" + github_token: ${{ secrets.GITHUB_TOKEN }} + clippy_flags: --lib --bins --examples --all-features + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - name: Setup | Checkout + uses: actions/checkout@v2 + + - name: Setup | Toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2024-02-07 + override: true + + - name: Setup | Install Rustfmt + run: rustup component add rustfmt + + - name: Format + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0df57c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..eae21cb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "cSpell.words": [], + "rust-analyzer.cargo.allTargets": false, + "rust-analyzer.check.targets": [ + "${workspaceFolder}/armv7a-vex-v5.json" + ], + "rust-analyzer.cargo.extraArgs": [ + "--examples", + "--lib", + "--bins" + ], + "rust-analyzer.cargo.features": "all", + "rust-analyzer.check.command": "clippy", + "rust-analyzer.showUnlinkedFileNotification": false, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, + "files.eol": "\n", +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..384a904 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +members = ["packages/*"] +resolver = "2" + +[workspace.lints.rust] +rust_2018_idioms = { level = "warn", priority = -1 } +missing_docs = "warn" +unsafe_op_in_unsafe_fn = "deny" + +[workspace.lints.clippy] +missing_const_for_fn = "warn" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ce05ee --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 vexide Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..35f3b9d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# vex-libunwind + +> High-level Rust bindings for libunwind on VEX V5 robots diff --git a/armv7a-vex-v5.json b/armv7a-vex-v5.json new file mode 100644 index 0000000..c80ad26 --- /dev/null +++ b/armv7a-vex-v5.json @@ -0,0 +1,23 @@ +{ + "cpu": "cortex-a9", + "arch": "arm", + "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", + "disable-redzone": true, + "emit-debug-gdb-scripts": false, + "env": "newlib", + "executables": true, + "features": "+thumb2,+neon,+vfp3", + "linker": "rust-lld", + "linker-flavor": "ld.lld", + "llvm-target": "armv7a-none-eabi", + "max-atomic-width": 64, + "panic-strategy": "abort", + "post-link-args": { + "ld.lld": ["--gc-sections", "--nostdlib", "-Tv5.ld", "-znorelro"] + }, + "relocation-model": "static", + "target-pointer-width": "32", + "os": "none", + "vendor": "vex", + "default-uwtable": true +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..cf797dc --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + cargo-v5.url = "github:vexide/cargo-v5"; + rust-overlay.url = "github:oxalica/rust-overlay"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; + rust-overlay.inputs.flake-utils.follows = "flake-utils"; + + cargo-v5.inputs.nixpkgs.follows = "nixpkgs"; + cargo-v5.inputs.flake-utils.follows = "flake-utils"; + }; + + outputs = { nixpkgs, flake-utils, cargo-v5, rust-overlay, ... }: + (flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { inherit system overlays; }; + cargo-v5' = cargo-v5.packages.${system}.default; + in { + devShell = pkgs.mkShell { + buildInputs = with pkgs; [ + (rust-bin.nightly."2024-02-07".default.override { + extensions = [ "rust-src" "rust-analyzer" "clippy" ]; + }) + cargo-v5' + ]; + }; + })); +} diff --git a/packages/vex-libunwind-sys/Cargo.toml b/packages/vex-libunwind-sys/Cargo.toml new file mode 100644 index 0000000..decdf16 --- /dev/null +++ b/packages/vex-libunwind-sys/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "vex-libunwind-sys" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "FFI bindings to libunwind for VEX V5 robots" +keywords = ["vex", "v5", "unwind", "libunwind"] +categories = [ + "external-ffi-bindings", + "embedded", + "science::robotics", + "no-std::no-alloc", +] +authors = [ + "vexide", + "doinkythederp ", + "Tropical", + "Gavin Niederman ", +] +repository = "https://github.com/vexide/vex-libunwind" +homepage = "https://vexide.dev" + +[dependencies] + +[lints] +workspace = true diff --git a/packages/vex-libunwind-sys/build.rs b/packages/vex-libunwind-sys/build.rs new file mode 100644 index 0000000..9d3df67 --- /dev/null +++ b/packages/vex-libunwind-sys/build.rs @@ -0,0 +1,6 @@ +#![allow(missing_docs)] + +fn main() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + println!("cargo:rustc-link-search=native={manifest_dir}/link"); +} diff --git a/packages/vex-libunwind-sys/link/libunwind.a b/packages/vex-libunwind-sys/link/libunwind.a new file mode 100644 index 0000000..1713b29 Binary files /dev/null and b/packages/vex-libunwind-sys/link/libunwind.a differ diff --git a/packages/vex-libunwind-sys/src/lib.rs b/packages/vex-libunwind-sys/src/lib.rs new file mode 100644 index 0000000..b3c026b --- /dev/null +++ b/packages/vex-libunwind-sys/src/lib.rs @@ -0,0 +1,270 @@ +//! Bindings to the low-level `unw_*` LLVM libunwind APIs which are an interface defined by the HP libunwind project. +#![allow(non_camel_case_types, missing_docs)] +#![no_std] + +#[cfg(not(all( + target_arch = "arm", + target_endian = "little", + target_pointer_width = "32" +)))] +compile_error!("vex-libunwind-sys only supports running in an armv7a environment."); + +use core::ffi::{c_char, c_int, c_void}; + +pub type uw_error_t = c_int; + +/// Error codes. +pub mod error { + use core::ffi::c_int; + /// No error. + pub const UNW_ESUCCESS: c_int = 0; + /// Unspecified (general) error. + pub const UNW_EUNSPEC: c_int = -6540; + /// Out of memory. + pub const UNW_ENOMEM: c_int = -6541; + /// Bad register number. + pub const UNW_EBADREG: c_int = -6542; + /// Attempt to write read-only register. + pub const UNW_EREADONLYREG: c_int = -6543; + /// Stop unwinding. + pub const UNW_ESTOPUNWIND: c_int = -6544; + /// Invalid IP. + pub const UNW_EINVALIDIP: c_int = -6545; + /// Bad frame. + pub const UNW_EBADFRAME: c_int = -6546; + /// Unsupported operation or bad value. + pub const UNW_EINVAL: c_int = -6547; + /// Unwind info has unsupported version. + pub const UNW_EBADVERSION: c_int = -6548; + /// No unwind info found. + pub const UNW_ENOINFO: c_int = -6549; +} + +/// Architecture-specific context size +#[cfg(target_arch = "arm")] +pub const CONTEXT_SIZE: usize = 42; +/// Architecture-specific cursor size +#[cfg(target_arch = "arm")] +pub const CURSOR_SIZE: usize = 49; + +/// The step was successful. +pub const UNW_STEP_SUCCESS: c_int = 1; +/// There are no more stack frames. +pub const UNW_STEP_END: c_int = 0; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_context_t { + _data: [u64; CONTEXT_SIZE], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_cursor_t { + _data: [u64; CURSOR_SIZE], +} + +pub type unw_addr_space_t = *mut c_void; + +pub type unw_regnum_t = c_int; +pub type unw_word_t = usize; +pub type unw_fpreg_t = u64; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct unw_proc_info_t { + /// Start address of function + pub start_ip: unw_word_t, + /// Address after end of function + pub end_ip: unw_word_t, + /// Address of language specific data area or zero if not used + pub lsda: unw_word_t, + /// Personality routine, or zero if not used + pub handler: unw_word_t, + /// Not used + pub gp: unw_word_t, + /// Not used + pub flags: unw_word_t, + /// Compact unwind encoding, or zero if none + pub format: u32, + /// Size of DWARF unwind info, or zero if none + pub unwind_info_size: u32, + /// Address of DWARF unwind info, or zero + pub unwind_info: unw_word_t, + /// mach_header of mach-o image containing func + pub extra: unw_word_t, +} + +#[link(name = "unwind")] +extern "C" { + pub fn unw_getcontext(ctx: *mut unw_context_t) -> c_int; + + pub fn unw_init_local(cur: *mut unw_cursor_t, ctx: *mut unw_context_t) -> c_int; + + pub fn unw_step(cur: *mut unw_cursor_t) -> c_int; + + pub fn unw_get_reg(cur: *mut unw_cursor_t, reg: unw_regnum_t, val: *mut unw_word_t) -> c_int; + + pub fn unw_get_fpreg(cur: *mut unw_cursor_t, reg: unw_regnum_t, val: *mut unw_fpreg_t) + -> c_int; + + pub fn unw_set_reg(cur: *mut unw_cursor_t, reg: unw_regnum_t, val: unw_word_t) -> c_int; + + pub fn unw_set_fpreg(cur: *mut unw_cursor_t, reg: unw_regnum_t, val: unw_fpreg_t) -> c_int; + + pub fn unw_resume(cur: *mut unw_cursor_t) -> c_int; + + #[cfg(target_arch = "arm")] + // Save VFP registers in FSTMX format (instead of FSTMD). + pub fn unw_save_vfp_as_X(cur: *mut unw_cursor_t); + + pub fn unw_regname(cur: *mut unw_cursor_t, reg: unw_regnum_t) -> *const c_char; + + pub fn unw_get_proc_info(cur: *mut unw_cursor_t, info: *mut unw_proc_info_t) -> c_int; + + pub fn unw_is_fpreg(cur: *mut unw_cursor_t, reg: unw_regnum_t) -> c_int; + + pub fn unw_is_signal_frame(cur: *mut unw_cursor_t) -> c_int; + + pub fn unw_get_proc_name( + cur: *mut unw_cursor_t, + buf: *mut c_char, + len: usize, + offp: *mut unw_word_t, + ) -> c_int; + + pub static mut unw_local_addr_space: unw_addr_space_t; +} + +/// Instruction pointer +pub const UNW_REG_IP: unw_regnum_t = -1; +/// Stack pointer +pub const UNW_REG_SP: unw_regnum_t = -2; + +// 32-bit ARM registers. Numbers match DWARF for ARM spec #3.1 Table 1. +// Naming scheme uses recommendations given in Note 4 for VFP-v2 and VFP-v3. +// In this scheme, even though the 64-bit floating point registers D0-D31 +// overlap physically with the 32-bit floating pointer registers S0-S31, +// they are given a non-overlapping range of register numbers. +// +// Commented out ranges are not preserved during unwinding. +pub const UNW_ARM_R0: unw_regnum_t = 0; +pub const UNW_ARM_R1: unw_regnum_t = 1; +pub const UNW_ARM_R2: unw_regnum_t = 2; +pub const UNW_ARM_R3: unw_regnum_t = 3; +pub const UNW_ARM_R4: unw_regnum_t = 4; +pub const UNW_ARM_R5: unw_regnum_t = 5; +pub const UNW_ARM_R6: unw_regnum_t = 6; +pub const UNW_ARM_R7: unw_regnum_t = 7; +pub const UNW_ARM_R8: unw_regnum_t = 8; +pub const UNW_ARM_R9: unw_regnum_t = 9; +pub const UNW_ARM_R10: unw_regnum_t = 10; +pub const UNW_ARM_R11: unw_regnum_t = 11; +pub const UNW_ARM_R12: unw_regnum_t = 12; +pub const UNW_ARM_SP: unw_regnum_t = 13; // Logical alias for UNW_REG_SP +pub const UNW_ARM_R13: unw_regnum_t = 13; +pub const UNW_ARM_LR: unw_regnum_t = 14; +pub const UNW_ARM_R14: unw_regnum_t = 14; +pub const UNW_ARM_IP: unw_regnum_t = 15; // Logical alias for UNW_REG_IP +pub const UNW_ARM_R15: unw_regnum_t = 15; +// 16-63 -- OBSOLETE. Used in VFP1 to represent both S0-S31 and D0-D31. +pub const UNW_ARM_S0: unw_regnum_t = 64; +pub const UNW_ARM_S1: unw_regnum_t = 65; +pub const UNW_ARM_S2: unw_regnum_t = 66; +pub const UNW_ARM_S3: unw_regnum_t = 67; +pub const UNW_ARM_S4: unw_regnum_t = 68; +pub const UNW_ARM_S5: unw_regnum_t = 69; +pub const UNW_ARM_S6: unw_regnum_t = 70; +pub const UNW_ARM_S7: unw_regnum_t = 71; +pub const UNW_ARM_S8: unw_regnum_t = 72; +pub const UNW_ARM_S9: unw_regnum_t = 73; +pub const UNW_ARM_S10: unw_regnum_t = 74; +pub const UNW_ARM_S11: unw_regnum_t = 75; +pub const UNW_ARM_S12: unw_regnum_t = 76; +pub const UNW_ARM_S13: unw_regnum_t = 77; +pub const UNW_ARM_S14: unw_regnum_t = 78; +pub const UNW_ARM_S15: unw_regnum_t = 79; +pub const UNW_ARM_S16: unw_regnum_t = 80; +pub const UNW_ARM_S17: unw_regnum_t = 81; +pub const UNW_ARM_S18: unw_regnum_t = 82; +pub const UNW_ARM_S19: unw_regnum_t = 83; +pub const UNW_ARM_S20: unw_regnum_t = 84; +pub const UNW_ARM_S21: unw_regnum_t = 85; +pub const UNW_ARM_S22: unw_regnum_t = 86; +pub const UNW_ARM_S23: unw_regnum_t = 87; +pub const UNW_ARM_S24: unw_regnum_t = 88; +pub const UNW_ARM_S25: unw_regnum_t = 89; +pub const UNW_ARM_S26: unw_regnum_t = 90; +pub const UNW_ARM_S27: unw_regnum_t = 91; +pub const UNW_ARM_S28: unw_regnum_t = 92; +pub const UNW_ARM_S29: unw_regnum_t = 93; +pub const UNW_ARM_S30: unw_regnum_t = 94; +pub const UNW_ARM_S31: unw_regnum_t = 95; +// 96-103 -- OBSOLETE. F0-F7. Used by the FPA system. Superseded by VFP. +// 104-111 -- wCGR0-wCGR7, ACC0-ACC7 (Intel wireless MMX) +pub const UNW_ARM_WR0: unw_regnum_t = 112; +pub const UNW_ARM_WR1: unw_regnum_t = 113; +pub const UNW_ARM_WR2: unw_regnum_t = 114; +pub const UNW_ARM_WR3: unw_regnum_t = 115; +pub const UNW_ARM_WR4: unw_regnum_t = 116; +pub const UNW_ARM_WR5: unw_regnum_t = 117; +pub const UNW_ARM_WR6: unw_regnum_t = 118; +pub const UNW_ARM_WR7: unw_regnum_t = 119; +pub const UNW_ARM_WR8: unw_regnum_t = 120; +pub const UNW_ARM_WR9: unw_regnum_t = 121; +pub const UNW_ARM_WR10: unw_regnum_t = 122; +pub const UNW_ARM_WR11: unw_regnum_t = 123; +pub const UNW_ARM_WR12: unw_regnum_t = 124; +pub const UNW_ARM_WR13: unw_regnum_t = 125; +pub const UNW_ARM_WR14: unw_regnum_t = 126; +pub const UNW_ARM_WR15: unw_regnum_t = 127; +// 128-133 -- SPSR, SPSR_{FIQ|IRQ|ABT|UND|SVC} +// 134-143 -- Reserved +// 144-150 -- R8_USR-R14_USR +// 151-157 -- R8_FIQ-R14_FIQ +// 158-159 -- R13_IRQ-R14_IRQ +// 160-161 -- R13_ABT-R14_ABT +// 162-163 -- R13_UND-R14_UND +// 164-165 -- R13_SVC-R14_SVC +// 166-191 -- Reserved +pub const UNW_ARM_WC0: unw_regnum_t = 192; +pub const UNW_ARM_WC1: unw_regnum_t = 193; +pub const UNW_ARM_WC2: unw_regnum_t = 194; +pub const UNW_ARM_WC3: unw_regnum_t = 195; +// 196-199 -- wC4-wC7 (Intel wireless MMX control) +// 200-255 -- Reserved +pub const UNW_ARM_D0: unw_regnum_t = 256; +pub const UNW_ARM_D1: unw_regnum_t = 257; +pub const UNW_ARM_D2: unw_regnum_t = 258; +pub const UNW_ARM_D3: unw_regnum_t = 259; +pub const UNW_ARM_D4: unw_regnum_t = 260; +pub const UNW_ARM_D5: unw_regnum_t = 261; +pub const UNW_ARM_D6: unw_regnum_t = 262; +pub const UNW_ARM_D7: unw_regnum_t = 263; +pub const UNW_ARM_D8: unw_regnum_t = 264; +pub const UNW_ARM_D9: unw_regnum_t = 265; +pub const UNW_ARM_D10: unw_regnum_t = 266; +pub const UNW_ARM_D11: unw_regnum_t = 267; +pub const UNW_ARM_D12: unw_regnum_t = 268; +pub const UNW_ARM_D13: unw_regnum_t = 269; +pub const UNW_ARM_D14: unw_regnum_t = 270; +pub const UNW_ARM_D15: unw_regnum_t = 271; +pub const UNW_ARM_D16: unw_regnum_t = 272; +pub const UNW_ARM_D17: unw_regnum_t = 273; +pub const UNW_ARM_D18: unw_regnum_t = 274; +pub const UNW_ARM_D19: unw_regnum_t = 275; +pub const UNW_ARM_D20: unw_regnum_t = 276; +pub const UNW_ARM_D21: unw_regnum_t = 277; +pub const UNW_ARM_D22: unw_regnum_t = 278; +pub const UNW_ARM_D23: unw_regnum_t = 279; +pub const UNW_ARM_D24: unw_regnum_t = 280; +pub const UNW_ARM_D25: unw_regnum_t = 281; +pub const UNW_ARM_D26: unw_regnum_t = 282; +pub const UNW_ARM_D27: unw_regnum_t = 283; +pub const UNW_ARM_D28: unw_regnum_t = 284; +pub const UNW_ARM_D29: unw_regnum_t = 285; +pub const UNW_ARM_D30: unw_regnum_t = 286; +pub const UNW_ARM_D31: unw_regnum_t = 287; +// 288-319 -- Reserved for VFP/Neon +// 320-8191 -- Reserved +// 8192-16383 -- Unspecified vendor co-processor register. diff --git a/packages/vex-libunwind/Cargo.toml b/packages/vex-libunwind/Cargo.toml new file mode 100644 index 0000000..8b31745 --- /dev/null +++ b/packages/vex-libunwind/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "vex-libunwind" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "High-level Rust bindings for libunwind on VEX V5 robots" +keywords = ["vex", "v5", "unwind", "libunwind", "backtrace"] +categories = ["api-bindings", "embedded", "science::robotics", "no-std"] +authors = [ + "vexide", + "Gavin Niederman ", + "doinkythederp ", + "Tropical", +] +repository = "https://github.com/vexide/vex-libunwind" +homepage = "https://vexide.dev" +readme = "../../README.md" + +[dependencies] +snafu = { version = "0.8.4", default-features = false, features = [ + "unstable-core-error", +] } +vex-libunwind-sys = { version = "0.1.0", path = "../vex-libunwind-sys" } + +[lints] +workspace = true diff --git a/packages/vex-libunwind/src/lib.rs b/packages/vex-libunwind/src/lib.rs new file mode 100644 index 0000000..e26b01f --- /dev/null +++ b/packages/vex-libunwind/src/lib.rs @@ -0,0 +1,132 @@ +//! High-level Rust bindings for libunwind on VEX V5 robots +#![no_std] + +use core::{cell::RefCell, mem::MaybeUninit}; +use vex_libunwind_sys::*; + +use snafu::Snafu; + +/// An error that can occur during unwinding. +#[derive(Debug, Snafu)] +pub enum UnwindError { + /// Unspecified/general error. + Unspecified, + /// Out of memory + NoMemory, + /// Invalid register number + BadRegisterNumber, + /// Attempt to write to a read-only register + WriteToReadOnlyRegister, + /// Stop unwinding + StopUnwinding, + /// Invalid instruction pointer + InvalidIP, + /// Bad frame + BadFrame, + /// Unsupported operation or bad value + BadValue, + /// Unwind info has unsupported version + BadVersion, + /// No unwind info found + NoInfo, + /// An error with an unknown error code occured + #[snafu(display("libunwind error {code}"))] + Unknown { + /// The error's code + code: uw_error_t, + }, +} + +impl UnwindError { + /// Creates a `Result` that is `Ok` if the error code represents a success + /// and `Err` if it represents an error. + pub const fn from_code(code: uw_error_t) -> Result<(), UnwindError> { + if code == error::UNW_ESUCCESS { + Ok(()) + } else { + Err(match code { + error::UNW_EUNSPEC => UnwindError::Unspecified, + error::UNW_ENOMEM => UnwindError::NoMemory, + error::UNW_EBADREG => UnwindError::BadRegisterNumber, + error::UNW_EREADONLYREG => UnwindError::WriteToReadOnlyRegister, + error::UNW_ESTOPUNWIND => UnwindError::StopUnwinding, + error::UNW_EINVALIDIP => UnwindError::InvalidIP, + error::UNW_EBADFRAME => UnwindError::BadFrame, + error::UNW_EINVAL => UnwindError::BadValue, + error::UNW_EBADVERSION => UnwindError::BadVersion, + error::UNW_ENOINFO => UnwindError::NoInfo, + code => UnwindError::Unknown { code }, + }) + } + } +} + +/// Holds context about an unwind operation. +pub struct UnwindContext { + inner: unw_context_t, +} + +impl UnwindContext { + /// Creates an new unwind context for the current point of execution. + #[inline(always)] // Inlining keeps this function from appearing in backtraces + pub fn new() -> Result { + let mut inner = MaybeUninit::::uninit(); + // SAFETY: `unw_getcontext` initializes the context struct. + let inner = unsafe { + let code = unw_getcontext(inner.as_mut_ptr()); + UnwindError::from_code(code)?; + inner.assume_init() + }; + Ok(Self { inner }) + } + + /// Returns the underlying libunwind object. + pub fn as_mut_ptr(&mut self) -> *mut unw_context_t { + &mut self.inner + } +} + +/// Allows access to information about stack frames. +pub struct UnwindCursor<'a> { + inner: RefCell, + lifetime: core::marker::PhantomData<&'a mut UnwindContext>, +} + +impl<'a> UnwindCursor<'a> { + /// Creates an unwind cursor for the given context. + pub fn new(context: &'a mut UnwindContext) -> Result { + let mut cursor = MaybeUninit::::uninit(); + // SAFETY: `unw_init_local` initializes the cursor struct. + let cursor = unsafe { + let code = unw_init_local(cursor.as_mut_ptr(), context.as_mut_ptr()); + UnwindError::from_code(code)?; + cursor.assume_init() + }; + Ok(Self { + inner: RefCell::new(cursor), + lifetime: core::marker::PhantomData, + }) + } + + /// Steps to the next frame of the unwind cursor. + /// + /// Returns true if was another frame to step to or false + /// if the cursor has reached the end. + pub fn step(&mut self) -> bool { + let code = unsafe { unw_step(&mut *self.inner.borrow_mut()) }; + code == UNW_STEP_SUCCESS + } + + /// Returns the value of the given register for the current frame. + pub fn get_register(&self, register: unw_regnum_t) -> Result { + let mut reg_value = 0; + let code = unsafe { unw_get_reg(&mut *self.inner.borrow_mut(), register, &mut reg_value) }; + UnwindError::from_code(code)?; + Ok(reg_value) + } + + /// Returns whether the current frame is a signal frame. + pub fn is_signal_frame(&self) -> bool { + unsafe { unw_is_signal_frame(&mut *self.inner.borrow_mut()) > 0 } + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a2fae3f --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-02-07" +components = ["rust-src"] +targets = ["armv7a-none-eabi"]