同事在拿到修改后的weston相关的多线程代码中,怀疑有double close。期望我可以帮忙确定出来。
修改后的weston,我们拿到的只有一个executive elf文件,并没有source code。对于这种hook要求,一般有三种方法,但是各不相同:
因为有内核源码,因此第一种方式成为了首选。double close,一般第二次close的时候会出错,但是有的时候会错误的关闭其他线程的fd,因此我们只需要在关闭出错的时候打印出这个线程的名字、pid即可。在close system calll的实现代码中添加一行即可(文件位于fs/file.c):
int __close_fd(struct files_struct *files, unsigned fd) { struct file *file; struct fdtable *fdt; spin_lock(&files->file_lock); fdt = files_fdtable(files); if (fd >= fdt->max_fds) goto out_unlock; file = fdt->fd[fd]; if (!file) goto out_unlock; rcu_assign_pointer(fdt->fd[fd], NULL); __clear_close_on_exec(fd, fdt); __put_unused_fd(files, fd); spin_unlock(&files->file_lock); return filp_close(file, files); out_unlock: spin_unlock(&files->file_lock); printk(KERN_ERR "---process id/pid[%d] name=%s, close fd[%d], failed with EBADF\n", current->pid,current->comm,fd); return -EBADF; }
显然根据程序的从编译到运行的状态迁移,我们可以想象得到有以下几种方法:
对于link的时候使用ld的--wrap=symbol选项在链接的时候将所有的symbol替换成__wrap_symbol,而libc中实际的symbol,那么就变成了__real_symbol,因此我们需要定义__wrap_symbol函数,并可能需要在里面调用__real_symbol。
对于LD_PRELOAD,这个是加载器ld的功能,ld还有一些其他用于调试的功能选项,例如LD_DEBUG,这些都可以使用man 8 ld.so来查看,或者查看文档The LD_DEBUG environment variable。
不管是使用LD_PRELOAD还是使用链接器的--wrap=symbol方式,都容易出现有些System Call没有被Hook住的情况,出现这种情况的原因一般都是因为使用strace来查看一个程序的system call,正确的做法应该是使用lstrace。对此,可以查看参考2的文章,以及文章的评论。
静态链接的可执行文件,不需要再去其他so中解析和查找符号,因此直接就执行起来了,并不需要ld.so(加载器),因此这些方法不适用。参考StackOverFlow中答案的Commnet:How to hook ALL linux system calls during a binary execution
另外有许多程序都是使用用户态的Hook来调试与监控用户态程序的行为,例如valgrind wrap了malloc/free,socksify wrap了connect调用。因此这些代码都值得参考。
原因在于同一个进程的所有线程共享fd,即其范围都是一样的,大家open一个file/socket得到的fd的值的范围都是在同一个范围内,使用完的fd,在close之后(reference count == 0),会被回收并重复利用。
在linux中,使用ulimit -a可以看到userspace上,一个Process可以使用的fd的范围:
$ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 94963 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 94963 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited
因此可以同时最多打开的文件/sockect数目为1024,这个值有两种方法来修改:
①可以在命令行中使用 ulimit -n 4096(这里4096为设置后的可以使用的句柄数)来配置一个进程可以使用的最多句柄数。
②可以在程序中调用setrlimit这个System Call来配置进程可以使用的最多的句柄数,参考代码如下,亦可参考valgrind的test代码:
#include <sys/time.h> #include <sys/resource.h> struct rlimit rlp; rlp.rlim_cur = 4096; setrlimit(RLIMIT_NOFILE, &rlp);
如果有source code,那么根据参考链接1中的建议,我们应该在代码中对调用了close的地方加上返回值判断。man close可以看到:
ERRORS EBADF fd isn't a valid open file descriptor. EINTR The close() call was interrupted by a signal; see signal(7). EIO An I/O error occurred.
可以看到close并无法保证每次调用都成功,这个和free是不一样的。