Fixture 156
plt and got calls
C · 6 functions · 4 lanes · 18 of 24 function-lanes behave identically
4 of 4 lanes have a function that returns a different result after decompilation: clang-O2 (4/6), gcc-O2 (4/6), clang-O0 (5/6), gcc-O0 (5/6).
In a shared object built -fPIC, WHO the callee is decides the instruction.
A call to a default-visibility exported sibling is interposable: the dynamic loader is allowed to rebind it to a definition supplied by another object (LD_PRELOAD, or an earlier library in the global scope), so the call site cannot be a fixed PC-relative branch. It becomes call plt_step_public@PLT, a branch into a stub that jumps through the .got.plt slot the loader filled in (lazily, through _dl_runtime_resolve, unless BIND_NOW).
A call to an internal-linkage sibling can never be rebound, so it is a plain PC-relative call straight at the body.
Both call sites look identical in the source and compute the same value, but they have different relocation shapes, and only one of them can be replaced at load time. A decompiler has to (a) not stop at the stub and report a call to .plt+0x30 or an indirect jump through a GOT address, (b) recover the callee's real name from the PLT/GOT relocation rather than from a direct branch target, and (c) not conflate the two: folding the stub away silently loses the only evidence that one of the calls is interposable. gcc and clang disagree here by default (clang assumes no semantic interposition), so the same source yields two different call shapes with one behaviour.
#include <stdint.h>
/* In a shared object built -fPIC, WHO the callee is decides the instruction.
*
* A call to a default-visibility exported sibling is interposable: the dynamic
* loader is allowed to rebind it to a definition supplied by another object
* (LD_PRELOAD, or an earlier library in the global scope), so the call site
* cannot be a fixed PC-relative branch. It becomes `call plt_step_public@PLT`,
* a branch into a stub that jumps through the .got.plt slot the loader filled
* in (lazily, through _dl_runtime_resolve, unless BIND_NOW).
*
* A call to an internal-linkage sibling can never be rebound, so it is a plain
* PC-relative `call` straight at the body.
*
* Both call sites look identical in the source and compute the same value, but
* they have different relocation shapes, and only one of them can be replaced
* at load time. A decompiler has to (a) not stop at the stub and report a call
* to `.plt+0x30` or an indirect jump through a GOT address, (b) recover the
* callee's real name from the PLT/GOT relocation rather than from a direct
* branch target, and (c) not conflate the two: folding the stub away silently
* loses the only evidence that one of the calls is interposable. gcc and clang
* disagree here by default (clang assumes no semantic interposition), so the
* same source yields two different call shapes with one behaviour.
*/
/* Exported with default visibility: every call to this from inside the object
* still goes through the PLT under -fPIC. */
__attribute__((noinline)) int32_t plt_step_public(int32_t value) {
return (int32_t)((uint32_t)value * 3u + 7u);
}
/* Internal linkage: identical arithmetic, unpreemptable, direct call. */
static __attribute__((noinline)) int32_t plt156_step_local(int32_t value) {
return (int32_t)((uint32_t)value * 3u + 7u);
}
__attribute__((noinline)) int32_t plt_call_interposable(int32_t value) {
return plt_step_public(value);
}
__attribute__((noinline)) int32_t plt_call_local(int32_t value) {
return plt156_step_local(value);
}
/* The two paths compute the same thing, so this is 0 for every input unless
* something interposed the exported symbol. Deterministic, and it forces both
* call shapes into one function. */
__attribute__((noinline)) int32_t plt_paths_agree(int32_t value) {
uint32_t through_plt = (uint32_t)plt_call_interposable(value);
uint32_t direct = (uint32_t)plt_call_local(value);
return (int32_t)(through_plt - direct);
}
/* Alternating relocation kinds inside one loop body: the odd iterations go
* through the PLT, the even ones branch directly. */
__attribute__((noinline)) int32_t
plt_fold_calls(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) {
int32_t stepped;
if ((index & 1) != 0) {
stepped = plt_step_public(values[index]);
} else {
stepped = plt156_step_local(values[index]);
}
accumulator += (uint32_t)stepped;
}
return (int32_t)accumulator;
}
/* A call through a pointer to the interposable symbol: taking the address of an
* exported function in PIC code loads it from the GOT, so the same callee is
* reached by a third relocation shape (GOT load + indirect call) that must
* still resolve to the same name. */
__attribute__((noinline)) int32_t plt_call_via_address(int32_t value) {
int32_t (*indirect)(int32_t) = plt_step_public;
uint32_t through_pointer = (uint32_t)indirect(value);
uint32_t through_plt = (uint32_t)plt_step_public(value);
return (int32_t)(through_pointer - through_plt);
} 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 -O2
4/6plt_call_interposable pass 7 lines
// glaurung: plt_call_interposable @ 0x1140
int32_t plt_call_interposable(int32_t arg0) {
extern int plt_step_public(int);
int ret;
ret = plt_step_public(arg0);
return ret;
} plt_call_local fail 7 lines
// glaurung: plt_call_local @ 0x1150
int plt_call_local(int arg0, long arg1, long arg2, long arg3, long arg4, long arg5) {
extern int plt156_step_local(int, long, long, long, long, long);
int ret;
ret = plt156_step_local(arg0, arg1, arg2, arg3, arg4, arg5);
return ret;
} plt_call_via_address pass 4 lines
// glaurung: plt_call_via_address @ 0x11f0
int32_t plt_call_via_address(int32_t arg0) {
return 0;
} plt_fold_calls fail 49 lines
// glaurung: plt_fold_calls @ 0x1190
int32_t plt_fold_calls(const int32_t * arg0, int32_t arg1) {
extern int plt156_step_local(int);
extern int plt_step_public(int);
int stepped;
unsigned int accumulator;
int index;
long var0;
long var1;
long var10;
long var11;
int var13;
int var16;
long var5;
// x86-64 prologue: save callee registers, frame 40 bytes
var0 = 0xffffffff;
var1 = 0xffffffff;
if ((arg0 == 0)) {
// x86-64 epilogue: restore callee registers
return (unsigned int)(var1);
}
var1 = var0;
if (((unsigned long)(16) < (unsigned long)((unsigned long)((unsigned int)(arg1))))) {
// x86-64 epilogue: restore callee registers
return (unsigned int)(var1);
}
if (((unsigned long)((unsigned int)(arg1)) == 0)) {
return 0;
}
var5 = (unsigned long)((unsigned int)(arg1));
var10 = 0;
var11 = 0;
do {
if (((unsigned long)((unsigned char)((var11 & 1))) == 0)) {
var13 = ((int (*)(void))plt156_step_local)();
stepped = var13;
} else {
var16 = ((int (*)(void))plt_step_public)();
stepped = var16;
}
accumulator = (var10 + stepped);
index = (var11 + 1);
var10 = (unsigned long)(accumulator);
var11 = (unsigned long)((unsigned int)(index));
var1 = (unsigned long)(accumulator);
} while ((var5 != index));
// x86-64 epilogue: restore callee registers
return (unsigned int)(var1);
} plt_paths_agree pass 18 lines
// glaurung: plt_paths_agree @ 0x1170
int32_t plt_paths_agree(int32_t arg0) {
extern int plt_call_interposable(int);
extern int plt_call_local(int);
unsigned int direct;
unsigned int through_plt;
long var0;
int var1;
long var3;
int var4;
// x86-64 prologue: save callee registers, frame 24 bytes
var0 = (unsigned long)((unsigned int)(arg0));
var1 = plt_call_interposable(arg0);
var3 = (unsigned long)((unsigned int)(var1));
var4 = plt_call_local((unsigned long)((unsigned int)(var0)));
// x86-64 epilogue: restore callee registers
return (unsigned int)((var3 - var4));
} plt_step_public pass 4 lines
// glaurung: plt_step_public @ 0x1130
int32_t plt_step_public(int32_t arg0) {
return (unsigned int)(((unsigned long)((unsigned int)((arg0 + (arg0 * 2)))) + 7));
} gcc -O2
4/6plt_call_interposable pass 7 lines
// glaurung: plt_call_interposable @ 0x1180
int32_t plt_call_interposable(int32_t arg0) {
extern int plt_step_public(int);
int ret;
ret = plt_step_public(arg0);
return ret;
} plt_call_local fail 7 lines
// glaurung: plt_call_local @ 0x1190
int plt_call_local(int arg0, long arg1, long arg2, long arg3, long arg4, long arg5) {
extern int plt156_step_local(int, long, long, long, long, long);
int ret;
ret = plt156_step_local(arg0, arg1, arg2, arg3, arg4, arg5);
return ret;
} plt_call_via_address pass 15 lines
// glaurung: plt_call_via_address @ 0x1250
int32_t plt_call_via_address(int32_t arg0) {
extern int plt_step_public(int);
unsigned int through_plt;
unsigned int through_pointer;
long var0;
int var1;
long var3;
int var4;
var0 = (unsigned long)((unsigned int)(arg0));
var1 = plt_step_public(arg0);
var3 = (unsigned long)((unsigned int)(var1));
var4 = plt_step_public((unsigned long)((unsigned int)(var0)));
return (unsigned int)(((unsigned long)((unsigned int)(var3)) - (unsigned long)((unsigned int)(var4))));
} plt_fold_calls fail 57 lines
// glaurung: plt_fold_calls @ 0x11d0
int32_t plt_fold_calls(const int32_t * arg0, int32_t arg1) {
extern int plt156_step_local(int);
extern int plt_step_public(int);
int index;
unsigned int accumulator;
int stepped;
long var0;
int var10;
long var11;
long var13;
long var14;
int var17;
int var19;
long var6;
int var8;
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;
}
var0 = (unsigned long)((unsigned int)((arg1 - 1)));
var6 = 0;
index = 0;
goto L_1213;
L_1200: ;
var8 = plt156_step_local((int)(arg0));
var10 = (var6 + var8);
var11 = (unsigned long)((unsigned int)(var10));
var13 = ((unsigned long)((unsigned int)(index)) + 1);
var14 = (unsigned long)((unsigned int)(var10));
if ((var0 == index)) {
// x86-64 epilogue: tear down frame
return (unsigned int)(var14);
}
L_1210: ;
var6 = var11;
index = var13;
L_1213: ;
if (((unsigned long)((unsigned char)((index & 1))) == 0)) {
goto L_1200;
}
var17 = ((int (*)(void))plt_step_public)();
var19 = (var6 + var17);
var11 = (unsigned long)((unsigned int)(var19));
var13 = ((unsigned long)((unsigned int)(index)) + 1);
var14 = (unsigned long)((unsigned int)(var19));
if ((var0 != index)) {
goto L_1210;
}
// x86-64 epilogue: tear down frame
return (unsigned int)(var14);
} plt_paths_agree pass 16 lines
// glaurung: plt_paths_agree @ 0x11a0
int32_t plt_paths_agree(int32_t arg0) {
extern int plt_call_interposable(int);
extern int plt_call_local(int);
unsigned int direct;
unsigned int through_plt;
long var0;
int var1;
long var3;
int var4;
var0 = (unsigned long)((unsigned int)(arg0));
var1 = plt_call_interposable(arg0);
var3 = (unsigned long)((unsigned int)(var1));
var4 = plt_call_local((unsigned long)((unsigned int)(var0)));
return (unsigned int)(((unsigned long)((unsigned int)(var3)) - (unsigned long)((unsigned int)(var4))));
} plt_step_public pass 4 lines
// glaurung: plt_step_public @ 0x1170
int32_t plt_step_public(int32_t arg0) {
return (unsigned int)(((arg0 + (arg0 * 2)) + 7));
} clang -O0
5/6plt_call_interposable pass 9 lines
// glaurung: plt_call_interposable @ 0x1130
int32_t plt_call_interposable(int32_t arg0) {
extern int plt_step_public(int);
int ret;
// x86-64 prologue: save rbp, frame 16 bytes
ret = plt_step_public((unsigned long)((unsigned int)(arg0)));
// x86-64 epilogue: restore rbp
return ret;
} plt_call_local pass 9 lines
// glaurung: plt_call_local @ 0x1150
int32_t plt_call_local(int32_t arg0) {
extern int plt156_step_local(int);
int ret;
// x86-64 prologue: save rbp, frame 16 bytes
ret = plt156_step_local((unsigned long)((unsigned int)(arg0)));
// x86-64 epilogue: restore rbp
return ret;
} plt_call_via_address fail 19 lines
// glaurung: plt_call_via_address @ 0x1270
static unsigned char glaurung_global_3fd8[16] __attribute__((aligned(16)));
int32_t plt_call_via_address(int32_t arg0) {
extern int plt_step_public(int);
extern unsigned char glaurung_global_3fd8[16];
char * indirect;
unsigned int through_pointer;
unsigned int through_plt;
long var2;
int var4;
// x86-64 prologue: save rbp, frame 32 bytes
indirect = (char *)(*(long *)(&glaurung_global_3fd8[0]));
var2 = ((long (*)(long))(indirect))((unsigned long)((unsigned int)(arg0)));
through_pointer = var2;
var4 = plt_step_public((unsigned long)((unsigned int)(arg0)));
through_plt = var4;
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)(through_pointer) - through_plt));
} plt_fold_calls pass 41 lines
// glaurung: plt_fold_calls @ 0x11b0
int32_t plt_fold_calls(const int32_t * arg0, int32_t arg1) {
extern int plt156_step_local(int);
extern int plt_step_public(int);
unsigned int accumulator;
int index;
int stepped;
int local_4;
int var10;
int var6;
// 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++) {
if (((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(index)) & 1))) == 0)) {
var6 = plt156_step_local((unsigned long)((unsigned int)(arg0[(long)(index)])));
stepped = var6;
} else {
var10 = plt_step_public((unsigned long)((unsigned int)(arg0[(long)(index)])));
stepped = var10;
}
accumulator = ((unsigned int)(stepped) + accumulator);
}
local_4 = accumulator;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
} plt_paths_agree pass 16 lines
// glaurung: plt_paths_agree @ 0x1180
int32_t plt_paths_agree(int32_t arg0) {
extern int plt_call_interposable(int);
extern int plt_call_local(int);
unsigned int through_plt;
unsigned int direct;
int var0;
int var2;
// x86-64 prologue: save rbp, frame 16 bytes
var0 = plt_call_interposable((unsigned long)((unsigned int)(arg0)));
through_plt = var0;
var2 = plt_call_local((unsigned long)((unsigned int)(arg0)));
direct = var2;
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)(through_plt) - direct));
} plt_step_public pass 6 lines
// glaurung: plt_step_public @ 0x1120
int32_t plt_step_public(int32_t arg0) {
// x86-64 prologue: save rbp
// x86-64 epilogue: restore rbp
return (unsigned int)(((arg0 * 3) + 7));
} gcc -O0
5/6plt_call_interposable pass 9 lines
// glaurung: plt_call_interposable @ 0x117b
int32_t plt_call_interposable(int32_t arg0) {
extern int plt_step_public(int);
int ret;
// x86-64 prologue: save rbp, frame 16 bytes
ret = plt_step_public((unsigned long)((unsigned int)(arg0)));
// x86-64 epilogue: restore rbp
return ret;
} plt_call_local pass 9 lines
// glaurung: plt_call_local @ 0x1196
int32_t plt_call_local(int32_t arg0) {
extern int plt156_step_local(int);
int ret;
// x86-64 prologue: save rbp, frame 8 bytes
ret = plt156_step_local((unsigned long)((unsigned int)(arg0)));
// x86-64 epilogue: restore rbp
return ret;
} plt_call_via_address fail 19 lines
// glaurung: plt_call_via_address @ 0x1282
static unsigned char glaurung_global_3fd8[16] __attribute__((aligned(16)));
int32_t plt_call_via_address(int32_t arg0) {
extern int plt_step_public(int);
extern unsigned char glaurung_global_3fd8[16];
char * indirect;
unsigned int through_pointer;
unsigned int through_plt;
long var3;
int var6;
// x86-64 prologue: save rbp, frame 32 bytes
indirect = (char *)(*(long *)(&glaurung_global_3fd8[0]));
var3 = ((long (*)(long))(indirect))((unsigned long)((unsigned int)(arg0)));
through_pointer = var3;
var6 = plt_step_public((unsigned long)((unsigned int)(arg0)));
through_plt = var6;
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)(through_pointer) - through_plt));
} plt_fold_calls pass 36 lines
// glaurung: plt_fold_calls @ 0x11e2
int32_t plt_fold_calls(const int32_t * arg0, int32_t arg1) {
extern int plt156_step_local(int);
extern int plt_step_public(int);
unsigned int accumulator;
int index;
int stepped;
int var10;
int var18;
// 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++) {
if (((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(index)) & 1))) == 0)) {
var10 = plt156_step_local((unsigned long)((unsigned int)(arg0[(long)(index)])));
stepped = var10;
} else {
var18 = plt_step_public((unsigned long)((unsigned int)(arg0[(long)(index)])));
stepped = var18;
}
accumulator = (accumulator + (unsigned int)(stepped));
}
// x86-64 epilogue: restore rbp
return accumulator;
} plt_paths_agree pass 16 lines
// glaurung: plt_paths_agree @ 0x11b1
int32_t plt_paths_agree(int32_t arg0) {
extern int plt_call_interposable(int);
extern int plt_call_local(int);
unsigned int through_plt;
unsigned int direct;
int var1;
int var4;
// x86-64 prologue: save rbp, frame 32 bytes
var1 = plt_call_interposable((unsigned long)((unsigned int)(arg0)));
through_plt = var1;
var4 = plt_call_local((unsigned long)((unsigned int)(arg0)));
direct = var4;
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)(through_plt) - direct));
} plt_step_public pass 6 lines
// glaurung: plt_step_public @ 0x1149
int32_t plt_step_public(int32_t arg0) {
// x86-64 prologue: save rbp
// x86-64 epilogue: restore rbp
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) + (unsigned long)((unsigned int)(arg0))))) + (unsigned long)((unsigned int)(arg0))))) + 7));
}