C语言中的内嵌汇编

C语言中的内嵌汇编语法解释

内嵌语法格式

1
2
3
4
5
6
asm(
内嵌汇编指令
:输出操作数
:输入操作数
:破坏描述
);

其中内嵌汇编指令是必不可少的,但可以为空。其他3部分根据程序需要可选。如果只有内嵌汇编指令时,后面的“:”可以省略。如:

1
2
3
4
5
6
7
asm(
"break"
);

__asm__(
"break"
);

asm是__asm__的别名,所以二者都是可以使用的,但是asm是gcc的扩展。为了保证可移植性,最好使用__asm__

关于:的省略问题,只有内嵌汇编指令,而后面都没有时,可以省略所有:。内嵌汇编程序中如果没有输出部分,但是有输入部分,那么输出部分的“:”不能省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
"pushl %1\n\t" /* push ebp */
"pushl %0\n\t" /* push task[pid].thread.ip */
"ret\n\t" /* pop task[pid].thread.ip to eip */
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);


asm("move $31,%0\n\t"
: /*此处的:不能省略*/
:"r"(a)
);
  1. 每条指令都以" "为单位。多条指令可以使用" ;"号、\n\t或者换行来分割。
  2. asm模板里面可以使用/**/或者//添加注释。
  3. 使用寄存器时,要在前面加上$
  4. "__volatile__"表示编译器不要优化代码,后面的指令保留原样,"volatile"是它的别名。括号里面是汇编指令。

输入操作数和输出操作数

内嵌汇编中的操作数包括输出操作数和输入操作数,输出操作数和输入操作数里的每一个操作数都由一个约束字符串和一个带括号的c语言表达式或变量组成,比如“r”(src)。多个操作数之间使用“,”分割。内嵌汇编指令中使用%num的形式依次表示每一个操作数,num从0开始。比如:

1
2
3
4
 asm("daddu %0,%1,%2\n\t"
:"=r"(ret) /* 输出操作数,也是第0个操作数%0 */
:"r"(a),"r"(b) /* 输入操作数,也是第1个操作数和第2个操作数 %1,%2 */
);

破坏描述部分

所谓的破坏描述就是告诉gcc,哪些寄存器是会在这个内联汇编中被改变。gcc以此做好防护,防止出现导致程序出错或致命异常。一般是寄存器的名字或者是memorycc. cc的作用是表示汇编代码修改标致寄存器.

其实就是告诉编译器我们在内联汇编中使用了哪些寄存器或内存

1
2
3
4
5
6
 asm("dadd %0,%1,%2\n\t"
"move $31,%0\n\t"
:"=g"(ret)
:"r"(a),"r"(b)
:"$31"
);

上述代码中,"%31"就是告诉gcc,$31遭到了破坏。

有名操作数和指定操作数

从gcc的3.1版本之后,内嵌汇编支持有名操作数。就是可以在内嵌汇编中为输入操作数、输出操作数取名字,名字形式是[name],放在每个操作数的前面,然后汇编程序模板里面就可以使用%[name]的形式,而不是上面%num形式。例如:

1
2
3
4
asm("daddu %[out],%[in1],%[in2]\n\t"
:[out]"=g"(ret)
:[in1]"r"(a),[in2]"r"(b)
);

当然我们也可以只给一个操作数命令

1
2
3
4
asm("daddu %[out],%1,%2\n\t"
:[out]"=g"(ret)
:"r"(a),"r"(b)
);

内联汇编的奇特用法

1
2
register int sys_id asm("$RAX") = 5001;
//将RAX寄存器中的值设置为5001

约束描述符

输出约束必须以“ = ”(覆盖现有值的变量)或“ + ”(读取和写入时)开头。

前面我们提到了=r +r。这些 = + 都是约束描述符。 约束字符就是输入操作数和输出操作数前面的修饰符。约束字符可以说明操作数是否可以在寄存器中,以及哪种寄存器;操作数是否可以是内存引用,以及哪种地址;操作数是否可以是立即常数,以及它可能具有的值。本节介绍常用的约束字符信息。

常用的约束描述符

  1. “b” 将输入变量放入ebx

  2. “c” 将输入变量放入ecx

  3. “d” 将输入变量放入edx

  4. “s” 将输入变量放入esi

  5. “d” 将输入变量放入edi

  6. “q” 将输入变量放入eax,ebx,ecx,edx中的一个

  7. “r” 将输入变量放入通用寄存器,也就是eax,ebx,ecx,
    edx,esi,edi中的一个

  8. “A” 把eax和edx合成一个64 位的寄存器(use long longs)

  9. 内存 “m” 内存变量

  10. “o” 操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址

  11. “V” 操作数为内存变量,但寻址方式不是偏移量类型

  12. “ “ 操作数为内存变量,但寻址方式为自动增量

  13. “p” 操作数是一个合法的内存地址(指针)

更多的选项可以去GCC官方手册上查找

参考资料

GCC官方手册

https://www.linuxprobe.com/gcc-how-to.html

http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html#s4

https://blog.csdn.net/weixin_38669561/article/details/105192200?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3.control

https://blog.csdn.net/yxc135/article/details/11537763


C语言中的内嵌汇编
https://ysc2.github.io/ysc2.github.io/2023/11/09/C语言中的内嵌汇编/
作者
Ysc
发布于
2023年11月9日
许可协议