为什么在C上对动态链接符号执行指针算术时会出现错误结果?

为什么在C上对动态链接符号执行指针算术时会出现错误结果?

问题描述:

我遇到了一个奇怪的情况,其中执行指针算术涉及 动态链接的符号导致不正确的结果。我不确定 是否仅仅缺少一些链接器参数,或者它是链接器错误。有人可以在下面的例子中解释有什么不对吗?为什么在C上对动态链接符号执行指针算术时会出现错误结果?

考虑一个简单的共享库的下面的代码(lib.c):

#include <inttypes.h> 
#include <stdio.h> 

uintptr_t getmask() 
{ 
    return 0xffffffff; 
} 

int fn1() 
{ 
    return 42; 
} 

void fn2() 
{ 
    uintptr_t mask; 
    uintptr_t p; 

    mask = getmask(); 
    p = (uintptr_t)fn1 & mask; 
    printf("mask: %08x\n", mask); 
    printf("fn1: %p\n", fn1); 
    printf("p: %08x\n", p); 
} 

有问题的操作的位与fn1和 可变mask地址之间。应用程序(app.c)只是调用fn2这样的:

extern int fn2(); 

int main() 
{ 
    fn2(); 

    return 0; 
} 

它导致下面的输出...

mask: ffffffff 
fn1: 0x2aab43c0 
p: 000003c0 

...这显然是不正确的,因为相同的结果将预计fn1p。代码运行上的AVR32架构和如下被编译:

$ avr32-linux-uclibc-gcc -Os -Wextra -Wall -c -o lib.o lib.c 
$ avr32-linux-uclibc-gcc -Os -Wextra -Wall -shared -o libfoo.so lib.o 
$ avr32-linux-uclibc-gcc -Os -Wextra -Wall -o app app.c -L. -lfoo 

编译器认为,它是将可变 mask加载到32位寄存器7的最优解和分裂& -operation成两个汇编 立即操作数的操作。

$ avr32-linux-uclibc-objdump -d libfoo.so 

000003ce <fn1>: 
3ce: 32 ac   mov  r12,42 
3d0: 5e fc   retal r12 

000003d2 <fn2>: 
... 
3f0: e4 17 00 00  andh r7,0x0 
3f4: e0 17 03 ce  andl r7,0x3ce 

我承担的and指令立即数不搬迁 到fn1加载地址时,共享库被加载到 应用程序的地址空间:

  • 这种行为是故意的吗?
  • 如何调查链接共享库时或加载可执行文件时是否出现问题?

背景:这不是一个学术问题。 OpenSSL和LibreSSL 使用类似的代码,所以更改C源不是一个选项。该代码在其他体系结构上运行的代码为 ,当然, 对函数指针执行按位运算的确有一个不明显的原因。

+2

'返回0xffffffff' - > 'return〜(uintptr_t)0'? – Bathsheba

+0

什么'print(“fn1-x:%08x \ n”,(uintptr_t)fn1);'给? –

+0

如果你没有优化编译,你会得到什么? –

后的代码校正所有的 'slopiness',其结果是:

#include <inttypes.h> 
#include <stdio.h> 

int fn1(void); 
void fn2(void); 
uintptr_t getmask(void); 

int main(void) 
{ 
    fn2(); 

    return 0; 
} 

uintptr_t getmask() 
{ 
    return 0xffffffff; 
} 

int fn1() 
{ 
    return 42; 
} 

void fn2() 
{ 
    uintptr_t mask; 
    uintptr_t p; 

    mask = getmask(); 
    p = (uintptr_t)fn1 & mask; 
    printf("mask: %08x\n", (unsigned int)mask); 
    printf("fn1: %p\n", fn1); 
    printf("p: %08x\n", (unsigned int)p); 
} 

和输出(我的Linux 64位计算机上)是:

mask: ffffffff 
fn1: 0x4007c1 
p: 004007c1 
+1

'%p'格式说明符必须采用'void *'参数 –

+0

@ M.M:否则会导致UB。 – Destructor

+0

因为任何指针都可以作为参数列表中的'void *'使用,所以编译器不会抱怨语句:'printf(“fn1:%p \ n”,fn1);'并且会产生正确的结果。 – user3629249