- 紧急现网bug修复
- 验证测试问题
- 定制化功能
等等
补丁实现机制:
系统启动时需要加载系统软件和配置文件。如果指定下次启动的补丁文件,则需要加载补丁文件。系统启动场景一般如下 :
设备指定升级后的补丁文件。
设备升级时,可指定以前未安装的补丁文件。设备升级后,补丁将立即生效。
补丁是一种与设备系统软件兼容的软件,用于解决设备系统软件数量少、急需解决的问题。在设备运行过程中,有时需要修改设备系统软件的适应性和错误,如纠正系统中的缺陷,优化功能以满足业务需求。
补丁通常以补丁文件的形式发布。补丁文件可能包含一个或多个补丁,不同的补丁具有不同的功能。当用户将补丁文件从存储器加载到内存补丁区时,补丁文件中的补丁将被分配到内存补丁区中唯一的单元序列号,用于标记、管理和操作补丁。
补丁分为热补丁和冷补丁:
1、热补丁HP(Hot Patch):
补丁生效不中断业务,不影响业务运行,降低设备升级成本,避免升级风险。
2、冷补丁CP(Cold Patch):
要使补丁生效,需要复位单板或重启设备,影响业务运行。
补丁可分为增量补丁和非增量补丁。
3、增量补丁:
指依赖于前面的补丁。新的补丁文件必须包含前一个补丁文件中的所有补丁信息。用户可以直接安装新的补丁文件,而无需卸载原始补丁文件。
4.非增量补丁:
只允许当前系统安装补丁文件。如果用户想在安装补丁后重新安装另一个补丁文件,则需要在重新安装和运行新的补丁文件之前卸载当前的补丁文件。
每个补丁都有自己的状态,只有在用户命令的干预下才能切换。补丁状态的详细信息如下:
1、空闲状态(Idle)
此时,补丁文件存储在设备的存储器中,但文件中的补丁尚未加载到内存补丁区。当用户将补丁从存储器加载到内存补丁区时,将设置补丁状态进行激活。
2.激活状态(Deactive)
当补丁加载到内存补丁区或激活补丁停止运行时,补丁处于去激活状态。
用户可以使用以下两种操作来激活补丁:
卸载此补丁,使补丁从内存补丁区中被删除。临时运行此补丁,使补丁的状态变为激活状态。
3、激活状态(Active)
当补丁储存在内存补丁区域并临时运行时,补丁处于激活状态。当单板复位时,复位前单板上的激活补丁仍然恢复到激活状态。只有当整机复位时,复位前的激活补丁才会被激活。
用户可以对激活补丁进行以下三种操作:
卸载此补丁,从内存补丁区域删除补丁。停止操作此补丁,使补丁状态变为去激活状态。此补丁将永久运行,使补丁状态变为运行状态。
4、运行状态(Running)
当补丁存储在内存补丁区域并永久运行时,补丁处于运行状态。当单板或整机复位时,复位前的补丁将保持运行。
用户可以卸载运行补丁,从内存补丁区删除补丁。
。
设备安装补丁也是设备升级的一种方式。安装补丁有两种方法:
一般采用不中断业务的方式,在设备运行过程中直接加载运行补丁,这也是热补丁的优点。
另一种方法是指定系统下次启动的补丁文件,需要设备重启后才能生效。补丁文件通常用于设备升级和安装。
3.操作系统核热补丁原理:
Kpatch基于ftrace替换内核函数,似于ftrace的动态探测点。利用mcount机制,在内核编译时在每个函数入口保留数个字节,然后在打补丁时将“被替换函数”入口保留的字节替换为跳转指令,跳转到Kpatch的相关流程中,最终进入“新函数”的执行流程,实现函数级别的执行流程在线替换。具体而言如下图:
4, L inux 补丁模块加载机制
略
5, VxWorks 补丁模块加载机制
使用动态加载目标模块的方式有很多好处,比如可以在不破坏原来的环境下增加调试定位功能,相当于给系统打“补丁”,不需要编译原来的代码(甚至可以不用原来的代码)而只需要关注正在调试的代码,这样能减少编译时间和减少映像的加载量。
实现目标模块的动态加载有很多种方法,如在主机环境的界面上通过在目标模块上单击鼠标右键,选择“Download 文件名”;也可以通过wShell和GDB命令行窗口实现。本文通过tshell下使用ld()、loadModule()、loadModuleAt()中一个函数来实现,当然在代码中也可以自如地调用它们。
ld命令是由用户接口子程序库usrLib提供的一个加载命令。使用ld的前提是在config.h中定义INCLUDE_LOADER。这样,在usrRoot()函数中就会自动调用加载模块初始化函数moduleLibInit();同时,根据CPU类型,自动决定目标模块的格式。如果CPU是MIPS、PPC、ARM、I80X86、COLDFIRE、SIMSPARCSOLARIS、SH等,加载的目标模块格式是elf类型,就会调用loadElfInit();如果CPU是I960、AM29XXX等,加载的目标模块的格式则是coff类型,就会调用loadCoffInit()函数。
在ARM和PPC下,Tornado编译器生成的.o或.out都是elf类型,打开目标文件都会看到文件头有ELF(45,4C,46)标记。这时可以通过ftp工具把它加载到文件系统(如使用copy命令加载到RAM盘)中,再调用ld()或loadModule()函数加载到内存中运行。
ld的函数原型是:MODULE_ID ld( int syms, BOOL noAbort, char *name )。参数syms决定目标文件的符号怎么处理:0,添加全局符号到系统符号表中;1,添加全局和局部符号到系统符号表中;-1,符号不添加到系统符号表中。一般选1,便于在shell下使用其中的符号。参数noAbort表明是否可以忽略加载期间出现的错误,为TRUE则忽略,FALSE则不忽略。name则为加载的文件名,包含文件路径。注意:ld是一条shell命令,也就是它是为在shell下调用而设计的一个函数,所以尽量不要用在代码内部,因为在之后的vxworks版本中直接调用ld可能会不支持。
loadModule的函数原型是:MODULE_ID loadModule( int fd, int loadFlag )。fd为文件描述符号,需要先打开文件获取fd;参数loadFlag含义有LOAD_NO_SYMBOLS(2)、LOAD_LOCAL_SYMBOLS(4)、LOAD_GLOBAL_SYMBOLS(8)、LOAD_ALL_SYMBOLS(0xC)三种。
假如已经将需要加载的文件demo.o放到ram盘中,则加载到内存中的方式有以下几种:
(1)-> ld(1,0,”/tffs0/demo.o”)
(2)-> ld </tffs0/demo.o
(3)-> fd = open(“/tffs0/demo.o”,O_RDONLY);
-> loadModule(fd,0xC);
->close(fd);
加载函数返回的是MODULE_ID,这是该加载模块的标识,使用卸载unldByModuleId时可以模块ID。查看加载模块的具体信息的函数是moduleShow()。
对于某些应用,使用ld或loadModule会出现以下错误:
Relocation value does not fit in 24 bits.
ld error: error loading file (errno = 0x3d0001).
这个问题主要在内存空间大于32M,当往目标机上downloading编译好的模块时出现。
原因:
VxWorks和GNU/Wind River(diab)compiler要遵循EABI(Embedded Application Binary Interface),这是PowerPC架构的一个标准。这个标准对函数的调用是branching而不是jumping,而branching在 PowerPC架构下面是限制在32M以内的。(The PowerPC relative branch instruction is limited to jumps between +/- 32MB (24 bits = +/- 4M instructions, 4 bytes per instruction = +/- 32MB) of the current instruction. If an instruction cannot be resolved within a 24 bit range, it will print out the error above.)
一般来说,如果通过主机往目标机download,是不会出现这个问题的,因为这个时候的目标文件(比如.out文件)是download到了 WDB POOL里面,而WDB POOL是紧挨着VxWorks映像的,所以只要WDB POOL有足够大的空间就不会出现上述问题。
而通过目标机download(比如tshell)时,目标文件是download到系统内存池的最后,这个时候如果你的内存大于32M,就会出现上述问题。
为了规避这个问题,可以使用长跳转来完成函数的调用。可以通过在C/C++ compiler下加入编译选项解决。对于GNU,该选项是-mlongcall;对于diab,该选项是-Xcode-absolute-far。
当然也可以使用loadModuleAt()解决这个问题,将目标模块加载到指定地址,使所需要的函数在跳转地址以内。其原型是:MODULE_ID loadModuleAt( int fd, int loadFlag, void **ppText, void **ppData, void **ppBss)。ppText、ppData、ppBss分别是加载后的text段、data段和bss段所指向的地址。假如加载demo.o到1M空间处(保证这个空间没有被占用):
-> fd = open(“/tffs0/demo.o”,O_RDONLY);
-> pText = 0x100000; pData = pBss = 0xffffffff; /* (LD_NO_ADDRESS) */
-> loadModuleAt( fd, 8, &pText, &pData, &pBss);close(fd);
加载完成后,在shell下就可以执行最新的目标文件中的函数了。如果函数以前有的,按照最后加载的函数为准。可见,加载的目标模块都经过重定位、符号解析和添加的过程。
如果目标模块中使用的全局变量或函数在本模块和以前的程序中都没有定义,则加载时会出现类似undefined symbol的错误。
有时候从主机上下载的目标模块中的函数符号不会出现在目标机的shell中,反过来也一样,目标机的shell中生成的符号对主机wShell也不可见。出现这种情况的主要原因是目标机和主机的符号没有同步化,需要在编译映像时定义INCLUDE_SYM_TBL_SYNC.
6,实现细节
一,设备端补丁生效
第一种方案:
- kpatch 基于ftrace 实现内核函数的替换,类似于ftrace 的动态探测点,利用mcount 机制,在内核编译时在每个函数入口保留数个字节
- ftrace : function trace . 最早主要用于记录内核函数运行轨迹,随桌功能的逐渐增加, 演变成了一个跟踪框架。
- 当内核CONFIG_FUNCTION_TRACER 打开时,编译时会增加-pg 编译选项
- gcc 4.6 新增加了-pg -m fentry支持, 这样可以在函数的最开始插入一条调用fentry 的指令
- 补丁去除时,自动代码区全部恢复为空指令 nop
第二种方案:
- 使用传统方式,保存要打补丁函数的入口指令(一般为链表),改为跳转指令,添转到新函数入口
- 补丁去除时,从链表获取旧的函数的入口指令恢复
二 ,主机侧补丁制作:
- 头文件管理
- 补丁代码编写,拷贝代码到补丁制作文件夹
- 补丁源文件中增加补丁的构造函数,析构函数 用于模块 初始化和卸载
(可以完成任务创建,资源初始化等工作)
- 编译补丁代码,生成操作系统可加载的文件
- 打包加头补丁文件 ,携带版本,大小 ,crc校验,描述等信息
三, 配套版本补丁的管理:
补丁文件只能在指定的大包版本范围上生效。
四, 补丁命令行
- 设置下次启动生效的补丁文件
- 动态生效热补丁文件
- 显示补丁信息
- 卸载补丁
五, 补丁缺陷
- 数据结构,全局数据
- 函数参数
- 硬件初始化函数