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()的实现。代码不难理解,而且读起来很有意思。