Fixture 157

symbol visibility

C · 5 functions · 4 lanes · 8 of 20 function-lanes behave identically

4 of 4 lanes have a function that returns a different result after decompilation: clang-O0 (2/5), clang-O2 (2/5), gcc-O0 (2/5), gcc-O2 (2/5).

Visibility is the knob that decides whether a symbol reaches .dynsym at all, and therefore how code and data inside a shared object address themselves.

A default-visibility global variable defined in this object is still preemptable: another object earlier in the lookup scope may define the same name, and an executable that links against this library gets a copy relocation. So -fPIC code cannot reach it PC-relatively; it loads the object's address out of the GOT first (mov sym@GOTPCREL(%rip),%rax then a load through %rax) - two memory accesses to read one int.

A hidden symbol can never be preempted and is not exported at all, so the compiler addresses it with a single PC-relative access (mov sym(%rip),%eax) and calls hidden functions directly rather than through the PLT.

This is a decompiler problem in three ways. The GOT-indirect load looks like a pointer dereference of a pointer variable that does not exist in the source, so a naive recovery invents one and reports *(*got_slot) where the source says bias. The name is only recoverable from the R_X86_64_GLOB_DAT / GOTPCREL relocation, not from the instruction. And a hidden function has no dynamic symbol at all, so its name survives only in .symtab, which a stripped object does not have - the same call is vis157_hidden_helper here and an anonymous sub_XXXX after strip.

tests/decompiler_fixtures/src/157_symbol_visibility.c source
#include <stdint.h>

/* Visibility is the knob that decides whether a symbol reaches .dynsym at all,
 * and therefore how code and data inside a shared object address themselves.
 *
 * A default-visibility global variable defined in this object is still
 * preemptable: another object earlier in the lookup scope may define the same
 * name, and an executable that links against this library gets a copy
 * relocation. So -fPIC code cannot reach it PC-relatively; it loads the
 * object's address out of the GOT first (`mov sym@GOTPCREL(%rip),%rax` then a
 * load through %rax) - two memory accesses to read one int.
 *
 * A hidden symbol can never be preempted and is not exported at all, so the
 * compiler addresses it with a single PC-relative access (`mov sym(%rip),%eax`)
 * and calls hidden functions directly rather than through the PLT.
 *
 * This is a decompiler problem in three ways. The GOT-indirect load looks like
 * a pointer dereference of a pointer variable that does not exist in the
 * source, so a naive recovery invents one and reports `*(*got_slot)` where the
 * source says `bias`. The name is only recoverable from the R_X86_64_GLOB_DAT /
 * GOTPCREL relocation, not from the instruction. And a hidden function has no
 * dynamic symbol at all, so its name survives only in .symtab, which a stripped
 * object does not have - the same call is `vis157_hidden_helper` here and an
 * anonymous sub_XXXX after `strip`.
 */

/* Default visibility: exported, preemptable, reached through the GOT. */
int32_t vis_public_bias = 11;

/* Hidden: absent from .dynsym, reached PC-relatively. */
__attribute__((visibility("hidden"))) int32_t vis157_hidden_bias = 5;

/* Hidden function: direct call, no PLT entry, no dynamic symbol. */
__attribute__((visibility("hidden"))) __attribute__((noinline)) int32_t
vis157_hidden_helper(int32_t value) {
    return (int32_t)((uint32_t)value ^ 0x5a5au);
}

/* Same body, default visibility: exported, so the sibling call below is an
 * interposable PLT call. */
__attribute__((noinline)) int32_t vis_public_helper(int32_t value) {
    return (int32_t)((uint32_t)value ^ 0x5a5au);
}

/* Writes both globals: one store through a GOT-loaded address, one store to a
 * PC-relative address. The two are kept complementary so the sum is a constant
 * 100 for every input, which no overflow can perturb (|clamped| <= 100). */
__attribute__((noinline)) int32_t vis_set_biases(int32_t value) {
    int32_t clamped = value;
    if (clamped < -100) {
        clamped = -100;
    }
    if (clamped > 100) {
        clamped = 100;
    }
    vis_public_bias = clamped;
    vis157_hidden_bias = 100 - clamped;
    return vis_public_bias + vis157_hidden_bias;
}

/* Reads them back through the two different addressing modes. */
__attribute__((noinline)) int32_t vis_read_bias(int32_t selector) {
    if (selector == 0) {
        return vis_public_bias;
    }
    if (selector == 1) {
        return vis157_hidden_bias;
    }
    return (int32_t)((uint32_t)vis_public_bias + (uint32_t)vis157_hidden_bias);
}

/* Both call shapes in one body. The helpers are identical, so the difference is
 * 0 for every input and the verdict depends only on whether both calls were
 * recovered as calls to the right thing. */
__attribute__((noinline)) int32_t vis_call_both(int32_t value) {
    uint32_t hidden = (uint32_t)vis157_hidden_helper(value);
    uint32_t exported = (uint32_t)vis_public_helper(value);
    return (int32_t)(hidden - exported);
}

/* A loop whose body touches a GOT-addressed global, a PC-relative global and a
 * hidden call: at -O2 the GOT load is hoisted out of the loop, which is exactly
 * the shape that tempts a recovery to invent a loop-invariant pointer local. */
__attribute__((noinline)) int32_t
vis_fold_with_biases(const int32_t *values, int32_t count) {
    uint32_t accumulator = 0u;
    int32_t index;
    if (values == 0 || count < 0 || count > 16) {
        return -1;
    }
    for (index = 0; index < count; ++index) {
        uint32_t item = (uint32_t)vis157_hidden_helper(values[index]);
        accumulator += item + (uint32_t)vis_public_bias +
                       (uint32_t)vis157_hidden_bias;
    }
    return (int32_t)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.

clang -O0

2/5
vis_call_both pass 16 lines
// glaurung: vis_call_both @ 0x1210
int32_t vis_call_both(int32_t arg0) {
    extern int vis157_hidden_helper(int);
    extern int vis_public_helper(int);
    unsigned int hidden;
    unsigned int exported;
    int var0;
    int var2;
    // x86-64 prologue: save rbp, frame 16 bytes
    var0 = vis157_hidden_helper((unsigned long)((unsigned int)(arg0)));
    hidden = var0;
    var2 = vis_public_helper((unsigned long)((unsigned int)(arg0)));
    exported = var2;
    // x86-64 epilogue: restore rbp
    return (unsigned int)(((unsigned long)(hidden) - exported));
}
vis_fold_with_biases fail 38 lines
// glaurung: vis_fold_with_biases @ 0x1240
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_fold_with_biases(const int32_t * arg0, int32_t arg1) {
    extern int vis157_hidden_helper(int);
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    unsigned int accumulator;
    int index;
    unsigned int item;
    int local_4;
    int var3;
    // x86-64 prologue: save rbp, frame 32 bytes
    accumulator = 0;
    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);
    }
    for (index = 0; (index < arg1); index++) {
        var3 = vis157_hidden_helper((unsigned long)((unsigned int)(arg0[(long)(index)])));
        item = var3;
        accumulator = ((unsigned int)(((unsigned long)((unsigned int)((item + *(int *)(*(long *)(&glaurung_global_3fe8[0]))))) + *(int *)(&glaurung_global_402c[0]))) + accumulator);
    }
    local_4 = accumulator;
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}
vis_public_helper pass 6 lines
// glaurung: vis_public_helper @ 0x1130
int32_t vis_public_helper(int32_t arg0) {
    // x86-64 prologue: save rbp
    // x86-64 epilogue: restore rbp
    return (unsigned int)(((unsigned long)((unsigned int)(arg0)) ^ 0x5a5a));
}
vis_read_bias fail 17 lines
// glaurung: vis_read_bias @ 0x11b0
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_read_bias(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    // x86-64 prologue: save rbp
    if (((unsigned long)((unsigned int)(arg0)) != 0)) {
        if (((unsigned long)((unsigned int)(arg0)) != 1)) {
            return (unsigned int)(((unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0])))) + *(int *)(&glaurung_global_402c[0])));
        } else {
            return (unsigned int)(*(int *)(&glaurung_global_402c[0]));
        }
    } else {
        return (unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0])));
    }
}
vis_set_biases fail 20 lines
// glaurung: vis_set_biases @ 0x1150
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_set_biases(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    int clamped;
    // x86-64 prologue: save rbp
    clamped = arg0;
    if (((long)(clamped) < -100)) {
        clamped = -100;
    }
    if (((((unsigned long)((unsigned int)(clamped)) == 100) | ((long)(clamped) < 100)) == 0)) {
        clamped = 100;
    }
    *(int *)(*(long *)(&glaurung_global_3fe8[0])) = clamped;
    *(int *)(&glaurung_global_402c[0]) = (100 - clamped);
    // x86-64 epilogue: restore rbp
    return (unsigned int)(((unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0])))) + *(int *)(&glaurung_global_402c[0])));
}

clang -O2

2/5
vis_call_both pass 18 lines
// glaurung: vis_call_both @ 0x1190
int32_t vis_call_both(int32_t arg0) {
    extern int vis157_hidden_helper(int);
    extern int vis_public_helper(int);
    unsigned int exported;
    unsigned int hidden;
    long var0;
    int var1;
    long var3;
    int var4;
    // x86-64 prologue: save callee registers, frame 24 bytes
    var0 = (unsigned long)((unsigned int)(arg0));
    var1 = vis157_hidden_helper(arg0);
    var3 = (unsigned long)((unsigned int)(var1));
    var4 = vis_public_helper((unsigned long)((unsigned int)(var0)));
    // x86-64 epilogue: restore callee registers
    return (unsigned int)((var3 - var4));
}
vis_fold_with_biases fail 49 lines
// glaurung: vis_fold_with_biases @ 0x11b0
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_fold_with_biases(const int32_t * arg0, int32_t arg1) {
    extern int vis157_hidden_helper(int);
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    unsigned int accumulator;
    int index;
    unsigned int item;
    long ret;
    long var0;
    int var13;
    int var19;
    long var3;
    long var5;
    long var6;
    long var7;
    // x86-64 prologue: save callee registers, frame 56 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 0;
    }
    var3 = (long)arg0;
    var5 = (unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0]))));
    var6 = (unsigned long)((unsigned int)(*(int *)(&glaurung_global_402c[0])));
    var7 = (unsigned long)((unsigned int)(arg1));
    accumulator = 0;
    index = 0;
    do {
        var13 = vis157_hidden_helper((unsigned long)((unsigned int)(*(int *)((var3 + index * 4)))));
        var19 = ((unsigned int)(((unsigned long)((unsigned int)((var13 + accumulator))) + var5)) + var6);
        ret = (unsigned long)((unsigned int)(var19));
        index = (index + 1);
        accumulator = (unsigned long)((unsigned int)(var19));
    } while ((var7 != index));
    // x86-64 epilogue: restore callee registers
    return ret;
}
vis_public_helper pass 4 lines
// glaurung: vis_public_helper @ 0x1120
int32_t vis_public_helper(int32_t arg0) {
    return (unsigned int)(((unsigned long)((unsigned int)(arg0)) ^ 0x5a5a));
}
vis_read_bias fail 14 lines
// glaurung: vis_read_bias @ 0x1160
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_read_bias(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    if (((unsigned long)((unsigned int)(arg0)) == 1)) {
        return (unsigned int)(*(int *)(&glaurung_global_402c[0]));
    }
    if (((unsigned long)((unsigned int)(arg0)) != 0)) {
        return (unsigned int)(((unsigned long)((unsigned int)(*(int *)(&glaurung_global_402c[0]))) + *(int *)(*(long *)(&glaurung_global_3fe8[0]))));
    }
    return (unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0])));
}
vis_set_biases fail 15 lines
// glaurung: vis_set_biases @ 0x1130
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_set_biases(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    int clamped;
    int var1;
    int var3;
    var1 = ((-99 <= (long)(arg0)) ? arg0 : 0xffffff9c);
    var3 = ((100 <= (long)((int)(var1))) ? 100 : var1);
    *(int *)(*(long *)(&glaurung_global_3fe8[0])) = var3;
    *(int *)(&glaurung_global_402c[0]) = (100 - var3);
    return 100;
}

gcc -O0

2/5
vis_call_both pass 16 lines
// glaurung: vis_call_both @ 0x11da
int32_t vis_call_both(int32_t arg0) {
    extern int vis157_hidden_helper(int);
    extern int vis_public_helper(int);
    unsigned int hidden;
    unsigned int exported;
    int var1;
    int var4;
    // x86-64 prologue: save rbp, frame 32 bytes
    var1 = vis157_hidden_helper((unsigned long)((unsigned int)(arg0)));
    hidden = var1;
    var4 = vis_public_helper((unsigned long)((unsigned int)(arg0)));
    exported = var4;
    // x86-64 epilogue: restore rbp
    return (unsigned int)(((unsigned long)(hidden) - exported));
}
vis_fold_with_biases fail 33 lines
// glaurung: vis_fold_with_biases @ 0x120b
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_fold_with_biases(const int32_t * arg0, int32_t arg1) {
    extern int vis157_hidden_helper(int);
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    unsigned int accumulator;
    int index;
    unsigned int item;
    int var7;
    // x86-64 prologue: save rbp, frame 32 bytes
    accumulator = 0;
    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++) {
        var7 = vis157_hidden_helper((unsigned long)((unsigned int)(arg0[(long)(index)])));
        item = var7;
        accumulator = (accumulator + (unsigned int)(((unsigned long)((unsigned int)(*(int *)(&glaurung_global_402c[0]))) + (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0])))) + item))))));
    }
    // x86-64 epilogue: restore rbp
    return accumulator;
}
vis_public_helper pass 6 lines
// glaurung: vis_public_helper @ 0x112e
int32_t vis_public_helper(int32_t arg0) {
    // x86-64 prologue: save rbp
    // x86-64 epilogue: restore rbp
    return (unsigned int)(((unsigned long)((unsigned int)(arg0)) ^ 0x5a5a));
}
vis_read_bias fail 17 lines
// glaurung: vis_read_bias @ 0x119b
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_read_bias(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    // x86-64 prologue: save rbp
    if (((unsigned long)((unsigned int)(arg0)) != 0)) {
        if (((unsigned long)((unsigned int)(arg0)) != 1)) {
            return (unsigned int)(((unsigned long)((unsigned int)(*(int *)(&glaurung_global_402c[0]))) + (unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0]))))));
        } else {
            return (unsigned int)(*(int *)(&glaurung_global_402c[0]));
        }
    } else {
        return (unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0])));
    }
}
vis_set_biases fail 20 lines
// glaurung: vis_set_biases @ 0x1143
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_402c[16] __attribute__((aligned(16)));
int32_t vis_set_biases(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_402c[16];
    int clamped;
    // x86-64 prologue: save rbp
    clamped = arg0;
    if (((long)(clamped) < -100)) {
        clamped = -100;
    }
    if (((((unsigned long)((unsigned int)(clamped)) == 100) | ((long)(clamped) < 100)) == 0)) {
        clamped = 100;
    }
    *(int *)(*(long *)(&glaurung_global_3fe8[0])) = clamped;
    *(int *)(&glaurung_global_402c[0]) = (100 - clamped);
    // x86-64 epilogue: restore rbp
    return (unsigned int)(((unsigned long)((unsigned int)(*(int *)(&glaurung_global_402c[0]))) + (unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0]))))));
}

gcc -O2

2/5
vis_call_both pass 14 lines
// glaurung: vis_call_both @ 0x11b0
int32_t vis_call_both(int32_t arg0) {
    extern int vis157_hidden_helper(int);
    extern int vis_public_helper(int);
    unsigned int exported;
    unsigned int hidden;
    int var0;
    long var2;
    int var3;
    var0 = vis157_hidden_helper(arg0);
    var2 = (unsigned long)((unsigned int)(var0));
    var3 = ((int (*)(void))vis_public_helper)();
    return (unsigned int)(((unsigned long)((unsigned int)(var2)) - (unsigned long)((unsigned int)(var3))));
}
vis_fold_with_biases fail 40 lines
// glaurung: vis_fold_with_biases @ 0x11d0
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_4028[16] __attribute__((aligned(16)));
int32_t vis_fold_with_biases(const int32_t * arg0, int32_t arg1) {
    extern int vis157_hidden_helper(int);
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_4028[16];
    unsigned int accumulator;
    int index;
    unsigned int item;
    long var0;
    long var10;
    long var11;
    int var12;
    long var17;
    long var7;
    long var9;
    var0 = (long)arg0;
    if ((arg0 == 0)) {
        return 0xffffffff;
    }
    if (((unsigned long)(16) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
        return 0xffffffff;
    }
    if (((unsigned long)((unsigned int)(arg1)) == 0)) {
        return 0;
    }
    var7 = (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(*(int *)(&glaurung_global_4028[0]))) + *(int *)(*(long *)(&glaurung_global_3fe8[0])))));
    var9 = (long)((((long)arg0 + ((unsigned long)((unsigned int)((arg1 - 1))) * 4)) + 4));
    var10 = 0;
    do {
        var11 = (unsigned long)((unsigned int)(*(int *)((var0))));
        var0 = (var0 + 4);
        var12 = vis157_hidden_helper(var11);
        accumulator = (var10 + (unsigned int)((var12 + var7)));
        var17 = (unsigned long)(accumulator);
        var10 = (unsigned long)(accumulator);
    } while ((var9 != var0));
    return (unsigned int)(var17);
}
vis_public_helper pass 4 lines
// glaurung: vis_public_helper @ 0x1130
int32_t vis_public_helper(int32_t arg0) {
    return (unsigned int)(((unsigned long)((unsigned int)(arg0)) ^ 0x5a5a));
}
vis_read_bias fail 16 lines
// glaurung: vis_read_bias @ 0x1180
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_4028[16] __attribute__((aligned(16)));
int32_t vis_read_bias(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_4028[16];
    long ret;
    if (((unsigned long)((unsigned int)(arg0)) == 0)) {
        return (unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0])));
    }
    ret = (unsigned long)((unsigned int)(*(int *)(&glaurung_global_4028[0])));
    if (((unsigned long)((unsigned int)(arg0)) != 1)) {
        ret = (unsigned long)((unsigned int)((ret + *(int *)(*(long *)(&glaurung_global_3fe8[0])))));
    }
    return ret;
}
vis_set_biases fail 15 lines
// glaurung: vis_set_biases @ 0x1140
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_4028[16] __attribute__((aligned(16)));
int32_t vis_set_biases(int32_t arg0) {
    extern unsigned char glaurung_global_3fe8[16];
    extern unsigned char glaurung_global_4028[16];
    int clamped;
    int var1;
    int var3;
    var1 = (((((unsigned long)((unsigned int)(arg0)) == 100) | ((long)(arg0) < 100)) == 0) ? 100 : arg0);
    var3 = (((long)((int)(var1)) < (long)((int)(0xffffff9c))) ? 0xffffff9c : var1);
    *(int *)(*(long *)(&glaurung_global_3fe8[0])) = var3;
    *(int *)(&glaurung_global_4028[0]) = (100 - var3);
    return 100;
}

← 213 fixtures