Linux Dynamic Library (.so) 使用指南

1. Dynamic Library的編譯

假設我們有下面兩個文件a.h, a.cpp际度,放在同一目錄下愉择。兩個文件的內(nèi)容分別是:

// a.h

extern "C" void foo();
// a.cpp
 
#include <iostream>
#include "a.h"
 
using namespace std;
 
extern "C" void foo() {
    cout << "a.foo" << endl;
}

使用下面的命令行可以產(chǎn)生liba.so動態(tài)鏈接庫:

g++ -fPIC -c a.cpp
g++ -shared -o liba.so a.o

上面第一行的-fPIC是要求編譯器生成位置無關代碼(Position Independent Code),這對于動態(tài)庫來說是必須的盯蝴。關于位置無關代碼的細節(jié)碘耳,可以查看后面列出的參考文獻胁黑,不再贅述。第二行使用-shared要求編譯器生成動態(tài)庫貌踏,而不是一個可執(zhí)行文件十饥。

另外,我們聲明和定義foo函數(shù)時使用了extern "C"祖乳,這是希望c++編譯器不要對函數(shù)名進行改名(mangle)逗堵。對于共享庫來說,這樣定義接口函數(shù)更容易在Dynamic Loading時使用眷昆。至于什么是Dynamic Loading蜒秤,在2.2節(jié)描述。

2. 動態(tài)庫的使用

2.1 Dynamic Linking方式

Dynamic Linking方式亚斋,是指在鏈接生成可執(zhí)行文件時垦藏,通過-l指定要連接的共享庫,這種方式和使用靜態(tài)庫非常相似伞访。

假設我們有一個main_dyn_link.cpp文件掂骏,內(nèi)容如下:

// main_dyn_link.cpp
 
#include "a.h"
 
int main(int argc, char *argv[]) {
    foo();
    return 0;
}

我們可以使用下面的命令,將和其liba.so一起編譯鏈接為可執(zhí)行文件test:

g++ main_dyn_link.cpp -o test -L`pwd` -la

當我們運行這個test程序時厚掷,會報錯弟灼,因為系統(tǒng)找不到liba.so文件级解。默認情況下,系統(tǒng)只會在/usr/lib田绑、/usr/local/lib目錄下查找.so文件勤哗。為了能夠讓系統(tǒng)找到我們的liba.so,我們要么把liba.so放到上述兩個目錄中掩驱,要么使用LD_LIBRARY_PATH環(huán)境變量將liba.so所在的目錄添加為.so搜索目錄芒划。這里我們使用第二種方法,在命令行輸入:

export LD_LIBRARY_PATH=`pwd`

這時欧穴,程序就能正常運行了民逼。
此外還有其他方法能夠讓系統(tǒng)找到liba.so,可以查看下面的參考文檔1涮帘,不再贅述拼苍。

2.2 Dynamic Loading方式

使用dlopen、dlsym等函數(shù)调缨,我們可以在運行期加載任意一個共享庫疮鲫。我們把前面的main.cpp改為使用Dynamic Loading的方式:

// main_dyn_load.cpp
 
#include <dlfcn.h>
#include <iostream>
#include "a.h"
 
using namespace std;
 
typedef void (*Foo)();
 
Foo get_foo() {
    void *lib_handle = dlopen("liba.so", RTLD_LAZY);
    if (!lib_handle) {
        cerr << "load liba.so failed (" << dlerror() << ")" << endl;
        return 0;
    }
 
    char *error = 0;
    Foo foo_a = (Foo) dlsym(lib_handle, "foo");
    if ((error = dlerror()) != NULL) {
        cerr << "get foo failed (" << error << ")" << endl;
        return 0
    }
 
    return foo_a;
}
 
int main(int argc, char *argv[]) {
    Foo foo_a = get_foo();
    foo_a();
    return 0;
}

首先,為了使用dlopen弦叶、dlsym召边、dlerror等函數(shù)基茵,我們需要包含dlfcn.h頭文件。

第12行,我們使用dlopen函數(shù)闷愤,傳遞liba.so的路徑名(本例是當前目錄)课蔬,系統(tǒng)會嘗試加載liba.so何暇。如果成功句柠,返回給我們一個句柄。RTLD_LAZY是說加載時不處理unresolved symbols桃序。對于本例杖虾,就是加載liba.so時,不會去查找foo的地址媒熊,只有在第一次調(diào)用foo時才會去找foo的實際地址奇适。需要了解進一步詳細信息可以查找手冊(命令行輸入:man dlopen)。

第19行芦鳍,我們使用dlsym函數(shù)嚷往,傳遞dlopen返回的句柄和我們想要獲取的函數(shù)名稱。如果這個
名稱是存在的柠衅,dlsym會返回其相應的地址皮仁。這就是為什么我們需要把.so的接口函數(shù)聲明為extern "C",否則,我們就必須給dlsym傳遞經(jīng)過c++編譯器mingle之后的奇怪名字贷祈,才能找到相應的函數(shù)趋急。

出現(xiàn)任何錯誤的時候,dlerror會返回相應的錯誤信息字符串势誊;否則它會返回一個空指針呜达。dlerror提供的信息對我們定位問題是非常有幫助的。

一旦獲取了函數(shù)地址粟耻,我們可以把它保存在函數(shù)指針中(第29行)查近,隨后就可以像使用函數(shù)一樣來使用它(第30行)。

接著挤忙,我們編譯main.cpp霜威,并生成可執(zhí)行文件:

g++ main_dyn_load.cpp -o test -ldl

因為我們使用的是Dynamic Loading,因此就不需要在編譯時鏈接liba.so了(去掉了-la)饭玲,因為我們使用了dlxxx函數(shù)侥祭,所以需要增加鏈接-ldl叁执。

3. 使用Dynamic Library的注意事項

Dynamic Library使用要比Static Library復雜茄厘,下面是一些需要注意的問題。

3.1 不同的.so內(nèi)包含同名全局函數(shù)

3.1.1 Dynamic Linking

.so允許出現(xiàn)同名的強符號谈宛。因此次哈,如果不同的.so包含同名的全局函數(shù),鏈接時編譯器不會報錯吆录。編譯器會使用命令行中先鏈接的那個庫的版本窑滞。例如,我們再增加一個b.cpp文件:

// b.cpp
  
#include <iostream>
#include "a.h"
  
using namespace std;
  
extern "C" void foo() {
    cout << "b.foo" << endl;
}

將其編譯恢筝、生成為libb.so:

g++ -fPIC -c b.cpp
g++ main_dyn_link.cpp -o test -shared -L`pwd` -la -lb

這時哀卫,test將使用liba.so版本的foo,也就是將打印a.foo撬槽。如果我們把上面第二行的-la -lb倒過來:

g++ main_dyn_link.cpp -o test -shared -L`pwd` -lb -la

這時此改,test將使用libb.so版本的foo,也就是將打印b.foo侄柔。
這個不會成為太大的問題共啃,因為使用靜態(tài)庫也是這樣的。

3.1.2 Dynamic Loading

使用Dynamic Loading暂题,我們可以從兩個.so中分別取出不同的版本移剪,并按照自己的意圖來使用。我們修改一下main_dyn_load.cpp文件薪者,使之使用兩個foo版本:

// main_dyn_load.cpp
 
#include <dlfcn.h>
#include <iostream>
#include "a.h"
 
using namespace std;
  
typedef void (*Foo)();
 
Foo get_foo(const char *lib_path) {
    void *lib_handle = dlopen(lib_path, RTLD_LAZY);
    if (!lib_handle) {
        cerr << "load liba.so failed (" << dlerror() << ")" << endl;
        return 0;
    }
  
    char *error = 0;
    Foo foo_a = (Foo) dlsym(lib_handle, "foo");
    if ((error = dlerror()) != NULL) {
        cerr << "get foo failed (" << error << ")" << endl;
        return 0;
    }
  
    return foo_a;
}
 
int main(int argc, char *argv[]) {
    Foo foo_a = get_foo("liba.so");
    Foo foo_b = get_foo("libb.so");
    foo_a();
    foo_b();
    return 0;
}

首先纵苛,稍微重構(gòu)了一下get_foo函數(shù),使之能夠接收一個.so路徑作為參數(shù),然后它回取出相應.so里面的foo函數(shù)的地址攻人。

第29和第30行幔虏,我們分別從liba.so和libb.so中取出了foo函數(shù)地址,將他們保存在foo_a和foo_b兩個函數(shù)指針中贝椿,并在第31和第32行分別進行了調(diào)用想括。

最后,程序?qū)蛴.foo和b.foo烙博。

3.2 .so反向調(diào)用bin里面的函數(shù)

bin可以調(diào)用.so定義的函數(shù)瑟蜈,以及.so可以調(diào)用其它.so定義的函數(shù),這是毫無疑問的渣窜。那么铺根,.so能反過來調(diào)用bin里面的函數(shù)么?答案是肯定的乔宿,只要我們在編譯bin時制定-rdynamic選項就可以了位迂。

我們只舉Dynamic Linking的例子,因為Dynamic Loading也是一樣的详瑞。

我們在main_dyn_linking里面定義一個新的函數(shù)bar:

// main_dyn_link.cpp
 
#include <iostream>
#include "a.h"
 
using namespace std;
extern "C" void bar() {
    cout << "main.bar" << endl;
}
 
int main(int argc, char *argv[]) {
    foo();
    return 0;
}

然后掂林,我們在a.cpp里面調(diào)用這個函數(shù):

// a.cpp
  
#include <iostream>
#include "a.h"
  
using namespace std;
 
extern "C" void bar();
extern "C" void foo() {
    cout << "a.foo" << endl;
    bar();
}

編譯,注意增加-rdynamic選項:

g++ -fPIC -c a.cpp
g++ -shared -o liba.so a.o
g++ main_dyn_link.cpp -o test -L`pwd` -la -rdynamic

執(zhí)行程序坝橡,將會打有喊铩:
a.foo main.bar

3.3 不同的.so內(nèi)出現(xiàn)同名的全局變量

終于要面對這個非常tricky的場景了。這里說的全局變量计寇,既包括通常意義的『全局變量』锣杂,也包括類的靜態(tài)成員變量,因為后者本質(zhì)上就是改了名字全局變量番宁。

3.3.1 Dynamic Linking

我們先來考慮Dynamic Linking的情況元莫。我首先添加一個類:MyClass,并把它實現(xiàn)為singleton蝶押。因為singleton模式是使用類靜態(tài)成員最常見的場景之一踱蠢。
先來定義MyClass的頭文件:

// my_class.h
 
class MyClass {
public:
    MyClass();
    ~MyClass();
    void what();
    static MyClass &get_instance();
private:
    int _count;
    static MyClass _instance;
};

接著定義MyClass的源文件:

// my_class.cpp
 
#include <iostream>
#include "my_class.h"
 
using namespace std;
 
MyClass MyClass::_instance;
 
MyClass::MyClass()
    : _count(0) {
    cout << "the count init to 0" << endl;
}
 
MyClass::~MyClass() {
    cout << "(" << this << ") destory" << endl;
}
 
void MyClass::what() {
    _count++;
    cout << "(" << this << ") the count is " << _count << endl;
}
 
MyClass &MyClass::get_instance() {
    return _instance;
}

每次調(diào)用what方法,MyClass對象內(nèi)部計數(shù)會加1播聪,并隨后打印對象的地址和當前的計數(shù)值朽基。
我們在a.cpp和b.cpp里面分別調(diào)用MyClass::what方法。

// a.cpp
  
#include <iostream>
#include "a.h"
#include "my_class.h"
  
using namespace std;
  
extern "C" void bar();
extern "C" void foo() {
    cout << "a.foo" << endl;
    bar();
    MyClass::get_instance().what();
}

我們需要把my_class.cpp編譯到liba.so和libb.so中:

g++ -fPIC -c a.cpp
g++ -fPIC -c my_class.cpp
g++ -shared -o liba.so a.o my_class.o
 
g++ -fPIC -c b.cpp
g++ -shared -o libb.so b.o my_class.o
 
g++ main_dyn_link.cpp -o test -L\`pwd\` -la -lb -rdynamic

執(zhí)行這個程序离陶,我們發(fā)現(xiàn)稼虎,盡管在不同的.so內(nèi)都包含了my_class.cpp(里面定義了_instance靜態(tài)靜態(tài)變量),但最終全局只有一個_instance實例招刨。但是霎俩,這個實例被初始化了兩次和析構(gòu)了兩次。重復析構(gòu)可能會導致core,因此在.so場景下使用單例模式要更加小心(或選擇其它的單例實現(xiàn)方法)打却。

3.3.2 Dynamic Loading

現(xiàn)在我們看看Dynamic Loading的情況杉适。這次,我們使用main_dyn_load.cpp進行編譯:

g++ main_dyn_load.cpp -o test -ldl -rdynamic

這次柳击,我們驚訝的發(fā)現(xiàn)猿推,居然存在兩個不同的_instance實例!當然捌肴,重復初始化和析構(gòu)不存在了蹬叭,每個對象上都只進行了一次初始化和析構(gòu)。

這說明状知,在Dynamic Loading情況下秽五,不同的.so中同名全局變量都會是不同的實例。

等等饥悴,如果你以為這是全部真相那就錯了坦喘。如果我們在bin中也定義同名的全局變量會怎么樣呢?我們修改一下main_dyn_load.cpp中的bar函數(shù)西设,使之也調(diào)用MyClass::get_instance().what()方法:

// main_dyn_load.cpp
 
#include <dlfcn.h>
#include <iostream>
#include "a.h"
#include "my_class.h"
using namespace std;
  
typedef void (*Foo)();
 
extern "C" void bar() {
    cout << "main.bar" << endl;
    MyClass::get_instance().what();
} 
Foo get_foo(const char *lib_path) {
    void *lib_handle = dlopen(lib_path, RTLD_LAZY);
    if (!lib_handle) {
        cerr << "load liba.so failed (" << dlerror() << ")" << endl;
        return 0;
    }
  
    char *error = 0;
    Foo foo_a = (Foo) dlsym(lib_handle, "foo");
    if ((error = dlerror()) != NULL) {
        cerr << "get foo failed (" << error << ")" << endl;
        return 0;
    }
  
    return foo_a;
}
int main(int argc, char *argv[]) {
 
    Foo foo_a = get_foo("liba.so");
    Foo foo_b = get_foo("libb.so");
    foo_a();
    foo_b();
    return 0;
}

我們還需要把my_class.cpp也直接編譯到bin里面瓣铣,否則會找不到get_instance()、what()等符號济榨。

g++ main_dyn_load.cpp my_class.o -o test -ldl -rdynamic

執(zhí)行程序坯沪,結(jié)果再次令人意外:

全局變量再次合為一個绿映,而且被重復初始化-析構(gòu)了三次擒滑。

總結(jié)上述規(guī)律,在Dynamic Loading場景下叉弦,如果.so中出現(xiàn)了同名全局變量丐一,那么每個.so都會有其單獨的全局變量實例,每個實例單獨初始化/析構(gòu)淹冰;如果bin中也包括同名的全局變量库车,那么系統(tǒng)將只有唯一一份實例,在這個實例上會出現(xiàn)多次重復的初始化/析構(gòu)樱拴。

這再次說明柠衍,在.so中使用全局變量(以及類的靜態(tài)成員變量)要非常謹慎,整個系統(tǒng)也要形成統(tǒng)一的規(guī)范晶乔,否則很可能出現(xiàn)未預期的行為珍坊。

3.4 dynamic_cast

從一個.so中創(chuàng)建的對象,在另外一個.so中進行dynamic_cast正罢,即使第二個.so完全編譯了子類的定義阵漏,dynamic_cast也可能會失敗。為了演示,先修改一下MyClass的定義:

// my_class.h
 
class MyBase {
public:
    virtual ~MyBase() {}
};
class MyClass : public MyBase {
public:
    MyClass(const char *name);
    ~MyClass();
    void what();
private:
    int _count;
    const char *_name;
};

接著修改MyClass的實現(xiàn):

// my_class.cpp
  
#include <iostream>
#include "my_class.h"
  
using namespace std;
  
MyClass::MyClass(const char *name)
    : _count(0), _name(name) {
    cout << "the count init to 0" << endl;
}
  
MyClass::~MyClass() {
    cout << "(" << this << ") destory" << endl;
}
  
void MyClass::what() {
    _count++;
    cout << "(" << this << ") created in " << _name << ", the _count is " << count << endl;
}

為了能夠讓.so產(chǎn)生出MyClass對象履怯,我們給.so增加一個接口函數(shù):create回还。此外,我們把foo改為接收一個MyBase對象的指針叹洲。

// a.h
 
class MyBase;
extern "C" void foo(MyBase*);
extern "C" MyBase *create();

在a.cpp和b.cpp中實現(xiàn)create函數(shù)柠硕。并且,在foo函數(shù)中使用dynamic_cast強制向下轉(zhuǎn)型:

// a.cpp
  
#include <iostream>
#include "a.h"
#include "my_class.h"
  
using namespace std;
  
extern "C" void bar();
 
extern "C" void foo(MyBase* base) {
    cout << "a.foo" << endl;
    bar();
    MyClass *cls = dynamic_cast<MyClass*>(base);
    if (!cls) {
        cerr << "dynamic_cast failed" << endl;
        return;
    }
    cls->what();    
}
 
extern "C" MyBase *create() {
    return new MyClass("liba.so");
} 
// b.cpp
  
#include <iostream>
#include "a.h"
#include "my_class.h"
using namespace std;
  
extern "C" void foo(MyBase *base) {
    cout << "b.foo" << endl;
    MyClass *cls = dynamic_cast<MyClass*>(base);
    if (!cls) {
        cerr << "dynamic_cast failed" << endl;
        return;
    }
    cls->what();   
}
 
extern "C" MyBase *create() {
    return new MyClass("libb.so");
}

最后运提,修改main_dyn_load.cpp文件仅叫,使之從liba.so創(chuàng)建對象,再libb.so中轉(zhuǎn)型糙捺、使用诫咱;然后反方向再來一次。

#include <dlfcn.h>
#include <iostream>
#include "a.h"
#include "my_class.h"
#include "fn.h"
 
using namespace std;
 
typedef void (*Foo)(MyBase*);
typedef MyBase *(*Create)();
 
extern "C" void bar() {
    cout << "main.bar" << endl;
}
 
int main(int argc, char *argv[]) {
    Foo foo_a = get_fn<Foo>("liba.so", "foo");
    Foo foo_b = get_fn<Foo>("libb.so", "foo");
    Create create_a = get_fn<Create>("liba.so", "create");
    Create create_b = get_fn<Create>("libb.so", "create");
    MyBase *base_a = create_a();
    MyBase *base_b = create_b();
    foo_a(base_a);
    foo_b(base_b);
    foo_a(base_b);
    foo_b(base_a);
    return 0;
}

第17到第20行洪灯,使用工具函數(shù)get_fn從.so中獲取函數(shù)地址坎缭,get_fn的源碼在附件中。第21行和第22行分別在liba.so和libb.so中創(chuàng)建了對象签钩。第23行掏呼,liba.so創(chuàng)建的對象在liba.so中轉(zhuǎn)型,第24行同樣測試了libb.so的情形铅檩。第25行和第26行測試了交叉轉(zhuǎn)型的情況憎夷。

編譯:

g++ -fPIC -c a.cpp
g++ -fPIC -c my_class.cpp
g++ -shared -o liba.so a.o my_class.o

g++ -fPIC -c b.cpp
++ -shared -o libb.so b.o my_class.o

g++ main_dyn_load.cpp -o test -L`pwd` -ldl -rdynamic

程序運行結(jié)果如下:

可以看到,出錯的代碼就是第25和第26行昧旨,說明在一個.so中創(chuàng)建的對象無法在另一個.so中轉(zhuǎn)型成功拾给。

怎樣解決這個問題呢?答案是把my_class.o也編譯到bin里面兔沃。如下:

g++ main_dyn_load.cpp my_class.o -o test -L`pwd` -ldl -rdynamic

編譯蒋得、運行,可以看到這次轉(zhuǎn)型成功了:

為什么會這樣呢乒疏?這其實和3.3.2的情景是一樣的:dynamic_cast時使用的類的虛函數(shù)表和RTTI元數(shù)據(jù)也是全局變量额衙。當bin沒有同名的全局變量時,各個.so擁有各自獨立的虛函數(shù)表實例怕吴,導致轉(zhuǎn)型時認為不是同一個繼承體系而失敗窍侧。而當bin也編譯了同樣的虛函數(shù)表時,所有的虛函數(shù)表就只會出現(xiàn)為同一個實例了转绷。

5. 總結(jié)

.so帶來了靈活性的同時伟件,也使我們要面對很多tricky的場景,一不小心就可能落到坑里暇咆。因此锋爪,使用.so必須小心丙曙,只在安全的范圍內(nèi)應用,并且在整個系統(tǒng)要有統(tǒng)一的規(guī)范其骄。如果在使用.so的過程中發(fā)現(xiàn)了任何問題亏镰,歡迎隨時與作者交流。

6. 參考資料

  1. Static, Shared Dynamic and Loadable Linux Libraries
  2. Program Library HOWTO Shared Libraries
  3. Shared libraries with GCC on Linux
  4. Anatomy of Linux dynamic libraries
  5. Resolving ELF Relocation Name / Symbols
  6. PLT and GOT - the key to code sharing and dynamic libraries
  7. Linkers and Loaders
  8. C++ dynamic_cast實現(xiàn)原理
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拯爽,一起剝皮案震驚了整個濱河市索抓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毯炮,老刑警劉巖逼肯,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桃煎,居然都是意外死亡篮幢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門为迈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來三椿,“玉大人,你說我怎么就攤上這事葫辐∷衙蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵耿战,是天一觀的道長蛋叼。 經(jīng)常有香客問我,道長剂陡,這世上最難降的妖魔是什么狈涮? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮鹏倘,結(jié)果婚禮上薯嗤,老公的妹妹穿的比我還像新娘。我一直安慰自己纤泵,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布镜粤。 她就那樣靜靜地躺著捏题,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肉渴。 梳的紋絲不亂的頭發(fā)上公荧,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音同规,去河邊找鬼循狰。 笑死窟社,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绪钥。 我是一名探鬼主播灿里,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼程腹!你這毒婦竟也來了匣吊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤寸潦,失蹤者是張志新(化名)和其女友劉穎色鸳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體见转,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡命雀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了斩箫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咏雌。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖校焦,靈堂內(nèi)的尸體忽然破棺而出赊抖,到底是詐尸還是另有隱情,我是刑警寧澤寨典,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布氛雪,位于F島的核電站,受9級特大地震影響耸成,放射性物質(zhì)發(fā)生泄漏报亩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一井氢、第九天 我趴在偏房一處隱蔽的房頂上張望弦追。 院中可真熱鬧,春花似錦花竞、人聲如沸劲件。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽零远。三九已至,卻和暖如春厌蔽,著一層夾襖步出監(jiān)牢的瞬間牵辣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工奴饮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纬向,地道東北人择浊。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像逾条,于是被迫代替她去往敵國和親琢岩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容