Fixture 158
weak symbols
C · 5 functions · 4 lanes · 4 of 20 function-lanes behave identically
4 of 4 lanes have a function that returns a different result after decompilation: clang-O0 (1/5), clang-O2 (1/5), gcc-O0 (1/5), gcc-O2 (1/5).
Weak binding is the ELF mechanism behind every "optional dependency" idiom: pthread stubs in libc, __gcov_dump hooks, plugin callbacks a host may or may not provide. Two distinct shapes share the keyword.
A weak DEFINITION (STB_WEAK, defined here) is a real body that any strong definition of the same name silently displaces at link or load time. It looks exactly like a normal function in the disassembly; the only trace that it is displaceable is the st_info binding byte in the symbol table.
A weak REFERENCE (STB_WEAK, undefined) is a symbol this object mentions but nobody defines. It is not a link error - the loader simply resolves it to address 0, and correct code has to test it before use. That test is the interesting shape: if (&sym) on a weak undefined symbol is NOT the tautology the same text would be for an ordinary symbol, so the compiler must not fold it, and a decompiler must not fold it either. In PIC code the test reads a GOT slot the loader wrote 0 into, which decompiles as a null check on a pointer whose origin is a relocation rather than any assignment - if that check is dropped as dead, the recovered code dereferences a null pointer the moment the symbol IS supplied.
On this build nothing defines wk158_absent_bias / wk158_absent_scale, so every guarded path here is not taken and the results are constant across loads. What the gate measures is whether the guard itself survived.
#include <stdint.h>
/* Weak binding is the ELF mechanism behind every "optional dependency" idiom:
* pthread stubs in libc, __gcov_dump hooks, plugin callbacks a host may or may
* not provide. Two distinct shapes share the keyword.
*
* A weak DEFINITION (STB_WEAK, defined here) is a real body that any strong
* definition of the same name silently displaces at link or load time. It looks
* exactly like a normal function in the disassembly; the only trace that it is
* displaceable is the st_info binding byte in the symbol table.
*
* A weak REFERENCE (STB_WEAK, undefined) is a symbol this object mentions but
* nobody defines. It is not a link error - the loader simply resolves it to
* address 0, and correct code has to test it before use. That test is the
* interesting shape: `if (&sym)` on a weak undefined symbol is NOT the
* tautology the same text would be for an ordinary symbol, so the compiler must
* not fold it, and a decompiler must not fold it either. In PIC code the test
* reads a GOT slot the loader wrote 0 into, which decompiles as a null check on
* a pointer whose origin is a relocation rather than any assignment - if that
* check is dropped as dead, the recovered code dereferences a null pointer the
* moment the symbol IS supplied.
*
* On this build nothing defines wk158_absent_bias / wk158_absent_scale, so
* every guarded path here is not taken and the results are constant across
* loads. What the gate measures is whether the guard itself survived.
*/
/* Weak definition, exported: a strong definition elsewhere would replace it. */
__attribute__((weak)) int32_t weak_scale(int32_t value) {
return (int32_t)((uint32_t)value * 2u);
}
/* Weak data definition, exported. */
__attribute__((weak)) int32_t weak_shared_bias = 21;
/* Weak references, deliberately never defined anywhere in the corpus: the
* loader binds both to 0. The names carry the fixture number so that no other
* translation unit can accidentally satisfy them. */
extern int32_t wk158_absent_bias __attribute__((weak));
extern int32_t wk158_absent_scale(int32_t value) __attribute__((weak));
/* Uses the weak definitions directly. Note what is NOT written here: a
* `if (weak_scale != 0)` guard, because gcc rejects that under -Wall as an
* always-true comparison once it can see the definition in this translation
* unit - it treats a weak DEFINITION as non-null even though a strong
* definition may displace it. Only a weak REFERENCE (below) can be tested in
* source. The shape here is still distinct: a call to a weak, exported,
* interposable function, and a load of weak data through the GOT. */
__attribute__((noinline)) int32_t weak_defined_probe(int32_t value) {
uint32_t scaled = (uint32_t)weak_scale(value);
return (int32_t)(scaled + (uint32_t)weak_shared_bias);
}
/* The guarded paths are unreachable on this build (both weak references are
* unresolved), so the result is `value` for every input. A recovery that drops
* either guard produces code that calls through address 0 as soon as the
* symbols are supplied - and a recovery that turns the guard into an
* unconditional true changes the answer here, immediately. */
__attribute__((noinline)) int32_t weak_absent_probe(int32_t value) {
uint32_t result = (uint32_t)value;
if (&wk158_absent_bias != 0) {
result += (uint32_t)wk158_absent_bias;
}
if (wk158_absent_scale != 0) {
result = (uint32_t)wk158_absent_scale((int32_t)result);
}
return (int32_t)result;
}
/* Present-or-absent selected at run time by an argument, so both the taken and
* the untaken weak tests appear in one control-flow graph. */
__attribute__((noinline)) int32_t weak_dispatch(int32_t value, int32_t use_absent) {
if (use_absent != 0) {
if (wk158_absent_scale != 0) {
return wk158_absent_scale(value);
}
return -1;
}
return weak_scale(value);
}
/* A weak-guarded call inside a bounded loop: the check is loop-invariant, so at
* -O2 it is hoisted and the loop is duplicated or the branch is sunk, and the
* recovered shape has to keep the two versions consistent. */
__attribute__((noinline)) int32_t
weak_fold(const int32_t *values, int32_t count) {
uint32_t accumulator = (uint32_t)weak_shared_bias;
int32_t index;
if (values == 0 || count < 0 || count > 16) {
return -1;
}
for (index = 0; index < count; ++index) {
if (wk158_absent_scale != 0) {
accumulator += (uint32_t)wk158_absent_scale(values[index]);
} else {
accumulator += (uint32_t)weak_scale(values[index]);
}
}
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
1/5weak_absent_probe fail 23 lines
// glaurung: weak_absent_probe @ 0x1150
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
int32_t weak_absent_probe(int32_t arg0) {
extern long wk158_absent_scale(unsigned int);
extern unsigned char glaurung_global_3fd0[16];
extern unsigned char glaurung_global_3fe8[16];
unsigned int result;
long var7;
// x86-64 prologue: save rbp, frame 16 bytes
result = arg0;
if ((*(long *)(&glaurung_global_3fe8[0]) != 0)) {
result = ((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fe8[0]))) + result);
}
if ((*(long *)(&glaurung_global_3fd0[0]) == 0)) {
// x86-64 epilogue: restore rbp
return result;
}
var7 = wk158_absent_scale(result);
result = var7;
// x86-64 epilogue: restore rbp
return result;
} weak_defined_probe fail 13 lines
// glaurung: weak_defined_probe @ 0x1120
static unsigned char glaurung_global_3fd8[16] __attribute__((aligned(16)));
int32_t weak_defined_probe(int32_t arg0) {
extern int weak_scale(int);
extern unsigned char glaurung_global_3fd8[16];
unsigned int scaled;
int var0;
// x86-64 prologue: save rbp, frame 16 bytes
var0 = weak_scale((unsigned long)((unsigned int)(arg0)));
scaled = var0;
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)(scaled) + *(int *)(*(long *)(&glaurung_global_3fd8[0]))));
} weak_dispatch fail 21 lines
// glaurung: weak_dispatch @ 0x11b0
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
int32_t weak_dispatch(int32_t arg0, int32_t arg1) {
extern int weak_scale(int);
extern long wk158_absent_scale(long);
extern unsigned char glaurung_global_3fd0[16];
int var0;
long var3;
// x86-64 prologue: save rbp, frame 16 bytes
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
var0 = weak_scale((unsigned long)((unsigned int)(arg0)));
return (unsigned int)(var0);
} else {
if ((*(long *)(&glaurung_global_3fd0[0]) == 0)) {
return (unsigned int)(-1);
} else {
var3 = wk158_absent_scale((unsigned long)((unsigned int)(arg0)));
return (unsigned int)(var3);
}
}
} weak_fold fail 43 lines
// glaurung: weak_fold @ 0x1210
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3fd8[16] __attribute__((aligned(16)));
int32_t weak_fold(const int32_t * arg0, int32_t arg1) {
extern int weak_scale(int);
extern long wk158_absent_scale(long);
extern unsigned char glaurung_global_3fd0[16];
extern unsigned char glaurung_global_3fd8[16];
unsigned int accumulator;
int index;
int local_4;
long var12;
int var6;
// x86-64 prologue: save rbp, frame 32 bytes
accumulator = *(int *)(*(long *)(&glaurung_global_3fd8[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++) {
if ((*(long *)(&glaurung_global_3fd0[0]) == 0)) {
var6 = weak_scale((unsigned long)((unsigned int)(arg0[(long)(index)])));
accumulator = (var6 + accumulator);
} else {
var12 = wk158_absent_scale((unsigned long)((unsigned int)(arg0[(long)(index)])));
accumulator = (var12 + accumulator);
}
}
local_4 = accumulator;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
} weak_scale pass 6 lines
// glaurung: weak_scale @ 0x1110
int32_t weak_scale(int32_t arg0) {
// x86-64 prologue: save rbp
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)((unsigned int)(arg0)) << 1));
} clang -O2
1/5weak_absent_probe fail 20 lines
// glaurung: weak_absent_probe @ 0x1140
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3fe8[16] __attribute__((aligned(16)));
int32_t weak_absent_probe(int32_t arg0) {
extern int wk158_absent_scale(void);
extern unsigned char glaurung_global_3fd0[16];
extern unsigned char glaurung_global_3fe8[16];
unsigned int result;
int ret;
long var0;
var0 = (unsigned long)((unsigned int)(arg0));
if ((*(long *)(&glaurung_global_3fe8[0]) != 0)) {
var0 = (unsigned long)((unsigned int)((arg0 + *(int *)(*(long *)(&glaurung_global_3fe8[0])))));
}
if ((*(long *)(&glaurung_global_3fd0[0]) == 0)) {
return (unsigned int)(var0);
}
ret = wk158_absent_scale();
return ret;
} weak_defined_probe fail 16 lines
// glaurung: weak_defined_probe @ 0x1120
static unsigned char glaurung_global_3fd8[16] __attribute__((aligned(16)));
int32_t weak_defined_probe(int32_t arg0) {
extern int weak_scale(int);
extern unsigned char glaurung_global_3fd8[16];
unsigned int scaled;
long local_8;
long ret;
int var0;
local_8 = ret;
var0 = weak_scale(arg0);
scaled = (var0 + *(int *)(*(long *)(&glaurung_global_3fd8[0])));
ret = (unsigned long)(scaled);
// x86-64 epilogue: tear down frame
return scaled;
} weak_dispatch fail 17 lines
// glaurung: weak_dispatch @ 0x1170
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
int32_t weak_dispatch(int32_t arg0, int32_t arg1) {
extern int weak_scale(int);
extern int wk158_absent_scale(void);
extern unsigned char glaurung_global_3fd0[16];
int ret;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
ret = weak_scale(arg0);
return ret;
}
if ((*(long *)(&glaurung_global_3fd0[0]) == 0)) {
return 0xffffffff;
}
ret = wk158_absent_scale();
return ret;
} weak_fold fail 68 lines
// glaurung: weak_fold @ 0x1190
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3fd8[16] __attribute__((aligned(16)));
int32_t weak_fold(const int32_t * arg0, int32_t arg1) {
extern int weak_scale(int);
extern long wk158_absent_scale(void);
extern unsigned char glaurung_global_3fd0[16];
extern unsigned char glaurung_global_3fd8[16];
unsigned int accumulator;
int index;
long local_10;
long local_18;
long local_20;
long local_28;
long var0;
long var1;
long var14;
long var17;
long var18;
long var2;
int var20;
long var21;
long var22;
int var23;
long var3;
long var4;
long var5;
local_10 = var0;
local_18 = var1;
local_20 = var2;
local_28 = var3;
var4 = 0xffffffff;
var5 = 0xffffffff;
if ((arg0 == 0)) {
// x86-64 epilogue: tear down frame
return (unsigned int)(var5);
}
var5 = var4;
if (((unsigned long)(16) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
// x86-64 epilogue: tear down frame
return (unsigned int)(var5);
}
accumulator = (unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3fd8[0]))));
var5 = (unsigned long)(accumulator);
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
// x86-64 epilogue: tear down frame
return (unsigned int)(var5);
}
var14 = (unsigned long)((unsigned int)(arg1));
var17 = *(long *)(&glaurung_global_3fd0[0]);
var18 = 0;
do {
if ((var17 == 0)) {
var20 = ((int (*)(void))weak_scale)();
var21 = (unsigned long)((unsigned int)(var20));
} else {
var22 = wk158_absent_scale();
var21 = var22;
}
var23 = (accumulator + var21);
accumulator = (unsigned long)((unsigned int)(var23));
index = (var18 + 1);
var18 = (unsigned long)((unsigned int)(index));
var5 = (unsigned long)((unsigned int)(var23));
} while ((var14 != index));
// x86-64 epilogue: tear down frame
return (unsigned int)(var5);
} weak_scale pass 4 lines
// glaurung: weak_scale @ 0x1110
int32_t weak_scale(int32_t arg0) {
return (unsigned int)((arg0 + arg0));
} gcc -O0
1/5weak_absent_probe fail 21 lines
// glaurung: weak_absent_probe @ 0x1169
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3fe0[16] __attribute__((aligned(16)));
int32_t weak_absent_probe(int32_t arg0) {
extern long wk158_absent_scale(unsigned int);
extern unsigned char glaurung_global_3fd0[16];
extern unsigned char glaurung_global_3fe0[16];
unsigned int result;
long var6;
// x86-64 prologue: save rbp, frame 32 bytes
result = arg0;
if ((*(long *)(&glaurung_global_3fd0[0]) != 0)) {
result = (result + (unsigned int)(*(int *)(*(long *)(&glaurung_global_3fd0[0]))));
}
if ((*(long *)(&glaurung_global_3fe0[0]) != 0)) {
var6 = wk158_absent_scale(result);
result = var6;
}
// x86-64 epilogue: restore rbp
return result;
} weak_defined_probe fail 13 lines
// glaurung: weak_defined_probe @ 0x113b
static unsigned char glaurung_global_3ff0[16] __attribute__((aligned(16)));
int32_t weak_defined_probe(int32_t arg0) {
extern int weak_scale(int);
extern unsigned char glaurung_global_3ff0[16];
unsigned int scaled;
int var1;
// x86-64 prologue: save rbp, frame 32 bytes
var1 = weak_scale((unsigned long)((unsigned int)(arg0)));
scaled = var1;
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)(scaled) + (unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3ff0[0]))))));
} weak_dispatch fail 21 lines
// glaurung: weak_dispatch @ 0x11b4
static unsigned char glaurung_global_3fe0[16] __attribute__((aligned(16)));
int32_t weak_dispatch(int32_t arg0, int32_t arg1) {
extern int weak_scale(int);
extern long wk158_absent_scale(long);
extern unsigned char glaurung_global_3fe0[16];
int var1;
long var4;
// x86-64 prologue: save rbp, frame 16 bytes
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
var1 = weak_scale((unsigned long)((unsigned int)(arg0)));
return var1;
} else {
if ((*(long *)(&glaurung_global_3fe0[0]) == 0)) {
return 0xffffffff;
} else {
var4 = wk158_absent_scale((unsigned long)((unsigned int)(arg0)));
return var4;
}
}
} weak_fold fail 38 lines
// glaurung: weak_fold @ 0x11f7
static unsigned char glaurung_global_3fe0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3ff0[16] __attribute__((aligned(16)));
int32_t weak_fold(const int32_t * arg0, int32_t arg1) {
extern int weak_scale(int);
extern long wk158_absent_scale(long);
extern unsigned char glaurung_global_3fe0[16];
extern unsigned char glaurung_global_3ff0[16];
unsigned int accumulator;
int index;
int var10;
long var18;
// x86-64 prologue: save rbp, frame 32 bytes
accumulator = *(int *)(*(long *)(&glaurung_global_3ff0[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++) {
if ((*(long *)(&glaurung_global_3fe0[0]) == 0)) {
var10 = weak_scale((unsigned long)((unsigned int)(arg0[(long)(index)])));
accumulator = (accumulator + var10);
} else {
var18 = wk158_absent_scale((unsigned long)((unsigned int)(arg0[(long)(index)])));
accumulator = (accumulator + var18);
}
}
// x86-64 epilogue: restore rbp
return accumulator;
} weak_scale pass 6 lines
// glaurung: weak_scale @ 0x1129
int32_t weak_scale(int32_t arg0) {
// x86-64 prologue: save rbp
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)((unsigned int)(arg0)) + (unsigned long)((unsigned int)(arg0))));
} gcc -O2
1/5weak_absent_probe fail 21 lines
// glaurung: weak_absent_probe @ 0x1160
static unsigned char glaurung_global_3fd0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3fe0[16] __attribute__((aligned(16)));
int32_t weak_absent_probe(int32_t arg0) {
extern int wk158_absent_scale(void);
extern unsigned char glaurung_global_3fd0[16];
extern unsigned char glaurung_global_3fe0[16];
int ret;
long var0;
long var1;
var0 = *(long *)(&glaurung_global_3fd0[0]);
var1 = (unsigned long)((unsigned int)(arg0));
if ((var0 != 0)) {
var1 = (unsigned long)((unsigned int)((arg0 + *(int *)((var0)))));
}
if ((*(long *)(&glaurung_global_3fe0[0]) == 0)) {
return (unsigned int)(var1);
}
ret = wk158_absent_scale();
return ret;
} weak_defined_probe fail 10 lines
// glaurung: weak_defined_probe @ 0x1140
static unsigned char glaurung_global_3ff0[16] __attribute__((aligned(16)));
int32_t weak_defined_probe(int32_t arg0) {
extern int weak_scale(int);
extern unsigned char glaurung_global_3ff0[16];
unsigned int scaled;
int var0;
var0 = weak_scale(arg0);
return (unsigned int)((var0 + *(int *)(*(long *)(&glaurung_global_3ff0[0]))));
} weak_dispatch fail 17 lines
// glaurung: weak_dispatch @ 0x1190
static unsigned char glaurung_global_3fe0[16] __attribute__((aligned(16)));
int32_t weak_dispatch(int32_t arg0, int32_t arg1) {
extern int weak_scale(int);
extern int wk158_absent_scale(void);
extern unsigned char glaurung_global_3fe0[16];
int ret;
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
ret = weak_scale(arg0);
return ret;
}
if ((*(long *)(&glaurung_global_3fe0[0]) == 0)) {
return 0xffffffff;
}
ret = wk158_absent_scale();
return ret;
} weak_fold fail 78 lines
// glaurung: weak_fold @ 0x11c0
static unsigned char glaurung_global_3fe0[16] __attribute__((aligned(16)));
static unsigned char glaurung_global_3ff0[16] __attribute__((aligned(16)));
int32_t weak_fold(const int32_t * arg0, int32_t arg1) {
extern int weak_scale(int);
extern long wk158_absent_scale(void);
extern unsigned char glaurung_global_3fe0[16];
extern unsigned char glaurung_global_3ff0[16];
unsigned int accumulator;
int index;
long local_10;
long local_20;
long local_8;
long ret;
long var0;
long var1;
long var10;
long var11;
long var12;
long var15;
int var17;
long var18;
int var19;
long var2;
int var21;
long var4;
local_8 = var0;
local_10 = var1;
local_20 = var2;
var4 = (unsigned long)((unsigned int)(*(int *)(*(long *)(&glaurung_global_3ff0[0]))));
if ((arg0 == 0)) {
// x86-64 epilogue: tear down frame
return 0xffffffff;
}
if (((unsigned long)(16) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
// x86-64 epilogue: tear down frame
return 0xffffffff;
}
ret = (unsigned long)((unsigned int)(var4));
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
// x86-64 epilogue: tear down frame
return ret;
}
var10 = *(long *)(&glaurung_global_3fe0[0]);
var11 = (long)arg0;
var12 = (long)((((long)arg0 + ((unsigned long)((unsigned int)((arg1 - 1))) * 4)) + 4));
accumulator = var4;
L_1210: ;
while (1) {
if ((var10 != 0)) {
var15 = wk158_absent_scale();
var11 = (var11 + 4);
var17 = (accumulator + var15);
accumulator = (unsigned long)((unsigned int)(var17));
var18 = (unsigned long)((unsigned int)(var17));
if ((var11 == var12)) {
ret = (unsigned long)((unsigned int)(var18));
// x86-64 epilogue: tear down frame
return (unsigned int)(var18);
}
goto L_1210;
}
var19 = ((int (*)(void))weak_scale)();
var11 = (var11 + 4);
var21 = (accumulator + var19);
accumulator = (unsigned long)((unsigned int)(var21));
var18 = (unsigned long)((unsigned int)(var21));
if ((var11 != var12)) {
goto L_1210;
}
ret = (unsigned long)((unsigned int)(var18));
// x86-64 epilogue: tear down frame
return (unsigned int)(var18);
}
ret = (unsigned long)((unsigned int)(var18));
// x86-64 epilogue: tear down frame
return (unsigned int)(var18);
} weak_scale pass 4 lines
// glaurung: weak_scale @ 0x1130
int32_t weak_scale(int32_t arg0) {
return (unsigned int)((arg0 + arg0));
}