Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

This exercise explores how Rust represents function trait objects at a low level. Specifically, we examine:

fn_trait: &(dyn Fn() -> i32 + Sync + RefUnwindSafe)

This is a trait object reference: a pointer to a function-like value that is thread-safe (Sync) and panic-safe (RefUnwindSafe).

source

Setting up the Trait Object

00007FF7C1D914B0 | 48:83EC 38       | sub rsp,38          ; allocate 56 bytes stack
00007FF7C1D914B4 | 48:8D4C24 36     | lea rcx,[rsp+36]   ; closure environment pointer (data)
00007FF7C1D914B9 | 48:8D15 687F0100 | lea rdx,[vtable]   ; vtable pointer
00007FF7C1D914D2 | E8 69FEFFFF      | call call_fn_trait  ; call the wrapper function

Loading the fat pointer

00007FF7C1D91425 | 48:8B4C24 20 | mov rcx,qword ptr ss:[rsp+20]  
00007FF7C1D9142A | 48:8B5424 28 | mov rdx,qword ptr ss:[rsp+28]  
00007FF7C1D9142F | FF52 28      | call qword ptr ds:[rdx+28]                                                        |

RCX = closure environment pointer (data) RDX = vtable pointer (vtable) call [rdx+28] = calls FnOnce::call_once via vtable.

Trait Object Layout

struct FnVtable {
    void* drop_in_place;     // offset 0x00
    unsigned long long size; // offset 0x08
    unsigned long long align;// offset 0x10
    void* call;              // offset 0x18 (Fn::call)
    void* call_mut;          // offset 0x20 (FnMut::call_mut)
    void* call_once;         // offset 0x28 (FnOnce::call_once)
};

struct FnTraitObject {
    void* data;              // 0x00 (closure environment)
    void* vtable;            // 0x08 (pointer to FnVtable)
};

Observing in x64dbg

Struct tab → Parse Header → Display Type

Define FnVtable

Map RCX → data and RDX → vtable.

Step into call_once via vtable to see the constant returned.

Return value appears in RAX:

Closure Execution void* call

00007FF7C1D911C0 | 48:83EC 38                       | sub rsp,38                                                   | function.rs:250
00007FF7C1D911C4 | 48:894C24 30                     | mov qword ptr ss:[rsp+30],rcx                                |
00007FF7C1D911C9 | E8 32000000                      | call <fn_trait_object.core::ops::function::FnOnce::call_once |
00007FF7C1D911CE | 90                               | nop                                                          |
00007FF7C1D911CF | 48:83C4 38                       | add rsp,38                                                   |
00007FF7C1D911D3 | C3                               | ret                                                          |                            |

Closure Execution void* call_mut, void* call_once

00007FF7C1D91500 | 50                               | push rax                                                     | main.rs:18
00007FF7C1D91501 | 48:890C24                        | mov qword ptr ss:[rsp],rcx                                   |
00007FF7C1D91505 | B8 87D61200                      | mov eax,12D687                                               |
00007FF7C1D9150A | 59                               | pop rcx                                                      |
00007FF7C1D9150B | C3                               | ret                                                          |                                                       |
#![allow(unused)]
fn main() {
let closure = || 1234567;
}

The closure simply loads the constant 1234567 into RAX

Captured variables example

#![allow(unused)]
fn main() {
let a = 1234567i32;
let b = 99u64;
let closure = || {
    a + b as i32
};
}

It compiles the closure into a struct-like environment containing these captured values.

This environment is passed as a pointer (RCX) to the generated closure code when called via a trait object.

00007FF7FA7914C0 | 48:83EC 48           | sub rsp,48           ; allocate 72 bytes for closure
00007FF7FA7914C4 | C74424 2C 87D61200   | mov dword ptr [rsp+2C],12D687  ; store 'a' (1234567)
00007FF7FA7914CC | 48:C74424 30 63000000| mov qword ptr [rsp+30],63      ; store 'b' (99)
00007FF7FA7914D5 | 48:8D4424 2C         | lea rax,[rsp+2C]    ; load address of 'a'
00007FF7FA7914DA | 48:894424 38         | mov [rsp+38],rax    ; save pointer in closure env
00007FF7FA7914DF | 48:8D4424 30         | lea rax,[rsp+30]    ; load address of 'b'
00007FF7FA7914E4 | 48:894424 40         | mov [rsp+40],rax    ; save pointer in closure env

[rsp+38] = pointer to a

[rsp+40] = pointer to b

This closure environment is what RCX will point to when the closure is called.

Inside the Closure

00007FF7FA791544 | 48:894C24 30 | mov [rsp+30], rcx

Store RCX, which points to the closure environment, on the stack at [rsp+30].

00007FF7FA791549 | 48:8B4424 30 | mov rax, [rsp+30]
00007FF7FA79154E | 48:8B00      | mov rax, [rax]
00007FF7FA791551 | 48:894424 38 | mov [rsp+38], rax

Load the closure environment pointer from [rsp+30] into RAX.

Dereference it ([rax]) to get the first vtable pointer (often drop_in_place).

Store this in [rsp+38] — this is now the first field of the fat pointer/closure struct.

00007FF7FA791556 | 48:8B4424 30 | mov rax, [rsp+30]
00007FF7FA79155B | 48:8B40 08   | mov rax, [rax+8]
00007FF7FA79155F | 48:894424 40 | mov [rsp+40], rax

Reload closure env pointer from [rsp+30] into RAX.

Dereference [rax+8] — second field of the environment (often the pointer to captured data).

Store at [rsp+40].

00007FF7FA791564 | 48:8B01         | mov rax,[rcx]       ; load pointer to 'a'
00007FF7FA791567 | 8B00            | mov eax,[rax]       ; load value of 'a'
00007FF7FA791569 | 48:8B49 08      | mov rcx,[rcx+8]     ; load pointer to 'b'
00007FF7FA79156D | 48:8B09         | mov rcx,[rcx]       ; load value of 'b'
00007FF7FA791570 | 01C8            | add eax,ecx         ; perform addition a + b

RCX points to the closure environment.

[RCX] = pointer to a, [RCX+8] = pointer to b.

mov eax,[rax] and mov rcx,[rcx] dereference the pointers to get the actual values.

add eax, ecx computes the closure result.