Linux 进程热升级

Linux 动态库hook注入 由于工作上的需要,之前只是对大概的原理了解,现对此进行详细的分析。 涉及到的关键API以及数据结构: 1. ptrace PTRACE_ATTACH:挂载到指定的pid进程上 PTRACE_GETREGSET:读取目标进程的寄存器 PTRACE_POKETEXT:复制一个word的数据 PTRACE_PEEKTEXT:复制一个word的数据 PTRACE_SETREGSET:设置寄存器 PTRAC... 阅读详情

http://www.ibm.com/developerworks/cn/linux/l-cn-prcss-hotupgrd/

为了实现 Linux 系统进程热升级,本文提供了一种底层的实现方法,即在不重启进程的条件下,升级进程的共享库模块。

 

背景

用户总是希望服务进程能保持稳定。如果可以 7*24 小时的工作,那就永远不要重启它。但是,软件产品的功能总是在不断的丰富。当用户发现一些新的功能正是他所需要的,他也许会主动要求进行一次升级。而当严重的安全问题出现时,用户就不得不接受强制的升级了。

不停机升级,也被称为热升级。通常实现热升级,需要用户部署两套业务系统。至少,被升级的关键模块是两块以上的。这一般是通过硬件方式支持的。由此而产生的成本压力,不是每个用户都可以接受的。

对于小型业务系统,频繁的升级总是不可避免的。如果升级过程中,业务进程不用重启,那么,升级将不再是一个令用户烦恼的事情了。

回页首

动态链接的共享库

Linux 环境中的应用依赖相当数量的共享库。通常,为了达到软件模块化的目的,开发人员会把逻辑上紧密相关的功能集中在一起,编译到共享库中。这样做,既有利于代码的管理,也便于模块的复用。同时,共享库的方式也有利于应用升级。许多时候,仅仅更新数个共享库就可以完成整个应用的升级,降低了升级时的开销。

如果应用支持手工触发重新装载共享库,就不需要重启。但如果应用正巧并不支持,那么,更换共享库后仍需要重启应用。本文提供了一种方法可以在应用保持运行状态下,替换共享库。在替换过程中,应用被无缝的切换到新的共享库中。整个过程,应用(进程)无需重启。

回页首

基本过程

完成不重启的升级,需要一系列的复杂步骤。一个独立升级程序 U 来负责触发目标应用(进程 T )挂载新的共享库 L 。假设 U , T , L 是它们的名字。基本步骤如下:

  1. 升级程序 U 要找到进程 T 的 dlopen 函数的入口地址。
  2. 升级进程 U 执行 attach 系统调用,贴附到进程 T 上。向进程 T 的堆栈里压入新的共享库 L 的名字,再把 dlopen 函数的入口地址赋值给 PC 寄存器。
  3. 让目标进程 T 继续运行。由于 PC 寄存器保存的是 dlopen 函数的入口地址,这样,在目标进程 T 空间里,dlopen 函数被调用。新的共享库 L 被目标进程 T 装载。
  4. 新的共享库 L 在被装载时,利用 dlsym 函数在目标进程 T 中找到被替换函数的地址。设置被替换函数的代码空间为可写状态。
  5. 将汇编指令 0xCC 和 0xC3 写入被替换函数入口。0xCC 是汇编 INT 3 的指令码。0xC3 是汇编 RET 的指令码(注: 0xC3 是 64 位系统的指令码)。显然,由于 INT 3 的存在,当目标进程 T 调用这个被替换函数时,就会触发一次 SIGTRAP 信号。
  6. 新的共享库 L 在被装载时,调用 sigaction 函数,接管 SIGTRAP 信号。在信号处理函数中,调用用于代替被替换函数的新函数。
  7. 至此,新的共享库 L 的函数替代了目标进程 T 中原先使用的旧函数。每当目标进程 T 试图调用被替换函数时,都会触发 SIGTRAP 信号。然后,信号处理函数调用新的函数。这个过程将一直存在于进程 T 的整个生存周期中。

从上面的步骤可以得知,本方法适用于共享库的升级。通过替换旧的共享库中函数,实现升级。在上面的步骤的实施前,可以先对文件系统中共享库进行替换。这样,在无鏠升级后,当目标进程 T 有机会进行重启,再度启动的应用将直接加载新的共享库,而不再需要上面的复杂升级过程了。

本方法在底层对进程的内存数据进行了修改。由于不同体系,不同位数的 CPU ,指令码,寄存器,以及函数调用的栈帧结构都是不同的,因此,不同的硬件条件,升级程序将会有所差别。但是,基本原理是相同的。下面,分别详细介绍 x86 和 ARM 版本的实现细节。

回页首

基于 x86 的实现

本节根据前一节的基本步骤所述的内容,展示在 x86_64 CPU 体系上的实现步骤和关键代码,并对代码给予详细的说明。本章所列出的步骤将更为详细。

得到 dlopen 函数在目标进程 T 的地址。

假设升级程序 U 已经得到目标进程 T 的 PID。PID 为 t_pid。

注:我们的目标进程 T 是 ELF 格式的程序。在 glibc 中,完成共享库加载的函数是 __libc_dlopen_mode。详情可参见 glibc 的相关资料和代码。

清单 1. 得到 dlopen 函数地址

 snprintf(path, sizeof(path), "/proc/%d/maps", my_pid); 

 if ((f = fopen(path, "r")) == NULL) 
 return -1; 

 for (;;) { 
    Read a line form maps file 
 Look for a line with “r-xp” and libc- substring 
 If found { 
 addr = the first field of line; 
 break; 
    } 
 } 

 fclose(f); 

 dlopen_entry = dlsym(NULL, "__libc_dlopen_mode"); 
 if (!dlopen_mode) { 
 printf("Unable to locate dlopen address.\n"); 
 return -1; 
 } 

 dlopen_offset = dlopen_entry – addr; /* calc offset */ 

 t_libc = begin of libc of target process T; /* get from maps file of target T */ 
 if (!t_libc) { 
 printf("Unable to locate begin of target's libc.\n"); 
 return -1; 
 } 
 dlopen_entry = t_libc + dlopen_offset;

升级程序 U 在启动后,调用 getpid 系统调用,得到自己的 PID (变量 my_pid ),进而确定 proc 目录下的 maps 文件的路径。打开 maps 文件,该文件描述了不同的 section 在进程空间里的分配情况。形式如下:

 2b779cdbf000-2b779cdc1000 r-xp 00000000 08:01 1446923 /lib64/libc-2.5.so

文件由多行组成。每行则由多个字段组成。字段间用空格分隔。

第一列描述了 section 的起始和结束地址:2b779cdbf000-2b779cdc1000。

第二列描述了 section 的权限: r-xp 。每个缩写字符的含义为 :

r=read,w=write,x=execute,s=shared,p=private(copy on write) 。

最后一列描述了被映射文件的文件名: /lib64/libc-2.5.so 。

升级程序 U 在 maps 文件中查找权限字段为“ r-xp ”和最后字段为“ libc-* ”的行。找到后,取出第一字段,存入 addr 变量中。

调用 dlsym 函数得到 __libc_dlopen_mode 函数在进程空间的入口地址。将其减入 addr ,得到与 __libc_dlopen_mode 函数在 libc 中的偏移量。

图 1. 偏移量

图 1. 偏移量

这个偏移量在不同的进程空间里是相同的。因为,不同的进程加载的是相同的 libc 库。所以,打开目标进程 T 的 maps 文件。采用相同的方法得到 libc 在目标进程 T 的起始地址。这个起始地址加上偏移量,升级程序 U 就得到了 __libc_dlopen_mode 函数在目标进程 T 的入口地址。

Attach 目标进程 T ,备份现场数据。

清单 2. attach 目标进程

 struct my_user_regs { 
	 unsigned long r15; 
	 unsigned long r14; 
	 unsigned long r13; 
	 unsigned long r12; 
	 unsigned long rbp; 
	 unsigned long rbx; 
	 unsigned long r11; 
	 unsigned long r10; 
	 unsigned long r9; 
	 unsigned long r8; 
	 unsigned long rax; 
	 unsigned long rcx; 
	 unsigned long rdx; 
	 unsigned long rsi; 
	 unsigned long rdi; 
	 unsigned long orig_rax; 
	 unsigned long rip; 
	 unsigned long cs; 
	 unsigned long eflags; 
	 unsigned long rsp; 
	 unsigned long ss; 
	 unsigned long fs_base; 
	 unsigned long gs_base; 
	 unsigned long ds; 
	 unsigned long es; 
	 unsigned long fs; 
	 unsigned long gs; 
 }; 
 char sbuf1[512], sbuf2[512]; 
 struct my_user_regs regs, saved_regs, aregs; 

 if (ptrace(PTRACE_ATTACH, t_pid, NULL, NULL) < 0) 
 return -1; 

 waitpid(t_pid, &status, 0); 
 ptrace(PTRACE_GETREGS, t_pid, NULL, &regs); 

 peek_text(t_pid, regs.rsp + 512, sbuf1, sizeof(sbuf1)); 
 peek_text(t_pid, regs.rsp, sbuf2, sizeof(sbuf2));

调用 ptrace 函数 attach 到目标进程。成功后,获取寄存器组。根据栈寄存器 rsp ,备份栈内共计 1024 字节的数据。这些工作都是为了最后恢复现场做准备。

注: peek_text 函数是自定义的。它对 ptrace(PTRACE_PEEKTEXT … ) 做了封装,以支持多字节的数据块的读取。系统调用 ptrace(PTRACE_PEEKTEXT … ) 调用一次只能读取一个字。函数 peek_text 根据入参指明的长度,多次调用 ptrace 读取多个字节。后文将提到的 poke_text 是对 ptrace(PTRACE_POKETEST … ) 的封装,以支持写入多字节的数据块。

在目标进程 T 的堆栈里准备好 dlopen 函数的数据,触发目标进程 T 执行 dlopen 函数。

清单 3. 触发目标进程 T 执行 dlopen 函数

 z=0; 
 strcpy(filename_new_so, “/usr/lib/libnew.so”); 

 poke_text(t_pid, regs.rsp, (char *)&z, sizeof(z)); 
 poke_text(t_pid, regs.rsp + 512, filename_new_so, strlen(filename_new_so) + 1); 

 memcpy(&saved_regs, &regs, sizeof(regs)); 

 regs.rdi = regs.rsp + 512; 
 regs.rsi = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE; 
 regs.rip = dlopen_entry + 2; 

 ptrace(PTRACE_SETREGS, t_pid, NULL, &regs); 
 ptrace(PTRACE_CONT, t_pid, NULL, NULL); 

 waitpid(t_pid, &status, 0);

首先,将 0 压栈,这个数据将成为从 dlopen 函数退出时的返回地址。这个非法地址将触发一个异常。这使得升级程序 U 可以在目标进程调用完 dlopen 函数后,重新获得对它的控制。

保存文件名的变量 filename_new_so 是在升级程序 U 的进程空间中,所以,需要把它放入目标进程 T 的堆栈里。regs.rsp + 512 开始的空间已经备份过,可以把文件名存放在这里。

然后,为 dlopen 函数准备入参。dlopen 函数的函数声明是

 void *dlopen(const char *filename, int flag)

在 64 位 CPU 中,函数参数的传递是使用寄存器。因此,在这里, rdi 寄存器保存了文件名的地址。它对应入参 filename 。寄存器 rsi 保存了标志,对应入参 flag 。

注:在 32 位 CPU 中,函数参数是通常栈空间完成。与上面的示例是完全不同的。

最后,将指令执行地址寄存器 rip 设定为 dlopen 函数的入口地址,调用 ptrace 函数将控制权交回给目标进程。

由于在上一步中,预置了非法的返回地址 0, SIGSEGV 信号将会发生。升级程序 U 将再次获得控制权。在本步骤执行结束后,新的共享库 L 将被目标进程 T 加载。用户可以通过执行

 $cat /proc/t_pid/maps

查看新的共享库 L 是否已经被加载。

升级程序 U 恢复现场

当新的共享库被加载后,升级程序 U 必须恢复目标进程 T 至 attach 前的时刻。

清单 4. 恢复目标进程的现场

 ptrace(PTRACE_SETREGS, t_pid, 0, &saved_regs); 

 poke_text(t_pid, saved_regs.rsp + 512, sbuf1, sizeof(sbuf1)); 
 poke_text(t_pid, saved_regs.rsp, sbuf2, sizeof(sbuf2)); 

 ptrace(PTRACE_DETACH, t_pid, NULL, NULL);

在第一次执行 ptrace 进行 attach 后,升级程序 U 就备份了目标进程 T 的堆栈空间和寄存器。在新的共享库 L 加载成功后,升级程序 U 将目标进程 T 的堆栈和寄存器恢复到 attach 前的状态。

升级程序 U 的任务到这里就完成了。为了替代目标进程 T 中的函数,新加载的共享库 L 需要执行一系列特定的步骤。下面的各节描述了新的共享库 L 里的实现细节。

将 INT 3 和 RET 指令写入要替代的函数

假设要替换的函数声明为:void old_func(void)。

该函数无入参和返回值。这里是为了简化问题,便于说明基本原理。带有入参和返回值会使处理代码更为复杂。

清单 5. 写入 INT3 和 RET 指令

 void _init() 
 { 
 unsigned char *aligned = NULL; 
 struct sigaction sa; 
 unsigned char * entrys [32] = {0, 0}; 

 void *handle=dlopen(NULL, RTLD_LAZY); 
 if (handle == NULL) 
        return ; 

 if ((entrys[0] = dlsym(handle, "old_func")) == NULL){ 
 return; 
 } 

 memset(&sa, 0, sizeof(sa)); 
 sa.sa_sigaction = sigtrap; 
 sa.sa_flags = SA_RESTART|SA_SIGINFO; 
 sigaction(SIGTRAP, &sa, NULL); 

 aligned = (unsigned char *)(((size_t)hooks[0]) & ~4095); 
 if (mprotect(aligned, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) != 0) { 
 return; 
 } 

 entrys [0][0] = 0xcc;   /* int 3 */ 
 entrys [0][1] = 0xc3;   /* 64bit ret instruction */ 
 }

代码清单 4 描述的是新的共享库的代码。

函数 _init 将在共享库被加载时隐式执行。值得注意的是,函数 _init 是在目标进程 T 的空间中运行。

首先,它调用入参为 NULL 的 dlopen 函数得到全局符号句柄。依靠全局符号句柄,再调用 dlsym 函数获得 old_func 函数的入口地址。

然后,设置信号 SIGTRAP 的处理函数为自已的 sigtrap 函数。

最后,它将 old_func 函数的内存空间修改为可读写执行模式。将 old_func 函数的第一个指令设置为 0xCC ;第二个指令设置为 0xC3 。 0xCC 是汇编指令 INT 3 的指令码。 0xC3 是汇编指令 RET 的指令码。由于被替换函数 old_func 是一个无入参,无返回值的函数,所以,在修改这个函数时,无需堆栈处理。但在实际应用中,如果函数有入参和返回值,就不可以直接使用 RET 指令,而是需要对堆栈进行精确的处理,保证目标进程 T 的堆栈的正确。

注:代码段是可读,可执行,但不可写的。所以,为了写入新的指令,必须将代码段设为可写模式。

在 sigtrap 信号处理函数里,调用 new_func 函数。

在上一步中,函数 _init 对信号 SIGTRAP 设置了处理函数。本节介绍这个处理函数的细节。该函数的实现代码属于新的共享库 L 。

清单 6. sigtrap 函数

 void new_func(void) 
 { 
 printf(">> this is new function\n"); 
 return ; 
 } 

 static void sigtrap(int x, siginfo_t *si, void *vp) 
 { 
 new_func(); 
 return; 
 }

信号处理函数异常简单,仅仅是调用新的函数 new_func 。这个函数正是用于替换函数 old_func 的。

到此,旧的函数 old_func 就完全被替代了。每当目标进程 T 调用 old_func 函数时,由于 old_func 函数第一个指令为 INT 3 ,这将触发一个 SIGTRAP 信号。导致 sigtap 信号处理函数被调用。在信号处理函数内部,用来替代 old_func 的 new_func 函数被调用。从 sigtrap 函数返回后,由于第二个指令是 RET ,目标进程 T 对 old_func 的调用完成。对于目标进程 T 来说,虽然它调用的是 old_func 函数,但实际得到执行的却是 new_func 。它根本无法查觉到 old_func 函数已经被替换成了 new_func 函数。

值得一提的是,在升级程序 U 执行热升级任务之前,可以先对磁盘上的共享库文件升级覆盖。在新的共享库文件中, old_func 函数已经被去除, new_func 函数已经编译在程序中。这样,当目标进程 T 重启后, new_func 函数将经由正常的启动途径被加载,而无需上面的复杂机制。下面的示意图可以帮助我们更好的理解新旧共享库,函数之间的关系:

图 2. 升级后,目标进程 T 内部调用关系

图 2. 升级后,目标进程 T 内部调用关系

基于 ARM 的实现

本文所述方法也适用于 ARM 体系。但是,一些与 CPU 有关的地方,则有所不同。本节详细说明不同之处。其余部分完全相同。

第一个不同之处是“触发目标进程 T 执行 dlopen 函数”。而获取 dlopen 函数的方法与 x86 相同。

清单 7. 触发目标进程 T 执行 dlopen 函数(ARM)

 peek_text(t_pid, regs.ARM_sp + 512, sbuf1, sizeof(sbuf1)); 
 peek_text(t_pid, regs.ARM_sp, sbuf2, sizeof(sbuf2)); 

 strcpy(filename_new_so, “/usr/lib/libnew.so”); 

 poke_text(t_pid, regs.ARM_sp + 512, filename_new_so, strlen(filename_new_so) + 1); 

 memcpy(&saved_regs, &regs, sizeof(regs)); 

 regs.ARM_r0 = regs.ARM_sp + 512; 
 regs.ARM_r1 = RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE; 
 regs.ARM_lr = 0; 
 regs.ARM_pc = (size_t)dlopen_entry; 

 ptrace(PTRACE_SETREGS, t_pid, NULL, &callso_regs); 
 ptrace(PTRACE_CONT, t_pid, NULL, NULL); 

 waitpid(ph->pid, &status, 0);

同样的,首先备份栈内数据。 ARM 的栈寄存器是 sp 。代码中记为 ARM_sp 。准备 dlopen 函数的入参的步骤与 x86 有很大的不同。这是因为 x86 使用栈来传递参数,而 ARM 则使用 R0~R3 寄存器来传递参数。如果参数个数大于 4 ,再使用栈空间。因此,这里, ARM_r0 寄存器指向新的共享库的文件名。 ARM_R1 寄存器保存了标志。 ARM 的函数返回地址是保存在 lr 寄存中的,为了触发异常,而使升级程序 U 在加载了新的共享库后,重新得到控制权,在这里,我们为 lr 寄存器设置了无效的返回值 0 。这与 x86 中的向栈内压入值为 0 的变量 z 是一样的目的。最后,为 pc 寄存器设置 dlopen 函数的入口地址。

第二处不同是向被替换函数写入的指令不同。

在 x86 里,我们使用 INT 3 来发出 SIGTRAP 信号。然后在信号函数里调用新的函数,以达到替换的目的。但是,利用 ARM 指令来实现 SIGTRAP 信号的触发,较为繁琐。故改用跳转指令。代码如下所示。

清单 8. 写入无条件转移指令

 void _init() 
 { 
 unsigned char *aligned = NULL; 
 struct sigaction sa; 
 unsigned char * entrys [32] = {0, 0}; 

 void *handle=dlopen(NULL, RTLD_LAZY); 
 if (handle == NULL) 
        return ; 

 if ((entrys[0] = dlsym(handle, "old_func")) == NULL){ 
 return; 
 } 
    
 aligned = (unsigned char *)(((size_t)hooks[0]) & ~4095); 
 if (mprotect(aligned, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) != 0) { 
 return; 
 } 

 entrys [0][0] = 0xe59ff008;;     /* ldr pc, [pc, #8] */ 
 entrys [0][1] = (int)new_func;   /* data */ 
 entrys [0][2] = (int)new_func; 
 entrys [0][3] = (int)new_func; 
 entrys [0][4] = (int)new_func; 
 }

ARM 里的无条件跳转指令有 B 、 BL 、 BX 、。但是它们都有 32MB 跳转范围的限制。ARM 可以通过直接修改 PC 寄存器,实现 4GB 空间的无条件跳转。在向 PC 寄存器存入地址时,不能直接使用 MOV 指令存入绝对地址,像下面的指令:

mov pc, #40200000;

是无法通过编译的。因此,我们在这里使用 ldr 指令,在指令后面的内存空间里存放跳转地址。entrys[0][1]~[0][3] 是用于填充空间,并无实际意义。 ldr 指令实际是从 entrys[0][4] 中取出地址。这个地址正是新的函数的入口地址。

当目标进程 T 调用 old_func 函数时,该函数的入口是一条跳转到 new_func 函数的指令。函数 new_func 被调用,而函数 old_func 就被绕过。函数 new_func 的入参和返回值 old_func 保持一致,实现了无缝升级。下面的示意图可以帮助我们更好的理解:

图 3. 升级后,目标进程 T 内部调用关系

图 3. 升级后,目标进程 T 内部调用关系

回页首

总结

沿着本文所述方法的思路,可以进一步扩展支持更为广泛的目标进程。比如,本文利用 dlsym 来定位旧函数的入口地址。但 dlsym 无法定位非共享库的函数。这时,就需要对进程的映射文件(外存设备上的 ELF 格式文件)进行解析,计算出在内存空间的的地址。

另外,在 x86 版本中,我们使用了 INT 3 的方法。其实,我们也可以使用 JMP 指令,采用 ARM 版本的方法来实现替换。而且,看起来这种方法更为完美。

总之,进程热升级可以很好的提高系统设备的可靠性和安全性。每当有 hotfix 补丁时,如果用户不希望设备关机升级,产品的开发商可以利用本文的方法对设备升级,避免因为不能停机的缘故,而无法打上安全补丁,而使产品带着漏洞运行。

参考资料

学习

  • 参考"程序员杂志 104 期"的一篇文章:杨广翔,2008. ELF 格式可执行程序的代码嵌入技术。
  • 如果希望了解链接和装载方面的信息,请参考: John R. Levine, October 1999. Linkers and Loaders, Morgan Kaufmann
  • 杜春蕾,2003,《ARM 体系结构与编程》,清华大学出版社
  • 在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括 Linux 新手入门)准备的更多参考资料,查阅我们 最受欢迎的文章和教程。
  • 在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。
  • 随时关注 developerWorks 技术活动和网络广播。
linux 热补丁机制基本概念 内核实现 livepatch 对架构,编译器等均有一些要求,比如编译器为架构提供可靠栈回溯,支持动态 ftrace regs 等,否则对应架构很难实现 livepatch,目前有 x86,powerpc,s390 能在内核直接支持 livepatch。kpatch 修补的单位是函数,而不是数据结构,如果补丁对数据结构进行了修改,那一般会出问题,需要重新制作补丁,默认不允许修改数据结构,如果这样做了,kpatch-build 可能会抛出错误。字符串,从打印中可以看到补丁应用成功,卸载补丁后恢复原来的字符串。 阅读详情

相关推荐

记录一次libc.so.6文件被替换、删除引发的系统级故障之修复过程

libc.so.6,Segmentation fault

yxj171273860的博客 4908

Linux dlopen 注入 和 hook分析

http://blog.51cto.com/haidragon/2135226 https://github.com/gaffe23/linux-inject   目的:将动态库so注入到目标程序中 核心原理:1、获取目标程序函数(__libc_dlopen_mode、malloc、free)的地址;2、获取一段可执行的内存地址;3、将汇编注入代码写入这段内存地址;4、通过int3断点来查...

u014288349的博客 4285

Linux进程管理:从基础到高级编程技巧

进程是操作系统资源分配的基本单位,Linux通过独特的轻进程模型实现多任务管理。理解进程创建(fork/exec)、状态监控和进程间通信(IPC)机制是系统编程的核心基础。在Linux环境下,进程管理涉及PCB控制块、写时复制等底层原理,通过pipe、FIFO等IPC技术实现高效数据交换。这些技术支撑了从简单脚本到复杂服务器架构的开发,特别是在高并发、分布式系统等场景中,合理的进程管理能显著提升系统稳定性和性能。本文以C语言为例,深入解析Linux进程管理的核心技术,包括进程控制、资源回收和现代容器技术基础

weixin_29228203的博客 310

应用程序热补丁(一): 几行代码构造免重启修复补丁

作者简介:王超,UCloud内核团队 前言热补丁是一种在程序运行时动态修复内存中代码bug的技术。在UCloud,我们使用内核热补丁和应用程序热补丁(也就是进程热补丁)来在线修复核心业务的缺陷和安全漏洞。多年来我们使用内核热补丁技术避免了系统重启导致的业务中断、保证了操作系统的可用性。属于核心业务组件的应用程序,尤其是单点的虚拟化组件和有状态的应用程序,同样面对高可用的挑战,每次重启都会导致...

karamos的专栏 3995

linux x86平台elf 进程注入so并且实现基于rel的hook ubuntu14.01测试通过

linux x86平台elf 进程注入so并且实现基于rel的hook ubuntu14.01测试通过

告别枯燥的Unity编辑器脚本:Odin插件如何让游戏数据配置变得像搭积木一样简单?

本文详细介绍了Odin插件如何简化Unity编辑器脚本开发,使游戏数据配置变得直观高效。通过声明式特性和丰富的可视化工具,Odin让开发者告别繁琐的OnGUI代码,实现所见即所得的开发体验。文章重点展示了Odin的核心优势、魔法特性及实战案例,帮助Unity开发者提升工作效率。

weixin_28717411的博客 345

linux程序热升级,Linux 进程热升级

用户总是希望服务进程能保持稳定。如果可以 7*24小时的工作,那就永远不要重启它。但是,软件产品的功能总是在不断的丰富。当用户发现一些新的功能正是他所需要的,他也许会主动要求进行一次升级。而当严重的安全问题出现时,用户就不得不接受强制的升级了。不停机升级,也被称为热升级。通常实现热升级,需要用户部署两套业务系统。至少,被升级的关键模块是两块以上的。这一般是通过硬件方式支持的。由此而产生的成本压力,...

weixin_35649491的博客 947

解决PADs VX2.7安装中的License失效与软件卡死问题

本文详细解析了PADs VX2.7安装过程中常见的License失效与软件卡死问题,提供了从破解工具选择、环境变量设置到输入法兼容性调整等实用解决方案。特别推荐使用Siemens EDA系列新版破解工具,并分享性能调优与定期维护技巧,帮助工程师高效解决安装难题。

weixin_29234239的博客 815

Linux 进程热升级(共享库的动态替换)

http://www.ibm.com/developerworks/cn/linux/l-cn-prcss-hotupgrd/ 为了实现 Linux 系统进程热升级,本文提供了一种底层的实现方法,即在不重启进程的条件下,升级进程的共享库模块。 背景 用户总是希望服务进程能保持稳定。如果可以 7*24 小时的工作,那就永远不要重启它。但是,软件产品的功能总是在不断的丰富

RandyLeonard的专栏 4320

loadStop的原因导致的SSUSP

Total内存值是可用来调度的可用内存,当前可用内存为 0M。 loadSched和loadStop的阈值是根据 Total 内存和节点内存来做限制的。 loadStop 50G,现在有大量作业SSUSP,说明之前某个时间点该节点内存可能有低于50G的时候,导致作业SSUSP,具体是否是这个原因引起的需要用 bjobs -l JobID 看下作业的详细信息。 如果是loadStop的原因导致的 SSUSP,根据loadStop设置的阈值,Total 值高于loadStop值后,将逐步恢复挂起的作业

Blueberry521的博客 667

手把手教你用Cursor + 自定义MCP工具链,让AI帮你搞定App逆向(附adb-mcp/frida-mcp源码)

本文详细介绍了如何利用Cursor IDE与自定义MCP工具链构建AI驱动的App逆向工作流,涵盖adb-mcp和frida-mcp的源码实现与实战应用。通过自然语言指令,AI可自动化完成设备控制、动态Hook和数据分析,显著提升移动安全测试效率。适合安全研究人员和开发者快速掌握智能逆向技术。

weixin_33737134的博客 1301

Linux 进程热升级 共享库的动态替换

背景 用户总是希望服务进程能保持稳定。如果可以 7*24 小时的工作,那就永远不要重启它。但是,软件产品的功能总是在不断的丰富。当用户发现一些新的功能正是他所需要的,他也许会主动要求进行一次升级。而当严重的安全问题出现时,用户就不得不接受强制的升级了。 不停机升级,也被称为热升级。通常实现热升级,需要用户部署两套业务系统。至少,被升级的关键模块是两块以上的。这一般是通过硬件方式支持的。由此而产生的成本压力,不是每个用户都可以接受的。 对于小型业务系统,频繁的升级总是不可避免的。如果升级过程中,业务进程

~~ LINUX ~~ 1228

TI AWR2944毫米波雷达开发板开箱:用官方Demo快速上手3D点云与热图生成

本文详细介绍了TI AWR2944毫米波雷达开发板的开箱与实战应用,包括硬件准备、软件开发环境搭建、Demo固件刷写与配置、实时可视化与数据解析等全流程指南。通过官方Demo快速上手3D点云与热图生成,帮助开发者高效掌握毫米波雷达技术在工业检测、智能交通等领域的应用。

weixin_42627853的博客 308

120 Linux C++ 通讯架构实战3 nginx整体结构,nginx进程模型,nginx调整worker进程数量,nginx重载配置文件,热升级,关闭

nginx 是一个多进程服务器,好处是:A worker 进程挂了,不会影响其他进程,master进程发现A挂了后,会重新启动一个进程。弊端是:进程间通讯消耗的资源多。有些服务器是多线程服务器,多线程的弊端是当一个线程挂了的时候,很可能影响其他的线程,到时整个进程都有问题。

hunandede的博客 1358

【STM32Cube HAL】输入捕获(六)——脉宽测量

1

HAHAHA 4908

流媒体协议-RTMP简介

与rtsp 不同,RTSP(Real-Time Streaming Protocol)是一个控制协议,负责控制媒体流的传输,但不传输媒体数据本身。RTSP 可以通过 TCP 或 UDP 传输 RTP 包来传输音视频数据。 RTMP(Real-Time Messaging Protocol)是一个综合性的协议,不仅可以传输音视频数据,还可以传输信令控制指令。RTMP 使用 TCP 作为传输协议,可以直接在 TCP 连接上传输音视频数据,也可以传输控制指令,实现了音视频流的实时传输和控制

xiaoshuaijinniu的博客 4553

Linux环境下对nginx热升级

八、将nginx-1.27.4/objs目录下的nginx放到/home/klink/nginx下,并赋予执行权限!三、查看上版本nginx的安装位置,以便后续替换nginx文件。七、安装后生产新的nginx文件。九、为新的nginx生成新的进程。十、停止旧版本nginx进程。一、下载新版本nginx,找到有./nginx的进程。四、备份旧版本nginx。五、获取旧版本编译参数。六、至新版本目录下编译。十一、验证版本和进程。

qq_42451900的博客 724

基于ESP32的AI 古琴:基于音频指纹的民族乐器智能校准系统设计与实现

硬件选型兼顾实用性与低成本,接线简单易实现;基于 MFCC 的音频指纹算法,能有效识别古琴弦的音高特征;人机交互友好,新手可快速上手。

weixin_42300449的博客 308

嵌入式Linux多进程编程

进程是操作系统资源分配的基本单位,进程是程序执行的实例。

嵌入式软件工程师养成记 1122

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

使用 Nginx 实现平滑升级

旧进程处理没有完成的请求,但不再接收新的处理请求,而是由新进程负责接收请求并进行处理;| 重新加载配置文件,使用新的配置启动工作进程,并逐步关闭旧进程 |最后通过关闭旧进程的 PID,使新的进程完全兼容。2)安装新版本(需要注意新版本是否需要安装依赖包)| 逐步关闭工作进程及工作进程支持的信息 || 启动新的主进程,实现热升级 || 等工作进程结束后再退出 |在旧进程运行时,启动新的进程;| 重新打开日志文件 || 信号 | 作用 || 强制终止进程 |4)使用信号实现热升级。1.主进程支持的信号。

2501_90433155的博客 1038

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

技术晨读_2015_3_9

技术晨读_2015_3_9 2015-03-09 11:11 by 轩脉刃, ... 阅读, ... 评论, 收藏, 编辑 技术晨读 Linux环境进程间通信:信号 了解信号的发送,安装,分类。。。 http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html ...

轩脉刃的刀光剑影 190

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

(一)通讯实战基础

一:nginx的整体结构 (1.1)master进程和worker进程概览(父子关系) (1.2)nginx进程模型 (1.3)调整worker进程数量 二:nginx进程模型细说 (2.1)nginx重载配置文件 (2.2)nginx热升级,热回滚 (2.3)nginx的关闭 (2.4)总结 三:终端和进程的关系 (3.1)终端与bash进程 (3.2)终端上的...

GSX 188

Shmipc:高性能进程间通讯库

Shmipc:高性能进程间通讯库 项目介绍 Shmipc是由字节跳动开发的高性能进程间通讯库,基于Linux的共享内存构建,使用unix/tcp连接进行进程同步,实现了进程间通讯的零拷贝。在IO密集型场景或大包场景下,Shmipc能够显著提升性能,是处理高并发、大数据量通讯的理想选择。 项目技术分析 零拷贝技术 传统的Unix domain socket和Tcp loopback在进程间通讯时,数...

gitblog_00716的博客 812

Linux的super super super easy教程 | 用户态 内核态 终端 reload

当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)。于是后面改为make update 就把核心的/sbin/nginx二进制文档给替换成新的。(一般来说用户态大于内核态 当用户态大量对硬件进行读写的时候 内核态会大于用户态)用户空间里面运行的叫用户态(自己的应用程序的代码运行起来的)内核空间里面运行的是内核态(系统调用和内核的代码运行起来的)到时候启动新的worker就是新的二进制文档产生的。

Neptune_keke的博客 280

Linux环境下Nacos单机版热升级技巧:不重启服务替换jar包全流程

本文详细解析了Linux环境下Nacos单机版热升级的全流程,重点介绍了如何在不重启服务的情况下替换jar包。通过文件锁检测、优雅停机和进程守护等技术手段,实现零停机升级,确保业务连续性。文章还提供了预升级检查、安全备份、灰度验证和回滚机制等实用技巧,帮助开发者高效完成Nacos升级。

wind6的博客 627
上一篇: 深入Linux内核网络堆栈
下一篇: 应用程序热补丁(一):如何用几行代码打造应用程序热补丁
废言Pro
博客等级 码龄16年 1093粉丝 142原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值