做个心里有光的孩子 發表於 2020-1-18 14:47:59

C 表达式中的汇编指令

<p>asm 为 gcc 中的关键字,asm 表达式为在 C代码中嵌套汇编指令,该表达式只是单纯的替换出汇编代码,并不对汇编代码的含义进行解析。</p>
<p>asm 表达式有两种形式,第二种 <code>asm-qualifiers</code> 包含了 <code>goto</code> 语句。<br />
第一种形式为常见的用法,AssemblerTemplate 和 OutputOperands 必须存在, 其中 Clobbers 存在需要 InputOperands 也出现。</p>
<div class="jb51code">
<pre class="brush:plain;">
asm asm-qualifiers ( AssemblerTemplate
   : OutputOperands
   [ : InputOperands
   [ : Clobbers ] ])

asm asm-qualifiers ( AssemblerTemplate
      :
      : InputOperands
      : Clobbers
      : GotoLabels)</pre>
</div>
<p>Qualifiers 的类型</p>
<p>volatile, 避免编译器的优化inline, 内敛限定符,最小的体积goto, 包含跳转指令</p>
<p>参数</p>
<p>AssemblerTemplate<br />
- 汇编指令模板是包含汇编器指令的文字字符串,编辑器替换引用输入,编译器不会解析该指令的含义。OutputOperands<br />
- 由 AssemblerTemplate 中的指令修改的C变量的逗号分隔列表,允许使用空列表。InputOperands<br />
- 由 AssemblerTemplate 中的指令读取的C变量的逗号分隔列表,允许使用空列表。Clobbers<br />
- 用逗号分隔的寄存器列表或由 AssemblerTemplate 修改的值,不能出现在 OutputOperands 和 InputOperands 中被提及,允许使用空列表。GotoLabels<br />
- 当使用asm的goto形式时,此部分包含 AssemblerTemplate 中的代码可能跳转到的所有C标签的列表。</p>
<p>AssemblerTemplate</p>
<p>汇编指令由一个字符串给出,多条汇编指令结合在一起使用的时候,中间以 <code>\r\t</code> 隔开,如</p>
<div class="jb51code">
<pre class="brush:plain;">
asm("inc %0\n\tinc %0" : "=r"(res) : "0"(res));

/APP
# 11 "asm.c" 1
inc %rax
inc %rax
# 0 "" 2
/NO_APPs</pre>
</div>
<p>需要转义的字符:<code>%</code>, <code>=</code>, <code>{</code>, <code>}</code>, <code>|</code></p>
<p>故在ATT汇编中,对寄存器进行操作的需要双 %%, 如 <code>inc %%rax</code>.</p>
<p>OutputOperands</p>
<p>操作数之间用逗号分隔。 每个操作数具有以下格式:</p>
<div class="jb51code">
<pre class="brush:plain;">
[ ] constraint (cvariablename)</pre>
</div>
<p></p>
<p>asmSymbolicName<br />
- 为操作数指定名称,格式为 <code>%</code><br />
<code>c // res = num asm("movq %, %" : "=r"(res) : "m"(num));</code><br />
- 如果未指定名称使用数字, 从 output 域开始,第一个参数为 %0, 一次类推, 这里的 res 为 %0, num 为 %1<br />
<code>c // res = num asm("movq %1, %0" : "=r"(res) : "m"(num));</code>constraint<br />
- 一个字符串常量,用于指定对操作数的存储的 约束, 需要以 "=" 或 "+" 开头cvariablename<br />
- 指定一个C左值表达式来保存输出,通常是一个变量名。 括号是语法的必需部分</p>
<p>第一个参数为增加可读性使用的,现在我们有代码如下</p>
<div class="jb51code">
<pre class="brush:plain;">
int64_t res;
int64_t num = 1;

asm("movq %, %" : "=r"(res) : "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</pre>
</div>
<ol>
<li>使用名称替换和数字替换效果一样,见 asm-1 和 asm-2约束的用法,这里使用比较简单通用的的两种情况,<code>r</code> 为通过寄存器寻址操作,<code>m</code> 通过内存寻址操作,所以看到当</li>
<li>约束了 <code>r</code> 就对应寄存器的操作。</li>
<li>结果保存在 res 也就是 cvariablename 中</li>
</ol>
<p>InputOperands</p>
<p>输入操作数使C变量和表达式中的值可用于汇编代码。</p>
<div class="jb51code">
<pre class="brush:plain;">
[ ] constraint (cexpression)</pre>
</div>
<p>asmSymbolicName 和输出列表的用法完全一致</p>
<p>constraint 约束不能使用 <code>=</code> 和 <code>+</code>. 可以使用 "0", 这表明在输出约束列表中(从零开始)的条目,指定的输入必须与输出约束位于同一位置。</p>
<div class="jb51code">
<pre class="brush:plain;">
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</pre>
</div>
<ul>
<li>cexpression 可以不为左值,作为汇编表达式的输入值即可</li>
<li>Clobbers</li>
</ul>
<p>破坏列表,主要用于指示编译器生成的汇编指令。</p>
<p>从asm表达式中看到输出操作数中列出条目的更改编译器是可以确定的,但内联汇编代码可能不仅对输出进行了修改。 例如,计算可能需要其他寄存器,或者处理器可能会由于特定汇编程序指令而破坏寄存器的值。 为了将这些更改通知编译器,在Clobber列表中列出这些会产生副作用的条目。 破坏列表条目可以是寄存器名称,也可以是特殊的破坏列表项(在下面列出)。 每个内容列表条目都是一个字符串常量,用双引号引起来并用逗号分隔。</p>
<p>寄存器</p>
<div class="jb51code">
<pre class="brush:plain;">
```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
```</pre>
</div>
<p>特殊破坏列表项<br />
</p>
<p>- "cc", 表示汇编代码修改了标志寄存器<br />
- "memory", 为了确保内存中包含正确的值,编译器可能需要在执行asm之前将特定的寄存器值刷新到内存中</p>
<p>编译器为了破坏列表项的值受到破坏,当这些条目是寄存器时,不对其进行使用;为特殊参数时,重新刷新得到最新的值。</p>
<p>约束</p>
<p>一些基础的约束</p>
<p>
<table>
<thead>
    <tr>
      <th>约束名</th>
      <th>说明</th>
    </tr>
</thead>
<tbody>
    <tr>
      <td>whitespace</td>
      <td>空白字符被忽略</td>
    </tr>
    <tr>
      <td>m</td>
      <td>允许使用内存操作数,以及机器通常支持的任何类型的地址</td>
    </tr>
    <tr>
      <td>o</td>
      <td>允许使用内存操作数,但前提是地址是可偏移的</td>
    </tr>
    <tr>
      <td>V</td>
      <td>允许使用内存操作数,不可偏移的内存地址,与 "o'互斥</td>
    </tr>
    <tr>
      <td>r</td>
      <td>允许在通用寄存器中使用的寄存器操作数,其中可以指定寄存器,如 a(%rax), b(%rbx)</td>
    </tr>
    <tr>
      <td>i</td>
      <td>允许使用立即整数操作数</td>
    </tr>
    <tr>
      <td>n</td>
      <td>允许使用具有已知数值的立即整数操作数, ‘I', ‘J', ‘K', … ‘P' 更应该使用 n</td>
    </tr>
    <tr>
      <td>F</td>
      <td>允许使用浮点立即数</td>
    </tr>
    <tr>
      <td>g</td>
      <td>允许使用任何寄存器,内存或立即数整数操作数,但非通用寄存器除外</td>
    </tr>
    <tr>
      <td>X</td>
      <td>允许任何操作数, ‘0', ‘1', ‘2', … ‘9'</td>
    </tr>
    <tr>
      <td>p</td>
      <td>允许使用有效内存地址的操作数</td>
    </tr>
</tbody>
</table>
<thead>
</thead>
<th></th>
</p>
<p>标识符约束</p>
<p>
<table>
<thead>
    <tr>
      <th>标识符</th>
      <th>说明</th>
    </tr>
</thead>
<tbody>
    <tr>
      <td>=</td>
      <td>表示此操作数是由该指令写入的:先前的值将被丢弃并由新数据替换</td>
    </tr>
    <tr>
      <td>+</td>
      <td>表示该操作数由指令读取和写入</td>
    </tr>
    <tr>
      <td>&amp;</td>
      <td>表示(在特定替代方法中)此操作数是早期指令操作数,它是在使用输入操作数完成指令之前写入的,故输入操作数部分不能分配与输出操作数相同的寄存器</td>
    </tr>
    <tr>
      <td>%</td>
      <td>表示该操作数与后续操作数的可交换指令</td>
    </tr>
</tbody>
</table>
</p>
<p>内核示例</p>
<p>x86 的内存屏障指令。</p>
<div class="jb51code">
<pre class="brush:plain;">
// 避免编译器的优化,声明此处内存可能发生破坏
#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</pre>
</div>
<p>x86 下获取 current 的值</p>
<div class="jb51code">
<pre class="brush:plain;">
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" (&amp;(var)));   \
    break;   \
}      \
pfo_ret__;   \
})</pre>
</div>
<p>current_task 为一个 struct task_struct 类型的指针,追踪宏调用,在x86-64 下命中了 case 8: 的汇编代码, 展开的代码为<code> </code></p>
<div class="jb51code">
<pre class="brush:plain;">
asm("mov" "q ""%%""gs" ":" "%" "P1"",%0" : "=r" (pfo_ret__) : "p" (&amp;(current_task)));
// 变换一下为
asm("movq %%gs:%P1, %0" : "=r"(pfo_ret__) : "p"(&amp;(current_task)));</pre>
</div>
<p>这行代码的含义为将 约束输入部分必须为有效的地址(p约束), 将CPU id(通过段寄存器gs和偏移通过GDT得到,这里后文分析了)通过寄存器(r约束)赋值给 pfo_ret__.</p>
<p>参考</p>
<p>GCC文档<br />
C语言ASM汇编内嵌语法zz</p>
<p><span style="color: #ff0000"><strong>总结</strong></span></p>
<p>以上所述是小编给大家介绍的C 表达式中的汇编指令,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对琼殿技术社区网站的支持!<br />
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>汇编语言lea指令使用方法解析</li><li>汇编语言中mov和lea指令的区别详解</li><li>常用的汇编指令与技巧(收藏)</li><li>16位汇编语言寄存器及指令整理(小结)</li><li>汇编语言 and和or逻辑运算指令的实现</li><li>汇编语言无效指令错误概述</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: C 表达式中的汇编指令