9.10. Symbol Resolution

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的功能。 該方法運行良好,但應謹慎使用從系統頭中複製定義的任何源代碼。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章