Jump Label

从gcc 4.5开始,gcc 内嵌汇编开始支持一个叫jump label的东西。说白了,其实就是在gcc 内嵌汇编中支持外面C语言的goto label。不能访问外面C语言的goto label一直以来都是gcc内嵌汇编的一大缺陷。来自Red Hat 的 Richard Henderson向gcc社区提交了这个想法

最初的动机是因为内核要tracer需要这个东西,因为现在的tracer都是静态实现的,类似于:

[c]
if (unlikely(tracer_is_enabled))
trace();
[/c]

这样显然增加了一个额外的 if 开销,每次都要判断是否需要调用trace()。

而如果jump label 实现的话,那么我们就可以用汇编把需要调用trace()的那部分代码放到一个goto label之下,把这个goto label存放到一个单独的 section 里。而当启用或禁用某个tracer时,我们就可以修改里面的代码!换句话说,如果tracer没有启用,它里面放的就是nop指令,而如果tracer开启,那么我们就把一个jmp指令复制到那个位置,让它跳转到那个label从而去调用trace()!

下面就是用jump label来实现的这个功能的最重要的部分:
[c]

define JUMP_LABEL_INITIAL_NOP “.byte 0xe9 nt .long 0nt”

define JUMP_LABEL(key, label)

   do {                                                    
           asm goto("1:"                                   
                   JUMP_LABEL_INITIAL_NOP                  
                    ".pushsection __jump_table,  "a" nt"
                    _ASM_PTR "1b, %l[" #label "], %c0 nt" 
                    ".popsection nt"                      
                    : :  "i" (key) :  : label);            
    } while (0)

[/c]

所谓.pushsection和.popsection就是把当前的section也保存起来,然后建立一个新的section,名字就是在后面指定。可见,所有的label都是放在了__jump_table这个section里,格式固定如下:

[instruction address] [jump target] [tracepoint key]

先前代码的地址也是要保存的,因为jmp需要一个offset。更详细的介绍可以参考Jump Label的内核文档

要完全理解它的原理,你还需要读一下整个patch set,尤其是arch_jump_label_transform()的实现。代码不难理解,而且读起来很有意思。