Fixture 95

function pointer table

C · 2 functions · 4 lanes · 7 of 8 function-lanes behave identically

One lane has a function that returns a different result after decompilation: gcc-O2 (1/2).

An array of function pointers is an indirect call whose target is a loaded value, not a relocation. Recovering the dispatch means recovering the table's contents, its stride, and the bounds check that guards it.

tests/decompiler_fixtures/src/95_function_pointer_table.c source
#include <stdint.h>

/* An array of function pointers is an indirect call whose target is a loaded
 * value, not a relocation. Recovering the dispatch means recovering the table's
 * contents, its stride, and the bounds check that guards it. */

static int32_t op_add(int32_t a, int32_t b) { return (int32_t)((uint32_t)a + (uint32_t)b); }
static int32_t op_sub(int32_t a, int32_t b) { return (int32_t)((uint32_t)a - (uint32_t)b); }
static int32_t op_and(int32_t a, int32_t b) { return a & b; }
static int32_t op_xor(int32_t a, int32_t b) { return a ^ b; }
static int32_t op_max(int32_t a, int32_t b) { return a > b ? a : b; }

typedef int32_t (*BinaryOp)(int32_t, int32_t);

static BinaryOp const OPERATIONS[5] = {op_add, op_sub, op_and, op_xor, op_max};

__attribute__((noinline)) int32_t
dispatch_operation(int32_t which, int32_t a, int32_t b) {
    if (which < 0 || which >= 5) {
        return -1;
    }
    return OPERATIONS[which](a, b);
}

__attribute__((noinline)) int32_t
fold_operations(const int32_t *selectors, int32_t count, int32_t seed) {
    int32_t accumulator = seed;
    int32_t index;
    if (selectors == 0 || count < 0 || count > 16) {
        return -1;
    }
    for (index = 0; index < count; ++index) {
        int32_t which = selectors[index];
        if (which < 0 || which >= 5) {
            continue;
        }
        accumulator = OPERATIONS[which](accumulator, index + 1);
    }
    return accumulator;
}

Recovered C

Generated by glaurung decompile --style decbench at b47f6b43. baseline.json records the result after recompiling the C and calling it beside the original with seeded inputs.

gcc -O2

1/2
dispatch_operation pass 27 lines
// glaurung: dispatch_operation @ 0x1150
int32_t dispatch_operation(int32_t arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int ret;
    long var0;
    long var1;
    long var2;
    var0 = (long)(arg0);
    var1 = (unsigned long)((unsigned int)(arg1));
    var2 = (unsigned long)((unsigned int)(arg2));
    if (((unsigned long)((unsigned long)((unsigned int)(arg0))) <= (unsigned long)(4))) {
        ret = ((int (*)(long, long))(OPERATIONS[var0]))(var1, var2);
        return ret;
    }
    return 0xffffffff;
}
fold_operations fail 66 lines
// glaurung: fold_operations @ 0x1180
int32_t fold_operations(const int32_t * arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int which;
    int accumulator;
    int index;
    long local_10;
    long local_20;
    long local_8;
    long var0;
    long var1;
    long var12;
    long var14;
    long var2;
    long var3;
    long var4;
    long var6;
    long var9;
    if ((arg0 == 0)) {
        return (unsigned int)(-1);
    }
    local_8 = var0;
    local_10 = var1;
    var2 = (unsigned long)((unsigned int)(arg1));
    local_20 = var3;
    if (((unsigned long)(16) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
        goto L_11ef;
    }
    if (((unsigned long)((unsigned int)(arg1)) == 0)) {
        goto L_11e0;
    }
    var4 = (long)arg0;
    var6 = 0;
    var9 = (unsigned long)((unsigned int)(arg2));
    do {
        which = (long)((int)(*(int *)((var4))));
        var6 = (unsigned long)((unsigned int)((var6 + 1)));
        var12 = var9;
        if (((unsigned long)((unsigned long)((unsigned int)(which))) <= (unsigned long)(4))) {
            var14 = ((long (*)(void))(OPERATIONS[which]))();
            var12 = (unsigned long)((unsigned int)(var14));
        }
        var4 = (var4 + 4);
        var9 = var12;
    } while (((unsigned int)(var2) != (unsigned int)(var6)));
    L_11ce: ;
    // x86-64 epilogue: tear down frame
    return (unsigned int)(var12);
    L_11e0: ;
    // x86-64 epilogue: tear down frame
    return (unsigned int)(arg2);
    L_11ef: ;
    var12 = 0xffffffff;
    goto L_11ce;
}

clang -O0

2/2
dispatch_operation pass 32 lines
// glaurung: dispatch_operation @ 0x1100
int32_t dispatch_operation(int32_t arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int local_4;
    long var3;
    // x86-64 prologue: save rbp, frame 16 bytes
    if (((long)(arg0) < 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if ((5 <= (long)(arg0))) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    var3 = ((long (*)(long, long))(OPERATIONS[(long)(arg0)]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
    local_4 = var3;
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}
fold_operations pass 55 lines
// glaurung: fold_operations @ 0x1160
int32_t fold_operations(const int32_t * arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int accumulator;
    int index;
    int which;
    int local_4;
    long var10;
    // x86-64 prologue: save rbp, frame 48 bytes
    accumulator = arg2;
    if ((arg0 == 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((long)(arg1) < 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((((unsigned long)((unsigned int)(arg1)) == 16) | ((long)(arg1) < 16)) == 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    index = 0;
    while ((index < arg1)) {
        which = arg0[(long)(index)];
        if (((long)(which) < 0)) {
            L_11d8: ;
        } else {
            if (((long)(which) < 5)) {
                var10 = ((long (*)(long, long))(OPERATIONS[(long)(which)]))((unsigned long)((unsigned int)(accumulator)), (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(index)) + 1))));
                accumulator = var10;
            } else {
                goto L_11d8;
            }
        }
        index = ((unsigned int)(index) + 1);
    }
    local_4 = accumulator;
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}

clang -O2

2/2
dispatch_operation pass 21 lines
// glaurung: dispatch_operation @ 0x1100
int32_t dispatch_operation(int32_t arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int ret;
    if (((unsigned long)((unsigned long)((unsigned int)(arg0))) <= (unsigned long)(4))) {
        ret = ((int (*)(long, long))(OPERATIONS[(unsigned long)((unsigned int)(arg0))]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
        return ret;
    }
    return 0xffffffff;
}
fold_operations pass 57 lines
// glaurung: fold_operations @ 0x1120
int32_t fold_operations(const int32_t * arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int index;
    int accumulator;
    long ret;
    long var0;
    long var10;
    long var11;
    long var12;
    long var2;
    long var3;
    long var8;
    // x86-64 prologue: save callee registers, frame 40 bytes
    var0 = 0xffffffff;
    ret = 0xffffffff;
    if ((arg0 == 0)) {
        // x86-64 epilogue: restore callee registers
        return ret;
    }
    ret = var0;
    if (((unsigned long)(16) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
        // x86-64 epilogue: restore callee registers
        return ret;
    }
    if (((unsigned long)((unsigned int)(arg1)) == 0)) {
        return (unsigned int)(arg2);
    }
    var2 = (long)arg0;
    var3 = (unsigned long)((unsigned int)(arg1));
    var8 = (unsigned long)((unsigned int)(arg2));
    index = 0;
    do {
        var10 = (unsigned long)((unsigned int)(*(int *)((var2 + index * 4))));
        index = (index + 1);
        var11 = var8;
        if (((unsigned long)(var10) < (unsigned long)(5))) {
            var12 = ((long (*)(long, long))(OPERATIONS[var10]))((unsigned long)((unsigned int)(var8)), (unsigned long)((unsigned int)(index)));
            var11 = var12;
        }
        var8 = var11;
        ret = var11;
    } while ((index != var3));
    // x86-64 epilogue: restore callee registers
    return ret;
}

gcc -O0

2/2
dispatch_operation pass 28 lines
// glaurung: dispatch_operation @ 0x1170
int32_t dispatch_operation(int32_t arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int ret;
    // x86-64 prologue: save rbp, frame 16 bytes
    if (((long)(arg0) < 0)) {
        // x86-64 epilogue: restore rbp
        return 0xffffffff;
    }
    if (((((unsigned long)((unsigned int)(arg0)) == 4) | ((long)(arg0) < 4)) == 0)) {
        // x86-64 epilogue: restore rbp
        return 0xffffffff;
    }
    ret = ((int (*)(long, long))(OPERATIONS[(long)((int)((unsigned long)((unsigned int)(arg0))))]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
    // x86-64 epilogue: restore rbp
    return ret;
}
fold_operations pass 43 lines
// glaurung: fold_operations @ 0x11be
int32_t fold_operations(const int32_t * arg0, int32_t arg1, int32_t arg2) {
    extern void op_add(void);
    extern void op_and(void);
    extern void op_max(void);
    extern void op_sub(void);
    extern void op_xor(void);
    static void (*OPERATIONS[5])(void) = {
        (void (*)(void))op_add,
        (void (*)(void))op_sub,
        (void (*)(void))op_and,
        (void (*)(void))op_xor,
        (void (*)(void))op_max,
    };
    int accumulator;
    int index;
    int which;
    long var16;
    // x86-64 prologue: save rbp, frame 32 bytes
    accumulator = arg2;
    if ((arg0 == 0)) {
        // x86-64 epilogue: restore rbp
        return 0xffffffff;
    }
    if (((long)(arg1) < 0)) {
        // x86-64 epilogue: restore rbp
        return 0xffffffff;
    }
    if (((((unsigned long)((unsigned int)(arg1)) == 16) | ((long)(arg1) < 16)) == 0)) {
        // x86-64 epilogue: restore rbp
        return 0xffffffff;
    }
    for (index = 0; (index < arg1); index++) {
        which = arg0[(long)(index)];
        if ((((long)(which) < 0) || ((((unsigned long)((unsigned int)(which)) == 4) | ((long)(which) < 4)) == 0))) {
        } else {
            var16 = ((long (*)(long, long))(OPERATIONS[(long)((int)((unsigned long)((unsigned int)(which))))]))((unsigned long)((unsigned int)(accumulator)), (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(index)) + 1))));
            accumulator = var16;
        }
    }
    // x86-64 epilogue: restore rbp
    return (unsigned int)(accumulator);
}

← 213 fixtures