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.
#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/5vis_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/5vis_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/5vis_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/5vis_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;
}