libco hook原理简析
我们先看一下libco协程库的特性描述
libco的特性
无需侵入业务逻辑,把多进程、多线程服务改造成协程服务,并发能力得到百倍提升;
支持CGI框架,轻松构建web服务(New);
支持gethostbyname、mysqlclient、ssl等常用第三库(New);
可选的共享栈模式,单机轻松接入千万连接(New);
对于其第三点特性,支持gethostbyname、mysqlclient、ssl等常用第三库。这说明什么?说明它们的网络IO函数,使用的是libco中的网络IO函数,不然进入不了协程调度。那么lobco是如何实现的呢?如果你善于运用搜索引擎,一定会找到一些文章这样解释:因为libco协程库hook了系统的socket相关函数。
上面那句话,其实说了等于没说... 这是果,而不是因,而这也是我打算写这篇文章的原因,此文的相关版本最早发布在公司内部论坛,时间在17年初,由于x*&#%#(一堆敏感词),此文为裁剪版。
答案之一在co_sys_hook_call.cpp文件中,在该文件中实现了hook 系统socket相关函数的第一步(注意是第一步,还有后续条件),在该文件中,可看到其定义的大量与系统socket相关函数同名,同参数的函数,如下文的socket函数所示
//co_sys_hook_call.cpp
...
int socket(int domain, int type, int protocol)
{
HOOK_SYS_FUNC( socket );
if( !co_is_enable_sys_hook() )
{
return g_sys_socket_func( domain,type,protocol );
}
int fd = g_sys_socket_func(domain,type,protocol);
if( fd < 0 )
{
return fd;
}
rpchook_t *lp = alloc_by_fd( fd );
lp->domain = domain;
fcntl( fd, F_SETFL, g_sys_fcntl_func(fd, F_GETFL,0 ) );
return fd;
}
...
对于每一个其定义的同步socket相关函数,该文件中也定义了一个对应的函数指针
static socket_pfn_t g_sys_socket_func = (socket_pfn_t)dlsym(RTLD_NEXT,"socket");
细心的读者想必注意到了上面的socket函数,其第一行的
HOOK_SYS_FUNC( socket );
我们看一下HOOK_SYS_FUNC是干嘛的。
#define HOOK_SYS_FUNC(name) if( !g_sys_##name##_func ) { g_sys_##name##_func = (name##_pfn_t)dlsym(RTLD_NEXT,#name); }
结合上面的函数指针定义,我们可以知道该宏用于初始化对应的函数指针。对于dlsym,它是用于从加载进内存的动态库中(通过传入其句柄)寻找指定符号的函数或者变量,这里的关键点是传入的句柄参数是RTLD_NEXT,该参数表示从当前库之后的load进来的动态库中寻找该符号。比如对于socket函数,如果没有其它库也定义了该函数的话,在这里找到的会是glibc中相关socket函数的地址。
我们可以看到libco在自己的socket函数中加入了一些额外的逻辑,然后最终调用的还是系统库中的socket函数。这成功的实现了hook的第一步。
接下来分析hook的第二步,也就是如何保证第三方库调用的是libco自身实现的相关socket函数呢?
简单而言,就是通过调整最终生成的可执行文件的链接顺序,使其全局符号表中的跟socket相关函数的符号为libco协程库中的符号。
这里需要简述一下目标文件生成最终的可执行文件的链接过程。
我们先简单看一下链接过程中会发生什么。在链接过程中,链接器会按顺序扫描输入的目标文件,将其中的符号加入全局符号表,再计算出合并后的各个段的长度和位置。之后将进行符号解析和重定位。
另外对于动态链接来说,其还将遵循如下一条规则
全局符号介入
linux下的动态链接器存在以下原则:当共享对象被load进来的时候,它的符号表会被合并到进程的全局符号表中(这里说的全局符号表并不是指里面的符号全部是全局符号,而是指这是一个汇总的符号表),当一个符号需要加入全局符号表时,如果相同的符号名已经存在,则后面加入的符号被忽略。
由于glibc是c/cpp程序的运行库,因此它是最后以动态链接的形式链接进来的,我们可以保证其肯定是最后加入全局符号表的,由于全局符号介入机制,glibc中的相关socket函数符号被忽略了(但是libco中巧妙的运用RTLD_NEXT参数获取到了其地址),也因此只要最终的可执行文件链接了libco协程库,就可以基本保证相关的socket函数被hook掉了。
为什么是基本保证?根据我的使用经验,有socket相关函数定义的动态库,不止glibc,pthread库中也有(不确定其它库是否也有)!不过也没有关系,只需要保证libco库位于pthread库之前链接即可。如下所示
gcc main.c -o test -LSOME_PATH -llibco -lpthread
另外我们可以看到在co_hook_sys_call.cpp文件的末尾有一个很有意思的函数
void co_enable_hook_sys() //这函数必须在这里,否则本文件会被忽略!!!
{
stCoRoutine_t *co = GetCurrThreadCo();
if( co )
{
co->cEnableSysHook = 1;
}
}
这里放这个函数其实是针对静态链接的情况,因为如果该文件生成的目标文件中的符号如果最终全部都没有被强引用到的话(比如在一个.h文件中对某个函数进行声明,那么是对这个函数符号的弱引用,而对这个函数进行调用,则是对这个函数符号的强引用),那么该目标文件在静态链接的过程中会被忽略掉。早期的计算机内存都非常小,因此能省一点是一点。
到这里基本就分析完毕了。另外假如你使用的是libco的动态库的话,可以通过readelf -d | grep 'NEEDED' 目标文件命令或者ldd 目标文件,查看当前动态库的链接顺序,确保hook掉了socket相关函数。
libco hook原理简析的更多相关文章
- Java Android 注解(Annotation) 及几个常用开源项目注解原理简析
不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...
- PHP的错误报错级别设置原理简析
原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...
- Java Annotation 及几个常用开源项目注解原理简析
PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...
- [转载] Thrift原理简析(JAVA)
转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...
- Spring系列.@EnableRedisHttpSession原理简析
在集群系统中,经常会需要将Session进行共享.不然会出现这样一个问题:用户在系统A上登陆以后,假如后续的一些操作被负载均衡到系统B上面,系统B发现本机上没有这个用户的Session,会强制让用户重 ...
- SIFT特征原理简析(HELU版)
SIFT(Scale-Invariant Feature Transform)是一种具有尺度不变性和光照不变性的特征描述子,也同时是一套特征提取的理论,首次由D. G. Lowe于2004年以< ...
- 基于IdentityServer4的OIDC实现单点登录(SSO)原理简析
写着前面 IdentityServer4的学习断断续续,兜兜转转,走了不少弯路,也花了不少时间.可能是因为没有阅读源码,也没有特别系统的学习资料,相关文章很多园子里的大佬都有涉及,有系列文章,比如: ...
- Android热补丁技术—dexposed原理简析(手机淘宝采用方案)
上篇文章<Android无线开发的几种常用技术>我们介绍了几种android移动应用开发中的常用技术,其中的热补丁正在被越来越多的开发团队所使用,它涉及到dalvik虚拟机和android ...
- Android热补丁技术—dexposed原理简析(阿里Hao)
本文由嵌入式企鹅圈原创团队成员.阿里资深工程师Hao分享. 上篇文章<Android无线开发的几种常用技术>我们介绍了几种android移动应用开发中的常用技术,其中的热补丁正在被越来越多 ...
随机推荐
- IQueryable的简单封装
IQueryable的简单封装 前言 前两天在园子上看到一个问题 半年前我也考虑过这些问题,但由于这样那样的问题,没有尝试去解决. 后来公司用上了 abp vnext ,然后有一部分代码可以这样写 p ...
- HashMap知识点总结,这一篇算是总结的不错的了,建议看看!
HashMap存储结构 内部包含了⼀个 Entry 类型的数组 Entry[] table.transient Entry[] table;(transient:表示不能被序列化)Entry类型存储着 ...
- C#——线程总结
#线程详解 1. Thread基础之从 WinDbg 角度理解你必须知道的时间和空间上的开销 一:空间上的开销 1.thread本身来说就是操作系统的概念... <1> thread的内核 ...
- [Deep Learning] 神经网络编程基础 (Basics of Neural Network Programming) - 逻辑回归-梯度下降-计算图
在神经网络中,假如有m个训练集,我们想把他们加入训练,第一个想到得就是用一个for循环来遍历训练集,从而开始训练.但是在神经网络中,我们换一个计算方法,这就是 前向传播和反向传播. 对于逻辑回归,就是 ...
- hive集群模式安装
hadoop3.2.0 完全分布式安装 hive-3.1.1 #解压缩tar -zxvf /usr/local/soft/apache-hive-3.1.1-bin.tar.gz -C /usr/lo ...
- Android开发用到的几种常用设计模式浅谈(一):组合模式
1:应用场景 Android中对组合模式的应用,可谓是泛滥成粥,随处可见,那就是View和ViewGroup类的使用.在android UI设计,几乎所有的widget和布局类都依靠这两个类.组合模式 ...
- Phoneix(一)简介及常用命令
一.简介 Apache Phoneix是运行在HBase之上的高性能关系型数据库,通过Phoneix可以像使用jdbc访问关系型数据库一样访问HBase. Phoneix操作的表以及数据存储在HBas ...
- mysql远程访问被拒绝问题
远程连接MySql数据库时: ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES) 远 ...
- 域名解析 看Cname 信息
CMD 命令: nslookup -q=cname www.yuzhentan.com
- python 中的sum( )函数 与 numpy中的 sum( )的区别
一. python sum函数 描述: sum() 对序列进行求和 用法: sum(iterable[, start]) iterable:可迭代对象,例如,列表,元组,集合. start:指定相加的 ...