Skip to main content

hermit/xstate_probe/
asm.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! The probe's measured windows, written in assembly
10//! (<https://github.com/rrnewton/hermit/issues/4243>).
11//!
12//! Each arm runs from "every register of every enabled component holds a known
13//! value" through "the case's initializing step" to "XSAVE of every enabled
14//! component" inside one assembly function. No compiler-generated code runs in
15//! that window, so nothing Rust emits (a `memcpy` using SSE, say) can change the
16//! state being measured. The Rust driver prepares the buffers before a call and
17//! reads the certificates (interrupt counter, `getrusage`, schedstat) outside it.
18
19use std::arch::global_asm;
20
21/// What an arm does after its initializing step and before its XSAVE.
22#[repr(u32)]
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Mode {
25    /// Nothing: the reference.
26    Reference = 0,
27    /// Hand off to the peer on the same CPU and wait for its reply, so the
28    /// kernel saves this thread's FPU state, runs the peer's, and reloads ours.
29    Reload = 1,
30    /// Execute CPUID, which exits to a hypervisor when there is one.
31    ForcedExit = 2,
32}
33
34/// The initializing step of a probe case. The numbering is shared with the
35/// assembly dispatch below.
36#[repr(u32)]
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum Kind {
39    /// Standard-form XRSTOR of one component with its header bit clear.
40    XrstorInit = 0,
41    /// Standard-form XRSTOR of one component with its header bit set and
42    /// initial values in the image.
43    XrstorHeaderSet = 1,
44    /// WRPKRU 0.
45    WrpkruZero = 2,
46    /// WRPKRU 0, then a signal to this thread, whose return restores the frame.
47    Sigreturn = 3,
48    /// VZEROUPPER with XMM live.
49    Vzeroupper = 4,
50    /// Legacy PXOR of XMM0-15 with the YMM upper halves live.
51    LegacyPxor = 5,
52    /// VEX-128 VPXOR of XMM0-15 with Hi16_ZMM live.
53    Vex128Zero = 6,
54    /// VPXORD of ZMM16-31 alone.
55    Hi16Zero = 7,
56    /// PXOR of XMM0-15, then VZEROUPPER, then ZMM16-31 zeroed.
57    PxorVzeroupperHi16 = 8,
58    /// FNINIT, FLDZ ×8, FNINIT: every ST register zero.
59    X87FullInit = 9,
60    /// Two more pushes first, then the same full x87 initialization.
61    X87DirtyFullInit = 10,
62    /// Compacted-form XRSTOR of one component with its header bit clear
63    /// (glibc's lazy-binding `_dl_runtime_resolve_xsavec` path).
64    CompactedXrstorInit = 11,
65    /// FXRSTOR of initial x87 and SSE values.
66    FxrstorInit = 12,
67    /// KXORQ of k0-7 with the vectors live.
68    OpmaskZero = 13,
69    /// VZEROALL, plus ZMM16-31 zeroed where AVX-512 is enabled.
70    VzeroallAll = 14,
71}
72
73/// The state of one arm call, shared with the assembly. Field offsets are fixed
74/// by the assembly below; the test `arm_ctx_layout_matches_the_assembly` checks them.
75#[repr(C)]
76#[derive(Debug)]
77pub struct ArmCtx {
78    pub kind: u32,          // 0
79    pub component: u32,     // 4
80    pub mxcsr: u32,         // 8
81    pub mode: u32,          // 12
82    pub xcr0: u64,          // 16
83    pub save_area: *mut u8, // 24: XSAVE destination, 64-byte aligned
84    pub image: *const u8,   // 32: XRSTOR/FXRSTOR source, 64-byte aligned
85    pub fd_to_peer: i32,    // 40
86    pub fd_from_peer: i32,  // 44
87    pub tgid: i32,          // 48
88    pub tid: i32,           // 52
89    pub ones: *const u8,    // 56: 64 nonzero bytes, 64-byte aligned
90    pub pkru_out: u32,      // 64
91    pub avx512: u32,        // 68: whether ZMM16-31 and k0-7 exist
92    pub cpuid_cycles: u64,  // 72: the forced exit's CPUID round trip
93    pub seq: u8,            // 80: handshake byte sent
94    pub reply: u8,          // 81: handshake byte received
95    pub pkru: u8,           // 82: nonzero when RDPKRU and WRPKRU may run
96    pub _pad: u8,           // 83
97    pub status: u32,        // 84: an `ArmStatus`
98    pub saved_pkru: u32,    // 88: scratch
99    pub saved_mxcsr: u32,   // 92: scratch
100}
101
102/// How an arm call ended. Anything but `Ok` voids the trial.
103#[repr(u32)]
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub enum ArmStatus {
106    Ok = 0,
107    WriteFailed = 1,
108    ReadFailed = 2,
109    WrongReply = 3,
110    TgkillFailed = 4,
111}
112
113impl ArmStatus {
114    pub fn from_raw(raw: u32) -> Option<Self> {
115        Some(match raw {
116            0 => ArmStatus::Ok,
117            1 => ArmStatus::WriteFailed,
118            2 => ArmStatus::ReadFailed,
119            3 => ArmStatus::WrongReply,
120            4 => ArmStatus::TgkillFailed,
121            _ => return None,
122        })
123    }
124}
125
126/// The state the peer leaves on the shared CPU before it replies.
127#[repr(u32)]
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub enum PeerState {
130    /// Every register dirty, PKRU `0x55555550` (differs from the probe's).
131    DirtyPkruDiffers = 0,
132    /// Every register dirty, PKRU 0 by WRPKRU.
133    DirtyWrpkruZero = 1,
134    /// Every component initialized by a zero-header XRSTOR (PKRU 0 included).
135    XrstorInitAll = 2,
136    /// Every register dirty, PKRU 0 by a header-loaded XRSTOR (bit 9 set).
137    DirtyPkruHeaderLoaded = 3,
138    /// Every register dirty, PKRU 0, then a signal and its return.
139    DirtyPkruSigreturn = 4,
140    /// Initial values with every header bit set.
141    InitValuesAllHeaderBits = 5,
142}
143
144/// The peer thread's state, shared with the assembly; offsets as noted.
145#[repr(C)]
146#[derive(Debug)]
147pub struct PeerCtx {
148    pub state: u32,        // 0: a `PeerState`, set before each handshake
149    pub fd_in: i32,        // 4
150    pub fd_out: i32,       // 8
151    pub avx512: u32,       // 12
152    pub image: *const u8,  // 16: the XRSTOR source for states 2, 3 and 5
153    pub xcr0: u64,         // 24
154    pub ones: *const u8,   // 32
155    pub tgid: i32,         // 40
156    pub tid: i32,          // 44
157    pub expected_cpu: u32, // 48
158    pub wrong_cpu: u32,    // 52: rounds the peer ran on another CPU
159    pub status: u32,       // 56: why the loop ended early, as `ArmStatus`
160    pub rounds: u32,       // 60
161    pub byte: u8,          // 64: received
162    pub reply: u8,         // 65: sent
163    pub pkru: u8,          // 66: nonzero when RDPKRU and WRPKRU may run
164    pub _pad: u8,          // 67
165    pub cpu_out: u32,      // 68: getcpu result
166    pub saved_pkru: u32,   // 72
167    pub saved_mxcsr: u32,  // 76
168}
169
170/// The handshake byte that ends the peer loop.
171pub const PEER_EXIT: u8 = 0xff;
172
173unsafe extern "C" {
174    /// Runs one arm and returns its `ArmStatus`. The save area receives the
175    /// XSAVE image and `pkru_out` the RDPKRU value.
176    pub fn hermit_xinuse_arm(ctx: *mut ArmCtx) -> u32;
177    /// Serves handshakes until it reads [`PEER_EXIT`] or a call fails.
178    pub fn hermit_xinuse_peer_loop(ctx: *mut PeerCtx) -> u32;
179    /// The TSC cycles of one plain XSAVE plus standard XRSTOR of `xcr0`.
180    pub fn hermit_xinuse_save_restore_cycles(area: *mut u8, xcr0: u64) -> u64;
181    /// The TSC cycles of one CPUID leaf 0.
182    pub fn hermit_xinuse_cpuid_cycles() -> u64;
183    /// The TSC cycles of one `getppid` round trip.
184    pub fn hermit_xinuse_syscall_cycles() -> u64;
185}
186
187// Register-state macros. DIRTY sets every register of every enabled component to
188// a known nonzero value and leaves the x87 stack with eight entries.
189global_asm!(
190    r#"
191.macro XINUSE_DIRTY ctxreg, avxoff, onesoff
192    mov rsi, [\ctxreg + \onesoff]
193    cmp dword ptr [\ctxreg + \avxoff], 0
194    je 2f
195    .irp r,0,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
196    vmovdqu32 zmm\r, [rsi]
197    .endr
198    .irp k,1,2,3,4,5,6,7
199    kxnorq k\k, k\k, k\k
200    .endr
201    kxorq k0, k0, k0
202    jmp 3f
2032:
204    .irp r,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
205    vmovdqu ymm\r, [rsi]
206    .endr
2073:
208    fninit
209    .rept 6
210    fld1
211    .endr
212    fldpi
213    fldl2e
214.endm
215
216// WRPKRU and RDPKRU raise #UD unless the kernel enables protection keys
217// (CR4.PKE), so both run only when the context's PKRU byte is set; without it
218// the PKRU value read is 0.
219.macro XINUSE_WRPKRU value, ctxreg, flagoff
220    cmp byte ptr [\ctxreg + \flagoff], 0
221    je 91f
222    mov eax, \value
223    xor ecx, ecx
224    xor edx, edx
225    wrpkru
22691:
227.endm
228
229.macro XINUSE_RDPKRU ctxreg, flagoff
230    xor eax, eax
231    cmp byte ptr [\ctxreg + \flagoff], 0
232    je 92f
233    xor ecx, ecx
234    rdpkru
23592:
236.endm
237
238.macro XINUSE_RESTORE_PKRU ctxreg, flagoff, savedoff
239    cmp byte ptr [\ctxreg + \flagoff], 0
240    je 93f
241    mov eax, [\ctxreg + \savedoff]
242    xor ecx, ecx
243    xor edx, edx
244    wrpkru
24593:
246.endm
247
248.macro XINUSE_XRSTOR_MASK ctxreg, imgoff, maskreg
249    mov rax, \maskreg
250    mov rdx, rax
251    shr rdx, 32
252    mov r8, [\ctxreg + \imgoff]
253    xrstor [r8]
254.endm
255"#
256);
257
258global_asm!(
259    r#"
260    .text
261    .globl hermit_xinuse_arm
262    .type hermit_xinuse_arm,@function
263hermit_xinuse_arm:
264    push rbx
265    push r12
266    push r13
267    mov r12, rdi
268    XINUSE_RDPKRU r12, 82
269    mov [r12 + 88], eax
270    stmxcsr dword ptr [r12 + 92]
271    XINUSE_WRPKRU 0x55555554, r12, 82
272    XINUSE_DIRTY r12, 68, 56
273    ldmxcsr dword ptr [r12 + 8]
274
275    mov eax, [r12 + 0]
276    cmp eax, 0
277    je .Larm_xrstor
278    cmp eax, 1
279    je .Larm_xrstor
280    cmp eax, 11
281    je .Larm_xrstor
282    cmp eax, 2
283    je .Larm_wrpkru
284    cmp eax, 3
285    je .Larm_sigreturn
286    cmp eax, 4
287    je .Larm_vzeroupper
288    cmp eax, 5
289    je .Larm_legacy_pxor
290    cmp eax, 6
291    je .Larm_vex128
292    cmp eax, 7
293    je .Larm_hi16
294    cmp eax, 8
295    je .Larm_mix
296    cmp eax, 9
297    je .Larm_x87_full
298    cmp eax, 10
299    je .Larm_x87_dirty_full
300    cmp eax, 12
301    je .Larm_fxrstor
302    cmp eax, 13
303    je .Larm_opmask
304    cmp eax, 14
305    je .Larm_vzeroall
306    jmp .Larm_mode
307
308.Larm_xrstor:
309    mov ecx, [r12 + 4]
310    mov r13, 1
311    shl r13, cl
312    XINUSE_XRSTOR_MASK r12, 32, r13
313    jmp .Larm_mode
314.Larm_wrpkru:
315    XINUSE_WRPKRU 0, r12, 82
316    jmp .Larm_mode
317.Larm_sigreturn:
318    XINUSE_WRPKRU 0, r12, 82
319    mov eax, 234
320    movsxd rdi, dword ptr [r12 + 48]
321    movsxd rsi, dword ptr [r12 + 52]
322    mov edx, 10
323    syscall
324    test rax, rax
325    jz .Larm_mode
326    mov dword ptr [r12 + 84], 4
327    jmp .Larm_finish
328.Larm_vzeroupper:
329    vzeroupper
330    jmp .Larm_mode
331.Larm_legacy_pxor:
332    .irp r,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
333    pxor xmm\r, xmm\r
334    .endr
335    jmp .Larm_mode
336.Larm_vex128:
337    .irp r,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
338    vpxor xmm\r, xmm\r, xmm\r
339    .endr
340    jmp .Larm_mode
341.Larm_hi16:
342    .irp r,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
343    vpxord zmm\r, zmm\r, zmm\r
344    .endr
345    jmp .Larm_mode
346.Larm_mix:
347    .irp r,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
348    pxor xmm\r, xmm\r
349    .endr
350    vzeroupper
351    .irp r,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
352    vpxord zmm\r, zmm\r, zmm\r
353    .endr
354    jmp .Larm_mode
355.Larm_x87_dirty_full:
356    fld1
357    fldpi
358.Larm_x87_full:
359    fninit
360    .rept 8
361    fldz
362    .endr
363    fninit
364    jmp .Larm_mode
365.Larm_fxrstor:
366    mov r8, [r12 + 32]
367    fxrstor [r8]
368    jmp .Larm_mode
369.Larm_opmask:
370    .irp k,0,1,2,3,4,5,6,7
371    kxorq k\k, k\k, k\k
372    .endr
373    jmp .Larm_mode
374.Larm_vzeroall:
375    vzeroall
376    cmp dword ptr [r12 + 68], 0
377    je .Larm_mode
378    .irp r,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
379    vpxord zmm\r, zmm\r, zmm\r
380    .endr
381
382.Larm_mode:
383    mov eax, [r12 + 12]
384    cmp eax, 1
385    je .Larm_reload
386    cmp eax, 2
387    je .Larm_exit
388    jmp .Larm_save
389.Larm_reload:
390    mov eax, 1
391    movsxd rdi, dword ptr [r12 + 40]
392    lea rsi, [r12 + 80]
393    mov edx, 1
394    syscall
395    cmp rax, 1
396    je 4f
397    mov dword ptr [r12 + 84], 1
398    jmp .Larm_finish
3994:
400    xor eax, eax
401    movsxd rdi, dword ptr [r12 + 44]
402    lea rsi, [r12 + 81]
403    mov edx, 1
404    syscall
405    cmp rax, 1
406    je 5f
407    mov dword ptr [r12 + 84], 2
408    jmp .Larm_finish
4095:
410    movzx eax, byte ptr [r12 + 80]
411    inc al
412    cmp al, byte ptr [r12 + 81]
413    je .Larm_save
414    mov dword ptr [r12 + 84], 3
415    jmp .Larm_finish
416.Larm_exit:
417    rdtscp
418    shl rdx, 32
419    or rax, rdx
420    mov r13, rax
421    xor eax, eax
422    xor ecx, ecx
423    cpuid
424    rdtscp
425    shl rdx, 32
426    or rax, rdx
427    sub rax, r13
428    mov [r12 + 72], rax
429
430.Larm_save:
431    mov rax, [r12 + 16]
432    mov rdx, rax
433    shr rdx, 32
434    mov r8, [r12 + 24]
435    xsave [r8]
436    XINUSE_RDPKRU r12, 82
437    mov [r12 + 64], eax
438
439.Larm_finish:
440    XINUSE_RESTORE_PKRU r12, 82, 88
441    ldmxcsr dword ptr [r12 + 92]
442    fninit
443    vzeroupper
444    mov eax, [r12 + 84]
445    pop r13
446    pop r12
447    pop rbx
448    ret
449    .size hermit_xinuse_arm, .-hermit_xinuse_arm
450
451    .globl hermit_xinuse_peer_loop
452    .type hermit_xinuse_peer_loop,@function
453hermit_xinuse_peer_loop:
454    push rbx
455    push r12
456    push r13
457    mov r12, rdi
458    XINUSE_RDPKRU r12, 66
459    mov [r12 + 72], eax
460    stmxcsr dword ptr [r12 + 76]
461.Lpeer_round:
462    xor eax, eax
463    movsxd rdi, dword ptr [r12 + 4]
464    lea rsi, [r12 + 64]
465    mov edx, 1
466    syscall
467    cmp rax, 1
468    je 6f
469    mov dword ptr [r12 + 56], 2
470    jmp .Lpeer_finish
4716:
472    cmp byte ptr [r12 + 64], 0xff
473    je .Lpeer_finish
474
475    mov eax, [r12 + 0]
476    cmp eax, 0
477    je .Lpeer_dirty_differs
478    cmp eax, 1
479    je .Lpeer_dirty_wrpkru
480    cmp eax, 2
481    je .Lpeer_xrstor_all
482    cmp eax, 3
483    je .Lpeer_header_loaded
484    cmp eax, 4
485    je .Lpeer_sigreturn
486    jmp .Lpeer_init_values
487
488.Lpeer_dirty_differs:
489    XINUSE_DIRTY r12, 12, 32
490    XINUSE_WRPKRU 0x55555550, r12, 66
491    jmp .Lpeer_reply
492.Lpeer_dirty_wrpkru:
493    XINUSE_DIRTY r12, 12, 32
494    XINUSE_WRPKRU 0, r12, 66
495    jmp .Lpeer_reply
496.Lpeer_xrstor_all:
497    XINUSE_WRPKRU 0x55555550, r12, 66
498    mov r13, [r12 + 24]
499    XINUSE_XRSTOR_MASK r12, 16, r13
500    jmp .Lpeer_reply
501.Lpeer_header_loaded:
502    XINUSE_DIRTY r12, 12, 32
503    mov r13, 0x200
504    XINUSE_XRSTOR_MASK r12, 16, r13
505    jmp .Lpeer_reply
506.Lpeer_sigreturn:
507    XINUSE_DIRTY r12, 12, 32
508    XINUSE_WRPKRU 0, r12, 66
509    mov eax, 234
510    movsxd rdi, dword ptr [r12 + 40]
511    movsxd rsi, dword ptr [r12 + 44]
512    mov edx, 10
513    syscall
514    test rax, rax
515    jz .Lpeer_reply
516    mov dword ptr [r12 + 56], 4
517    jmp .Lpeer_finish
518.Lpeer_init_values:
519    mov r13, [r12 + 24]
520    XINUSE_XRSTOR_MASK r12, 16, r13
521
522.Lpeer_reply:
523    mov eax, 309
524    lea rdi, [r12 + 68]
525    xor esi, esi
526    xor edx, edx
527    syscall
528    mov eax, [r12 + 68]
529    cmp eax, [r12 + 48]
530    je 7f
531    inc dword ptr [r12 + 52]
5327:
533    inc dword ptr [r12 + 60]
534    movzx eax, byte ptr [r12 + 64]
535    inc al
536    mov [r12 + 65], al
537    mov eax, 1
538    movsxd rdi, dword ptr [r12 + 8]
539    lea rsi, [r12 + 65]
540    mov edx, 1
541    syscall
542    cmp rax, 1
543    je .Lpeer_round
544    mov dword ptr [r12 + 56], 1
545
546.Lpeer_finish:
547    XINUSE_RESTORE_PKRU r12, 66, 72
548    ldmxcsr dword ptr [r12 + 76]
549    fninit
550    vzeroupper
551    mov eax, [r12 + 56]
552    pop r13
553    pop r12
554    pop rbx
555    ret
556    .size hermit_xinuse_peer_loop, .-hermit_xinuse_peer_loop
557
558    .globl hermit_xinuse_save_restore_cycles
559    .type hermit_xinuse_save_restore_cycles,@function
560hermit_xinuse_save_restore_cycles:
561    push rbx
562    mov r8, rdi
563    mov r9, rsi
564    rdtscp
565    shl rdx, 32
566    or rax, rdx
567    mov r10, rax
568    mov rax, r9
569    mov rdx, r9
570    shr rdx, 32
571    xsave [r8]
572    mov rax, r9
573    mov rdx, r9
574    shr rdx, 32
575    xrstor [r8]
576    rdtscp
577    shl rdx, 32
578    or rax, rdx
579    sub rax, r10
580    vzeroupper
581    pop rbx
582    ret
583    .size hermit_xinuse_save_restore_cycles, .-hermit_xinuse_save_restore_cycles
584
585    .globl hermit_xinuse_cpuid_cycles
586    .type hermit_xinuse_cpuid_cycles,@function
587hermit_xinuse_cpuid_cycles:
588    push rbx
589    rdtscp
590    shl rdx, 32
591    or rax, rdx
592    mov r8, rax
593    xor eax, eax
594    xor ecx, ecx
595    cpuid
596    rdtscp
597    shl rdx, 32
598    or rax, rdx
599    sub rax, r8
600    pop rbx
601    ret
602    .size hermit_xinuse_cpuid_cycles, .-hermit_xinuse_cpuid_cycles
603
604    .globl hermit_xinuse_syscall_cycles
605    .type hermit_xinuse_syscall_cycles,@function
606hermit_xinuse_syscall_cycles:
607    rdtscp
608    shl rdx, 32
609    or rax, rdx
610    mov r8, rax
611    mov eax, 110
612    syscall
613    rdtscp
614    shl rdx, 32
615    or rax, rdx
616    sub rax, r8
617    ret
618    .size hermit_xinuse_syscall_cycles, .-hermit_xinuse_syscall_cycles
619"#
620);
621
622#[cfg(test)]
623mod tests {
624    use std::mem::offset_of;
625
626    use super::*;
627
628    #[test]
629    fn arm_ctx_layout_matches_the_assembly() {
630        assert_eq!(offset_of!(ArmCtx, kind), 0);
631        assert_eq!(offset_of!(ArmCtx, component), 4);
632        assert_eq!(offset_of!(ArmCtx, mxcsr), 8);
633        assert_eq!(offset_of!(ArmCtx, mode), 12);
634        assert_eq!(offset_of!(ArmCtx, xcr0), 16);
635        assert_eq!(offset_of!(ArmCtx, save_area), 24);
636        assert_eq!(offset_of!(ArmCtx, image), 32);
637        assert_eq!(offset_of!(ArmCtx, fd_to_peer), 40);
638        assert_eq!(offset_of!(ArmCtx, fd_from_peer), 44);
639        assert_eq!(offset_of!(ArmCtx, tgid), 48);
640        assert_eq!(offset_of!(ArmCtx, tid), 52);
641        assert_eq!(offset_of!(ArmCtx, ones), 56);
642        assert_eq!(offset_of!(ArmCtx, pkru_out), 64);
643        assert_eq!(offset_of!(ArmCtx, avx512), 68);
644        assert_eq!(offset_of!(ArmCtx, cpuid_cycles), 72);
645        assert_eq!(offset_of!(ArmCtx, seq), 80);
646        assert_eq!(offset_of!(ArmCtx, pkru), 82);
647        assert_eq!(offset_of!(ArmCtx, reply), 81);
648        assert_eq!(offset_of!(ArmCtx, status), 84);
649        assert_eq!(offset_of!(ArmCtx, saved_pkru), 88);
650        assert_eq!(offset_of!(ArmCtx, saved_mxcsr), 92);
651    }
652
653    #[test]
654    fn peer_ctx_layout_matches_the_assembly() {
655        assert_eq!(offset_of!(PeerCtx, state), 0);
656        assert_eq!(offset_of!(PeerCtx, fd_in), 4);
657        assert_eq!(offset_of!(PeerCtx, fd_out), 8);
658        assert_eq!(offset_of!(PeerCtx, avx512), 12);
659        assert_eq!(offset_of!(PeerCtx, image), 16);
660        assert_eq!(offset_of!(PeerCtx, xcr0), 24);
661        assert_eq!(offset_of!(PeerCtx, ones), 32);
662        assert_eq!(offset_of!(PeerCtx, tgid), 40);
663        assert_eq!(offset_of!(PeerCtx, tid), 44);
664        assert_eq!(offset_of!(PeerCtx, expected_cpu), 48);
665        assert_eq!(offset_of!(PeerCtx, wrong_cpu), 52);
666        assert_eq!(offset_of!(PeerCtx, status), 56);
667        assert_eq!(offset_of!(PeerCtx, rounds), 60);
668        assert_eq!(offset_of!(PeerCtx, byte), 64);
669        assert_eq!(offset_of!(PeerCtx, reply), 65);
670        assert_eq!(offset_of!(PeerCtx, pkru), 66);
671        assert_eq!(offset_of!(PeerCtx, cpu_out), 68);
672        assert_eq!(offset_of!(PeerCtx, saved_pkru), 72);
673        assert_eq!(offset_of!(PeerCtx, saved_mxcsr), 76);
674    }
675}