菜单

零停重启程序工具Huptime琢磨

2019年8月15日 - 皇家赌场系统

零停重启程序工具Huptime商量

图片 1零停重启程序工具Huptime钻探.pdf

目录

目录1

1.官网1

2.功能1

3.条件要求2

4.兑现原理2

5.SIGHUP数字信号管理 3

6.重启线程4

7.重启指标程序5

8.系统调用钩子帮忙6

9.被勾住系统调用exit 6

10.被勾住系统调用listen 7

11.SymbolVersioning8

12.勾住bind等种类调用 10

13.体系调用进度13

14.测量检验代码13

14.1.Makefile13

14.2.s.c14

14.3.s.map14

14.4.x.cpp14

14.5.体会情势15

1.官网

2.功能

零停重启指标程序,比方一个网络服务程序,不用遗失和行车制动器踏板任何音信完毕重复启航,正在管理的音讯也不会暂停和错失,重启的艺术是给目的程序的进程发SIGHUP实信号。

3.情状须求

出于选用了Google牛人汤姆赫伯特为Linux内核打客车补丁SO_REUSEPORT性格,由此供给Linux内核版本为3.9或以上,SO_REUSEPORT允许多少个进程监听同一IP的同样端口。

4.落到实处原理

利用SIGHUP+SO_REUSEPORT+LD_PRELOAD,通过LD_PRELOAD将本身(huptime.so)注入到对象经过空间。

运用Python脚本huptime运转时会安装LD_PRELOAD,将huptime.so注入到指标程序的进度空间。

huptime.so运维时会实践setup函数,在setup中会创立多少个线程impl_restart_thread用于重启指标程序的经过,其余还恐怕会设置时限信号SIGHUP的管理器sighandler用于吸收接纳零重启时限信号SIGHUP:

staticvoid__attribute__((constructor))

setup(void)

{

#definelikely(x)__builtin_expect(!!(x),1)

if(likely(initialized))//只做一次

return;

initialized=1;

#defineGET_LIBC_FUNCTION(_name)\

libc._name=get_libc_function<_name _t="_t">(#_name,&_name)

//初始化全局变量libc,让其指向GLIBC库的bind等

GET_LIBC_FUNCTION(bind);//libc.bind=dlsym(RTLD_NEXT,bind);//系统的bind

GET_LIBC_FUNCTION(listen);

GET_LIBC_FUNCTION(accept);

GET_LIBC_FUNCTION(accept4);

GET_LIBC_FUNCTION(close);

GET_LIBC_FUNCTION(fork);

GET_LIBC_FUNCTION(dup);

GET_LIBC_FUNCTION(dup2);

GET_LIBC_FUNCTION(dup3);

GET_LIBC_FUNCTION(exit);

GET_LIBC_FUNCTION(wait);

GET_LIBC_FUNCTION(waitpid);

GET_LIBC_FUNCTION(syscall);

GET_LIBC_FUNCTION(epoll_create);

GET_LIBC_FUNCTION(epoll_create1);

#undefGET_LIBC_FUNCTION

impl_init();//安装信号SIGHUP处理器、创建重启线程等

}

template

staticFUNC_T

get_libc_function(constchar*name,FUNC_Tdef)

{

char*error;

FUNC_Tresult;

/*Clearlasterror(ifany).*/

dlerror();

/*Trytogetthesymbol.*/

result=(FUNC_T)dlsym(RTLD_NEXT,name);

error=dlerror();

if(result==NULL||error!=NULL)

{

fprintf(stderr,"dlsym(RTLD_NEXT,\"%s\")failed:%s",name,error);

result=def;

}

returnresult;

}

5.SIGHUP非时域信号处理

//信号SIGHUP处理函数,作用是通过管道通知重启线程impl_restart_thread,

//这里其实可以考虑使用eventfd替代pipe

staticvoid*impl_restart_thread(void*);

void

sighandler(intsigno)

{

/*Notifytherestartthread.

*Wehavetodothisinaseparatethread,because

*wehavenoguaranteesaboutwhichthreadhasbeen

*interruptedinordertoexecutethissignalhandler.

*Becausethiscouldhavehappenedduringacritical

*section(i.e.locksheld)wehavenochoicebutto

*firetherestartasycnhronouslysothatittoocan

*grablocksappropriately.*/

if(restart_pipe[1]==-1)

{

/*We’vealreadyrun.*/

return;

}

while(1)

{

chargo=’R’;

intrc=write(restart_pipe[1],&go,1);//通知重启线程

if(rc==0)

{

/*Wat?Tryagain.*/

continue;

}

elseif(rc==1)

{

/*Done.*/

libc.close(restart_pipe[1]);

restart_pipe[1]=-1;

break;

}

elseif(rc<0&&(errno==EAGAIN||errno==EINTR))

{

/*Goagain.*/

continue;

}

else

{

/*Shit.*/

DEBUG("Restartpipefubared!?Sorry.");

break;

}

}

}

6.重启线程

void*

impl_restart_thread(void*arg)

{

/*Waitforoursignal.*/

while(1)

{

chargo=0;

intrc=read(restart_pipe[0],&go,1);//等待SIGHUP信号

if(rc==1)

{

/*Go.*/

break;

}

elseif(rc==0)

{

/*Wat?Restart.*/

DEBUG("Restartpipeclosed?!");

break;

}

elseif(rc<0&&(errno==EAGAIN||errno==EINTR))

{

/*Keeptrying.*/

continue;

}

else

{

/*Realerror.Let’srestart.*/

DEBUG("Restartpipefubared?!");

break;

}

}

libc.close(restart_pipe[0]);

restart_pipe[0]=-1;

/*Seenoteaboveinsighandler().*/

impl_restart();//重启目标进程

returnarg;

}

7.重启目的程序

void

impl_restart(void)

{

/*Indicatethatwearenowexiting.*/

L();//加锁

impl_exit_start();

impl_exit_check();

U();//解锁

}

8.系统调用钩子援助

funcs_timpl=

{

.bind=do_bind,

.listen=do_listen,

.accept=do_accept_retry,

.accept4=do_accept4_retry,

.close=do_close,

.fork=do_fork,

.dup=do_dup,

.dup2=do_dup2,

.dup3=do_dup3,

.exit=do_exit,

.wait=do_wait,

.waitpid=do_waitpid,

.syscall=(syscall_t)do_syscall,

.epoll_create=do_epoll_create,

.epoll_create1=do_epoll_create1,

};

funcs_tlibc;//目标程序的进程调用的实际是huptime中的do_XXX系列

9.被勾住系统调用exit

staticvoid

do_exit(intstatus)

{

if(revive_mode==TRUE)//如果是复活模式,也就是需要重启时

{

DEBUG("Reviving…");

impl_exec();//调用execve重新启动目标程序

}

libc.exit(status);//调用系统的exit

}

10.被勾住系统调用listen

staticint

do_listen(intsockfd,intbacklog)

{

intrval=-1;

fdinfo_t*info=NULL;

if(sockfd<0)

{

errno=EINVAL;

return-1;

}

DEBUG("do_listen(%d,…)…",sockfd);

L();

info=fd_lookup(sockfd);

if(info==NULL||info->type!=BOUND)

{

U();

DEBUG("do_listen(%d,%d)=>-1(notBOUND)",sockfd,backlog);

errno=EINVAL;

return-1;

}

/*Checkifwecanshort-circuitthis.*/

if(info->bound.real_listened)

{

info->bound.stub_listened=1;

U();

DEBUG("do_listen(%d,%d)=>0(stub)",sockfd,backlog);

return0;

}

/*Canwereallycalllisten()?*/

if(is_exiting==TRUE)

{

info->bound.stub_listened=1;

U();

DEBUG("do_listen(%d,%d)=>0(is_exiting)",sockfd,backlog);

return0;

}

/*Welargelyignorethebacklogparameter.People

*don’treallyusesensiblevalueshereforthemost

*part.Hopefully(asisdefaultonsomesystems),

*tcpsyncookiesareenabled,andthere’snoreal

*limitforthisqueueandthisparameterissilently

*ignored.Ifnot,thenweusethelargestvaluewe

*cansensiblyuse.*/

(void)backlog;

rval=libc.listen(sockfd,SOMAXCONN);

if(rval<0)

{

U();

DEBUG("do_listen(%d,%d)=>%d",sockfd,backlog,rval);

returnrval;

}

/*We’redone.*/

info->bound.real_listened=1;

info->bound.stub_listened=1;

U();

DEBUG("do_listen(%d,%d)=>%d",sockfd,backlog,rval);

returnrval;

}

11.SymbolVersioning

Huptime使用到了GCC的依据符号的本子机制SymbolVersioning。本节内容入眼源自:

在linux上运营三个在别的机器上编写翻译的可试行文件时恐怕会遇见错误:/lib64/libc.so.6:version‘GLIBC_2.14’notfound(requiredby./a.out),该错误的原由是GLIBC的版本偏低。

从GLIBC2.1从头引进了SymbolVersioning机制,各样符号对应一个版本号,二个glibc库可含蓄二个函数的多个本子:

#nm/lib64/libc.so.6|grepmemcpy

[email protected]@GLIBC_2.14

[email protected]_2.2.5

其中[email protected]@GLIBC_2.14为暗中同意版本。使用SymbolVersioning能够更动一个已存在的接口:

__asm__(".symveroriginal_foo,[email protected]");

__asm__(".symverold_foo,[email protected]_1.1");

__asm__(".symverold_foo1,[email protected]_1.2");

__asm__(".symvernew_foo,[email protected]@VERS_2.0");

一经未有一些名版本号,这么些示例中的“[email protected]”代表了符号foo。源文件应当涵盖多少个C函数的落到实处:original_foo、old_foo、old_foo1和new_foo。它的MAP文件必须在VERAV4S_1.1、VERFS_1.2和VERS_2.0中包含foo。

也能够在本身的库中使用SymbolVersioning,如:

///@[email protected]/10/2015

///@[email protected]

__asm__(".symverfoo_1,[email protected]@libX_1.0");

intfoo_1(){

return1;

}

__asm__(".symverbar_1,[email protected]@libX_1.0");

intbar_1(){

return-1;

}

配套的MAP文件:

libX_1.0{

global:

foo;

bar;

local:*;

};

编译:

gcc-shared-fPIC-Wl,–version-scriptlibx-1.maplibx-1.c-olib1/libx.so

当通告新本子,希望维持非凡,能够扩大贰个版本号:

///@[email protected]/10/2015

///@[email protected]

/*oldfoo*/

__asm__(".symverfoo_1,[email protected]_1.0");

intfoo_1(){

return1;

}

/*newfoo*/

__asm__(".symverfoo_2,[email protected]@libX_2.0");

intfoo_2(){

return2;

}

__asm__(".symverbar_1,[email protected]@libX_1.0");

intbar_1(){

return-1;

}

相应的MAP文件形成:

libX_1.0{

global:

foo;

bar;

local:*;

};

libX_2.0{

global:foo;

local:*;

};

安装情况变量LD_DEBUG,能够展开动态链接器的调节和测量试验功效。分享库的结商谈析构函数:

void__attribute__((constructor(5)))init_function(void);

void__attribute__((destructor(10)))fini_function(void);

括号中的数字越小优先级越高,不能使用gcc-nostartfiles或-nostdlib。通过链接脚本能够将多少个现行反革命的分享库通过一定措施结合发生新的库:

GROUP(/lib/libc.so.6/lib/libm.so.2)

12.勾住bind等类别调用

/*Exportsnameasaliasnamein.dynsym.*/

#definePUBLIC_ALIAS(name,aliasname)\

typeof(name)aliasname__attribute__((alias(#name)))\

__attribute__((visibility("default")));

/*Exportsstub_##[email protected]*/

#defineSYMBOL_VERSION(name,version,version_ident)\

PUBLIC_ALIAS(stub_##name,stub_##name##_##version_ident);\

asm(".symverstub_"#name"_"#version_ident","#name"@"version);

/*Exportsstub_##[email protected]@(i.e.,theunversionedsymbolforname).*/

#defineGLIBC_DEFAULT(name)\

SYMBOL_VERSION(name,"@",default_)

/*Exportsstub_##[email protected]@GLIBC_MAJOR.MINOR.PATCH.*/

#defineGLIBC_VERSION(name,major,minor)\

SYMBOL_VERSION(name,"GLIBC_"#major"."#minor,\

glibc_##major##minor)

#defineGLIBC_VERSION2(name,major,minor,patch)\

SYMBOL_VERSION(name,"GLIBC_"#major"."#minor"."#patch,\

glibc_##major##minor##patch)

GLIBC_DEFAULT(bind)//当目标程序调用bind时,实际调用的将是Huptime库中的stub_bind

GLIBC_VERSION2(bind,2,2,5)

GLIBC_DEFAULT(listen)

GLIBC_VERSION2(listen,2,2,5)

GLIBC_DEFAULT(accept)

GLIBC_VERSION2(accept,2,2,5)

GLIBC_DEFAULT(accept4)

GLIBC_VERSION2(accept4,2,2,5)

GLIBC_DEFAULT(close)

GLIBC_VERSION2(close,2,2,5)

GLIBC_DEFAULT(fork)

GLIBC_VERSION2(fork,2,2,5)

GLIBC_DEFAULT(dup)

GLIBC_VERSION2(dup,2,2,5)

GLIBC_DEFAULT(dup2)

GLIBC_VERSION2(dup2,2,2,5)

GLIBC_DEFAULT(dup3)

GLIBC_VERSION2(dup3,2,2,5)

GLIBC_DEFAULT(exit)

GLIBC_VERSION(exit,2,0)

GLIBC_DEFAULT(wait)

GLIBC_VERSION2(wait,2,2,5)

GLIBC_DEFAULT(waitpid)

GLIBC_VERSION2(waitpid,2,2,5)

GLIBC_DEFAULT(syscall)

GLIBC_VERSION2(syscall,2,2,5)

GLIBC_DEFAULT(epoll_create)

GLIBC_VERSION2(epoll_create,2,3,2)

GLIBC_DEFAULT(epoll_create1)

GLIBC_VERSION(epoll_create1,2,9)

对应的MAP文件:

GLIBC_2.2.5{

global:

bind;

listen;

accept;

accept4;

close;

fork;

dup;

dup2;

dup3;

syscall;

local:*;

};

GLIBC_2.3.2{

global:

epoll_create;

local:*;

};

GLIBC_2.0{

global:

exit;

local:*;

};

GLIBC_2.9{

global:

epoll_create1;

local:*;

};

GLIBC_DEFAULT(bind)展开

typeof(stub_bind)stub_bind_default___attribute__((alias("stub_bind")))__attribute__((visibility("default")));;

asm(".symverstub_""bind""_""default_"",""bind""@""@");

//上面这一句等效于:asm(.symverstub_bind_default_,[email protected]@);

GLIBC_VERSION2(bind,2,2,5)

typeof(stub_bind)stub_bind_glibc_225__attribute__((alias("stub_bind")))__attribute__((visibility("default")));;

asm(".symverstub_""bind""_""glibc_225"",""bind""@""GLIBC_""2"".""2"".""5");

//上面这一句等效于:asm(.symverstub_bind_glibc_225,[email protected]_2.2.5);

13.体系调用进度

以bind为例:

目标程序的进程->stub_bind->impl.bind->do_bind->libc.bind

impl为一全局变量,impl->bind为函数指针,指向于do_bind。而libc.bind也为一函数指针,指向系统的bind。

14.测验代码

用来体验SymbolVersioning和勾住系统函数:

1)Makefile

用来编写翻译和测验

2)s.c

兑现勾住库函数memcpy。

3)s.map

s.c的MAP文件。

4)x.cpp

用来测量检验被勾住的memcpy程序。

14.1.Makefile

all:x

x:x.cpplibS.so

[email protected]$<

libS.so:s.cs.map

gcc-g-shared-fPIC-D_GNU_SOURCE-Wl,–version-scripts.map$<[email protected]

clean:

rm-flibS.sox

test:all

exportLD_PRELOAD=`pwd`/libS.so;./x;exportLD_PRELOAD=

14.2.s.c

#include

#include

void*stub_memcpy(void*dst,constvoid*src,size_tn)

{

printf("stub_memcpy\n");

void*(*libc_memcpy)(void*,constvoid*,size_t)=dlsym(RTLD_NEXT,"memcpy");

returnlibc_memcpy(dst,src,n);

}

typeof(stub_memcpy)stub_memcpy_default___attribute__((alias("stub_memcpy")))__attribute__((visibility("default")));;

asm(".symverstub_""memcpy""_""default_"",""memcpy""@""@");

14.3.s.map

libS_1.0{

global:

memcpy;

local:*;

};

14.4.x.cpp

//Test:

//exportLD_PRELOAD=`pwd`/libS.so;./x;exportLD_PRELOAD=

#include

#include

intmain()

{

chardst[100]={‘1′,’2′,’3′,’\0’};

constchar*src="abc";

memcpy(dst,src,strlen(src)+1);

printf("%s\n",dst);

return0;

}

14.5.感受格局

直白实行maketest就能够:

$maketest

exportLD_PRELOAD=`pwd`/libS.so;./x;exportLD_PRELOAD=

stub_memcpy

abc

若果不勾,则毫不设置LD_PRELOAD直接施行x:

$./x

abc

零停重启程序工具Huptime探讨.pdf 目录 目录1 1.官方网址1 2.功用1 3.情形要求2
4.贯彻原理2 5.SIGHUP信号管理 3 6.重启线程…

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图