可执行共享库无法打印浮点数

10

我正在开发一个共享库,可以独立执行并打印自己的版本号。

我定义了一个自定义入口点:

const char my_interp[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";

void my_main() {
   printf("VERSION: %d\n", 0);
   _exit(0);
}

我使用以下命令进行编译:

gcc -o list.os -c -g -Wall -fPIC list.c
gcc -o liblist.so -g -Wl,-e,my_main -shared list.os -lc

这段代码可以编译运行得很完美。

我的问题在于当我将printf的参数更改为float或double(%f或%lf)时,库会编译但在运行时出现segfault

有人有任何想法吗?

编辑1:

以下是导致系统崩溃的代码:

const char my_interp[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2"; 

void my_main() { 
    printf("VERSION: %f\n", 0.1f); 
    _exit(0); 
} 

编辑2:

其他环境细节:

uname -a

Linux mjolnir.site 3.1.10-1.16-desktop #1 SMP PREEMPT Wed Jun 27 05:21:40 UTC 2012 (d016078) x86_64 x86_64 x86_64 GNU/Linux

gcc --version

gcc (SUSE Linux) 4.6.2

/lib64/libc.so.6

配置为x86_64-suse-linux。 使用GNU CC版本4.6.2进行编译。 于2012-03-30在Linux 3.1.0系统上编译。

编辑3:

发生segfault时,/var/log/messages中的输出:

Aug 11 08:27:45 mjolnir kernel: [10560.068741] liblist.so[11222] general protection ip:7fc2b3cb2314 sp:7fff4f5c7de8 error:0 in libc-2.14.1.so[7fc2b3c63000+187000]


1
这在我的32位机器上完美运行。它也应该在64位上工作。你是否包含了<unistd.h>和<stdio.h>? - TOC
1
@TOC 我做了。你试过将浮点数作为版本号打印吗?我发布的代码运行良好,除了打印浮点数之外。 - kobrien
你能展示一下打印浮点数的代码吗? - Jonathan Leffler
@ThomasPadron-McCarthy 我可以确认这也在我的机器上发生。目前,有人建议重新创建 /dev/null 作为解决方案,但那似乎是一个笨拙且不令人满意的方法。 - kobrien
1
“重新创建 /dev/null 作为解决方案”?我完全不理解。 - Thomas Padron-McCarthy
显示剩余14条评论
1个回答

5
搞定了。 :)
x86_64 上的浮点运算使用 xmm 向量寄存器。对这些寄存器的访问必须在 16 字节边界上对齐。这就解释了为什么 32 位平台不受影响,整数和字符打印工作正常。
我已经用以下命令将我的代码编译为汇编:
gcc -W list.c -o list.S -shared -Wl,-e,my_main -S -fPIC

然后修改了“my_main”函数以增加堆栈空间。

之前:

my_main:
 .LFB6:
 .cfi_startproc
 pushq   %rbp
 .cfi_def_cfa_offset 16
 .cfi_offset 6, -16
 movq    %rsp, %rbp
 .cfi_def_cfa_register 6
 movl    $.LC0, %eax
 movsd   .LC1(%rip), %xmm0
 movq    %rax, %rdi
 movl    $1, %eax
 call    printf
 movl    $0, %edi
 call    _exit
 .cfi_endproc

之后:
my_main:
 .LFB6:
 .cfi_startproc
 pushq   %rbp
 .cfi_def_cfa_offset 16
 .cfi_offset 6, -16
 subq    $8, %rsp ;;;;;;;;;;;;;;; ADDED THIS LINE
 movq    %rsp, %rbp
 .cfi_def_cfa_register 6
 movl    $.LC0, %eax
 movsd   .LC1(%rip), %xmm0
 movq    %rax, %rdi
 movl    $1, %eax
 call    printf
 movl    $0, %edi
 call    _exit
 .cfi_endproc

然后,我通过以下方式编译这个 .S 文件:
gcc list.S -o liblist.so -Wl,-e,my_main -shared

这解决了问题,但我会将这个线程转发到GCC和GLIBC邮件列表中,因为它看起来像是一个bug。
编辑1:
根据gcc irc中的noshadow所说,这是一种非标准的方法。他说如果要使用gcc -e选项,要么手动初始化C运行时,要么不使用libc函数。有道理。

我记得gcc有一个选项可以将堆栈对齐到比默认值更大的边界上。在修复该错误之前,您可以使用它作为解决方法,而不是修改汇编代码。 - AProgrammer
听起来不错。另一种方法是不使用libc函数,而是直接使用系统调用,但请注意这会增加代码量并降低可移植性。 - kobrien
1
将单浮点值加载到XMM寄存器中的指令不需要16字节对齐。它们只需要单精度四字节对齐和双精度八字节对齐。您可能遇到其他问题,例如违反了调用子程序的应用程序二进制接口要求(可能错误地对齐了堆栈,可能没有设置指示浮点参数已传递的位或其他问题)。 - Eric Postpischil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接