我们先看一下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原理简析的更多相关文章

  1. Java Android 注解(Annotation) 及几个常用开源项目注解原理简析

    不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...

  2. PHP的错误报错级别设置原理简析

    原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...

  3. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

  4. [转载] Thrift原理简析(JAVA)

    转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...

  5. Spring系列.@EnableRedisHttpSession原理简析

    在集群系统中,经常会需要将Session进行共享.不然会出现这样一个问题:用户在系统A上登陆以后,假如后续的一些操作被负载均衡到系统B上面,系统B发现本机上没有这个用户的Session,会强制让用户重 ...

  6. SIFT特征原理简析(HELU版)

    SIFT(Scale-Invariant Feature Transform)是一种具有尺度不变性和光照不变性的特征描述子,也同时是一套特征提取的理论,首次由D. G. Lowe于2004年以< ...

  7. 基于IdentityServer4的OIDC实现单点登录(SSO)原理简析

    写着前面 IdentityServer4的学习断断续续,兜兜转转,走了不少弯路,也花了不少时间.可能是因为没有阅读源码,也没有特别系统的学习资料,相关文章很多园子里的大佬都有涉及,有系列文章,比如: ...

  8. Android热补丁技术—dexposed原理简析(手机淘宝采用方案)

    上篇文章<Android无线开发的几种常用技术>我们介绍了几种android移动应用开发中的常用技术,其中的热补丁正在被越来越多的开发团队所使用,它涉及到dalvik虚拟机和android ...

  9. Android热补丁技术—dexposed原理简析(阿里Hao)

    本文由嵌入式企鹅圈原创团队成员.阿里资深工程师Hao分享. 上篇文章<Android无线开发的几种常用技术>我们介绍了几种android移动应用开发中的常用技术,其中的热补丁正在被越来越多 ...

随机推荐

  1. Java获取某年某月的第一天和最后一天

    /** * 获取某年某月的第一天 * @Title:getFisrtDayOfMonth * @Description: * @param:@param year * @param:@param mo ...

  2. 还在使用Future轮询获取结果吗?CompletionService快来了解下吧。

    背景 二胖上次写完参数校验(<二胖写参数校验的坎坷之路>)之后,领导一直不给他安排其他开发任务,就一直让他看看代码熟悉业务.二胖每天上班除了偶尔跟坐在隔壁的前端小姐姐聊聊天,就是看看这些 ...

  3. J.U.C关于Execute实现

    JAVASE5的Execute将为你管理Thread对象,是启动任务的优选方案 /***newCachedThreadPool在程序的执行过程中通常会创建于所需任务相同数量的线程即可以达到Intege ...

  4. CF Grakn Forces 2020 1408E Avoid Rainbow Cycles(最小生成树)

    1408E Avoid Rainbow Cycles 概述 非常有趣的题目(指解法,不难,但很难想) 非常崇拜300iq,今天想做一套div1时看见了他出的这套题Grakn Forces 2020,就 ...

  5. MobPush厂商通道排查

    开启log 一.添加开启log代码 有AndroidManifest.xml的 //在AndroidManifest.xml的application标签中添加 <meta-data androi ...

  6. C#中的格式

    格式模式 说明 : d 月中的某一天.一位数的日期没有前导零. dd 月中的某一天.一位数的日期有一个前导零. ddd 周中某天的缩写名称,在 AbbreviatedDayNames 中定义. ddd ...

  7. PHPExcel-Helper快速构建Excel

    项目介绍 PHPExcel-Helper是什么? PHPExcel辅助开发类,帮助开发者快速创建各类excel. github PHPExcel-Helper存在的意义? 官方phpexcel库功能全 ...

  8. U盘使用技巧篇 制作一般人删除不了的文件(宣传视频) (量产开卡)

    一. 视频制作成ISO ,放好 视频  图标文件 制作工具 : UltraISO 图标制作: 插入光盘状态:用autorun.inf格式:[autorun]open=Install.exe 点击光盘时 ...

  9. 20210107 - python 的Excel自动化

    1.前置条件:用python, 开发工具pycharm 2.安装包: pip install openpyxl,  该包支持:xlsx, 但是不支持xlrd.xlwt格式的文件 3.概念介绍: 一个e ...

  10. 为Github仓库添加Github Actions实现持续集成: Android apk自动编译发布以及github pages同步推送coding.net

    内容转载自我的博客 目录 说明 1. 编写Android项目的CI配置文件 2. 编写Jekyll项目的CI配置文件 2.1 配置coding.net 2.2 配置github 2.3 自动部署到co ...