在运行时加载动态库会产生不一致和意外的结果,缺少符号和空的PLT条目。为什么? 第一台机器

我一直在与这个问题作斗争很长时间,但是我一直无法找到解决方案甚至是解释。非常抱歉,如果问题很长,请允许我,我只是想100%明确地说,希望比我更有经验的人能够解决这个问题。

我将所有代码段的C语法高亮显示,因为即使不是很正确,它也可以使它们更加清晰。

我想做什么

我有一个C程序,该程序使用动态库(libzip)中的某些功能。这里将其归结为一个最小的可重现示例(它基本上不执行任何操作,但是效果很好):

#include <zip.h>

int main(void) {
    int err;
    zip_t *myzip;

    myzip = zip_open("myzip.zip",ZIP_CREATE | ZIP_TRUNCATE,&err);
    if (myzip == NULL)
        return 1;

    zip_close(myzip);

    return 0;
}

通常,要编译它,我只会这样做:

gcc -c prog.c
gcc -o prog prog.o -lzip

这将按预期创建一个ELF,该ELF需要运行libzip

$ ldd prog
linux-vdso.so.1 (0x00007ffdafb53000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f81eedc7000)
/lib64/ld-linux-x86-64.so.2 (0x00007f81ef780000)
libzip.so.4 => /usr/lib/x86_64-linux-gnu/libzip.so.4 (0x00007f81ef166000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f81eebad000)

libz只是libzip的依赖项)

我真正想做的事是,我自己使用dlopen()加载库。很简单的任务,不是吗?是的,或者至少我认为。

要实现此目的,我只需要调用dlopen并让加载程序完成其工作即可:

#include <zip.h>
#include <dlfcn.h>

int main(void) {
    void *lib;
    int err;
    zip_t *myzip;

    lib = dlopen("libzip.so",RTLD_LAZY | RTLD_GLOBAL);
    if (lib == NULL)
        return 1;

    myzip = zip_open("myzip.zip",&err);
    if (myzip == NULL)
        return 1;

    zip_close(myzip);

    return 0;
}

当然,由于我想手动自己加载库,因此这次我不会链接它:

# Create prog.o
gcc -c prog.c

# Do a dry-run just to make sure all symbols are resolved
gcc -o /dev/null prog.o -ldl -lzip

# Now recompile only with libdl
gcc -o prog prog.o -ldl -Wl,--unresolved-symbols=ignore-in-object-files

标志--unresolved-symbols=ignore-in-object-files告诉ld不必担心我的prog.o在链接时有未解析的符号(我想在运行时照顾自己)。

问题

上面的 Should Just Work™,的确确实如此……但是我有两台机器,作为书呆子,我只是想:“好吧,最好确保编译一下放在两个人身上。”

第一台机器

x86-64,Linux 4.9,Debian 9,gcc 6.3.0,ld 2.28。在这里一切正常

我可以清楚地看到其中的符号:

$ readelf --dyn-syms prog

Symbol table '.dynsym' contains 15 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 notyPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 notyPE  WEAK   DEFAULT  UND _ITM_deregisterTMClonetab
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 notyPE  WEAK   DEFAULT  UND __gmon_start__
===> 4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND zip_close
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND dlopen@GLIBC_2.2.5 (3)
===> 6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND zip_open
     7: 0000000000000000     0 notyPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     8: 0000000000000000     0 notyPE  WEAK   DEFAULT  UND _ITM_registerTMClonetable
     9: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
    10: 0000000000201040     0 notyPE  GLOBAL DEFAULT   25 _edata
    11: 0000000000201048     0 notyPE  GLOBAL DEFAULT   26 _end
    12: 0000000000201040     0 notyPE  GLOBAL DEFAULT   26 __bss_start
    13: 00000000000006a0     0 FUNC    GLOBAL DEFAULT   11 _init
    14: 0000000000000924     0 FUNC    GLOBAL DEFAULT   15 _fini

PLT条目也按预期存在,并且看起来不错:

$ objdump -j .plt -M intel -d prog

Disassembly of section .plt:

00000000000006c0 <.plt>:
 6c0:   ff 35 42 09 20 00       push   QWORD PTR [rip+0x200942]        # 201008 <_GLOBAL_OFFSET_TABLE_+0x8>
 6c6:   ff 25 44 09 20 00       jmp    QWORD PTR [rip+0x200944]        # 201010 <_GLOBAL_OFFSET_TABLE_+0x10>
 6cc:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]

00000000000006d0 <zip_close@plt>:
 6d0:   ff 25 42 09 20 00       jmp    QWORD PTR [rip+0x200942]        # 201018 <zip_close>
 6d6:   68 00 00 00 00          push   0x0
 6db:   e9 e0 ff ff ff          jmp    6c0 <.plt>

00000000000006e0 <dlopen@plt>:
 6e0:   ff 25 3a 09 20 00       jmp    QWORD PTR [rip+0x20093a]        # 201020 <dlopen@GLIBC_2.2.5>
 6e6:   68 01 00 00 00          push   0x1
 6eb:   e9 d0 ff ff ff          jmp    6c0 <.plt>

00000000000006f0 <zip_open@plt>:
 6f0:   ff 25 32 09 20 00       jmp    QWORD PTR [rip+0x200932]        # 201028 <zip_open>
 6f6:   68 02 00 00 00          push   0x2
 6fb:   e9 c0 ff ff ff          jmp    6c0 <.plt>

程序运行没有问题:

$ ./prog
$ echo $?
0

即使使用调试器查看它的内部,我也可以清楚地看到符号像任何普通的动态符号一样被正确解析:

0x55555555479b <main+43>                       lea    rax,[rbp - 0x14]
0x55555555479f <main+47>                       mov    rdx,rax
0x5555555547a2 <main+50>                       mov    esi,9
0x5555555547a7 <main+55>                       lea    rdi,[rip + 0xc0] <0x7ffff7ffd948>
0x5555555547ae <main+62>                       call   zip_open@plt <0x555555554620>
 |
 v ### PLT entry:
0x555555554620 <zip_open@plt>                  jmp    qword ptr [rip + 0x200a02] <0x555555755028>
 |
 v 
0x555555554626 <zip_open@plt+6>                push   2
0x55555555462b <zip_open@plt+11>               jmp    0x5555555545f0
 |
 v ### PLT stub:
0x5555555545f0                                 push   qword ptr [rip + 0x200a12] <0x555555755008>
0x5555555545f6                                 jmp    qword ptr [rip + 0x200a14] <0x7ffff7def0d0>
 |
 v ### Symbol gets correctly resolved
0x7ffff7def0d0 <_dl_runtime_resolve_fxsave>    push   rbx
0x7ffff7def0d1 <_dl_runtime_resolve_fxsave+1>  mov    rbx,rsp
0x7ffff7def0d4 <_dl_runtime_resolve_fxsave+4>  and    rsp,0xfffffffffffffff0
0x7ffff7def0d8 <_dl_runtime_resolve_fxsave+8>  sub    rsp,0x240

第二台机器

x86-64,Linux 4.15,Ubuntu 18.04,gcc 7.4,ld 2.30。在这里,发生了一件非常奇怪的事情

编译不会产生任何警告或错误,但是我看不到符号:

$ readelf --dyn-syms prog

Symbol table '.dynsym' contains 7 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 notyPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 notyPE  WEAK   DEFAULT  UND _ITM_deregisterTMClonetab
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 notyPE  WEAK   DEFAULT  UND __gmon_start__
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND dlopen@GLIBC_2.2.5 (3)
     5: 0000000000000000     0 notyPE  WEAK   DEFAULT  UND _ITM_registerTMClonetable
     6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)

PLT条目在那里,但是它们用零填充,甚至没有被objdump识别:

$ objdump -j .plt -M intel -d prog

Disassembly of section .plt:

0000000000000560 <.plt>:
 560:   ff 35 4a 0a 20 00       push   QWORD PTR [rip+0x200a4a]        # 200fb0 <_GLOBAL_OFFSET_TABLE_+0x8>
 566:   ff 25 4c 0a 20 00       jmp    QWORD PTR [rip+0x200a4c]        # 200fb8 <_GLOBAL_OFFSET_TABLE_+0x10>
 56c:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]
    ...

#   ^^^
# Here,these three dots are actually hiding another 0x10+ bytes filled of 0x0
# zip_close@plt should be here instead...

0000000000000580 <dlopen@plt>:
 580:   ff 25 42 0a 20 00       jmp    QWORD PTR [rip+0x200a42]        # 200fc8 <dlopen@GLIBC_2.2.5>
 586:   68 00 00 00 00          push   0x0
 58b:   e9 d0 ff ff ff          jmp    560 <.plt>
    ...

#   ^^^
# Here,these three dots are actually hiding another 0x10+ bytes filled of 0x0
# zip_open@plt should be here instead...

程序运行时,dlopen()可以正常工作并将libzip加载到内存中,但是当调用zip_open()时,它只会生成分段错误:

$ ./prog
Segmentation fault (code dumped)

与调试器一起查看,问题甚至更加明显(以防万一它还不够明显)。填充有零的PLT条目最终会解码为解引用add的一堆rax指令,该指令包含无效地址并使程序段错误并死:

0x5555555546e5 <main+43>               lea    rax,[rbp - 0x14]
0x5555555546e9 <main+47>               mov    rdx,rax
0x5555555546ec <main+50>               mov    esi,9
0x5555555546f1 <main+55>               lea    rdi,[rip + 0xc6]
0x5555555546f8 <main+62>               call   dlopen@plt+16 <0x555555554590>
 |
 v ### Broken PLT enrty (all 0x0,will cause a segfault):
0x555555554590 <dlopen@plt+16>         add    byte ptr [rax],al
0x555555554592 <dlopen@plt+18>         add    byte ptr [rax],al
0x555555554594 <dlopen@plt+20>         add    byte ptr [rax],al
0x555555554596 <dlopen@plt+22>         add    byte ptr [rax],al
0x555555554598 <dlopen@plt+24>         add    byte ptr [rax],al
0x55555555459a <dlopen@plt+26>         add    byte ptr [rax],al
0x55555555459c <dlopen@plt+28>         add    byte ptr [rax],al
0x55555555459e <dlopen@plt+30>         add    byte ptr [rax],al
   ### Next PLT entry...
0x5555555545a0 <__cxa_finalize@plt>    jmp    qword ptr [rip + 0x200a52] <0x7ffff7823520>
 |
 v
0x7ffff7823520 <__cxa_finalize>        push   r15
0x7ffff7823522 <__cxa_finalize+2>      push   r14

问题

  1. 所以,首先...为什么会发生这种情况?
  2. 我认为这应该可行,不是吗?如果没有,为什么?为什么只在两台机器之一上?
  3. 但最重要的是:我该如何解决

对于问题3,我想强调一点,就是要我自己加载该库而不链接它,因此请不要仅评论这是不好的做法或其他任何做法。

jining123456 回答:在运行时加载动态库会产生不一致和意外的结果,缺少符号和空的PLT条目。为什么? 第一台机器

  

以上“ Should Just Work™”应该确实存在,而且确实...

不,它不应该,而且如果出现的话,那更像是一次意外。通常,使用--unresolved-symbols=...是一个非常糟糕的主意™,几乎永远不会做您想做的事。

解决方案很简单:您只需要查找zip_openzip_close,就像这样:

int main(void) {
    void *lib;
    zip_t *p_open(const char *,int,int *);
    void *p_close(zip_t*);
    int err;
    zip_t *myzip;

    lib = dlopen("libzip.so",RTLD_LAZY | RTLD_GLOBAL);
    if (lib == NULL)
        return 1;

    p_open = (zip_t(*)(const char *,int *))dlsym(lib,"zip_open");
    if (p_open == NULL)
        return 1;
    p_close = (void(*)(zip_t*))dlsym(lib,"zip_close");
    if (p_close == NULL)
        return 1;

    myzip = p_open("myzip.zip",ZIP_CREATE | ZIP_TRUNCATE,&err);
    if (myzip == NULL)
        return 1;

    p_close(myzip);

    return 0;
}
,

要添加到EmployedRussian的答案中,您可以借助Implib.so工具来实现所需的功能。它将为所有将在内部调用zip_open / dlopen的库符号(例如dlsym)生成存根,并将调用从程序转发到共享库:

$ gcc -c prog.c
$ implib-gen.py path/to/libzip.so
$ gcc -o prog prog.o libzip.tramp.S libzip.init.c -ldl

(请注意,您不再需要精美的链接器标志和链接器空运行)。

请注意,您尝试做的事情称为延迟加载,它是Windows DLLS的standard feature

本文链接:https://www.f2er.com/3155259.html

大家都在问