我们先看一下libco协程库的特性描述

  1. libco的特性
  2. 无需侵入业务逻辑,把多进程、多线程服务改造成协程服务,并发能力得到百倍提升;
  3. 支持CGI框架,轻松构建web服务(New);
  4. 支持gethostbynamemysqlclientssl等常用第三库(New);
  5. 可选的共享栈模式,单机轻松接入千万连接(New);

对于其第三点特性,支持gethostbyname、mysqlclient、ssl等常用第三库。这说明什么?说明它们的网络IO函数,使用的是libco中的网络IO函数,不然进入不了协程调度。那么lobco是如何实现的呢?如果你善于运用搜索引擎,一定会找到一些文章这样解释:因为libco协程库hook了系统的socket相关函数。

上面那句话,其实说了等于没说... 这是果,而不是因,而这也是我打算写这篇文章的原因,此文的相关版本最早发布在公司内部论坛,时间在17年初,由于x*&#%#(一堆敏感词),此文为裁剪版。

答案之一在co_sys_hook_call.cpp文件中,在该文件中实现了hook 系统socket相关函数的第一步(注意是第一步,还有后续条件),在该文件中,可看到其定义的大量与系统socket相关函数同名,同参数的函数,如下文的socket函数所示

  1. //co_sys_hook_call.cpp
  2. ...
  3. int socket(int domain, int type, int protocol)
  4. {
  5. HOOK_SYS_FUNC( socket );
  6. if( !co_is_enable_sys_hook() )
  7. {
  8. return g_sys_socket_func( domain,type,protocol );
  9. }
  10. int fd = g_sys_socket_func(domain,type,protocol);
  11. if( fd < 0 )
  12. {
  13. return fd;
  14. }
  15. rpchook_t *lp = alloc_by_fd( fd );
  16. lp->domain = domain;
  17. fcntl( fd, F_SETFL, g_sys_fcntl_func(fd, F_GETFL,0 ) );
  18. return fd;
  19. }
  20. ...

对于每一个其定义的同步socket相关函数,该文件中也定义了一个对应的函数指针

  1. static socket_pfn_t g_sys_socket_func = (socket_pfn_t)dlsym(RTLD_NEXT,"socket");

细心的读者想必注意到了上面的socket函数,其第一行的

  1. HOOK_SYS_FUNC( socket );

我们看一下HOOK_SYS_FUNC是干嘛的。

  1. #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协程库中的符号。

这里需要简述一下目标文件生成最终的可执行文件的链接过程。

我们先简单看一下链接过程中会发生什么。在链接过程中,链接器会按顺序扫描输入的目标文件,将其中的符号加入全局符号表,再计算出合并后的各个段的长度和位置。之后将进行符号解析和重定位。

另外对于动态链接来说,其还将遵循如下一条规则

  1. 全局符号介入
  2. linux下的动态链接器存在以下原则:当共享对象被load进来的时候,它的符号表会被合并到进程的全局符号表中(这里说的全局符号表并不是指里面的符号全部是全局符号,而是指这是一个汇总的符号表),当一个符号需要加入全局符号表时,如果相同的符号名已经存在,则后面加入的符号被忽略。

由于glibc是c/cpp程序的运行库,因此它是最后以动态链接的形式链接进来的,我们可以保证其肯定是最后加入全局符号表的,由于全局符号介入机制,glibc中的相关socket函数符号被忽略了(但是libco中巧妙的运用RTLD_NEXT参数获取到了其地址),也因此只要最终的可执行文件链接了libco协程库,就可以基本保证相关的socket函数被hook掉了。

为什么是基本保证?根据我的使用经验,有socket相关函数定义的动态库,不止glibc,pthread库中也有(不确定其它库是否也有)!不过也没有关系,只需要保证libco库位于pthread库之前链接即可。如下所示

  1. gcc main.c -o test -LSOME_PATH -llibco -lpthread

另外我们可以看到在co_hook_sys_call.cpp文件的末尾有一个很有意思的函数

  1. void co_enable_hook_sys() //这函数必须在这里,否则本文件会被忽略!!!
  2. {
  3. stCoRoutine_t *co = GetCurrThreadCo();
  4. if( co )
  5. {
  6. co->cEnableSysHook = 1;
  7. }
  8. }

这里放这个函数其实是针对静态链接的情况,因为如果该文件生成的目标文件中的符号如果最终全部都没有被强引用到的话(比如在一个.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. 进入mysql数据库修改密码

    mysql -hlocalhost -uroot -p #修改密码mysql> set password for root@localhost = password('root');#启动数据库 ...

  2. JavaDailyReports10_06

    今日收获: 一.所有引用类型变量的初始化一定要使用new 关键字定义声明,空指针异常的错误原因可能是变量没有初始化导致的. 每一个类体的数据成员一定要在实例化的同时赋值,用一个实例化的类实现问题中最小 ...

  3. Java学习日报7.26

    package leijia;import java.util.*;public class Sum { public static void main(String[] args) { // TOD ...

  4. Base 128 Varints 编码(压缩算法)

    Base 128 Varint可以说是一种编码方式,也可以说是一种压缩算法.这种压缩算法是用来压缩数字的传输的,压缩的依据是基于一个现实:越小的数字,越经常使用 我们来看看一个例子: 如果我们要网络传 ...

  5. 第13章节 BJROBOT 雷达跟随【ROS全开源阿克曼转向智能网联无人驾驶车】

    雷达跟随说明:注意深度摄像头的 USB 延长线,可能会对雷达扫描造成影响, 所以在雷达跟随前,把深度摄像头的 USB 延长线取下.另外雷达跟随范围大概是前方 50cm 和 120°内扫描到的物体都可以 ...

  6. LeapMotion控制器 java语言开发笔记--(连接控制器)

    (1)为了连接LeapMotion控制器,创建controller对象,这个对象自动创建与控制器的服务或者后台程序连接,然后捕获数据提供给你的应用程序.可以使用该对象来获取连接信息. (2)获取Fra ...

  7. Spark MLlib中KMeans聚类算法的解析和应用

    聚类算法是机器学习中的一种无监督学习算法,它在数据科学领域应用场景很广泛,比如基于用户购买行为.兴趣等来构建推荐系统. 核心思想可以理解为,在给定的数据集中(数据集中的每个元素有可被观察的n个属性), ...

  8. 三、hadoop、yarn安装配置

    本文hadoop的安装版本为hadoop-2.6.5 关闭防火墙 systemctl stop firewalld 一.安装JDK 1.下载java jdk1.8版本,放在/mnt/sata1目录下, ...

  9. 建立索引和创建视图(结合YGGL.sql)

    一.请按要求对YGGL库建立相关索引 (1)使用create index 语句创建索引 1.对employees表中的员工部门号创建普通索引depart_ind. mysql> create i ...

  10. uber_go_guide解析(二)

    前言 接上回 正文 错误消息 Go中声明错误有几种方式 errors.New() 简单的声明静态字符串信息的错误 fmt.Errorf 可以格式化插入信息的错误 自己实现 Error() 方法 使用e ...