1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Optional support for registering stacks with Valgrind.
//!
//! When running under Valgrind, we need to notify it when we allocate/free a
//! stack otherwise it gets confused when the stack pointer starts to randomly
//! move to a different addess.
//!
//! This is done through special instruction sequences which are recognized by
//! Valgrind but otherwise executes as a NOP on real hardware.

cfg_if::cfg_if! {
    if #[cfg(target_arch = "x86_64")] {
        type Value = u64;

        #[inline]
        unsafe fn valgrind_request(default: Value, args: &[Value; 6]) -> Value {
            let result;
            core::arch::asm!(
                "rol rdi, 3",
                "rol rdi, 13",
                "rol rdi, 61",
                "rol rdi, 51",
                "xchg rbx, rbx",
                inout("rdx") default => result,
                in("rax") args.as_ptr(),
                options(nostack),
            );
            result
        }
    } else if #[cfg(target_arch = "x86")] {
        type Value = u32;

        #[inline]
        unsafe fn valgrind_request(default: Value, args: &[Value; 6]) -> Value {
            let result;
            core::arch::asm!(
                "rol edi, 3",
                "rol edi, 13",
                "rol edi, 29",
                "rol edi, 19",
                "xchg ebx, ebx",
                inout("edx") default => result,
                in("eax") args.as_ptr(),
                options(nostack),
            );
            result
        }
    } else if #[cfg(target_arch = "aarch64")] {
        type Value = u64;

        #[inline]
        unsafe fn valgrind_request(default: Value, args: &[Value; 6]) -> Value {
            let result;
            core::arch::asm!(
                "ror x12, x12, #3",
                "ror x12, x12, #13",
                "ror x12, x12, #61",
                "ror x12, x12, #51",
                "orr x10, x10, x10",
                inout("x3") default => result,
                in("x4") args.as_ptr(),
                options(nostack),
            );
            result
        }
    } else if #[cfg(target_arch = "arm")] {
        type Value = u32;

        #[inline]
        unsafe fn valgrind_request(default: Value, args: &[Value; 6]) -> Value {
            let result;
            core::arch::asm!(
                "ror r12, r12, #3",
                "ror r12, r12, #13",
                "ror r12, r12, #29",
                "ror r12, r12, #19",
                "orr r10, r10, r10",
                inout("r3") default => result,
                in("r4") args.as_ptr(),
                options(nostack),
            );
            result
        }
    } else if #[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))] {
        type Value = usize;

        // Valgrind doesn't support RISC-V yet, use a no-op for now.
        #[inline]
        unsafe fn valgrind_request(default: Value, _args: &[Value; 6]) -> Value {
            default
        }
    } else if #[cfg(target_arch = "loongarch64")] {
        type Value = usize;

        // Valgrind doesn't support LoongArch yet, use a no-op for now.
        #[inline]
        unsafe fn valgrind_request(default: Value, _args: &[Value; 6]) -> Value {
            default
        }
    } else {
        compile_error!("Unsupported target");
    }
}

const STACK_REGISTER: Value = 0x1501;
const STACK_DEREGISTER: Value = 0x1502;

/// Helper type which registers a stack with Valgrind and automatically
/// de-registers it when dropped.
///
/// This has no effect when not running under Valgrind.
#[derive(Debug)]
pub struct ValgrindStackRegistration {
    id: Value,
}

impl ValgrindStackRegistration {
    /// Registers the given region of memory as a stack so that Valgrind can
    /// properly recognize legitimate stack switches.
    #[inline]
    pub fn new(addr: *mut u8, len: usize) -> Self {
        Self {
            id: unsafe {
                valgrind_request(
                    0,
                    &[
                        STACK_REGISTER,
                        addr as Value,
                        addr as Value + len as Value,
                        0,
                        0,
                        0,
                    ],
                )
            },
        }
    }
}

impl Drop for ValgrindStackRegistration {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            valgrind_request(0, &[STACK_DEREGISTER, self.id, 0, 0, 0, 0]);
        }
    }
}