在通常情况下,׃n库都是通过使用附加选项 -fpic ?nbsp;-fPIC q行~译Q从目标代码产生位置无关的代码(Position Independent CodeQPICQ,使用 -shared选项目标代码放q共享目标库中。位|无关代码需要能够被加蝲C同进E的不同地址Qƈ且能得以正确的执行,故其代码要经q特别的~译处理Q位|无关代码(PICQ对帔R和函数入口地址的操作都是采用基于基寄存器(base registerQBASE+ 偏移量的相对地址的寻址方式。即使程序被装蝲到内存中的不同地址Q即 BASE g同,而偏U量是不变的Q所以程序仍然可以找到正的入口地址或者常量?/p>
然而,当应用程序链接了多个׃n库,如果在这些共享库中,存在相同作用域范围的同名静态成员变量或者同?( 非静?) 全局变量Q那么当E序讉K完静态成员变量或全局变量l束析构Ӟ׃某内存块?double free ?x)导?core dumpQ这是由?Linux ~译器的~陷造成的?/p>
该问题源于笔者所从事的开发项目:(x)IBM Tivoli Workload Scheduler (TWS) LoadLeveler?strong>LoadLeveler?nbsp;IBM在高性能计算Q?strong>High Performance ComputingQHPCQ领域的一ƾ作业调度Y件。它主要分ؓ(f)两个大的模块Q分别是调度模块QschedulerQ和资源理模块Qresource mangerQ?两个模块中分别含有关于配|管理功能的׃n库,׃某些配置理选项Z模块所共同采用Q所以两模块之间׃n了部分源文g代码Q其中包含有同名的类静态成员?/p>
可以通过以下单的模型q行描述Q?/p>
?1. 应用场景
对应的各模块代码片段如下图所C:(x)
其中Qtest.c 是主E序Q包含有两个头文Ӟ(x)api1.h ?api2.hQ头文g api1.h 包含头文?lib1/lib.h 和一功能函数 func_api1()Qapi2.h 包含头文?lib2/lib.h 和一功能函数 func_api2()Q目?lib1 ?lib2 下的源文件分别编译生成共享库 lib1.so ?lib2.so。同Ӟ头文?lib1/lib.h ?lib2/lib.h 链接到同一׃n文g lib.h。在文g lib.h 中定义有一静态成员变?#8220;static std::vector<int> vec_int”?/p>
功能函数 func_api1() ?func_api2() 的实现类|通过调用静态成员函数达到访问静态成员变?nbsp;vec_int的目的:(x)
void func_api1(int i) { printf("%s.\n", __FILE__); A::set(i); A::print(); return; } |
静态成员函?A::set() ?A::print() 的实现如下:(x)
void A::set(int num) { vec_int.clear(); for (int i = 0; i < num; i++) { vec_int.push_back(i); } return; } |
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() 寚w态成?nbsp;vec_intq行赋值操作,?A::print() 则打印其中的g当前的内存地址?/p>
如果两个׃n库是通过选项 -fpic?nbsp;-fPIC~译的话Q运行程?testQ输出如下:(x)
$ 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) $ |
从程序的输出直观的看刎ͼcore 产生是由于堆内存区域Q?strong>09cbf000-09ce0000Q中起始地址?nbsp;0x09cbf028的内存区被释放了两次D的,该地址正式静态成员变?nbsp;vec_int的第一个元素的地址?/p>
Z么会(x)出现同一块内存区Q被释放两次的情形呢Q?/p>
我们知道Q静态成员变量与全局变量cMQ都采用了静态存储方式。对于加了选项 -fpic?nbsp;-fPIC的共享库Q这些变量的地址都存攑֜该共享库的全局偏移表(Global Offset TableQGOTQ中?/p>
通过 objdump或?nbsp;readelf命o(h)分析׃n?nbsp;lib1.soQ结果如下:(x)
$ 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 ...... |
$ 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 ...... |
从上面两个命令的输出l果中可以看出,׃n?nbsp;lib1.so?nbsp;GOTD늚起始内存地址?nbsp;000030f4Q大ؓ(f) 20 字节 (0x14)Q静态成员变?nbsp;vec_int在共享库 lib1.so中的起始偏移地址?nbsp;000030fc。显Ӟvec_int位于该共享库?nbsp;GOTD内?/p>
当应用程序同旉?nbsp;lib1.so?nbsp;lib2.soӞ同名静态成员变?nbsp;vec_int分别位于其共享库?nbsp;GOT区。当E序q行Ӟpȝ从符可中查扑ƈ装蝲构造一?nbsp;vec_int数据Q这点从E序q行的输出结果(清单 4Q的“Backtrace”部分可以看到Q只?nbsp;lib1.so中的静态成员变量被装蝲构造;同时Q通过内存映射Q?strong>Memory mapQ部分(清单 4Q,可以观察?nbsp;vec_int对象的地址 0xe89228正好处在为共享库 lib1.so分配的可d存区 00e89000-00e8a000中:(x)
00e89000-00e8a000 rwxp 00003000 00:1b 7668022 ./lib1.so |
然后Q当E序l束Ӟ却对该变量进行了两次析构操作Q通过 gdb分析 core 文gQ?/p>
清单 7. core 文g分析l果
$ 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) |
从清?7 中可以看出,从 #14 开始,E序q行 lib2.so中的析构操作Q直?#11Q都q行?nbsp;lib2.so中,当进入 #10 Ӟq行变量析构Ӟ其地址?nbsp;0x00e8850cQ该地址中的对象是程序启动时由共享库 lib1.so装蝲构造出来的Q清?1Q:(x)
./lib1.so(_ZNSt6vectorIiSaIiEED1Ev+0x60)[0xe8850c] |
当程序结束时Q运行库 glibc到׃n?nbsp;lib2.so析构了ƈ非由其构造的对象Q导致了 core dump?/p>
q种情况下,如果替换使用选项 -fpie?nbsp;-fPIEQ操作步骤与q行l果如下所C:(x)
$ 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. $ |
E序q行l果W合期望q正常结束?/p>
q是因ؓ(f)Q当使用选项 -fpie?nbsp;-fPIEӞ生成的共享库不会(x)为静态成员变量或全局变量?nbsp;GOT中创建对应的条目Q通过 objdump?strong>readelf命o(h)可以查看Q此处不再赘qͼQ从而避免了׃静态对?#8220;构造一ơ,析构两次”而对同一内存区域释放两次引v的程?core dump?/p>
选项 -fpie?nbsp;-fPIE?nbsp;-fpic?nbsp;-fPIC的用法很怼Q区别在于前者L生成的位置无关代码看作是属于程序本w,q直接链接进该可执行E序Q而非存入全局偏移?nbsp;GOT中;q样Q对于同名的静态或全局对象的访问,其构造与析构操作保持一一对应?/p>
通过使用选项 -fpie?nbsp;-fPIE代替 -fpic或?nbsp;-fPICQ得生成的׃n库不?x)?f)静态成员变量或全局变量?nbsp;GOT中创建对应的条目Q同时也避免了针对同名静态对?#8220;构造一ơ,析构两次”的不当操作?br />
转自Q?a >http://www.ibm.com/developerworks/cn/linux/l-cn-sdlstatic/
Linux 下飞鸽传书设计实?span lang="EN-US">
1.pȝ功能
Ҏ(gu)飞鸽传书协议?span lang="EN-US"> linux 下实现飞鸽传输程?span lang="EN-US">,q且?span lang="EN-US"> windows 下飞鸽兼宏V具体功能模块包括用户上U?span lang="EN-US">,下线,h查看在线用户,收发消息,传送文?span lang="EN-US">/文g夹功能模块?span lang="EN-US">
2.具体实现
2.1 关键数据l构
/*命o(h)的结?span lang="EN-US">*/
typedef struct _command
{
int version;/*命o(h)的版?span lang="EN-US">*/
int seq;/*包编?span lang="EN-US">*/
char srcName[100];/*发送者姓?span lang="EN-US">*/
char srcHost[100];/*发送者主机名*/
int flag;/*命o(h)*/
char addtion[100];/*附加字段*/
}command;
/*在线用户信息*/
typedef struct _userInfo
{
char name[MAXLINE]; /*姓名*/
char host[MAXLINE]; /*L?span lang="EN-US">*/
char group[MAXLINE]; /*所在的l名*/
struct sockaddr_in addr; /*地址信息*/
struct _userInfo next; /*链表中下一?span lang="EN-US">*/
}userInfo;
/*在线用户列表*/
typedef struct _uList
{
userInfo *userListHead; /*链表?span lang="EN-US">*/
userInfo userListTail; /*链表?span lang="EN-US">*/
}uList;
/*消息队列*/
typedef struct _mesList
{
command *mesHead;
command *mesTail;
}mesList;
2.2 E序主要l构
本程序主要采用多U程l构,分ؓ(f) receive(接收消息), process(处理收到的消?span lang="EN-US">), sendData(发送文?span lang="EN-US">) 三个子线E。线E间通信互斥锁与 Posix 信号量进行通信?span lang="EN-US">
2.3 函数接口
(1) /*从文件描q符fd中读?span lang="EN-US">count个字W存?span lang="EN-US">buf?span lang="EN-US">*/
ssize_t readn(int fd,void *buf,size_t count)Q?span lang="EN-US">
(2) /*?span lang="EN-US">buf所指向的存储区中的len个字W吸入文件描q符fd?span lang="EN-US">*/
ssize_t writen(int fd,char *buf,int len);
(3) /*用于字符串{?span lang="EN-US">,|络传输中用gb2312~码Q?span lang="EN-US">linux?span lang="EN-US">gtk?span lang="EN-US">utf-8~码Q需要进行{?span lang="EN-US">*/
int code_convert(char *from_charset,char *to_charset,char *inbuf,int inlen,char *outbuf,int outlen);
(4) /*在用户链表中加入新用户信息,加入成功q回1Q否则返?span lang="EN-US">0,使用userInfoMutexq行U程间通信控制*/
int pushBack(uList *list,userInfo user);
(5) /*在用户链表中删除指定地址信息的用P删除成功后返?span lang="EN-US">1Q否则返?span lang="EN-US">0Q?span lang="EN-US">userInfoMutexq行U程间控?span lang="EN-US">*/
int delUser(uList *list, struct sockaddr_in addr);
(6) /*判断该用h否已l存在,已经存在则返?span lang="EN-US">1Q否则返?span lang="EN-US">0,使用userInfoMutexq行U程间控?span lang="EN-US">*/
int isExist(uList *list,struct sockaddr_in addr);
(7)清空用户链表Q释攄_(d)用于用户退出和用户h旉攄?span lang="EN-US">,使用userInfoMutexq行U程间控?span lang="EN-US">*/
int destroyList(uList *list);
(8)/*创徏命o(h)?span lang="EN-US">,comq回的命令字,flag 为消息标?span lang="EN-US">,addtion 为附加标?span lang="EN-US">*/
void createCmd(command & com,int flag,char addtion[])
(9)/*发送消息,com发送的消息Q?span lang="EN-US">servaddr发送的地址Q?span lang="EN-US">attach为文仉件信?span lang="EN-US">*/
void sendCmd(command com, struct sockaddr_in servaddr,char attach[]);
(10) /*把收到的消息加入到消息队列中*/
void addMes(mesList *mHead,command cmd);
(11) /*把消息队列中头部的节Ҏ(gu)息提取出来用于处?span lang="EN-US">*/
int delMes(mesList *mHead,command *cmd);
(12)/*初始化操作,飞鸽d时初始化消息链表Q用户链表,信号量,套接字信?span lang="EN-US">*/
void init();
(13)/*d操作,发送用户上U消?span lang="EN-US">*/
void login();
(14)/*解析收到的消息命令,提取各个字段*/
int analysisCmd(command *cmd,char *buf);
(15) /*接收消息U程处理函数,收到的消息加入消息队列中,通过信号?span lang="EN-US">waitNoFull?span lang="EN-US">waitNoEmpty和消息处理线E进行通信。消息队列用mesMutex与其他线E进行通信Q保证消息队列的正确?span lang="EN-US">*/
void *receive(void *arg);
(16)/*gtk界面中显C在U用户信?span lang="EN-US">*/
void showUser(uList *list);
(17)/*?span lang="EN-US">gtk界面中显C消?span lang="EN-US">*/
void showMessage(char *message);
(18)/*昄收到的信?span lang="EN-US">*/
void showRecvMessage(char *host,char *message);
(19)/*分析文g的信息,提取有用的字D?span lang="EN-US">*/
void fileAnalysis(char *recv,int *fNum,char *fName,int *fSize,int *fTime,int *fType);
(20) /*保存收到的单个文?span lang="EN-US">,saveNameZ存的文g?span lang="EN-US">*/
void saveSignalFile(char *saveName);
(21)/*分析目录附gQ获得目录文件的文g名,文g大小Q文件类?span lang="EN-US">*/
void getDirInfo(char *recv,char *fName,int *fSize,int *fType);
(22) /*保存目录,saveName保存的目?span lang="EN-US">*/
void saveDir(char *saveName);
(23)/*保存文g,recvType=1Z存文ӞrecvType=2Z存的目录,使用fileMutex来设|互斥?span lang="EN-US">*/
void saveFile();
(24)/*收到单个文g*/
void receiveSignalFile(char *recvFileName);
(25)/*收到单个目录*/
void receiveDir(char *recvDirName);
(26)/*接收文g*/
void receiveFile(command cmd);
(27)/*信号处理U程,从消息队列中取出消息q行处理*/
void *process(void *arg);
(28)/*发送消?span lang="EN-US">*/
int sendMes();
(29) /*文件名q行转换*/
char *transName(char *fileName);
(30)/*发送文?span lang="EN-US">*/
void sendFile();
(31)/*发送文件夹*/
void sendDir();
(32)/*用户点击h,h在线用户*/
void refresh();
(33) /*用户退?span lang="EN-US">*/
void quit();
(34)/*传输文gҎ(gu)据,递归函数*/
void transferDir(int fd,char *dir);
(35)/*监听TCP套接口,发送文件与文g夹线E?span lang="EN-US">*/
void *sendData(void *arg);
(36)/*创徏菜单*/
static void create_popup_menu(GtkWidget *menu,GtkWidget *view);
(37)/*叛_选中treeview,昄传送文件与文g夹菜?span lang="EN-US">*/
static gboolean showTreeView(GtkWidget *eventBox,GdkEventButton *event,GtkWidget *menu);
(38)/*选择要发送的文g */
static void selectFile();
(39)/*选择要发送的文g?span lang="EN-US">*/
static void selectDir();
(40)/*选择要保存的文g名或文g夹名*/
static void selectSaveFile();
3.ȝ
实现?span lang="EN-US">linux下飞鸽传书的基本功能Qƈ且能?span lang="EN-US">window下飞鸽进行通信Q传文g。熟(zhn)了linux下网l编E,多线E编E及(qing)U程间通信Q主要用C号量与互斥锁Q。但加密解密那块没有完成Q程序结构不是很好,界面做得太差。有I应该看看设计模?span lang="EN-US">.
界面截图Q界面比较垃?Q?/p>
附:(x)
飞鸽协议Q?http://bbs.chinaunix.net/viewthread.php?tid=1015775