linux内核的指令替换-alternative instruction

前言

内核的开发一直遵循向前兼容的特性,最新版本的内核还可以在最古老的机器上运行,有时候看不懂内核代码为什么这样写,可能是当时的处理器设备有什么特殊的限制。不过兼容性也带来了一些问题,老的处理器不能使用内核新的feature。
通常有两种方法能够配置feature:

1.可以在运行时动态选择;
2.可以在编译时通过配置的方式选择feature,但是这种方式编出来的内核可能不能在老机器上运行

用户程序例如java,python等有多个版本安装到系统上时,可以通过alternative的方式在系统运行时选择当前的版本。内核也有一个方法实现功能选择:alternative instruction可选指令替换,它可以在内核启动时候决定哪种指令方式是最优的。下面我会简称通用指令为原始指令,alternative instruction为优化指令。

实现

目前内核中实现指令替换的主要是smp和memory barrier,还有常用的内存操作:memset,memcpy,memmove,copy_page,copy_user等,下面以mb为例。

#define mb() asm volatile(ALTERNATIVE("lock; addl $0,0(%%esp)", "mfence", \                                                                                                              
                      X86_FEATURE_XMM2) ::: "memory", "cc") 

mb()保证了在它之后的代码执行前,它之前的所有内存读写都已经完成。默认的实现是一个锁总线+等待,它无论在x86哪种架构下都支持,不过在新的处理器上,新增了mfence指令支持,可以更好的支持这个操作。
ALTERNATIVE宏定义如下:

#define ALTERNATIVE(oldinstr, newinstr, feature)            \                                                                                                                                                      
    OLDINSTR(oldinstr, 1)                       \                                   
    ".pushsection .altinstructions,\"a\"\n"             \                           
    ALTINSTR_ENTRY(feature, 1)                  \                                   
    ".popsection\n"                         \                                       
    ".pushsection .altinstr_replacement, \"ax\"\n"          \                       
    ALTINSTR_REPLACEMENT(newinstr, feature, 1)          \                           
    ".popsection" 

将宏相关的代码单独拿出来,然后使用gcc -E对其进行展开,我把这段摘出来放在github上,简单格式化一下:

asm volatile("
661:
    lock; addl $0,0(%%esp)  //原始指令
662:
	//如果优化指令长度大于原始指令长度,使用nop在原始指令后面填充,最后原始指令>=优化指令长度
	.skip -(((6651f-6641f)-(662b-661b)) > 0) * ((6651f-6641f)-(662b-661b)),0x90 
663:
	.pushsection .altinstructions,"a"   //创建一个新的section,pushsection和popsection的内容放到该section中
	.long 661b - .  //中间整个是alt_instr对象,管理如何修改指令
	.long 6641f - .
	.word __stringify(X86_FEATURE_XMM2) 
	.byte 663b-661b
	.byte 6651f-6641f
	.byte 663b-662b
	.popsection
	.pushsection .altinstr_replacement, "ax"
6641:
    mfence                         //优化指令放在altinstr_replacement section中
6651:
    .popsection" ::: "memory", "cc");

struct alt_instr {                                                              
    s32 instr_offset;   /* original instruction */                              
    s32 repl_offset;    /* offset to replacement instruction*/                                                                                                                                                    
    u16 cpuid;      /* cpuid bit set for replacement */                         
    u8  instrlen;       /* length of original instruction */                    
    u8  replacementlen; /* length of new instruction */                         
    u8  padlen;     /* length of build-time padding */                          
} __packed; 

ALTERNATIVE宏会在链接阶段创建两个特殊的section .altinstructions和.altinstr_replacement,而且arch/x86/kernel/vmlinux.lds.S脚本将两个section顺序放在一起,所以可使用偏移来计算指令地址。
在内核启动的时候会调用apply_alternatives()开始修改指令,遍历.altinstructions节中的内容,判断当前cpu是否支持该特性来决定是否应该使用优化指令来覆盖原始指令,还有两个处理:

1.如果优化指令长度 > 原始指令长度,且不会覆盖,会尝试优化原始指令后面填充的nop指令
2.如果优化指令有相对跳转,对其跳转地址进行重新计算

这种方式对于发行版非常有利,能够发布一个通用的内核,在实际运行的时候自修改优化,例如在单核或SMP系统上它就能自己选择合适的指令。

SMP场景下的alternative

早期的alternative是Gerd的SMP patch,它封装alternative_smp()宏,其中需要提供feature存在时使用优化指令替换通用指令,在启动时选择合适的版本,它主要应用在spinlock的底层实现。

在i386上使用lock前缀指令,在原子操作时会进行锁总线动作,当它在多核系统上这样可以正确的运行,但是在单核上不需要锁总线,徒增消耗,这时候可以使用nops指令覆盖原始指令。当cpu发生热插拔时,进行单核<--->多核切换时可以动态替换指令,这种情况下在现在的虚拟技术中应该会有比较多的应用,虚拟技术可以动态的配置cpu和数量,这样动态替换的方式可以免除系统重启的问题。

1.记录lock修饰指令的位置,并且将相对偏移位置放到了.smp_locks section中,这样可以实现动态替换
2.在替换前,原始指令会被记录下来挂到smp_alt_modules中,在后面可以再恢复回来

结语

在进行hook的时候,如果我们恰好需要hook到这样的可选指令位置时,需要监控系统发生指令替换的事件。
这种方式提供了一种能够兼顾兼容性和速度的新方式,不过这种方式还是使用起来有些复杂。更多的启示在于:软件不仅是指C,java,汇编语言这种可读的源代码,配置文件,不仅是可执行文件还有内存中的二进制,凡是属于软件的都是可修改的,软件是软的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值