C表达式中的汇编指令
C 表达式中的汇编指令
asm 为 gcc 中的关键字,asm 表达式为在 C代码中嵌套汇编指令,该表达式只是单纯的替换出汇编代码,并不对汇编代码的含义进行解析。
asm 表达式有两种形式,第二种 asm-qualifiers 包含了 goto 语句。
第一种形式为常见的用法,AssemblerTemplate 和 OutputOperands 必须存在, 其中 Clobbers 存在需要 InputOperands 也出现。
asm asm-qualifiers ( AssemblerTemplate
                 : OutputOperands
                 [ : InputOperands
                 [ : Clobbers ] ])
asm asm-qualifiers ( AssemblerTemplate
                      :
                      : InputOperands
                      : Clobbers
                      : GotoLabels)
Qualifiers 的类型
- volatile, 避免编译器的优化
- inline, 内敛限定符,最小的体积
- goto, 包含跳转指令
参数
- AssemblerTemplate
 - 汇编指令模板是包含汇编器指令的文字字符串,编辑器替换引用输入,编译器不会解析该指令的含义。
- OutputOperands
 - 由 AssemblerTemplate 中的指令修改的C变量的逗号分隔列表,允许使用空列表。
- InputOperands
 - 由 AssemblerTemplate 中的指令读取的C变量的逗号分隔列表,允许使用空列表。
- Clobbers
 - 用逗号分隔的寄存器列表或由 AssemblerTemplate 修改的值,不能出现在 OutputOperands 和 InputOperands 中被提及,允许使用空列表。
- GotoLabels
 - 当使用asm的goto形式时,此部分包含 AssemblerTemplate 中的代码可能跳转到的所有C标签的列表。
AssemblerTemplate
汇编指令由一个字符串给出,多条汇编指令结合在一起使用的时候,中间以 \r\t 隔开,如
asm("inc %0\n\tinc %0" : "=r"(res) : "0"(res));
/APP
# 11 "asm.c" 1
        inc %rax
        inc %rax
# 0 "" 2
/NO_APPs
需要转义的字符:%, =, {, }, |
故在ATT汇编中,对寄存器进行操作的需要双 %%, 如 inc %%rax.
OutputOperands
操作数之间用逗号分隔。 每个操作数具有以下格式:
[ [asmSymbolicName] ] constraint (cvariablename)
- asmSymbolicName
 - 为操作数指定名称,格式为%[name]
 c // res = num asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
 - 如果未指定名称使用数字, 从 output 域开始,第一个参数为 %0, 一次类推, 这里的 res 为 %0, num 为 %1
 c // res = num asm("movq %1, %0" : "=r"(res) : "m"(num));
- constraint
 - 一个字符串常量,用于指定对操作数的存储的 约束, 需要以 "=" 或 "+" 开头
- cvariablename
 - 指定一个C左值表达式来保存输出,通常是一个变量名。 括号是语法的必需部分
第一个参数为增加可读性使用的,现在我们有代码如下
int64_t res;
int64_t num = 1;
asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
asm("movq %1, %0" : "=r"(res) : "m"(num));
asm("movq %1, %0" : "=m"(res) : "m"(num));
asm("movq %1, %0" : "=r"(res) : "r"(num));
// 对应的汇编代码, 只保留asm表达式中的代码
# 13 "asm.c" 1
        movq -16(%rbp), %rax  // asm-1
 # 0 "" 2
/NO_APP
/APP
 # 15 "asm.c" 1
        movq -16(%rbp), %rax  // asm-2
 # 0 "" 2
/NO_APP
/APP
 # 17 "asm.c" 1
        movq -16(%rbp), -8(%rbp)  // asm-3
 # 0 "" 2
/NO_APP
/APP
 # 19 "asm.c" 1
        movq %rax, %rax  // asm-4
 # 0 "" 2
/NO_APP
- 使用名称替换和数字替换效果一样,见 asm-1 和 asm-2
- 约束的用法,这里使用比较简单通用的的两种情况,r为通过寄存器寻址操作,m通过内存寻址操作,所以看到当约束了r就对应寄存器的操作。
- 结果保存在 res 也就是 cvariablename 中
InputOperands
输入操作数使C变量和表达式中的值可用于汇编代码。
[ [asmSymbolicName] ] constraint (cexpression)
- asmSymbolicName 和输出列表的用法完全一致
- constraint 约束不能使用 =和+. 可以使用 "0", 这表明在输出约束列表中(从零开始)的条目,指定的输入必须与输出约束位于同一位置。
int64_t res = 3;
int64_t num = 1;
asm("addq %1, %0" : "=g"(res) : "0"(num));
// 输入输出位置相同
        movq	$3, -8(%rbp)
        movq	$1, -16(%rbp)
        movq	-16(%rbp), %rax
/APP
# 32 "asm.c" 1
        addq %rax, %rax
# 0 "" 2
/NO_APP
- cexpression 可以不为左值,作为汇编表达式的输入值即可
Clobbers
破坏列表,主要用于指示编译器生成的汇编指令。
从asm表达式中看到输出操作数中列出条目的更改编译器是可以确定的,但内联汇编代码可能不仅对输出进行了修改。 例如,计算可能需要其他寄存器,或者处理器可能会由于特定汇编程序指令而破坏寄存器的值。 为了将这些更改通知编译器,在Clobber列表中列出这些会产生副作用的条目。 破坏列表条目可以是寄存器名称,也可以是特殊的破坏列表项(在下面列出)。 每个内容列表条目都是一个字符串常量,用双引号引起来并用逗号分隔。
- 寄存器 - ```c
 asm volatile("movc3 %0, %1, %2"
 : /* No outputs. */
 : "r"(from), "r"(to), "g"(count)
 : "%rbx", "%rcx", "%rdx", "memory"); /APP
 # 25 "asm.c" 1
 movc3 %rax, %r8, -72(%rbp)
 # 0 "" 2
 /NO_APP
 ``` 可以看到使用到了 rax 寄存器,然后修改程序在 Clobbers 增加 %rax, 结果如下 ```c
 asm volatile("movc3 %0, %1, %2"
 : /* No outputs. */
 : "r"(from), "r"(to), "g"(count)
 : "%rax", "%rbx", "%rcx", "%rdx", "memory"); /APP
 # 25 "asm.c" 1
 movc3 %r8, %r9, -72(%rbp)
 # 0 "" 2
 /NO_APP
 ```
 编译器在产生的汇编代码中就未使用 %rax 寄存器了。
 
- 特殊破坏列表项 
 - "cc", 表示汇编代码修改了标志寄存器
 - "memory", 为了确保内存中包含正确的值,编译器可能需要在执行asm之前将特定的寄存器值刷新到内存中
编译器为了破坏列表项的值受到破坏,当这些条目是寄存器时,不对其进行使用;为特殊参数时,重新刷新得到最新的值。
约束
- 一些基础的约束
| 约束名 | 说明 | 
|---|---|
| whitespace | 空白字符被忽略 | 
| m | 允许使用内存操作数,以及机器通常支持的任何类型的地址 | 
| o | 允许使用内存操作数,但前提是地址是可偏移的 | 
| V | 允许使用内存操作数,不可偏移的内存地址,与 "o'互斥 | 
| r | 允许在通用寄存器中使用的寄存器操作数,其中可以指定寄存器,如 a(%rax), b(%rbx) | 
| i | 允许使用立即整数操作数 | 
| n | 允许使用具有已知数值的立即整数操作数, ‘I’, ‘J’, ‘K’, … ‘P’ 更应该使用 n | 
| F | 允许使用浮点立即数 | 
| g | 允许使用任何寄存器,内存或立即数整数操作数,但非通用寄存器除外 | 
| X | 允许任何操作数, ‘0’, ‘1’, ‘2’, … ‘9’ | 
| p | 允许使用有效内存地址的操作数 | 
- 标识符约束
| 标识符 | 说明 | 
|---|---|
| = | 表示此操作数是由该指令写入的:先前的值将被丢弃并由新数据替换 | 
| + | 表示该操作数由指令读取和写入 | 
| & | 表示(在特定替代方法中)此操作数是早期指令操作数,它是在使用输入操作数完成指令之前写入的,故输入操作数部分不能分配与输出操作数相同的寄存器 | 
| % | 表示该操作数与后续操作数的可交换指令 | 
内核示例
- x86 的内存屏障指令。
// 避免编译器的优化,声明此处内存可能发生破坏
#define barrier() asm volatile("" ::: "memory")
// 在32位的CPU下,lock 指令为锁总线,加上一条内存操作指令就达到了内存屏障的作用,64位的cpu已经有新增的 *fence 指令可以使用
// mb() 执行一个内存屏障作用的指令,为指定CPU操作;破坏列表声明 cc memory 指示避免编译器进行优化
#ifdef CONFIG_X86_32
#define mb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "mfence", \
                                X86_FEATURE_XMM2) ::: "memory", "cc")
#define rmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "lfence", \
                                X86_FEATURE_XMM2) ::: "memory", "cc")
#define wmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "sfence", \
                                X86_FEATURE_XMM2) ::: "memory", "cc")
#else
#define mb() 	asm volatile("mfence":::"memory")
#define rmb()	asm volatile("lfence":::"memory")
#define wmb()	asm volatile("sfence" ::: "memory")
#endif
- x86 下获取 current 的值
DECLARE_PER_CPU(struct task_struct *, current_task);
#define this_cpu_read_stable(var)	percpu_stable_op("mov", var)
static __always_inline struct task_struct *get_current(void)
{
        return this_cpu_read_stable(current_task);
}
#define percpu_stable_op(op, var)			\
({							\
        typeof(var) pfo_ret__;				\
        switch (sizeof(var)) {				\
        case 8:						\
                asm(op "q "__percpu_arg(P1)",%0"	\
                : "=r" (pfo_ret__)			\
                : "p" (&(var)));			\
                break;					\
        }						\
        pfo_ret__;					\
})
current_task 为一个 struct task_struct 类型的指针,追踪宏调用,在x86-64 下命中了 case 8: 的汇编代码, 展开的代码为
asm("mov" "q ""%%""gs" ":" "%" "P1"",%0" : "=r" (pfo_ret__) : "p" (&(current_task)));
// 变换一下为
asm("movq %%gs:%P1, %0" : "=r"(pfo_ret__) : "p"(&(current_task)));
这行代码的含义为将 约束输入部分必须为有效的地址(p约束), 将CPU id(通过段寄存器gs和偏移通过GDT得到,这里后文分析了)通过寄存器(r约束)赋值给 pfo_ret__.
参考
C表达式中的汇编指令的更多相关文章
- C 表达式中的汇编指令
		asm 为 gcc 中的关键字,asm 表达式为在 C代码中嵌套汇编指令,该表达式只是单纯的替换出汇编代码,并不对汇编代码的含义进行解析. asm 表达式有两种形式,第二种 asm-qualifier ... 
- [zhuan]arm中的汇编指令
		http://blog.csdn.net/qqliyunpeng/article/details/45116615 一. 带点的(一般都是ARM GNU伪汇编指令) 1. ".text& ... 
- ARM中的---汇编指令
		一. 带点的(一般都是ARM GNU伪汇编指令) 1. ".text".".data".".bss" 依次表示的是"以下是代码段& ... 
- C/C++中书写汇编指令
		汇编语言的指令格式目前有两种不同的标准:Windows下的汇编语言基本上都遵循Intel风格的语法,比如:MASM.NASM,Unix/Linux下的汇编语言基本上都遵循AT&T风格的语法. ... 
- ARM中的汇编指令
		Arm指令,32位的指令集,一共有16条的基本指令,每条指令都可以按条件执行, 指令都是32bit的,高四位是条件码[31:28], Thumb指令,16位的指令集,执行效率比arm指令集要低,但是节 ... 
- Uboot中汇编指令
		LDR(load register)指令将内存内容加载入通用寄存器 ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令.比如想把数据从内存中某处读取到 ... 
- 计算机系统6-> 计组与体系结构3 | MIPS指令集(中)| MIPS汇编指令与机器表示
		上一篇计算机系统5-> 计组与体系结构2 | MIPS指令集(上)| 指令系统从顶层讲解了一个指令集 / 指令系统应当具备哪些特征和工作原理.这一篇就聚焦MIPS指令集(MIPS32),看看其汇 ... 
- C语言中插入汇编nop指令
		工作过程中,有的时候需要打桩cycle,想在C语言中插入nop指令,可以采取的方法是 头文件中加入#inlude <stdio.h> 定义一个内联函数,然后调用这个函数,不过得测一下平台调 ... 
- 学习linux内核时常碰到的汇编指令(2)
		转载:http://blog.sina.com.cn/s/blog_4be6adec01007xvh.html JNGE∶指令助记符——(有符号数比较)不大于且不等于转移(等价于JL).当SF和OF异 ... 
随机推荐
- 第三方登录之GitHub篇
			第一步,准备工作.获取Client ID和Client Secret 1.自行登陆GitHub官网,点击Setting,如下图: 2.继续,点击Developer settings,如下图: 3.继续 ... 
- CSS3属性—— line-clamp控制文本行数
			说明: 限制在一个块元素显示的文本的行数. -webkit-line-clamp 是一个 不规范的属性(unsupported WebKit property),它没有出现在 CSS 规范草案中. 为 ... 
- markdown下载、安装、破解、汉化与常用语法
			markdown是一种纯文本格式的标记语言.通过简单的标记语法,它可以使普通文本内容具有一定的格式. 一.markdown下载 markdown破解版下载地址: http://www.pc6.com/ ... 
- homebrew安装问题(Failed during: git fetch origin master:refs/remotes/origin/master --tags --force)
			在mac系统中,使用homebrew可以很方便的管理包.按照官网的说明执行以下命令时总是报错: /usr/bin/ruby -e "$(curl -fsSL https://raw.gith ... 
- Jenkins构建Jmeter项目
			1.启动jenkins 2.新建自由风格的项目 定时任务 构建操作 安装HTML Publisher插件 构建后操作 最后保存构建,查看报告 
- 小白学 Python(4):变量基础操作
			人生苦短,我选Python 引言 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 前面的文章中,我们介绍了 ... 
- PMP 德尔菲技术
			1.德尔菲技术,必须遵守以下几个规则: 每个专家只与主持人单线联系. 专家之间完全背靠背,更不能进行讨论.为保证专家提出独立见解,甚至需要把专家分散在不同的物理地点. 专家以匿名的书面形式提出意见. ... 
- Cocos2d-x 学习笔记(12) Speed Follow
			Speed Follow都是直接继承了Action. Speed对其他action进行包装,改变action的速度. Follow可用于node在scene中的运动,scene将node作为Follo ... 
- vue —— Toast 内 加变量
			toast正常使用: 在页面内引入: import { Toast } from 'mint-ui' 使用的时候,简单到飞起: Toast('领取成功'); 而如果想在toast中加入变量,也很简单: ... 
- 百万年薪python之路   --  模拟三次账号登录锁定功能
			用代码实现三次用户登录及锁定(选做,时间充足建议做一做) 项目分析: 一.首先程序启动,显示下面内容供用户选择: 1.注册 2.登录 a.用户选择登录的时候,首先判断用户名在userinfo.txt表 ... 
