我们先看一下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. 常见的JS手写函数汇总(代码注释、持续更新)

    最近在复习面试中常见的JS手写函数,顺便进行代码注释和总结,方便自己回顾也加深记,内容也会陆陆续续进行补充和改善. 一.手写深拷贝 <script> const obj1 = { name ...

  2. [论文分享]Channel Pruning via Automatic Structure Search

    authors: Mingbao Lin, Rongrong Ji, etc. comments: IJCAL2020 cite: [2001.08565v3] Channel Pruning via ...

  3. Centos7 根目录存储空间扩展方法

    Centos7 根目录存储空间扩展方法   一.首先通过 df -hl 命令查看磁盘占用情况,其中根目录已经被占满,此时需要对其进行扩容   二.针对虚拟机环境的centos7系统根存储空间扩容,可利 ...

  4. Java:利用BigDecimal类巧妙处理Double类型精度丢失

    目录 本篇要点 经典问题:浮点数精度丢失 十进制整数如何转化为二进制整数? 十进制小数如何转化为二进制数? 如何用BigDecimal解决double精度问题? new BigDecimal(doub ...

  5. 【代码周边】npm是What?

    社区 程序员自古以来就有社区文化: 社区的意思是:拥有共同职业或兴趣的人们,自发组织在一起,通过分享信息和资源进行合作.虚拟社区的参与者经常会在线讨论相关话题,或访问某些网站.前端程序员也有社区,世界 ...

  6. 【探索之路】机器人篇(2)-ROS系统并创建工作空间和项目

    在ROS官网,已经给出了详细的教程.下面我就般一下砖,把相应的操作写到这里.官方网址:http://wiki.ros.org/cn/ 安装ROS系统 indigo在ubuntu上的安装教程.官网:ht ...

  7. Java学习日报8.5

    package student;import java.util.*;public class student { Scanner sc=new Scanner(System.in); private ...

  8. 这是一篇SQL注入文章

    目录 注入原理: 1.寻找注入点的方式或注入的地方可能包括. 2.注入点判断方法. 3.注入分类. 数字型: 字符型: 搜索型: XX型(也叫其他型): 4.注入提交方式. 5.注入攻击类型与方式. ...

  9. 01 . Go之从零实现Web框架(框架雏形, 上下文Context,路由)

    设计一个框架 大部分时候,我们需要实现一个 Web 应用,第一反应是应该使用哪个框架.不同的框架设计理念和提供的功能有很大的差别.比如 Python 语言的 django和flask,前者大而全,后者 ...

  10. 多图详解Go中的Channel源码

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的go的源码时14.4 chan介绍 package main import & ...