程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 零停重啟程序工具Huptime研究

零停重啟程序工具Huptime研究

編輯:JAVA綜合教程

零停重啟程序工具Huptime研究


零停重啟程序工具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.官網

https://github.com/amscanne/huptime

2.功能

零停重啟目標程序,比如一個網絡服務程序,不用丟失和中斷任何消息實現重新啟動,正在處理的消息也不會中斷和丟失,重啟的方法是給目標程序的進程發SIGHUP信號。

3.環境要求

由於使用了Google牛人TomHerbert為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>(#_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。本節內容主要源自:https://blog.blahgeek.com/glibc-and-symbol-versioning/。

在linux上運行一個在其他機器上編譯的可執行文件時可能會遇到錯誤:/lib64/libc.so.6:version‘GLIBC_2.14’notfound(requiredby./a.out),該錯誤的原因是GLIBC的版本偏低。

從GLIBC2.1開始引入了SymbolVersioning機制,每個符號對應一個版本號,一個glibc庫可包含一個函數的多個版本:

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

000000000008ee90imemcpy@@GLIBC_2.14

00000000000892b0imemcpy@GLIBC_2.2.5

其中memcpy@@GLIBC_2.14為默認版本。使用SymbolVersioning可以改變一個已存在的接口:

__asm__(".symveroriginal_foo,foo@");

__asm__(".symverold_foo,foo@VERS_1.1");

__asm__(".symverold_foo1,foo@VERS_1.2");

__asm__(".symvernew_foo,foo@@VERS_2.0");

如果沒有指定版本號,這個示例中的“foo@”代表了符號foo。源文件應當包含四個C函數的實現:original_foo、old_foo、old_foo1和new_foo。它的MAP文件必須在VERS_1.1、VERFS_1.2和VERS_2.0中包含foo。

也可以在自己的庫中使用SymbolVersioning,如:

///@filelibx-1.c@date05/10/2015

///@[email protected]

__asm__(".symverfoo_1,foo@@libX_1.0");

intfoo_1(){

return1;

}

__asm__(".symverbar_1,bar@@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

當發布新版本,希望保持兼容,可以增加一個版本號:

///@filelibx.c@date05/10/2015

///@[email protected]

/*oldfoo*/

__asm__(".symverfoo_1,foo@libX_1.0");

intfoo_1(){

return1;

}

/*newfoo*/

__asm__(".symverfoo_2,foo@@libX_2.0");

intfoo_2(){

return2;

}

__asm__(".symverbar_1,bar@@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_##nameasname@version.*/

#defineSYMBOL_VERSION(name,version,version_ident)\

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

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

/*Exportsstub_##nameasname@@(i.e.,theunversionedsymbolforname).*/

#defineGLIBC_DEFAULT(name)\

SYMBOL_VERSION(name,"@",default_)

/*Exportsstub_##nameasname@@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_,bind@@);

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,bind@GLIBC_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

g++-g-o$@$<

libS.so:s.cs.map

gcc-g-shared-fPIC-D_GNU_SOURCE-Wl,--version-scripts.map$<-o$@

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved