9.10. Symbol Resolution
We know that run time linking occurs, but what if there is more than one symbol with the same name? How does the order of dependencies affect which will be chosen at run time?
To better understand this, we need a multi-layer dependency tree of shared libraries such as the one in Figure 9.5.
我们知道会发生运行时链接,但是如果有多个具有相同名称的符号会怎么样? 依赖关系的顺序如何影响在运行时选择哪个?
为了更好地理解这一点,我们需要一个共享库的多层依赖树,如图9.5所示。
Figure 9.5. Symbol Resolution with Shared Libraries.
The executable resm comes from a source file called resm.C. It was linked with two shared libraries, libres.so (res.C) and libres2.so (res2.C). libres.so also has a dependent library called libresd.so (resd.C, the d is for deeper). Lastly, libresd.so and libres2 export the same symbols.
Here are the contents of the various source files. The global integers globInt1 and globInt2 are defined in libres.so and resm but with different names. Both resm and libres.so contain the functions, function1 and function2:
可执行文件resm来自名为resm.C的源文件。 它与两个共享库链接,libres.so(res.C)和libres2.so(res2.C)。 libres.so还有一个名为libresd.so的依赖库(resd.C,d代表更深层次)。 最后,libresd.so和libres2导出相同的符号。
以下是各种源文件的内容。 全局整数globInt1和globInt2在libres.so和resm中定义,但名称不同。 resm和libres.so都包含函数function1和function2:
Code View: Scroll / Show All
resm.C
#include <stdio.h>
int globInt1 = 1 ;
int globInt2 = 2 ;
int function1( )
{
printf( "function1: in resm.C\n" ) ;
}
int function2( )
{
printf( "function2: in resm.C\n" ) ;
}
extern int function3( ) ;
int main( )
{
printf( "calling function1 from resm.C\n" ) ;
function1( ) ;
printf( "value of globInt1: %u\n", globInt1 ) ;
printf( "transfering control to shared library\n" ) ;
function3( ) ;
}
res.C:
#include <stdio.h>
int globInt1 = 3 ;
int globInt2 = 4 ;
extern int function6( ) ;
int function1( )
{
printf( "function1: in res.C\n" ) ;
}
int function2( )
{
printf( "function2: in res.C\n" ) ;
}
int function3( )
{
printf( "calling function2 from res.C\n" ) ;
function2( ) ;
printf( "value of globInt2: %u\n", globInt2 ) ;
printf( "calling function6 from res.C\n" ) ;
function6( ) ;
}
Here are the important symbols in each of these ELF files: 下面是这些 ELF 文件中的重要符号:
penguin> nm resm |egrep "globInt|function"
0804862c T _Z9function1v
08048656 T _Z9function2v
U _Z9function3v
08049828 D globInt1
0804982c D globInt2
penguin> nm libres.so | egrep "globInt|function"
00000884 T _Z9function1v
000008ae T _Z9function2v
000008d8 T _Z9function3v
U _Z9function6v
00001a38 D globInt1
00001a3c D globInt2
Now to run the executable resm to see the link resolution and order in action: 现在运行可执行文件 resm 以查看链接解析和操作顺序:
penguin> resm
main: calling function1 from resm.C
function1: in resm.C
main: value of globInt1: 1 in resm.C
main: transferring control to shared library
function3: calling function2 from res.C
function2: in resm.C
function3: value of globInt2: 2 in res.C
function3: calling function6 from res.C
function6: in res2.C
function6: value of globInt2: 2 in res2.C
The call from the function main in resm.C to function1 called the version of function1 in resm.C. A reference to globInt1 from resm.C picked up the version of globInt1 from resm.C (the executable) as well. Things get more interesting when the executable transfers control to the shared library libres.so. libres.so references globInt2 and function2, which are defined in the executable as well as libres.so itself.
resm.C中的函数main调用resm.C的function1。resm.C对globInt1的引用也从resm.C(可执行文件)中获取了globInt1的版本。当可执行文件将控制转移到共享库libres.so时,事情变得更有趣。libres.so引用globInt2和function2,它们在可执行文件和libres.so本身中定义。
The function3 in res.C calls function2 although the version of function2 in resm.C (the executable) is called and not the version in res.C. The resolution of the function2 found the location in the executable, not the calling shared library. So imagine you’re the owner of a shared library and someone adds a function or global variable with the same name as one defined in your shared library. This would almost certainly cause a problem.
res.C中的function3调用function2虽然resm.C中的function2的版本(可执行文件)被调用,而不是res.C中的版本。 function2的解析在可执行文件中找到了位置,而不是调用共享库。 因此,假设您是共享库的所有者,并且有人添加了一个与共享库中定义的函数或全局变量同名的函数或全局变量。 这几乎肯定会引起问题。
The solution to this is symbolic linking. By using the -Bsymbolic switch, the linker will try to satisfy the undefined symbols using those in the immediate shared library.
对此的解决方案是符号链接。通过使用-Bsymbolic开关,链接器将尝试使用直接共享库中的那些符号来满足未定义的符号。
Code View: Scroll / Show All
penguin> g++ -shared res.o -o libres.so -L. -Wl,-rpath,. -lresd - Wl,-Bsymbolic
Now to run resm again:
penguin> resm
main: calling function1 from resm.C
function1: in resm.C
main: value of globInt1: 1 in resm.C
main: transferring control to shared library
function3: calling function2 from res.C
function2: in res.C
function3: value of globInt2: 4 in res.C
function3: calling function6 from res.C
function6: in res2.C
function6: value of globInt2: 2 in res2.C
This time, we see that function2 from res.C (libres.so) was called, and the value of globInt2 is from res.C.
这一次,我们看到来自res.C(libres.so)的function2被调用,而globInt2的值来自res.C.
Another interesting link order problem is illustrated via res2.C and resd.C. Here are the almost identical source files, res2.C and resd.C:
另一个有趣的链接顺序问题通过res2.C和resd.C来说明。 以下是几乎完全相同的源文件res2.C和resd.C:
Code View: Scroll / Show All
res2.C
#include <stdio.h>
extern int globInt1 ;
extern int globInt2 ;
int function4( )
{
printf( "function4: in res2.C\n" ) ;
}
int function5( )
{
printf( "function5: in res2.C\n" ) ;
}
int function6( )
{
printf( "function6: in res2.C\n" ) ;
printf( "value of globInt2: %u\n", globInt2 ) ;
}
resd.C
#include <stdio.h>
extern int globInt1 ;
extern int globInt2 ;
int function4( )
{
printf( "function4: in resd.C\n" ) ;
}
int function5( )
{
printf( "function5: in resd.C\n" ) ;
}
int function6( )
{
printf( "function6 in resd.C\n" ) ;
printf( "value of globInt2: %u\n", globInt2 ) ;
}
The library libres.so requires a library libresd.so to satistfy a symbol function6, which is defined in both libres2.so and libresd.so. The executable has libres2.so as a dependency as well as libres.so. Now, the code in libres.so is probably expecting to use the symbols in libresd.so because that library was required at link time to satisfy the reference to function6. Let’s see what happens in reality (here is the first output from the executable resm for quick reference):
库libres.so需要一个库libresd.so来为一个符号function6 satistfy,它在libres2.so和libresd.so中定义。 可执行文件将libres2.so作为依赖项以及libres.so。 现在,libres.so中的代码可能希望使用libresd.so中的符号,因为在链接时需要该库来满足对function6的引用。 让我们看看现实中发生了什么(这是可执行文件resm的第一个输出,供快速参考):
penguin> resm
main: calling function1 from resm.C
function1: in resm.C
main: value of globInt1: 1 in resm.C
main: transferring control to shared library
function3: calling function2 from res.C
function2: in resm.C
function3: value of globInt2: 2 in res.C
function3: calling function6 from res.C
function6: in res2.C
function6: value of globInt2: 2 in res2.C
The call to function6 actually calls the version in res2.C instead! This would also cause unwanted results, although -Bsymbolic will not help in this situation because the values are outside of the shared library.
对function6的调用实际上调用了res2.C中的版本! 这也会导致不需要的结果,尽管-Bsymbolic在这种情况下无济于事,因为这些值在共享库之外。
Some shared libraries support symbol versioning to reduce the chance of incompatible symbol collisions, although the details are quite complex and not covered here. Symbol versioning allows multiple, incompatible functions of the same name to exist in the same shared library. This allows older applications to continue to use the older versions of the function, while newer applications will use the newer functions. This is really meant for down-level compatibility, not necessarily unintentional duplicate symbols.
一些共享库支持符号版本控制,以减少不兼容的符号冲突的可能性,尽管细节非常复杂,此处未涉及。 符号版本控制允许同一个共享库中存在多个不兼容的同名函数。 这允许较旧的应用程序继续使用旧版本的功能,而较新的应用程序将使用较新的功能。 这实际上意味着低级兼容性,不一定是无意的重复符号。
9.11. Use of Weak Symbols for Problem Investigations
Using weak symbols is a very powerful feature that every developer or senior service analyst should know about.
使用弱符号是每个开发人员或高级服务分析师都应该了解的非常强大的功能。
Step #1: Choose a weak symbol from libc.
Code View: Scroll / Show All
penguin> nm /usr/lib/libc.so
nm: /usr/lib/libc.so: File format not recognized
penguin> cat /usr/lib/libc.so
/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a )
penguin> nm -n /lib/libc.so.6 | egrep " W " | egrep -v _
<...>
0006dd70 W malloc
0006e570 W cfree
0006e570 W free
0006e940 W realloc
<...>
000bf930 W fchmod
000bf970 W mkdir
000bf9b0 W open
000bf9f0 W open64
000bfa40 W close
000bfa80 W read
000bfac0 W write
<...>
You can choose any weak symbol, but it is usually most useful to choose one of the standard system calls of libc functions. Some good examples are malloc, free, realloc, mkdir, open, close, read, and so on.
您可以选择任何弱符号,但选择libc函数的标准系统调用之一通常最有用。 一些很好的例子是malloc,free,realloc,mkdir,open,close,read等等。
Step #2: Read the man page or header file for the function you want to intercept. You’ll want to steal the exact function prototype. The header file is better because it will be more accurate. In any case, here are the function definitions for malloc, realloc, and free:
步骤2:阅读要拦截的函数的手册或头文件。你想要拦截的函数的确切原型。 头文件更好,因为它会更准确。 无论如何,这里是malloc,realloc和free的函数定义:
void *malloc(size_t size);
void free(void *ptr);
void *realloc(void *ptr, size_t size);
Step #3: Find the non-weak symbols (the real functions that get called). The easiest and most reliable way to find the non-weak symbols is to find strong symbols that have the same address as the weak symbols you are interested in.
步骤#3:找到非弱符号(被调用的实际函数)。 找到非弱符号的最简单,最可靠的方法是找到与您感兴趣的弱符号具有相同地址的强符号。
penguin> nm -n /lib/libc.so.6 | egrep 'malloc$' | egrep " W "
0006dd70 W malloc
penguin> nm -n /lib/libc.so.6 | egrep 0006dd70
0006dd70 T __libc_malloc
0006dd70 t __malloc
0006dd70 W malloc
In the output here, we first find the address of the malloc symbol we’re interested in and then look for symbols that have the same “value,” meaning that they point to the same function. The matching symbol that has a class of “T,” meaning defined, text (a real function) is that one we need and is called __libc_malloc.__libc_ is the convention used for the real functions (also applicable to realloc and free).
在这里的输出中,我们首先找到我们感兴趣的malloc符号的地址,然后查找具有相同“值”的符号,这意味着它们指向相同的函数。 具有“T”类的匹配符号,意思是定义的,文本(实际函数)是我们需要的并且被称为__libc_malloc .__ libc_是用于实际函数的约定(也适用于realloc和free)。
Step #4: Build an interceptor library. You must define real functions using the prototypes from Step 1. We’re using malloc, free, and realloc as examples here. You can do the same thing with any weak function in libc.
步骤#4:构建拦截器库。 您必须使用步骤1中的原型定义实际函数。我们在此处使用malloc,free和realloc作为示例。 你可以用libc中的任何弱函数做同样的事情。
Building an interceptor library requires a special process. For example, you might not be able to include any system header files because of duplicate declarations. After all, you will be defining functions with the same definition as those in libc.
构建拦截器库需要一个特殊的过程。 例如,由于重复声明,您可能无法包含任何系统头文件。 毕竟,您将定义与libc中定义相同的函数。
Another consideration is unwanted recursion. Some libc functions such as fopen will call other libc functions such as malloc under the covers. If we override malloc, we have to be aware that calling fopen from within malloc might eventually and recursively (because we’re already in malloc) call malloc.
另一个考虑因素是不必要的递归。一些libc函数(如fopen)将调用其他libc函数,例如malloc。 如果我们覆盖malloc,我们必须要知道从malloc中调用fopen可能最终和递归(因为我们已经在malloc中)调用malloc。
In any case, here is the source for an example interception library for malloc, free, and realloc:
无论如何,这里是malloc,free和realloc的示例拦截库的源代码:
Code View: Scroll / Show All
intercept.c:
/* Here are the declarations needed to make all of this compile.
Copying these directly from a system header would normally be a very
bad idea. Here, we must do it to avoid any system headers */
typedef unsigned long int size_t;
/* Function definitions for the strong text symbols. The function
names should match that of their counterparts exactly with the
exception of having different function names. */
void *__libc_malloc(size_t size);
void *__libc_realloc(void *ptr, size_t size);
void __libc_free(void *ptr);
/* Actual function used to override malloc, free and realloc */
void *malloc(size_t size)
{
void *rptr ;
printf( "malloc : Requested block size: %u\n", size ) ;
rptr = __libc_malloc( size ) ;
printf( "malloc : ptr of allocated block: %p\n", rptr ) ;
return rptr ;
}
void free(void *ptr)
{
printf( "free : freeing block: %p\n", ptr ) ;
return __libc_free( ptr ) ;
}
void *realloc(void *ptr, size_t size)
{
void *rptr ;
printf( "realloc : ptr of previous block: %p\n", ptr ) ;
printf( "realloc : Requested block size: %u\n", size ) ;
rptr = __libc_realloc( ptr, size ) ;
printf( "realloc : ptr of allocated block: %p\n", rptr ) ;
return rptr ;
}
The first part defines (and almost apologizes for doing so) some basic types used by the functions we’re going to override. In this case, it defines size_t for malloc and realloc. The next section declares the strong text symbols for malloc, free, and realloc with the __libc_ prefix. The third and final section actually defines the functions we’re going to override.
第一部分定义(并且几乎为这样做道歉)我们要覆盖的函数使用的一些基本类型。 在这种情况下,它为malloc和realloc定义size_t。 下一节使用__libc_前缀声明malloc,free和realloc的强文本符号。 第三部分和最后一部分实际上定义了我们要覆盖的函数。
These overridden functions must eventually call the actual, matching libc functions, or any program run with this interceptor library will not run as expected. We also have to be careful not to change any behavior of the program we’re investigating. For example, the program might (incorrectly) close and never reopen file descriptor 1, stdout, until a subsequent call to open() to open a real file. In this case, all of our printf text will go to this file instead of stdout, which could corrupt the file. Such interactions are rare, but it’s good to keep them in mind.
这些重写的函数最终必须调用实际的匹配libc函数,否则运行此拦截器库的任何程序都不会按预期运行。 我们还必须小心,不要改变我们正在调查的程序的任何行为。 例如,程序可能(错误地)关闭并且永远不会重新打开文件描述符1,stdout,直到后续调用open()来打开真实文件。 在这种情况下,我们所有的printf文本都将转到此文件而不是stdout,这可能会破坏文件。 这种互动很少见,但记住这些互动是件好事。
Compiling the interceptor library is straightforward as shown here:
编译拦截器库很简单,如下所示:
penguin> gcc intercept.c -fPIC -c
penguin> gcc -shared intercept.o -o intercept.so
Step #5: Use LD_PRELOAD (and possibly LD_DYNAMIC_WEAK).
For this interceptor library to work, we need to get it into the address space of a program. The LD_PRELOAD environment variable instructs the program interpreter to load a library before executing a program, ensuring that our interceptor library will be in the address space as needed.
为了使这个拦截器库工作,我们需要将它放入程序的地址空间。 LD_PRELOAD环境变量指示程序解释器在执行程序之前加载库,确保我们的拦截器库将根据需要位于地址空间中。
penguin> export LD_PRELOAD=/home/wilding/src/Linuxbook/ELF/intercept.so
After this point, any program we run will call our versions of malloc and free. In the following example, we use the program ls to show how this works—with the resulting output:
在此之后,我们运行的任何程序都将调用我们的malloc和free版本。 在下面的示例中,我们使用程序ls来显示其工作原理 - 使用结果输出:
penguin> ls
realloc : ptr of previous block: (nil)
realloc : Requested block size: 16
malloc : Requested block size: 24
malloc : ptr of allocated block: 0x8059850
realloc : ptr of allocated block: 0x8059870
free : freeing block: 0x8059870
malloc : Requested block size: 26
malloc : ptr of allocated block: 0x8059870
malloc : Requested block size: 10
malloc : ptr of allocated block: 0x8059890
<...>
If this doesn’t work, you may also need to set the LD_DYNAMIC_WEAK environment variable. See the man page for ld.so for more information.
如果这不起作用,您可能还需要设置LD_DYNAMIC_WEAK环境变量。 有关更多信息,请参见ld.so的手册页。
This method of using weak symbols to override system functions can be very useful and can be used to:
这种使用弱符号覆盖系统函数的方法非常有用,可用于:
- Trigger an action if a system call takes too long.
- Find difficult memory leaks when all else fails.
- Redirect system calls to do something else.
- Trigger an action when a very specific system call is made (for example, to a particular file).
- and so on...
Unless special circumstances dictate, this method should only be used to debug a problem and not to supplement the functionality of libc. The method works well, but any source code that copies definitions from system headers should be used with care.
除非特殊情况需要,否则此方法仅用于调试问题而不是用于补充libc的功能。 该方法运行良好,但应谨慎使用从系统头中复制定义的任何源代码。
来源:https://blog.csdn.net/mounter625/article/details/102754279