libnids-1.24关于libglib错误引发对执行流程的思考

最近在自己PC上安装的虚拟机VMware里安装的CentOS7.0操作系统,在编译和使用libnids-1.24的时候,一直会出现奇怪的警告,最开始仅仅是编译的时候报警,库文件还是能正常生成,但是在使用用例的时候,调用库的时候还是会报错,也就是这问题不解决没法继续了;可服务器上CentOS6.4编译运行都无异常(可参考上篇),但是刚刚在疑惑中,仔细调试了一把,就更奇怪了,关于一个disable的选项貌似十分重要,大年初一,我也够拼的

编译libnids-1.24的时候会报出警告

libnids.c: In function ?.ids_run?.
libnids.c:689:5: warning: ?._thread_create_full?.is deprecated (declared at /usr/include/glib-2.0/glib/deprecated/gthread.h:106): Use 'g_thread_new' instead [-Wdeprecated-declarations]
     START_CAP_QUEUE_PROCESS_THREAD(); /* threading... */
     ^

有很多类似警告,大概可以看到是nids_run里,有个大写的关于Thread的函数调用导致,但是编译还是会正常生成库文件libnids.a;但是,在编译sample里的用例时

[lihui@localhost samples]$ make
gcc -c -g -O2 -D_BSD_SOURCE -I. -I../src -I/usr/local/include -I/usr/local/include -D_BSD_SOURCE -D__BSD_SOURCE -D__FAVOR_BSD -DHAVE_NET_ETHERNET_H overflows.c
gcc -o overflows overflows.o  -L../src -lnids -L/usr/local/lib -lpcap -lnet -lgthread-2.0 -lnsl 
/usr/bin/ld: ../src/libnids.a(libnids.o): undefined reference to symbol 'g_thread_exit'
/usr/bin/ld: note: 'g_thread_exit' is defined in DSO /lib64/libglib-2.0.so.0 so try adding it to the linker command line
/lib64/libglib-2.0.so.0: could not read symbols: Invalid operation
collect2: error: ld returned 1 exit status
make: *** [overflows] Error 1

这里就比较重要了,因为动态库文件的问题,但是从错误信息来看,还是跟带thread相关的错误,因此大概可以确定,编译报的那个警告十分重要,由此,我查看了下nids_run这个函数的内容

683 int nids_run()
684 {
685     if (!desc) {
686         strcpy(nids_errbuf, "Libnids not initialized");
687         return 0;
688     }
689     START_CAP_QUEUE_PROCESS_THREAD(); /* threading... */
690     pcap_loop(desc, -1, (pcap_handler) nids_pcap_handler, 0);
691     /* FIXME: will this code ever be called? Don't think so - mcree */
692     STOP_CAP_QUEUE_PROCESS_THREAD();
693     nids_exit();
694     return 0;
695 }

为此还对比了libnids-1.24和libnids-1.19对于此函数的差异,1.19的定义如下

522 void nids_run()
523 {  
524     if (!desc) {
525         strcpy(nids_errbuf, "Libnids not initialized");
526         return;           
527     }
528     pcap_loop(desc, -1, (pcap_handler) pcap_hand, 0);
529     clear_stream_buffers();
530     strcpy(nids_errbuf, "loop: "); 
531     strncat(nids_errbuf, pcap_geterr(desc), sizeof(nids_errbuf) - 7);
532     pcap_close(desc);     
533 }  

可见新版本才新添加了第689行,的的确确也与thread相关,查看下相关定义

541 #ifdef HAVE_LIBGTHREAD_2_0
542    
543 #define START_CAP_QUEUE_PROCESS_THREAD() \
544     if(nids_params.multiproc) { /* threading... */ \
545          if(!(g_thread_create_full((GThreadFunc)cap_queue_process_thread,NULL,0,FALSE,TRUE,G_THREAD_PRIORITY_LOW,&gerror))) { \
546             strcpy(nids_errbuf, "thread: "); \
547             strncat(nids_errbuf, gerror->message, sizeof(nids_errbuf) - 8); \
548             return 0; \   
549          }; \
550     }                     
551 
552 #define STOP_CAP_QUEUE_PROCESS_THREAD() \
553     if(nids_params.multiproc) { /* stop the capture process thread */ \
554          g_async_queue_push(cap_queue,&EOF_item); \
555     }

这两个宏定义取决于是否定义了宏HAVE_LIBGTHREAD_2_0,可以从configure里查看到

4350 #define HAVE_LIBGTHREAD_2_0 1

这么说在编译过程中,的确这个START_XXXXX是存在问题,而且仔细看看,才发现前面写的关于libnids的执行流程根本补完备,因为START_CAP_QUEUE_PROCESS_THREAD()函数和pcap_loop里的函数指针的调用最终都是调用的同一个函数call_ip_frag_procs,当然最终还是调用链表里各自处理函数

为此,特地在服务器CentOS6.4上编译运行无误的版本上通过gdb来调试了一把libnids,但是很奇怪的是程序经过这个大写字母函数,并没有创建新的线程,而且由于它是宏定义函数,不太好调试,无法定位,但是pcap_loop是可以进入的

于是仔细看了又看call_ip_frag_procs这个函数

192 /* called either directly from pcap_hand() or from cap_queue_process_thread()
193  * depending on the value of nids_params.multiproc - mcree
194  */
195 static void call_ip_frag_procs(void *data,bpf_u_int32 caplen)
196 {  
197     struct proc_node *i;  
198     for (i = ip_frag_procs; i; i = i->next)
199         (i->item) (data, caplen);   
200 }  

作者有两行注释,果然,这个函数可以从两个地方调用,到底从pcap_loop还是那个大写字母的玩意,取决于nids_params.multiproc的结果

再次查看START_CAP_QUEUE_PROCESS_THREAD()函数的定义,的确是nids_params.multiproc为真才会执行;继续查看下pcap_loop的函数指针调用的函数,的确也有这个判断

331  #ifdef HAVE_LIBGTHREAD_2_0
332      if(nids_params.multiproc) {    
333         /*                
334          * Insert received fragment into the async capture queue.
335          * We hope that the overhead of memcpy 
336          * will be saturated by the benefits of SMP - mcree
337          */               
338         qitem=malloc(sizeof(struct cap_queue_item));
339         if (qitem && (qitem->data=malloc(hdr->caplen - nids_linkoffset))) {
340           qitem->caplen=hdr->caplen - nids_linkoffset;
341           memcpy(qitem->data,data_aligned,qitem->caplen);
342           g_async_queue_lock(cap_queue); 
343           /* ensure queue does not overflow */
344           if(g_async_queue_length_unlocked(cap_queue) > nids_params.queue_limit) {
345             /* queue limit reached: drop packet - should we notify user via syslog? */
346             free(qitem->data);
347             free(qitem);  
348             } else {
349             /* insert packet to queue */   
350             g_async_queue_push_unlocked(cap_queue,qitem);
351           }
352           g_async_queue_unlock(cap_queue);
353         }
354      } else { /* user requested simple passthru - no threading */
355         call_ip_frag_procs(data_aligned,hdr->caplen - nids_linkoffset);
356      }
357  #else
358      call_ip_frag_procs(data_aligned,hdr->caplen - nids_linkoffset);
359  #endif

说实话看到这些大括号也if else,差不多也醉了,去掉中间一些匹配了的,不关心的

331  #ifdef HAVE_LIBGTHREAD_2_0
332      if(nids_params.multiproc) {
.................省略中间......................
354      } else { /* user requested simple passthru - no threading */
355         call_ip_frag_procs(data_aligned,hdr->caplen - nids_linkoffset);
356      }
357  #else
358      call_ip_frag_procs(data_aligned,hdr->caplen - nids_linkoffset);
359  #endif

终于,可以看到这里nids_params.multiproc为假才会调用这个call函数;正好和START_CAP_QUEUE_PROCESS_THREAD()的调用错开了,也就说明了作者的那几行注释,either or,最终执行链表处理函数的调用只能两者其中一个调用的

下面是gdb的信息

Breakpoint 1, nids_run () at libnids.c:685
685	    if (!desc) {
Missing separate debuginfos, use: debuginfo-install glib2-2.28.8-4.el6.x86_64 glibc-2.12-1.149.el6_6.5.x86_64
(gdb) p nids_params
$1 = {n_tcp_streams = 1040, n_hosts = 256, device = 0x7ffff7ff3a90 "eth0", filename = 0x0, sk_buff_size = 168, dev_addon = 16, 
  syslog = 0x401d02 <nids_syslog>, syslog_level = 1, scan_num_hosts = 256, scan_delay = 3000, scan_num_ports = 10, no_mem = 
    0x406048 <nids_no_mem>, ip_filter = 0x401cf0 <nids_ip_filter>, pcap_filter = 0x0, promisc = 1, one_loop_less = 0, pcap_timeout = 
    1024, multiproc = 0, queue_limit = 20000, tcp_workarounds = 0, pcap_desc = 0x0}
(gdb) p nids_params.multiproc 
$2 = 0
(gdb) 

说实话,到了这里,虚拟机里关于这个错误的解决问题还没找到,更奇怪的是,服务器没这个错误,gdb调试也是通过pcap_loop里的handle里调用的处理函数,如此看来这个大写字母的START函数还没环境执行到,至于函数的含义,还没有深究,但是必须得感激虚拟机的这个错误,让我对执行流程某些细节有了更深的了解

新版本问题始终要必须解决,这里由于从错误信息里可以看到跟一个库有关的,而且这个函数貌似也执行不到,于是我在想能否编译前直接disable掉呢?在configure里搜索,果然,找到了相关的说明和方法

 865 Optional Features:
 866   --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
 867   --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
 868   --disable-libglib     use glib2 for multiprocessing support
 869   --disable-libnet      whether to include code requiring libnet
 870   --enable-shared       enable building shared libraries

可以看到glib2的问题,可惜无论怎么安装都没有将虚拟机这个错误消除,既然目前看来用不上,就采用一种take around的方法,将它disable掉,而且libnet貌似也用不上,一并disable掉

[lihui@localhost libnids-1.24]$ ./configure --disable-libnet --disable-libglib
[lihui@localhost libnids-1.24]$ make clean
[lihui@localhost libnids-1.24]$ make
[lihui@localhost libnids-1.24]$ sudo make install

这样编译安装都没有出现警告和错误,运行用例也正确无误

发表回复