【源码阅读】预备知识总结:GNU内联汇编

背景

Linux内核代码、JVM源码中有很多关于内联汇编的操作,如果想阅读相关代码,比如JVM的Code Stub Generator,学习内联汇编是不可或缺的一环,在此进行总结一番。

汇编语法

语法特性intelAT&T/UNIX
寄存器命名eax%%eax(需要加’%%'前缀)
操作数顺序op ,op ,
立即数格式123, _C_static_variable$123, $_C_static_variable(添加’$'前缀)
操作数大小指定mov bx, axmovw %ax,%bx(添加b(byte)、w(word)、l(longword)后缀)
引用内存[base + index*scale + immed32]immed32(base,index,scale)
引用变量值_var(加’_'前缀)$_var(加’$_'前缀)
引用变量地址[_var]_var
引用寄存器地址[eax](%eax)
引用var变量为基址的偏移[eax + _var]_var(%eax)
引用int数组arr的某个元素[eax * 4 + _arr]_arr(, %eax, 4)

内联语法

为什么需要内联?能获得可预知大小的代码

asm <关键字> (汇编代码
            : 输出操作数
            : 输入操作数
            : Clobbers)
asm <关键字> (汇编代码
            :
            : 输入操作数
            : Clobbers
            : Goto C Labels)
  • 关键字

输入操作数、输出操作数、跳转Label列表的操作数总数不能超过30个

volatile:阻止代码优化

inline:内联代码,将会对代码进行压缩

goto:告知编译器代码中将会对列表中的某个Label进行跳转

  • 汇编代码
    GAS汇编

  • 输出操作数
    用于接收汇编代码计算结果

  • 输入操作数
    输入变量值,被汇编代码引用计算

  • Clobbers
    用于告知编译器汇编代码隐式的副作用

  • Goto C Label
    C语言定义的Label列表

关键字

  • asm或者__asm__
    定义一段内联汇编代码。

如果使用ANSI C编写代码,必须使用__asm__关键字

  • volatile或者__volatile__

参考《汇编语言程序设计》p296

在一般的C或者C++程序中,编译器通常会尝试通过下面方式来对代码进行优化,以提高程序性能:
1、消除不使用的函数
2、在不同时使用的值之间共享寄存器
3、重新编排代码

volatile会抑制GCC的上述的代码优化。

如果我们修改了不在输入、输出操作数列表中的内存,我们必须添加在asm后添加volatile关键字。

assembler template

1、存在多行汇编指令时,使用\n\t分隔,例如:

__asm__ ("movl %eax, %ebx\n\t"
        "movl %eax, %edx")

输出操作数

<[可选名称]> 约束 (C表达式)
  • 可选名称
    为操作数指定一个名称,可以通过%[名称]进行引用,如果不指定,则通过序号进行访问
__asm__ __volatile__ ("movw %%dx,%%ax\n\t"
    "movw %4,%%dx\n\t"
    "movl %%eax,%[gate_addr_1]\n\t"
    "movl %%edx,%1"
    :[gate_addr_1]"=m" (*((long *) (gate_addr))),
     "=m" (*(1+(long *) (gate_addr))),
     "=&a" (__d0),
     "=&d" (__d1)
    :"i" ((short) (0x8000+(14<<13)+(0<<8))),
     "3" ((char *) (addr)),"2" (__KERNEL_CS << 16),
     "d" (1)
     );

其中定义了个gate_addr_1的输出操作数,在汇编中可以通过%[gate_addr_1]进行引用

  • 约束
    必须以’+‘或者’='作为前缀
    ‘+’:可读写,操作数会在输入操作数完成读取之前进行写入;不能被输入操作数绑定
__asm__ __volatile__ ("movw %%dx,%%ax\n\t"
    "movw %4,%%dx\n\t"
    "movl %%eax,%[gate_addr_1]\n\t"
    "movl %%edx,%1"
    :[gate_addr_1]"=m" (*((long *) (gate_addr))),
     "=m" (*(1+(long *) (gate_addr))),
     "+a" (__d0),
     "+d" (__d1)
    :"i" ((short) (0x8000+(14<<13)+(0<<8))),
     "3" ((char *) (addr)),"2" (__KERNEL_CS << 16),
     "d" (1)
     );

输出操作数中,ax和dx寄存器使用’+‘进行约束,由于在输入操作数中又进行了操作数的绑定,这会报错。这是因为当使用’+'约束时,输入输出操作数是绑定在同一个C变量上的,当尝试进行绑定时,就发生了冲突

‘=’:只写、覆盖

  • C表达式
    C变量,必须是一个左操作数

1、’&‘约束表示寄存器不能与输入操作数重复;但可以进行操作数绑定
2、’+‘约束表示变量可能会被重写
3、’='约束表示可以进行读写,和输入操作数关联输出操作数语义类似

输入操作数

模版:

<[可选名称]> 约束 (C表达式)
  • 可选名称
    为操作数指定一个名称,可以通过%[名称]进行引用,如果不指定,则通过序号进行访问

  • 约束
    不以’+’(表示变量会被重写)或者’=’(表示)开头

  • C表达式
    右值表达式

1、不要修改只作为输入操作数的内容。一般通过将输入操作数绑定到输出操作数上
2、有且只有输入操作数可以在约束中使用数字,该数字必须是输出操作数的序号,以表示对该输出操作数的引用。(也可通过可选名称对输出操作数进行引用)

Clobbered Registers

示例:

"cc","r1","r2","cc","memory"

1、不能跟输入和输出操作数重复
2、新版GCC(10+)不支持栈寄存器,这是因为编译器要求栈指针在汇编指令执行前和执行后必须保持一致
3、"cc"表示条件寄存器的修改
4、"memory"告知编译器汇编代码会进行除了输入输出操作数之外的内存的读写,比如访问入参变量的值作为地址的内存
6、Clobber不会阻止通过指令进行的寄存器的预读,如有需要,必须通过处理器的屏障指令进行阻止。

跳转label

asm goto (汇编代码
        :
        : 输入操作数
        : Clobbers
        : Goto C Labels)

1、不能包含输出操作数
2、如果汇编代码发生修改,需要通过"memory" Clobber刷新内存
3、asm goto本身包含volatile的语义
4、引用标号:%l[<LABEL名称>]

    __asm__ goto (
        "cmp $1,$1\n\t"
        "jz %l[LABEL]"
        :
        :
        :
        : LABEL
    );
    ...
    LABEL:
    printf("jmp");

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子的木木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值