1. 程式人生 > >共享庫的初始化和~初始化函式分析

共享庫的初始化和~初始化函式分析

Win32下可以通過DllMain來初始化和~初始化動態庫,而Linux下則沒有與之完全對應的函式,但可以通過一些方法模擬它的部分功能。有人會說,很簡單,實現_init/_fini兩個函式就行了。好,我們來看看事實是不是這樣的。

很多資料上都說可以利用_init/_fini來實現,而我從來沒有測試成功過,原因是這兩個函式都已經被gcc佔用了。比如:

test.c

#include <stdio.h>

void _init(void)

{

    printf("%s", __func__);

}

void _fini(void)

{

    printf("%s", __func__);

}

編譯結果:

[[email protected] soinit]# gcc -g test.c -shared -o libtest.so

/tmp/cc4DUw68.o(.text+0x0): In function `_init':

/work/test/soinit/test.c:5: multiple definition of `_init'

/usr/lib/gcc/i386-redhat-linux/4.0.0/../../../crti.o(.init+0x0): first defined here

/tmp/cc4DUw68.o(.text+0x1d): In function `_fini':

/work/test/soinit/test.c:10: multiple definition of `_fini'

/usr/lib/gcc/i386-redhat-linux/4.0.0/../../../crti.o(.fini+0x0): first defined here

collect2: ld returned 1 exit status

由此可見,這兩個符號已經被編譯器的腳手架程式碼佔用了,我們不能再使用。編譯器用這兩個函式做什麼?我們能不能搶佔這兩個函式,不用編譯器提供的,而用我們自己的呢?先看看這兩個的實現:

00000594 <_fini>:

 594:   55                      push   %ebp

 595:   89 e5                   mov    %esp,%ebp

 597:   53                      push   %ebx

 598:   50                      push   %eax

 599:   e8 00 00 00 00          call   59e <_fini+0xa>

 59e:   5b                      pop    %ebx

 59f:   81 c3 02 11 00 00       add    $0x1102,%ebx

 5a5:   e8 de fe ff ff          call   488 <__do_global_dtors_aux>

 5aa:   58                      pop    %eax

 5ab:   5b                      pop    %ebx

 5ac:   c9                      leave 

 5ad:   c3                      ret   

 0000041c <_init>:

 41c:   55                      push   %ebp

 41d:   89 e5                   mov    %esp,%ebp

 41f:   83 ec 08                sub    $0x8,%esp

 422:   e8 3d 00 00 00          call   464 <call_gmon_start>

 427:   e8 b8 00 00 00          call   4e4 <frame_dummy>

 42c:   e8 2b 01 00 00          call   55c <__do_global_ctors_aux>

 431:   c9                      leave 

 432:   c3                      ret   

從以上程式碼中可以看出,這兩個函式是用來初始化/~初始化全域性變數/物件的,搶佔這兩個函式可能導致初始化/~初始化全域性變數/物件出錯。所以不能再打_init/_fini的主意,那怎麼辦呢?

使用全域性物件

test.cpp

#include <stdio.h>

class InitFini

{

public:

    InitFini()

    {

        printf("%s/n", __func__);

    }

    ~InitFini()

    {

        printf("%s/n", __func__);

    }

};

static InitFini aInitFini;

extern "C" int test(int n)

{

    return n;

}

Main.c

int test(int n);

int main(int argccharargv[])

{

    test(1);

    return 0;

}

測試結果:

[[email protected] soinit]# ./t.exe

InitFini

~InitFini

那麼這兩個函式是怎麼被呼叫的呢?我們在gdb裡看看:

Breakpoint 3, InitFini (this=0xa507bc) at test.cpp:7

7                       printf("%s/n", __func__);

Current language:  auto; currently c++

(gdb) bt

#0  InitFini (this=0xa507bc) at test.cpp:7

#1  0x00a4f5e0 in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at test.cpp:15

#2  0x00a4f611 in global constructors keyed to test () at test.cpp:21

#3  0x00a4f66a in __do_global_ctors_aux () from ./libtest.so

#4  0x00a4f4a9 in _init () from ./libtest.so

#5  0x002c8b4b in call_init () from /lib/ld-linux.so.2

#6  0x002c8c4a in _dl_init_internal () from /lib/ld-linux.so.2

#7  0x002bb83f in _dl_start_user () from /lib/ld-linux.so.2

Breakpoint 4, ~InitFini (this=0x0) at test.cpp:9

9               ~InitFini()

(gdb) bt

#0  ~InitFini (this=0x0) at test.cpp:9

#1  0x00a4f5b3 in __tcf_0 () at test.cpp:15

#2  0x00303e6f in __cxa_finalize () from /lib/libc.so.6

#3  0x00a4f532 in __do_global_dtors_aux () from ./libtest.so

#4  0x00a4f692 in _fini () from ./libtest.so

#5  0x002c9058 in _dl_fini () from /lib/ld-linux.so.2

#6  0x00303c69 in exit () from /lib/libc.so.6

#7  0x002eddee in __libc_start_main () from /lib/libc.so.6

#8  0x080483b5 in _start ()

從以上資訊可以看出,正是從_init/_fini兩個函式呼叫過來的。

使用gcc擴充套件

毫無疑問,以上方法可行,但是在C++中才行。而C語言中,根本沒有構造和解構函式。怎麼辦呢?這時我們可以使用gcc的擴充套件:

#include <stdio.h>

__attribute ((constructor)) void test_init(void)

{

    printf("%s/n", __func__);

}

__attribute ((destructor)) void test_fini(void)

{

    printf("%s/n", __func__);

}

int test(int n)

{

    return n;

}

測試結果:

[[email protected] soinit]# ./t.exe

test_init

test_fini

我們不防也看看這兩個函式是怎麼被調到的:

Breakpoint 3, test_init () at test.c:6

6               printf("%s/n", __func__);

(gdb) bt

#0  test_init () at test.c:6

#1  0x00860586 in __do_global_ctors_aux () from ./libtest.so

#2  0x00860439 in _init () from ./libtest.so

#3  0x002c8b4b in call_init () from /lib/ld-linux.so.2

#4  0x002c8c4a in _dl_init_internal () from /lib/ld-linux.so.2

#5  0x002bb83f in _dl_start_user () from /lib/ld-linux.so.2

(gdb) c

Breakpoint 4, test_fini () at test.c:11

11              printf("%s/n", __func__);

(gdb) bt

#0  test_fini () at test.c:11

#1  0x008604d3 in __do_global_dtors_aux () from ./libtest.so

#2  0x008605ae in _fini () from ./libtest.so

#3  0x002c9058 in _dl_fini () from /lib/ld-linux.so.2

#4  0x00303c69 in exit () from /lib/libc.so.6

#5  0x002eddee in __libc_start_main () from /lib/libc.so.6

#6  0x080483b5 in _start ()

從以上資訊可以看出,也是從_init/_fini兩個函式呼叫過來的。

總結:正如一些資料上所說的,在linux下,_init/_fini是共享庫的初始化和~初始化函式。但這兩個函式是給gcc用的,我們不能直接使用它們,但可以用本文中提到另外兩種方法來實現。

相關推薦

共享初始~初始函式分析

Win32下可以通過DllMain來初始化和~初始化動態庫,而Linux下則沒有與之完全對應的函式,但可以通過一些方法模擬它的部分功能。有人會說,很簡單,實現_init/_fini兩個函式就行了。好,我們來看看事實是不是這樣的。 很多資料上都說可以利用_init/_fini來實現,而我從來沒有測試成功

SQLAlchemy數據連接初始數據

數據 即使 conf 執行 int rop windows use VR 查看版本 >>> import sqlalchemy >>> sqlalchemy.__version__ ‘1.0.9‘ 創建連接 from sqlclach

Linux驅動開發(三)——模組初始解除安裝函式

        在(一)中,主要講述了模組的基本組成,載入,解除安裝和檢視工具的使用。本篇中,主要講述module_init()和module_exit()這兩個函式的使用。          在(一)中給的原始碼檔案中,模組初始化和解除安裝函式為:init_module()

第4章 初始結束過程

  每一個程式碼塊都具有一個BEGIN和一個END,儘管在BASM中用ASM關鍵字代替了BEGIN,但它仍然起著相同的作用。  有些情況下,BEGIN和END只決定一個語法結構的開始和結束,例如記錄和物件型別定義中使用的BEGIN和END。但更多的時候(例如例程定義中),BEGIN與END代表初始化與結束化過

Spring Bean的初始例項的區別

準確的說,標題中的初始化指的是Bean Definition的初始化,所以是完全不同的兩個概念。   普通的Java類如果想被SpringIOC容器託管,擁有強大的擴充套件功能和更強大的生命週期,使用者(程式設計師)要做的只是寫配置或者寫註解,然後Spring會做這些事:

Java類初始例項

摘要: Java有以下幾種方式建立類物件: 利用new關鍵字 利用反射Class.newInstance 利用Constructor.newIntance(相比Class.newInstance多了有參和私有建構函式) 利用Cloneable/Object.clone() 利

python關於初始例項之----log日誌列印兩次的問題綜述

我在寫appium-desktop自動化框架的時候,我發現在我執行demo的時候,日誌會執行兩次,我查了一堆資料也沒有發現我錯在哪裡。      一。開始我以為是  __init__的問題,我檢查了__init__方法,發現他們屬於同一物件,沒有多餘的物件。    二。然後我

Java基礎學習系列-Java類初始例項

Java有以下幾種方式建立類物件: 利用new關鍵字 利用反射Class.newInstance 利用Constructor.newIntance(相比Class.newInstance多了有參和私有建構函式) 利用Cloneable/Object.clon

STM32的HAL的 I2CUART使用函式,幾個好用的

void I2C_Write(uint8_t* pBuffer, uint8_t DeviceAddr, uint8_t RegisterAddr,uint16_t NumByteToWrite){ //HAL_I2C_Master_Transmit(&hi2c1

什麼是序列反序列 什麼是序列並行

當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種型別的資料,都會以二進位制序列的形式在網路上傳送。傳送方需要把這個物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為物件。 1、把物件轉換為位元組序列的過程稱為物件的序列化。

前端框架___元件模組

元件化和模組化 元件化和模組化有利於封裝以及複用。   元件化: 1.生命週期。     前端框架都有一個重要的詞語,生命週期,都大部分從幾個方面來看,初始化,渲染,存活期,銷燬。 2.資料通訊    元件之間的通訊,父傳

Python學習【第21篇】:程序池以及回撥函式 python併發程式設計之多程序2-------------資料共享及程序池回撥函式

python併發程式設計之多程序2-------------資料共享及程序池和回撥函式 一、資料共享 1.程序間的通訊應該儘量避免共享資料的方式 2.程序

JAVA 序列序列

狀態 NPU serializa output 上傳 讀取 mil 一個 輸出流 序列化 把對象轉換為字節碼序列化的過程 反序列化 把字節序列恢復為對象的過程 用途: 把對象的字節碼序列永遠地保存到硬盤上,通常存放在一個文件中或在網絡上傳輸對象的字節序列 是Java提供的一

Android模組元件開發簡單理解(一)

模組化和元件化可以理解為同一個概念: 將一個app分成多個模組,每個模組都是一個元件(module),開發過程中讓這些元件相互依賴或者單獨除錯某個元件。在釋出的時候將這些元件合併成一個apk。 Android元件化我的理解是 application與library之間相互

前端面試題(二)----前端模組元件的區別聯絡

前端元件化開發和模組化開發的區別 之前一直以為模組化開發和元件化開發是一個意思,有次看到了類似這樣的題,發現自己還是太年輕,現在整理一點出來。 首先,元件化和模組化的意義都在於實現了分治,目前我們開發的專案複雜度不斷的上升,早已不是我們一個人能完成的工作,團

模組、元件外掛

單工程模式 移動開發誕生,我們開發移動專案,我相信大多用的是單工程單任務的開發模式,二話不說,直接就開始寫起,是不是這樣呢? new Project -> 分包 -> 寫起。我相信都經歷過,也寫的比較爽,為什麼呢? 這種模式不涉及亂七八糟的處理方式,

Android元件外掛

元件化開發就是將一個app分成多個模組,每個模組都是一個元件(Module),開發的過程中我們可以讓這些元件相互依賴或者單獨除錯部分元件等,但是最終釋出的時候是將這些元件合併統一成一個apk元件化優勢:稍微改動一個模組的一點程式碼都要編譯整個工程,耗時耗力公共資源、業務、模組

微服務化之前需要先無狀態容器

本文是微服務實戰系列文章的第四篇,前三篇連結如下:一、為什麼要做無狀態化和容器化很多應用拆分成微

Redis中物件的序列序列的使用

最近專案開發用到Redis,然後使用到了將物件進行序列化和反序列化的方法,總結如下:package com.lz.test; import java.nio.charset.Charset; im

linux0.11程序睡眠sleep_on函式喚醒wake_up函式分析

核心中的這兩個函式主要用於訪問資源時的同步操作。高速緩衝區的訪問就是其中的一個例子:如果兩個程序都要訪問同一個緩衝塊,那麼其中的一個程序就必然睡眠等待,直到該緩衝塊被釋放才可訪問。趙炯博士所著的linux0.11核心完全註釋一書中也是對該問題進行詳細的討論,但是我在閱讀這部