Tip: solution to repeated Structure Problem of objects with the same name in multiple shared dynamic libraries

Source: Internet
Author: User

Shared libraries supported by Linux (lib *. so) technology can not only effectively use system resources, but also bring great convenience and versatility to program design, so it is widely used by various levels of application systems. A dynamically linked shared library is loaded when an application is loaded, and it is bound to the application at runtime: through the dynamic linker, map the dynamic shared library to the executable memory (Dynamic Link) of the application. When the application is started, the dynamic loader maps the required shared target Library to the application memory (dynamic loading ).

In general, shared libraries are compiled by using the additional option-FPIC or-FPIC to generate position-independent code (PIC) from the target code ), use the-shared option to put the target code into the shared target library. Location-independent code must be loaded to different addresses of different processes and correctly executed. Therefore, the Code must be specially compiled: location-independent code (PIC) the constant and function entry address operations adopt Addressing Based on the base register base + offset. Even if the program is loaded to a different address in the memory, that is, the base
The value is different, and the offset is unchanged, so the program can still find the correct entry address or constant.

However, when an application links to multiple shared libraries, if these shared libraries contain static member variables of the same scope or global variables of the same name (non-static, when the program accesses static member variables or global variables to end the destructor, the double free of a memory block will cause core dump, which is caused by the defects of the Linux compiler.

Application Scenario prototype

This problem stems from the author's development project: IBM Tivoli workload schedvel( TWS) loadleveler. Loadleveler is a job scheduling software developed by IBM in the high performance computing (HPC) field. It is mainly divided into two major modules: sched and resource manger ). The two modules contain shared libraries for the configuration management function. Some configuration management options are used by the two modules, so some source file code is shared between the two modules, it contains static class members with the same name.

You can describe the following simple model:

Figure 1. application scenarios

Shows the code snippets of each module:

Figure 2. Application Scenario Simulation code

Test. c is the main program and contains two header files: api1.h and api2.h. the header file api1.h contains the header file lib1/lib. H and one function func_api1 (). api2.h contains the header file lib2/lib. h. Compile the source files under lib1 and lib2 to generate the shared libraries lib1.so and lib2.so respectively. At the same time, the header file lib1/lib. h and lib2/lib. h are linked to the same shared file Lib. h. The file Lib. h defines a static member variable "static ".
STD: vector <int> vec_int ".

Code List of functional functions and various static member functions

The function func_api1 () is similar to func_api2 (). By calling the static member function, you can access the static member variable vec_int:

Listing 1. Function function func_api1 (INT)

 void func_api1(int i) {   printf("%s.\n", __FILE__);    A::set(i);   A::print();   return;  } 

The static member functions A: Set () and a: Print () are implemented as follows:

Listing 2. static member function A: Set (INT)

 void A::set(int num) {   vec_int.clear();   for (int i = 0; i < num; i++) {     vec_int.push_back(i);   }   return;  } 

Listing 3. static member function A: Print ()

 void A::print() {   for (int i = 0; i < vec_int.size(); i++) {     printf("vec_int[%d] = %d, addr: %p.\n", i, vec_int[i], &vec_int[i]);   }   printf("vec_int addr: %p.\n", &vec_int);   return;  } 

A: Set () assigns values to the static member vec_int, while a: Print () prints the value and the memory address of the current item.

Running result

If the two shared libraries are compiled by option-FPIC or-FPIC, run the program test and output the following:

Listing 4. Option-FPIC test results

 $ export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH  $ g++ -g -o lib1.so -fPIC-rdynamic -shared lib1/lib.c  $ g++ -g -o lib2.so -fPIC-rdynamic -shared lib2/lib.c  $ g++ -g -o test -L./ -l1 -l2 test.c  $ ./test  api1.h.  vec_int[0] = 0, addr: 0x9cbf028.  vec_int[1] = 1, addr: 0x9cbf02c.  vec_int[2] = 2, addr: 0x9cbf030.  vec_int[3] = 3, addr: 0x9cbf034.  vec_int addr: 0xe89228.  *** glibc detected *** ./test: double free or corruption (fasttop): 0x09cbf028***  ======= Backtrace:=========  /lib/libc.so.6[0x2b2b16]  /lib/libc.so.6(cfree+0x90)[0x2b6030]  /usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x5d1731]  ./lib1.so(_ZN9__gnu_cxx13new_allocatorIiE10deallocateEPij+0x1d)[0xe88417]     ./lib1.so(_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPij+0x33)[0xe88451]     ./lib1.so(_ZNSt12_Vector_baseIiSaIiEED2Ev+0x42)[0xe8849a]     ./lib1.so(_ZNSt6vectorIiSaIiEED1Ev+0x60)[0xe8850c]  ./lib2.so[0x961d6c]  /lib/libc.so.6(__cxa_finalize+0xa9)[0x275c79]  ./lib2.so[0x961c34]  ./lib2.so[0x962d3c]  /lib/ld-linux.so.2[0x23a7de]  /lib/libc.so.6(exit+0xe9)[0x2759c9]  /lib/libc.so.6(__libc_start_main+0xe4)[0x25fdf4]  ./test(__gxx_personality_v0+0x45)[0x80484c1]  ======= Memory map:========  ......  00960000-00963000 r-xp 00000000 00:1b 7668734  ./lib2.so  00963000-00964000 rwxp 00003000 00:1b 7668734  ./lib2.so  00970000-00971000 r-xp 00970000 00:00 0     [vdso]  00e86000-00e89000 r-xp 00000000 00:1b 7668022  ./lib1.so  00e89000-00e8a000 rwxp 00003000 00:1b 7668022  ./lib1.so  08048000-08049000 r-xp 00000000 00:1b 7668748  ./test  08049000-0804a000 rw-p 00000000 00:1b 7668748  ./test  09cbf000-09ce0000 rw-p 09cbf000 00:00 0     [heap]  ......  Abort(coredump)  $ 

From the program output, the core is generated because the memory zone with the starting address 0x09cbf028 In the heap memory area (09cbf000-09ce0000) is released twice, this address is the address of the first element of the static member variable vec_int.

Why is the same memory zone released twice?

Cause Analysis

We know that static member variables are similar to global variables and all adopt static storage. For Shared libraries with option-FPIC or-FPIC, the addresses of these variables are stored in the global Offset Table (got) of the shared database.

Use the objdump or readelf command to analyze the shared library lib1.so. The result is as follows:

Listing 5. objdump analyzes the output of the shared library lib1.so

 $ objdump -x -R lib1.so   lib1.so:   file format elf32-i386  ......  Sections:  Idx Name     Size   VMA    LMA    File off Algn  0 .gnu.hash   000001e8 000000d4 000000d4 000000d4 2**2          CONTENTS, ALLOC, LOAD, READONLY, DATA  ......  18 .dynamic   000000d8 0000301c 0000301c 0000301c 2**2          CONTENTS, ALLOC, LOAD, DATA  19 .got     00000014 000030f4 000030f4 000030f4 2**2          CONTENTS, ALLOC, LOAD, DATA  20 .got.plt   00000114 00003108 00003108 00003108 2**2          CONTENTS, ALLOC, LOAD, DATA  ......  DYNAMIC RELOCATION RECORDS  OFFSET  TYPE       VALUE  ......  000030f4 R_386_GLOB_DAT  __gmon_start__  000030f8 R_386_GLOB_DAT  _Jv_RegisterClasses  000030fc R_386_GLOB_DAT  _ZN1A7vec_intE  00003104 R_386_GLOB_DAT  __cxa_finalize  ...... 

Listing 6. readelf analyzes the output of the shared library lib1.so

 $ objdump -x -R lib1.so   lib1.so:   file format elf32-i386  ......  Sections:  Idx Name     Size   VMA    LMA    File off Algn  0 .gnu.hash   000001e8 000000d4 000000d4 000000d4 2**2          CONTENTS, ALLOC, LOAD, READONLY, DATA  ......  18 .dynamic   000000d8 0000301c 0000301c 0000301c 2**2          CONTENTS, ALLOC, LOAD, DATA  19 .got     00000014 000030f4 000030f4 000030f4 2**2          CONTENTS, ALLOC, LOAD, DATA  20 .got.plt   00000114 00003108 00003108 00003108 2**2          CONTENTS, ALLOC, LOAD, DATA  ......  DYNAMIC RELOCATION RECORDS  OFFSET  TYPE       VALUE  ......  000030f4 R_386_GLOB_DAT  __gmon_start__  000030f8 R_386_GLOB_DAT  _Jv_RegisterClasses  000030fc R_386_GLOB_DAT  _ZN1A7vec_intE  00003104 R_386_GLOB_DAT  __cxa_finalize  ...... 

From the output results of the above two commands, we can see that the starting memory address of the got segment in the shared library lib1.so is listen 30f4, And the size is 20 bytes (0x14 ); the starting offset of the static member variable vec_int in the shared library lib1.so is ipv30fc. Obviously, vec_int is located in the got segment of the shared library.

When the application connects lib1.so and lib2.so at the same time, the static member variable vec_int with the same name is located in the got area of the shared library. When the program is running, the system looks for and loads a vec_int data from the symbol table. This is shown in the "backtrace" section of the output result (listing 4) of the program running: only static member variables in lib1.so are loaded and constructed. At the same time, the memory map ing (memory map) Section (listing 4) is used ), we can see that the address of the vec_int object 0xe89228 is in the readable memory zone 00e89000-00e8a000 allocated to the shared library lib1.so:

    00e89000-00e8a000 rwxp 00003000 00:1b 7668022  ./lib1.so

Then, when the program ends, the variable is analyzed twice, and the core file is analyzed through GDB:

Listing 7. Core File Analysis Results

 $ gdb ./test core.28440 ……  Core was generated by `./test'.  Program terminated with signal 6, Aborted.  #0 0x00970402 in __kernel_vsyscall ()  (gdb)  (gdb) where  #0 0x00970402 in __kernel_vsyscall ()  #1 0x00272d10 in raise () from /lib/libc.so.6  #2 0x00274621 in abort () from /lib/libc.so.6  #3 0x002aae5b in __libc_message () from /lib/libc.so.6  #4 0x002b2b16 in _int_free () from /lib/libc.so.6  #5 0x002b6030 in free () from /lib/libc.so.6  #6 0x005d1731 in operator delete () from /usr/lib/libstdc++.so.6  #7 0x00e88417 in __gnu_cxx::new_allocator<int>::deallocate    (this=0xe89228, __p=0x9cbf028)   at /usr/lib/gcc/i386-redhat-linux/.../ext/new_allocator.h:94  #8 0x00e88451 in std::_Vector_base<int, ... (this=0xe89228, __p=0x9cbf028, __n=4)   at /usr/lib/gcc/.../include/c++/4.1.2/bits/stl_vector.h:133  #9 0x00e8849a in ~_Vector_base (this=0xe89228)   at /usr/lib/gcc/.../include/c++/4.1.2/bits/stl_vector.h:119  #10 0x00e8850cin ~vector (this=0xe89228) at /usr/lib/gcc/.../stl_vector.h:272  #11 0x00961d6c in __tcf_0 () at lib2/lib.c:3  #12 0x00275c79 in __cxa_finalize () from /lib/libc.so.6  #13 0x00961c34 in __do_global_dtors_aux () from ./lib2.so  #14 0x00962d3c in _fini () from ./lib2.so  #15 0x0023a7de in _dl_fini () from /lib/ld-linux.so.2  #16 0x002759c9 in exit () from /lib/libc.so.6  #17 0x0025fdf4 in __libc_start_main () from /lib/libc.so.6  #18 0x080484c1 in _start ()  (gdb) 

From list 7, we can see that, starting from frame #14, the program performs the Destructor operation in lib2.so until #11 is running in lib2.so. When frame #10 is entered, the address of variable analysis is 0x00e8850c. The objects in this address are constructed by the shared library lib1.so when the program is started (List 1 ):

    ./lib1.so(_ZNSt6vectorIiSaIiEED1Ev+0x60)[0xe8850c]

When the program ends, the Runtime library glibc detects that the shared library lib2.so parses objects not constructed by it, resulting in core dump.

In this case, if the option-fpie or-fpie is replaced, the operation steps and running results are as follows:

Listing 8. Option-fpie test results

 $ export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH  $ g++ -g -o lib1.so -fPIE-rdynamic -shared lib1/lib.c  $ g++ -g -o lib2.so -fPIE-rdynamic -shared lib2/lib.c  $ g++ -g -pie -o test -L./ -l1 -l2 test.c  $ ./test  api1.h.  vec_int[0] = 0, addr: 0x80e3028.  vec_int[1] = 1, addr: 0x80e302c.  vec_int[2] = 2, addr: 0x80e3030.  vec_int[3] = 3, addr: 0x80e3034.  vec_int addr: 0x75e224.  $ 

The program runs as expected and ends normally.

This is because when option-fpie or-fpie is used, the generated shared library does not create corresponding entries for static member variables or global variables in got (you can view them by using the objdump or readelf command, which will not be described here ), this avoids the program core dump caused by the release of the same memory region twice because the static object is "constructed once and destructed twice.

Option-fpie and-fpie are similar to-FPIC and-FPIC usage. The difference is that the former always regards the generated location-independent code as a program, and directly link it to the executable program, instead of storing it in the got of the global offset table. In this way, for access to static or global objects with the same name, the constructor corresponds to the Destructor one by one.

Conclusion

By using option-fpie or-fpie instead of-FPIC or-FPIC, the generated shared library will not create corresponding entries for static member variables or global variables in got, at the same time, this avoids improper operations on static objects with the same name, such as "one construction and two destructor.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.