使用gcc理解共享库

4

我正在尝试理解C中共享库的以下行为

机器一

$ cat one.c 
#include<stdio.h>

int main() {
    printf ("%d", 45);
}
$ gcc one.c -o one -O3
$ ldd one
    linux-gate.so.1 =>  (0x00331000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00bc2000)
    /lib/ld-linux.so.2 (0x006dc000)
$ cat two.c 
int main() {
    int i = 0;
}
$ gcc two.c -o two -O3
$ ldd two
    linux-gate.so.1 =>  (0x006f7000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00110000)
    /lib/ld-linux.so.2 (0x00eb0000)
$

第二台机器

$ cat three.c
#include<stdio.h>

int main() {
    printf ("%d", 45);
}
$ gcc three.c -o three -O3
$ ldd three
    /usr/lib/libcwait.so (0xb7ffd000)
    libc.so.6 => /lib/tls/i686/nosegneg/libc.so.6 (0x002de000)
    /lib/ld-linux.so.2 (0x002bf000)
$

目前有几个我不完全理解的事情:

  • What does the address given in brackets (for example, (0x002de000)) mean?

    These addresses are different even for the same library on the same machine, which suggests these are addresses of the locations in memory where these libraries are loaded. But, if that is true, why are these libraries loaded in memory at all (I did not execute the programs yet, shouldn't they be loaded only at runtime?).

  • Why does two need any libraries at all? I have used -O3, and the assembler output is

    $ gcc two.c -S -O3 
    $ cat two.s
        .file   "two.c"
        .text
        .p2align 4,,15
    .globl main
        .type   main, @function
    main:
        pushl   %ebp
        movl    %esp, %ebp
        popl    %ebp
        ret
        .size   main, .-main
        .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
        .section    .note.GNU-stack,"",@progbits
    $
    

    What is the need of any libraries at all?

  • On Machine Two, why is /usr/lib/libcwait.so being used instead of linux-gate.so.1?

    I think this is because the kernel on Machine Two is very old (2.6.9) and the library linux-gate.so.1 is not available. Is that the reason?

3个回答

5
方括号中给出的地址(例如(0x002de000))是什么意思?
这是库加载的(虚拟)内存地址。 最近的系统可以提供库加载的随机化,因此该地址可能在不同的调用之间有所变化。
它们不应该只在运行时加载吗?
是的,它们是。 ldd 通过类似于运行时的过程来进行许多操作,以便能够找出各种事情。
为什么两个都需要任何库? libc.so.6 是标准的 C 库(以及其他东西,如与内核的接口),并且默认情况下始终链接。 gcc 有控制这种情况的选项,例如 -nostdlib 标志 ld-linux.so 是动态加载程序,负责加载/重定位其他共享库并运行您的应用程序。 ld-linux.so 的手册为您提供详细信息。 linux-gate.so.1 是虚拟库,仅存在于内核中的内存中。它用于对内核执行系统调用,并根据您的 CPU 找出最有效的方法来执行此操作。这可能是稍后添加到您的其他 2.6.9 内核机器的 linux。
我不知道 /usr/lib/libcwait.so 是什么,但通过执行 rpm -qif /usr/lib/libcwait.so 可以获得一些信息。

2

这些地址基本上是随机数。在安全实现被设计之前,ldd 会一直指示程序段加载的内存地址。大约五年前,许多Linux版本现在故意随机化加载地址,以阻挠可能的病毒编写者等。我将 one.c(作为 t.c)进行编译并反复执行 ldd:

[wally@zenetfedora .bin]$ cat t.c
#include <stdio.h>
int main()
{
    printf ("%d", 45);
}
[wally@zenetfedora .bin]$ gcc -o t t.c -O3
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x009e5000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x00b8d000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x00238000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x002a0000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x00f93000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x00c7a000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x00d1a000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
[wally@zenetfedora .bin]$ ldd t
    linux-gate.so.1 =>  (0x00d12000)
    libc.so.6 => /lib/libc.so.6 (0x002e4000)
    /lib/ld-linux.so.2 (0x002c2000)
和的加载地址是一致的,但是随机的。

需要使用库文件,因为需要运行C运行时初始化和终止。当然,可以在很大程度上进行优化,因为不需要初始化stdinstdoutstderr等等。 但是是main()被调用的方式。

不同版本的Linux有不同之处。Glib的发展经历了许多曲折。一些东西已被移动到其他库中。这基本上与您当地的杂货店为什么会将商品摆放在其他区域没有太大区别。那并没有太多的意义。


two.c 可能被优化为一个 0 字节(或几乎为 0 字节)的程序。但是没有任何供应商会构建针对微不足道的程序进行优化的编译器。 - Fred Foo

1

数字是可执行文件运行时加载库的内存地址。它在链接时确定,并且通常是随机的,以使库函数地址不可预测,从而更难以在攻击中使用。标准C库默认由GCC链接。libcwait可能是另一个默认库,可能被旧版本的GCC使用。


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