Skip to content

Latest commit

 

History

History
135 lines (91 loc) · 5.07 KB

9_closure.md

File metadata and controls

135 lines (91 loc) · 5.07 KB

Closure

We conducted a study to clearly understand closure behavior and memory layouts to be used.

Study results

  • A closure implicitly receives as the first argument the address (referred to as "closure_env") of a stack region storing the address of a variable the closure captures. Even if it captures no variable, the first argument is closure_env.

  • A closure capturing a closure directly references the address of the closure's child closure it captures, not through closure_env of the parent closure.

  • Variables a child closure captures, however, are structured in a way the child's closure_env is built into the parent's closure_env.

Details

The sample program we used in this study is shown [later](#Sample program we used) in this document.

Basic closure

By building the sample program as a release version, we found, as shown in the figure below, that optimization embedded the value 15, which is the return value from sum(), in the binary. This means that we cannot identify a simple closure due to optimization.

closure

We debug built the sample program to avoid optimization and obtained a result (the sum function) as shown in the figure below. In general, for a 64-bit binary, the first, second and third arguments are stored in the rcx, rdx and r8 registers, respectively. By examining the arguments of the function described above, however, we can find that the values of 5 and 10, which are explicit arguments, are passed as the second and third arguments.

closure

The rcx register, supposed to store the first argument, stores the address of a stack named arguments::main::closure_env$0 * by IDA Pro. The stack address, which is passed to the first argument of the closure, should store the address closure_env of a variable the closure captures. In this program, however, the address to be passed to the first argument of the closure is not used because the closure captures no variable.

Closure capturing variable

Similar to the previous section, optimization made closures invisible, so we examined the closure through debug build. The figure below shows the location where add_to_x() was called in the debug build of the sample program. The program stores 0x0A(10), the value of a local variable x, in var_D4. Then, it stores the address of var_D4 in var_D0 which indicates closure_env. As shown here, the stack address passed to the first argument of the closure stores the address of a variable the closure has captured.

closure

In the inside of _ZN7capture4main28_$u7b$$u7b$closure$u7d$$u7d$17he4cd0740b21072e5E() of the closure, the value retrieved from closure_env is added to five, one of the explicit argument values. This processing is consistent with that of add_to_x().

closure

Closure capturing closure

Similar to the previous section, optimization made closures invisible, so we examined the closure through debug build. The location is shown below where double() was called in the debug build of the sample program:

closure

The value _ZN15capture_closure4main28_$u7b$$u7b$closure$u7d$$u7d$17hc3f3366177092b36E() is double(). Closure_env stores closure_env of user-input strings, add_one() and sub_one(). The structure of closure_env in this sample program is described below:

closure_env {
   0x00: User-input string
   0x08: add_one()のclosure_env
   0x10: sub_one()のclosure_env
}

At the location shown below, we call add_one() with closure_env + 0x08 specified for its first argument. You can see that the address of the struct described above is used as closure_env.

closure

Differences for 32-bit and size-minimized binaries

Steps necessary for 32-bit binaries are the same as those for 64-bit binaries, except how to pass arguments and the number of bytes in addresses. For size-minimized binaries, closures are inlined in all samples due to effects of optimization and simple calculation results are embedded in binaries.

Sample program we used

  • Basic closure
fn main() {
    let sum = |a: i32, b: i32| -> i32 {
        a + b
    }

    println!("The sum of 5 and 10 is: {}", sum(5, 10));
}
  • Closure capturing variable
fn main() {
    let x = 10;

    let add_to_x = |y: i32| -> i32 {
        x + y
    };

    println!("The result of adding 5 to {} is: {}", x, add_to_x(5));
}
  • Closure capturing closure
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() != 2 {
        eprintln!("Usage: {} <add|sub>", args[0]);
        return;
    }

    let operation = &args[1];

    let add_one = |x: i32| x + 1;
    let sub_one = |x: i32| x - 1;

    let double = |y: i32| {
        match operation.as_str() {
            "add" => add_one(y) * 2,
            "sub" => sub_one(y) * 2,
            _ => {
                eprintln!("Invalid operation. Use 'add' or 'sub'.");
                std::process::exit(1);
            }
        }
    };

    let input_value = 3;

    println!("The result is: {}", double(input_value));
}