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

Result

Source

Initialize a new workspace with cargo init --lib.

#![allow(unused)]
fn main() {
pub enum MathError {
    DivisionByZero,
    Overflow,
}

#[unsafe(no_mangle)]
pub fn divide_with_str_error(dividend: i32, divisor: i32) -> Result<i32, &'static str> {
    if divisor == 0 {
        Err("Division by zero")
    } else {
        Ok(dividend / divisor)
    }
}

#[unsafe(no_mangle)]
pub fn divide_with_enum_error(a: i32, b: i32) -> Result<i32, MathError> {
    if b == 0 {
        return Err(MathError::DivisionByZero);
    }

    if a == i32::MIN && b == -1 {
        return Err(MathError::Overflow);
    }

    Ok(a / b)
}

#[unsafe(no_mangle)]
pub fn process_result_str_error(value: Result<i32, &str>) -> i32 {
    match value {
        Ok(x) => x * 2,
        Err(_) => -1,
    }
}

#[unsafe(no_mangle)]
pub fn process_result_box_enum(value: Result<Box<i32>, MathError>) -> i32 {
    match value {
        Err(MathError::DivisionByZero) => -1,
        Err(MathError::Overflow) => -2,
        Ok(value) => *value,
    }
}
}

The Result type is described in the official docs in detail. We are using no_mangle to simplify things, the reason can be found here.

Build

$ cargo rustc --release -- --emit obj

Ghidra

Load the .o file (located at target/aarch64-unknown-linux-musl/release/deps/) into Ghidra and auto-analyze it.

Layout

Result is an enum type which is conceptually a tagged union with a discriminant and data (refer to chapter enum for more information about enums). The layout is unspecified, which similarly to Option enables certain niche optimizations.

divide_with_str_error

#![allow(unused)]
fn main() {
#[unsafe(no_mangle)]
pub fn divide_with_str_error(dividend: i32, divisor: i32) -> Result<i32, &'static str> {
    if divisor == 0 {
        Err("Division by zero")
    } else {
        Ok(dividend / divisor)
    }
}
}

If we look at the layout, we see that there is no discriminant field. Since the compiler knows that references always point to valid locations, it can use the null value as a discriminant. &str is a fat reference (16 bytes: pointer + length), therefore variant Err is 16 bytes.

$ cargo rustc --release --quiet -- -Z print-type-sizes
...
print-type-size type: `std::result::Result<i32, &str>`: 16 bytes, alignment: 8 bytes
print-type-size     variant `Err`: 16 bytes
print-type-size         field `.0`: 16 bytes
print-type-size     variant `Ok`: 12 bytes
print-type-size         padding: 8 bytes
print-type-size         field `.0`: 4 bytes, alignment: 4 bytes
...

Before looking at the assembly, we must mention that the compiler automatically generates a check that causes a panic if the result would overflow. For i32, there is only one such scenario: dividend is i32::MIN and divisor is -1.

Listing:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined divide_with_str_error()
             undefined         <UNASSIGNED>   <RETURN>
             undefined8        Stack[-0x10]:8 local_10                                XREF[1]:     0010003c(W)  
                             check if divisor is 0
                             divide_with_str_error                           XREF[4]:     Entry Point(*), 0010015c(*), 
                                                                                          _elfSectionHeaders::00000090(*), 
                                                                                          _elfSectionHeaders::000000d0(*)  
        00100000 41 01 00 34     cbz        w1,LAB_00100028
                             i32::MIN
        00100004 09 00 b0 52     mov        w9,#0x80000000
                             check if dividend is i32::MIN
        00100008 1f 00 09 6b     cmp        w0,w9
        0010000c 61 00 00 54     b.ne       LAB_00100018
                             check if divisor is -1
        00100010 3f 04 00 31     cmn        w1,#0x1
        00100014 40 01 00 54     b.eq       LAB_0010003c
                             LAB_00100018                                    XREF[1]:     0010000c(j)  
        00100018 09 0c c1 1a     sdiv       w9,w0,w1
                             Ok
        0010001c 1f 01 00 f9     str        xzr,[x8]
        00100020 09 09 00 b9     str        w9,[x8, #0x8]
        00100024 c0 03 5f d6     ret
                             LAB_00100028                                    XREF[1]:     00100000(j)  
        00100028 09 00 00 90     adrp       x9,0x100000
        0010002c 29 21 04 91     add        x9,x9,#0x108
                             length of "Division by zero"
        00100030 0a 02 80 52     mov        w10,#0x10
                             Err
        00100034 09 29 00 a9     stp        x9=>s_Division_by_zero_00100108,x10,[x8]         = "Division by zero"
        00100038 c0 03 5f d6     ret
                             LAB_0010003c                                    XREF[1]:     00100014(j)  
        0010003c fd 7b bf a9     stp        x29,x30,[sp, #local_10]!
        00100040 fd 03 00 91     mov        x29,sp
        00100044 00 00 00 90     adrp       x0,0x100000
        00100048 00 a0 04 91     add        x0=>PTR_DAT_00100128,x0,#0x128                   = 00100118
        0010004c ed 03 00 94     bl         <EXTERNAL>::core::panicking::panic_const::pani   undefined panic_const_div_overfl

divide_with_enum_error

#![allow(unused)]
fn main() {
pub enum MathError {
    DivisionByZero,
    Overflow,
}

#[unsafe(no_mangle)]
pub fn divide_with_enum_error(a: i32, b: i32) -> Result<i32, MathError> {
    if b == 0 {
        return Err(MathError::DivisionByZero);
    }

    if a == i32::MIN && b == -1 {
        return Err(MathError::Overflow);
    }

    Ok(a / b)
}
}

Here we have a discriminant which is either followed by the value (Ok) or the error type (Err).

$ cargo rustc --release --quiet -- -Z print-type-sizes
...
print-type-size type: `std::result::Result<i32, MathError>`: 8 bytes, alignment: 4 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `Ok`: 7 bytes
print-type-size         padding: 3 bytes
print-type-size         field `.0`: 4 bytes, alignment: 4 bytes
print-type-size     variant `Err`: 1 bytes
print-type-size         field `.0`: 1 bytes
...

Listing:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined divide_with_enum_error()
             undefined         <UNASSIGNED>   <RETURN>
                             check if divisor is 0
                             divide_with_enum_error                          XREF[3]:     Entry Point(*), 0010017c(*), 
                                                                                          _elfSectionHeaders::00000150(*)  
        00100050 41 01 00 34     cbz        w1,LAB_00100078
                             i32::MIN
        00100054 08 00 b0 52     mov        w8,#0x80000000
                             check if dividend is i32::MIN
        00100058 1f 00 08 6b     cmp        w0,w8
        0010005c 41 01 00 54     b.ne       LAB_00100084
                             check if divisor is -1
        00100060 3f 04 00 31     cmn        w1,#0x1
        00100064 01 01 00 54     b.ne       LAB_00100084
                             Overflow
        00100068 08 20 80 52     mov        w8,#0x100
                             Err
        0010006c 29 00 80 52     mov        w9,#0x1
                             Err(Overflow) encoded
        00100070 00 01 09 aa     orr        x0,x8,x9
        00100074 c0 03 5f d6     ret
                             Err
                             LAB_00100078                                    XREF[1]:     00100050(j)  
        00100078 29 00 80 52     mov        w9,#0x1
                             Err(DivisionByZero) encoded
        0010007c e0 03 09 aa     mov        x0,x9
        00100080 c0 03 5f d6     ret
                             LAB_00100084                                    XREF[2]:     0010005c(j), 00100064(j)  
        00100084 08 0c c1 1a     sdiv       w8,w0,w1
                             Ok (value is upper 4 bytes)
        00100088 08 7d 60 d3     lsl        x8,x8,#0x20
        0010008c 00 01 1f aa     orr        x0,x8,xzr
        00100090 c0 03 5f d6     ret

process_result_str_error

#![allow(unused)]
fn main() {
#[unsafe(no_mangle)]
pub fn process_result_str_error(value: Result<i32, &str>) -> i32 {
    match value {
        Ok(x) => x * 2,
        Err(_) => -1,
    }
}
}

The layout of the Result is the same as in divide_with_str_error. If the first 8 bytes are 0 it means Ok and the value is multiplied by 2 and returned, otherwise -1 is returned (wzr inverted: 0xffff).

Listing:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined process_result_str_error()
             undefined         <UNASSIGNED>   <RETURN>
                             process_result_str_error                        XREF[3]:     Entry Point(*), 00100190(*), 
                                                                                          _elfSectionHeaders::00000190(*)  
        00100094 08 08 40 b9     ldr        w8,[x0, #0x8]
        00100098 09 00 40 f9     ldr        x9,[x0]
        0010009c 08 79 1f 53     lsl        w8,w8,#0x1
        001000a0 3f 01 00 f1     cmp        x9,#0x0
        001000a4 00 01 9f 5a     csinv      w0,w8,wzr,eq
        001000a8 c0 03 5f d6     ret

process_result_box_enum

#![allow(unused)]
fn main() {
pub enum MathError {
    DivisionByZero,
    Overflow,
}

#[unsafe(no_mangle)]
pub fn process_result_box_enum(value: Result<Box<i32>, MathError>) -> i32 {
    match value {
        Err(MathError::DivisionByZero) => -1,
        Err(MathError::Overflow) => -2,
        Ok(value) => *value,
    }
}
}

Box<T> is a thin pointer pointing to a heap location, the memory is automatically deallocated by __rust_dealloc. For more information refer to chapter Box. The layout is similar to what we saw in the case of divide_with_enum_error. The difference is that the size of the Ok data is 8 bytes instead of 4.

$ cargo rustc --release --quiet -- -Z print-type-sizes
...
print-type-size type: `std::result::Result<std::boxed::Box<i32>, MathError>`: 16 bytes, alignment: 8 bytes
print-type-size     discriminant: 1 bytes
print-type-size     variant `Ok`: 15 bytes
print-type-size         padding: 7 bytes
print-type-size         field `.0`: 8 bytes, alignment: 8 bytes
print-type-size     variant `Err`: 1 bytes
print-type-size         field `.0`: 1 bytes
...

Listing:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined process_result_box_enum()
             undefined         <UNASSIGNED>   <RETURN>
             undefined8        Stack[-0x10]:8 local_10                                XREF[3]:     001000b0(W), 
                                                                                                   001000d8(R), 
                                                                                                   001000fc(R)  
             undefined8        Stack[-0x20]:8 local_20                                XREF[3]:     001000ac(W), 
                                                                                                   001000dc(*), 
                                                                                                   00100100(*)  
                             process_result_box_enum                         XREF[3]:     Entry Point(*), 001001a4(*), 
                                                                                          _elfSectionHeaders::000001d0(*)  
        001000ac fd 7b be a9     stp        x29,x30,[sp, #local_20]!
        001000b0 f3 0b 00 f9     str        x19,[sp, #local_10]
        001000b4 fd 03 00 91     mov        x29,sp
                             load discriminant
        001000b8 08 00 40 39     ldrb       w8,[x0]
                             Err
        001000bc 1f 05 00 71     cmp        w8,#0x1
        001000c0 21 01 00 54     b.ne       LAB_001000e4
        001000c4 08 04 40 39     ldrb       w8,[x0, #0x1]
                             DivisionByZero
        001000c8 1f 01 00 71     cmp        w8,#0x0
        001000cc 28 00 80 12     mov        w8,#0xfffffffe
                             if DivisionByZero: ret val is 0xfffffffe + 1 = -1,
                             otherwise (Overflow): 0xfffffffe = -2
        001000d0 13 15 88 1a     cinc       w19,w8,eq
        001000d4 e0 03 13 2a     mov        w0,w19
        001000d8 f3 0b 40 f9     ldr        x19,[sp, #local_10]
        001000dc fd 7b c2 a8     ldp        x29=>local_20,x30,[sp], #0x20
        001000e0 c0 03 5f d6     ret
                             arg0: ptr
                             LAB_001000e4                                    XREF[1]:     001000c0(j)  
        001000e4 00 04 40 f9     ldr        x0,[x0, #0x8]
                             arg1: size
        001000e8 81 00 80 52     mov        w1,#0x4
                             arg2: align
        001000ec 82 00 80 52     mov        w2,#0x4
                             save value
        001000f0 13 00 40 b9     ldr        w19,[x0]
        001000f4 c5 03 00 94     bl         <EXTERNAL>::__rustc[eb192786f4da5ea1]::__rust_   undefined __rust_dealloc()
        001000f8 e0 03 13 2a     mov        w0,w19
        001000fc f3 0b 40 f9     ldr        x19,[sp, #local_10]
        00100100 fd 7b c2 a8     ldp        x29=>local_20,x30,[sp], #0x20
        00100104 c0 03 5f d6     ret