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
  1. 使用名称替换和数字替换效果一样,见 asm-1 和 asm-2约束的用法,这里使用比较简单通用的的两种情况,r 为通过寄存器寻址操作,m 通过内存寻址操作,所以看到当
  2. 约束了 r 就对应寄存器的操作。
  3. 结果保存在 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
 ```

特殊破坏列表项

- "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__.

参考

GCC文档
C语言ASM汇编内嵌语法zz

总结

以上所述是小编给大家介绍的C 表达式中的汇编指令,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • 汇编语言lea指令使用方法解析

    这篇文章主要介绍了汇编语言lea指令使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 lea指令变种(按大小分类): leaw #2个字节 leal #4个字节 leaq #8个字节 lea的用法: leaq a(b, c, d), %rax 首先lea指令是mov指令的变种,据说,lea指令是x86体系结构中,是一条最古老但是从某个方面来讲又是最神奇的指令. 表面上看,它做的事情非常简单,根据括号里的源操作数来计算地址,然后把地址加

  • 常用的汇编指令与技巧(收藏)

    1.数据传送指令:mov move r1,r2 /*r1=r2*/ move r1,#4096 /*r1=4096*/ 2.大范围的地址读取指令:ldr ldr r1,=0x123456789 /*r1=0x123456789*/ ldr r1,=label /*获取绝对地址,即label的地址*/ label: -- 3.内存访问指令(当ldr后面没有=号时为内存读取指令) 读取指令:ldr ldr r1 ,[r2,#4] /*将内存地址为r2+4的数据读取到r1中,相当于C语言中的*操作*/

  • 汇编语言 and和or逻辑运算指令的实现

    本文介绍了汇编语言 and,or逻辑运算指令的实现,分享给大家 assume cs:code code segment ; 逻辑运算 按照二进制位运算 start: mov ax,0 mov al, 00001111B and al, 11110000B ; 00000000B mov ax,0 mov al, 00001111B or al, 11110000B ; 11111111B mov ax,4c00H int 21H code ends end start 通过and,or逻辑运算指

  • 汇编语言无效指令错误概述

    问题:cmpxchg8b 指令比较一个8字节值edx和EAX带有8字节值内存( 目的地操作数). 只有有效目标操作数用于此指令是内存 操作数. 如果目标操作数是一个寄存器处理器应产生一个 无效OpCode例外,执行的指令cmpxchg8b应当停止和 处理器应该执行无效OpCode异常处理程序. 此错误发生是 锁定前缀为使用cmpxchg8b指令与一个(无效)寄存器目的地 操作数. 在这种情况下,处理器可能无法启动执行无效OpCode 异常处理程序,因为总线已锁定. 这将导致系统挂起. 提示:如果

  • 汇编语言中mov和lea指令的区别详解

    指令(instruction)是一种语句,它在程序汇编编译时变得可执行.汇编器将指令翻译为机器语言字节,并且在运行时由 CPU 加载和执行. 一条指令有四个组成部分: 标号(可选) 指令助记符(必需) 操作数(通常是必需的) 注释(可选) 最近在学习汇编语言,过程中遇到很多问题,对此在以后的随笔会逐渐更新,这次谈谈mov,lea指令的区别   一,关于有没有加上[]的问题 1,对于mov指令来说: 有没有[]对于变量是无所谓的,其结果都是取值 如: num dw 2 mov bx,num mov

  • 16位汇编语言寄存器及指令整理(小结)

    寄存器 通用寄存器 8位寄存器 16位寄存器 32位寄存器 寄存器名称 AH,AL AX EAX 累加寄存器 BH,BL BX EBX 基地址寄存器 CH,CL CX ECX 计数器寄存器 DH,DL DX EDX 数据寄存器 SI ESI 源变址寄存器 DI EDI 源目标寄存器 DH,DL DX EDX 基地址寄存器 DH,DL DX EDX 栈顶寄存器 段寄存器 标号 名称 CS 代码段 DS 数据段 SS 栈段 ES 附加数据段 GS,FS 附加数据段(+80386) 专用寄存器 标号

  • C 表达式中的汇编指令

    asm 为 gcc 中的关键字,asm 表达式为在 C代码中嵌套汇编指令,该表达式只是单纯的替换出汇编代码,并不对汇编代码的含义进行解析. asm 表达式有两种形式,第二种 asm-qualifiers 包含了 goto 语句. 第一种形式为常见的用法,AssemblerTemplate 和 OutputOperands 必须存在, 其中 Clobbers 存在需要 InputOperands 也出现. asm asm-qualifiers ( AssemblerTemplate : Outpu

  • 基于angular中的重要指令详解($eval,$parse和$compile)

    在angular的服务中,有一些服务你不得不去了解,因为他可以说是ng的核心,而今天,我要介绍的就是ng的两个核心服务,$parse和$compile.其实这两个服务讲的人已经很多了,但是100个读者就有100个哈姆雷特,我在这里讲讲自己对于他们两个服务的理解. 大家可能会疑问,$eval呢,其实他并不是一个服务,他是scope里面的一个方法,并不能算服务,而且它也基于parse的,所以只能算是$parse的另一种写法而已,我们看一下ng源码中$eval的定义是怎样的就知道了 $eval: fu

  • iOS逆向工程之Hopper中的ARM指令详解

    虽然前段时间ARM被日本软银收购了,但是科技是无国界的,所以呢ARM相关知识该学的学.现在看ARM指令集还是倍感亲切的,毕竟大学里开了ARM这门课,并且做了不少的实验,当时自我感觉ARM这门课学的还是可以的.虽然当时感觉学这门课以后似乎不怎么用的上,可曾想这不就用上了吗,不过之前学的都差不多忘了,还得捡起来呢.ARM指令集是精简指令集,从名字我们就能看出指令的个数比那些负责指令集要少一些.当然本篇所涉及的ARM指令集是冰山一角,不过也算是基础,可以阅读Hopper中的汇编了,实践出真知,看多了自

  • Vue.js 中的 v-show 指令及用法详解

    1 用法 v-show 指令通过改变元素的 css 属性(display)来决定元素是显示还是隐藏. html: <div id="app"> <p v-show="type==='科技'">大数据之下的锦鲤:为什么你的微博总抽不到奖</p> </div> js: <script> var app = new Vue({ el: '#app', data: { type:'技术' } }); </sc

  • Vue插值、表达式、分隔符、指令知识小结

    最近打算重温一遍vue的知识,正好总结一份笔记. 插值 基本用法 <span>Text:{{text}}</span> <span v-text="text"></span> 这个属于指令 <li data-id="{{id}}"></li> 有时候只需要渲染一次数据,后续数据变化不再关心,可以通过"*"实现 <span>Text:{{*text}}</sp

  • 易语言代码中嵌入汇编/机器码方法

    很多朋友在编写易语言里要混合插入汇编语言等,如何解决呢?我们来看下 我们都知道,C++或Delphi的程序源代码中可以嵌入汇编代码,以达到某些特定目的.易语言作为实用而又功能强大的编程语言,也允许在代码中嵌入汇编,--当然严格来说,是嵌入机器指令代码.借助"特殊功能支持库"中的"置入代码"命令,可以完成这项功能.(liigo 2009.03.20补记:自易语言4.12版本起,"置入代码"已被移入核心支持库.) 为什么要在易语言中"置入代

  • 详解VueJs中的V-bind指令

    引子 v-bind  主要用于属性绑定,Vue官方提供了一个简写方式 :bind,例如: <!-- 完整语法 --> <a v-bind:href="url"></a> <!-- 缩写 --> <a :href="url"></a>   一.概述 v-bind  主要用于属性绑定,比方你的class属性,style属性,value属性,href属性等等,只要是属性,就可以用v-bind指令进行绑

  • 汇编语言伪指令和汇编指令的区别

    [指令语句] 每一条指令语句在源程序汇编时都要产生可供计算机执行的指令代码(即目标代码),所以这种语句又叫可执行语句.每一条指令语句表示计算机具有的一个基本能力,如数据传送,两数相加或相减,移位等,而这种能力是在目标程序(指令代码的有序集合)运行时完成的,是依赖于汁算机内的中央处理器(CPU).存储器.I/O接口等硬件设备来实现的. [伪指令语句] 伪指令语句是用于指示汇编程序如何汇编源程序,所以这种语句又叫命令语句.例如源程序中的伪指令语句告诉汇编程序:该源程序如何分段,有哪些逻辑段在程序段中

  • iOS汇编入门教程之在Xcode工程中嵌入汇编代码的方法

    简介 上一篇文章ARM64汇编基础中介绍了汇编在iOS开发中的应用以及ARM汇编基础知识,本文将介绍在C或Objective-C构成的工程中如何嵌入汇编代码. 注意 在调试ARM汇编时,Xcode的Build对象必须为真机,如果对象为模拟器则是x86汇编. 内联汇编 汇编与C间接通信 在函数中可以直接插入汇编代码来影响函数的运行逻辑,使用的语法为编译指令 __asm__ ,注意插入汇编有可能会被编译器忽略,因此需要加入 __volatile__ 修饰符保证汇编代码有效. 下面给出一个简单的例子,

随机推荐