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

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

背景

用户总是希望服务进程能保持稳定。如果可以 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 函数地址

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

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 在进程空间里的分配情况。形式如下:

1

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 目标进程

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

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 函数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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 函数的函数声明是

1

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

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

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

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

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

1

$cat /proc/t_pid/maps

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

升级程序 U 恢复现场

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

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

1

2

3

4

5

6

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 指令

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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 函数

1

2

3

4

5

6

7

8

9

10

11

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)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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. 写入无条件转移指令

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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 进程热升级

http://www.ibm.com/developerworks/cn/linux/l-cn-prcss-hotupgrd/ 为了实现 Linux 系统进程热升级,本文提供了一种底层的实现方法,即在不重启进程的条件下,升级进程的共享库模块。 背景 用户总是希望服务进程能保持稳定。如果可以 7*24 小时的工作,那就永远不要重启它。但是,软件产品的功能总是在不断的丰富。当用户发现一些新的功能正是他所需要的,他也许会主动要求进行一次升级。而当严重的安全问题出现时,用户就不得不接受强制的升级了。 .

~~ LINUX ~~ 1390

Linux系统升级及内核版本升级

一、背景 工作中有时会遇到系统版本过旧,无法满足安全扫描后的安全要求,系统漏洞数量多,处理难度较大,这时,我们就需要升级OS或Linux内核版本; 二、升级操作 2.1、 redhat/CentOS系统 三、附录:包升级命令 1)配置yum更新源 /etc/yum.repos.d目录下新建主机的yum源,.repos文件中,repositoryid:指定一个仓库,name指定仓库名称, baseurl:指定仓库的URL,mirrorlist指定仓库的镜像站点,enable是否启用本仓库,gpgcheck

企业实战系列集 ●●● https://ximenjianxue.blog.youkuaiyun.com 3万+

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

FreeBSD 15.1 维稳实战:四层健康检查与ZFS BE回滚体系

FreeBSD 是一款以稳定性著称的类Unix操作系统,其维护逻辑区别于Linux,核心在于状态管理、变更控制与可回滚设计。理解 freebsd-update(内核/基础工具链更新)、portsnap(Ports源码树同步)、pkg(二进制包事务管理)和 portmaster(源码编译调度)的职责边界,是构建可靠运维体系的前提。FreeBSD 15.1 引入 ZFS Boot Environments 原生支持,使系统快照与秒级回滚成为可能;同时强化了 pkg 依赖校验与 Ports ABI 兼容性管控。本

weixin_33849215的博客 371

uniapp+openlayers集成天地图实战:renderjs通信陷阱与性能优化避坑指南

本文深入探讨了uniapp与openlayers集成天地图时的renderjs通信陷阱与性能优化策略。通过实战案例解析了renderjs的特殊通信机制、地图实例生命周期管理、图层懒加载等关键技巧,帮助开发者避免常见坑点,提升应用流畅度。特别针对多语言切换、热重载偏移等疑难问题提供了解决方案,是uniapp+openlayers开发者的实用避坑指南。

weixin_30872157的博客 362

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

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

RandyLeonard的专栏 4320

解决若依Vue3前后端分离---路由切换时显示白屏

解决基于Vue3若依前后端分离项目中出现的路由正常切换但是就是不显示数据的问题,也就是不发起网络请求的问题。

简单点的博客 1605

热升级

热升级是什么? 热升级是什么呢?了解 nginx 的同学都知道,nginx 是支持热升级的,可以用老进程服务先前链接的链接,使用新进程服务新的链接,即在不停止服务的情况下完成系统的升级与运行参数修改。那么热升级和热编译是不同的概念,热编译是通过监控文件的变化重新编译,然后重启进程。 共享库的动态替换 为了实现 Linux 系统进程热升级,本文提供了一种底层

iie_libi的博客 964

逆向工程实战:如何一步步破解京东APP的sign签名算法(附Frida脚本)

本文详细解析了京东APP的sign签名算法逆向工程实战,通过Frida脚本动态分析Java层和Native层的多层签名机制,揭示商业应用的加密协议设计。文章提供了完整的逆向工程方法论与工具链,帮助开发者深入理解移动应用安全防护体系,提升安全防御能力。

weixin_28622215的博客 919

实测OV5640 DVP/MIPI接口720p@60fps配置,为什么我的Zynq-7020开发板只有30帧?

本文深入解析了OV5640摄像头在Zynq-7020开发板上帧率减半的问题,从寄存器配置到硬件链路进行全面排查。重点分析了DVP/MIPI接口的720p@60fps配置,提供了关键寄存器设置、时序参数调整及硬件带宽验证方法,帮助开发者快速定位并解决帧率下降问题。

weixin_42634811的博客 344

动态库热加载原理与工程实践:从dlopen到Qt/onnxruntime

动态链接技术是现代软件模块化与插件化开发的基石。通过将代码封装为独立动态库,程序可以在运行时按需加载、解析符号并调用函数,从而实现模块解耦与功能扩展。动态库热加载进一步将这种能力延伸到运行中的进程,做到不停机替换代码模块,对7x24小时在线服务、桌面客户端插件升级以及推理引擎版本迭代等场景意义重大。从Linux的dlopen/dlsym到Windows的LoadLibrary/GetProcAddress,再到Qt的QLibrary封装,热加载的实现原理统一但细节繁多,符号导出、生命周期管理、跨平台编译均是

weixin_28834169的博客 338

机器学习笔记——30种常见机器学习算法简要汇总

本笔记介绍机器学习中常见的30种机器学习算法。

haopinglianlian的博客 3199

c ++

编译器看到。

LDX_0225的博客 206

MobileViT替换YOLOv11主干网络:轻量化新思路-(用MobileViT等轻量视觉Transformer降低计算成本)

在计算机视觉领域,目标检测一直是核心任务之一,而YOLO(You Only Look Once)系列算法因其出色的速度-精度平衡成为工业界和学术界的热门选择。随着YOLOv11的推出,模型性能达到了新的高度,但其计算复杂度和参数量也随之增加,这对资源受限的移动设备和嵌入式系统提出了挑战。与此同时,视觉Transformer(ViT)的出现为计算机视觉带来了全新范式,但其庞大的计算需求限制了在移动端的应用。MobileViT作为一种创新的轻量级视觉Transformer架构,巧妙结合了CNN的归纳偏置优势和V

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 1647

NL2SQL进阶系列(3):Data-Copilot、Chat2DB、Vanna Text2SQL优化框架开源应用实践详解[Text2SQL]

NL2SQL进阶系列(3):Data-Copilot、Chat2DB、Vanna Text2SQL优化框架开源应用实践详解[Text2SQL]

丨汀、的博客 5421

Ubuntu 18.04 Snap打包实战:snapcraft.yaml与confinement深度解析

Snap 是 Linux 下面向安全隔离与跨发行版分发的现代应用打包范式,其核心在于通过声明式配置实现运行时沙箱、权限最小化与自动更新。其原理依托于 AppArmor/LSM 内核安全模块、mount namespace 隔离及 snapd 守护进程的策略代理机制,技术价值体现在强约束下的稳定性、老旧系统兼容性(如 Ubuntu 18.04)与产线级可审计性。典型应用场景覆盖工业边缘设备、嵌入式终端及等保合规系统,尤其适用于需在十年老硬件上运行新版 Python 应用或严格管控串口/网络访问的场景。本文聚焦

tyygming的专栏 332

ISO2626 --- Item Definition相关项定义

本文并不是针对ISO26262的一个项目,而是针对ISO26262的规范,我们在相关项定义阶段,我们需要做什么,我们的文档需要包含哪些内容的一个描述。

wj博客 909

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

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

karamos的专栏 3995

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

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

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

weixin_35649491的博客 947

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Linux在程序运行时替换文件,[操作系统]linux下实现在程序运行时的函数替换(热补丁)...

[操作系统]linux下实现在程序运行时的函数替换(热补丁)02016-07-02 10:00:07声明:以下的代码成果,是参考了网上的injso技术,文章最后会给出地址。但是injso文章中的代码存在一些问题,所以后面出现的代码是经过我个人修改和检测的。最近因为在学习一些调试的技术,但是很少有提到如何在函数运行时实现函数替换的。为什么会想到这一点?因为在学习调试时,难免会看到一些内核方面的调试技...

weixin_35754962的博客 695

linux运行进程热补丁(一)之函数替换

在Linux环境下(x86_64)对正在运行进程的函数替换,不改变该进程的可执行文件内容,通过使用汇编指令JMP完成运行中进程的函数替换。//编译myprint.c变为可执行文件。

ljbcharles的专栏 965

Linux实现热补丁

热补丁是什么热补丁(hotfix),又称为patch,指能够修复软件漏洞的一些代码,是一种快速、低成本修复产品软件版本缺陷的方式。通过电子邮件或者其他途径来通知用户有关热补丁的消息,一般在软件供应商的网站上可以免费下载补丁程序。和升级软件版本相比,热补丁的主要优势是不会使设备当前正在运行的业务中断,即在不重启设备的情况下,可以对设备当前软件版本的缺陷进行修复。Linux热补丁实现如果要修改一个已经

chrisnotfound 6471

linux热补丁(进程中的函数替换),x86, x86_64

背景知识 本文介绍Linux环境下正在运行进程的函数替换,不改变该进程的可执行文件内容。通过使用汇编指令JMP完成运行中进程的函数替换。为了更好地理解本文所述内容,我们需要了解以下几个知识点: 1) 了解汇编JMP指令实现无条件跳转,远跳转,近跳转; -- E9:本文所用 -- EA: -- EB: 2) 汇编指令JMP与call的区别;为什么不能使...

windy_huarj的博客 1766

linux 下C程序热补丁技术的原理和实现——要求

上半年做运营商的一个项目,设备规范是中兴华为这些大厂商写的,其中有关于热补丁的要求。运营商对设备运行时间要求很高,不会让更改一个小小的问题都要重启程序。所以就有了热补丁要求:要求程序在运行过程中能够更改程序的行为。 关于linux下热补丁的实现资料不多,我只找到一个:http://www.cnblogs.com/WuCountry/archive/2010/02/22/1671537.html ...

tibaloga 2731
上一篇: libco协程库上下文切换原理详解
下一篇: 深度报告:CPU研究框架(115页PPT)
废言Pro
博客等级 码龄16年 1093粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值