GCC 内联汇编基础

GCC 内联汇编

MIT6.828的实验中,有几处用到了很底层的函数,都以内联汇编的形式存在,例如

static inline uint32_t
read_esp(void)
{
    uint32_t esp;
    asm volatile("movl %%esp,%0" : "=r" (esp));
    return esp;
}
static inline uint32_t
read_ebp(void)
{
    uint32_t ebp;
    asm volatile("movl %%ebp,%0" : "=r" (ebp));
    return ebp;
}

因此这篇博客对于内联汇编的基本用法做一个总结。

首先是一般的形式,只能使用全局变量来传递数据,例如如下程序(插入在 kern/monitor.c):

uint32_t  test_val=0;
int mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
    uint32_t *ebp=(uint32_t*)read_ebp();
    cprintf("the ebp provided is :%8x and eip is %8x\n",ebp,ebp[1]);
    // you could use simple asm if you use global variables
    asm volatile(
            "push %eax          \n"
        "mov  %ebp,%eax             \n"
        "mov  %eax,test_val         \n"
        "popl  %eax         \n"
    );
    cprintf("my ebp is %8x\n",(uint32_t*)test_val);
    return 0;
}

运行结果如下

GCC 内联汇编基础

这里就跟普通的汇编一样的使用方式,注意关键字 volatile是为了防止被优化,还有每行汇编语句后面的 \n。又或者如这段程序

int32_t eip=0;
int main()
{
    //basic inline assembly
    //global variable can be use
    asm volatile(
                "push %eax      \n"
                "call .testpop  \n"
                ".testpop:      \n"
                "pop  %ebx      \n"
                "movl %ebx,%eax \n"
                "movl %eax,eip  \n"
                "pop  %eax      \n"
    );
    printf("the eip is :%x\n",eip);

    asm volatile(
                "push %eax      \n"
                "movl %ebp,%eax \n"
                "movl %eax,eip  \n"
                "pop  %eax      \n"
    );
    printf("the ebp is :%x\n",eip);
    return 0;
}

如果只能使用全局变量,必然会有很多不方便。为了能使用局部变量,需要使用扩展的内联汇编。扩展的内联汇编形式如下

asm("assembly code"
        :output location
        :input orperands
        :changed registers
);

其中

output location :输出的放哪儿
input operands :哪些输入
changed registers:改变了哪些寄存器

并且输入和输出部分都是如下形式

"constraint"(variable)

约束符主要是限制寄存器的使用

a   use %eax %ax or %al
b   use %ebx,%bx,or %bl
c   use %ecx,%cx,or %cl
d   use %edx,%dx,or %dl
S   use %esi or %si
D   use %edi or %si
r   use any available register
q   use one of %eax,%ebx,%ecx or %edx
A   use %eax&%edx for 64-bit value
f   use float register
m   use memory location of variable

同时可以加上修饰符

+   read & write
=   only write

来看一段程序

uint32_t ebp;
    __asm__ __volatile__(
            "push %%eax        \n\t"
            "mov  %%ebp,%%eax  \n\t"
            "mov  %%eax,%%edx  \n\t"
            "pop  %%eax       "
            :"=d"(ebp)
            :
            :"%eax"
    );
    printf("the ebp is %8x\n",(uint32_t*)ebp);

这里 "=d"(ebp)意思是输出使用寄存器 %edx,并且把结果放到变量 ebp中。没有输入所以省略,但是冒号不能省。这个过程改变了 %eax。注意的是,编译器默认输入输出中涉及的寄存器都被改变,因此不能再将这部分寄存器写到改变部分去。注意汇编代码中的寄存器 %eax要写成 %%eax,每条语句完要写 \n\t

再来看一段程序

int xa=6;
int xb=2;
int result_1;
__asm__ __volatile__(
    "add  %%ebx,%%eax    \n\t"
    "movl $2,%%ecx       \n\t"
    "mul  %%ecx          \n\t"
    "movl %%eax,%%edi    \n\t"
    "movl %%eax,%%edx"
    :"=d"(result_1)
    :"a"(xa),"b"(xb)
    :"%ecx","%edi"
);
printf("the result is %d\n",result_1);

:"=d"(result_1)输出使用 %edx,放到 result_1这个变量中;

:"a"(xa),"b"(xb)输入变量 xa的值放到 %eax中, xb的值放到 %ebx中;

:"%ecx","%edi"这个过程还改变了 %ecx%edi

再来看一个例子

int data1=10;
int data2=20;
int result;
__asm__ __volatile__(
    "imul %%edx ,%%ecx \n\t"
    "movl %%ecx ,%%eax \n\t"
    :"=a"(result)
    :"d"(data1),"c"(data2)
);
printf("10*20 is %d\n",result);

有了上面的例子,这个应该就很好理解了。然而在我们看别人写的内联汇编中,有时会出现 %0,%1这种。这叫占位符,就是代表第几个操作数所在的寄存器,例如看如下代码

// %0 is the register to store result
// %1 is the register to store data1
// %2 is the register to store data2
__asm__ __volatile__(
    "imul %1 ,%2 \n\t"
    "movl %2 ,%0 \n\t"
    :"=r"(result)
    :"r"(data1),"r"(data2)
);
printf("10*20 is %d\n",result);

这里使用了限定符 r就是,让编译器自己选择可用的寄存器。注意这里改变的寄存器列表为空,需要连带冒号一起省略。

同时,也可以用输入变量来接受结果,结合占位符,有如下代码

__asm__ __volatile__(
    "imul %1 ,%0 \n\t"
    :"=r"(data2)
    :"r"(data1),"0"(data2)
);

输入和输出都是 data2

但是,如果输入输出过多,还用数字就会显得不太好,因此 gcc也有一个方便的做法

data1=10;
data2=20;
__asm__ __volatile__(
    "imul %[value1] ,%[value2]  \n\t"
    :[value2]"=r"(data2)
    :[value1]"r"(data1),"0"(data2)
);
printf("10*20 is %d\n",data2);

全文的测试代码如下:


//test_asm.c
#include
#include

int32_t eip=0;
int main()
{
    //basic inline assembly
    asm volatile(
        "push %eax  \n"
        "call .testpop  \n"
        ".testpop:  \n"
        "pop  %ebx  \n"
        "movl %ebx,%eax\n"
        "movl %eax,eip  \n"
        "pop  %eax  \n"
    );
    printf("the eip is :%x\n",eip);

    asm volatile(
        "push %eax  \n"
        "movl %ebp,%eax\n"
        "movl %eax,eip  \n"
        "pop  %eax  \n"
    );
    printf("the ebp is :%x\n",eip);

   uint32_t ebp;
    __asm__ __volatile__(
        "push %%eax        \n\t"
        "mov  %%ebp,%%eax  \n\t"
        "mov  %%eax,%%edx  \n\t"
        "pop  %%eax       "
        :"=d"(ebp)
        :
        :"%eax"
    );
    printf("the ebp is %8x\n",(uint32_t*)ebp);

    int xa=6;
    int xb=2;
    int result_1;
    __asm__ __volatile__(
        "add  %%ebx,%%eax    \n\t"
        "movl $2,%%ecx       \n\t"
        "mul  %%ecx          \n\t"
        "movl %%eax,%%edi    \n\t"
        "movl %%eax,%%edx"
        :"=d"(result_1)
        :"a"(xa),"b"(xb)
        :"%ecx","%edi"
    );
    printf("the result is %d\n",result_1);

    int data1=10;
    int data2=20;
    int result;
    __asm__ __volatile__(
        "imul %%edx ,%%ecx \n\t"
        "movl %%ecx ,%%eax \n\t"
        :"=a"(result)
        :"d"(data1),"c"(data2)
    );
    printf("10*20 is %d\n",result);

    __asm__ __volatile__(
        "imul %1 ,%2 \n\t"
        "movl %2 ,%0 \n\t"
        :"=r"(result)
        :"r"(data1),"r"(data2)
    );
    printf("10*20 is %d\n",result);

    // you could refer them
    // 0 means use the first register to store the input and output
    __asm__ __volatile__(
        "imul %1 ,%0 \n\t"
        :"=r"(data2)
        :"r"(data1),"0"(data2)
    );
    printf("10*20 is %d\n",data2);

    // you could rename
    // [name] "constraint"(variable)
    data1=10;
    data2=20;
    __asm__ __volatile__(
        "imul %[value1] ,%[value2] \n\t"
        :[value2]"=r"(data2)
        :[value1]"r"(data1),"0"(data2)
    );
    printf("10*20 is %d\n",data2);
    return 0;
}

编译指令: gcc -O0 -m32 test_asm.c -o test

结果如下

GCC 内联汇编基础

Original: https://www.cnblogs.com/oasisyang/p/15367055.html
Author: OasisYang
Title: GCC 内联汇编基础

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585683/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • PHP代码审计_用==与===的区别

    背景介绍 如何审计 绕过案例1 绕过案例2 背景介绍 比较 ==与 ===的差别 == 是等于符号,=== 是恒等于符号,两个符号的功能都是用来比较两个变量是否相等的,只不过两个符…

    Linux 2023年6月6日
    0115
  • [20210917]ssh: error while loading shared libraries: libcrypto.so.1.0.0.txt

    [20210917]ssh: error while loading shared libraries: libcrypto.so.1.0.0.txt –//以后写一些…

    Linux 2023年5月27日
    0137
  • 目录遍历漏洞

    一.目录遍历漏洞原理目录遍历(路径遍历)是由于Web服务器或者Web应用程序对用户输入的文件名称的安全性验证不足而导致的一种安全漏洞,使得攻击者通过利用一些特殊字符就可以绕过服务器…

    Linux 2023年6月14日
    0106
  • 多线程/哈希slot/集群

    io多线程 以前的redis是单线程模型,其实就是多路复用机制,知道多路复用的来一波6,我们在架构师课程中讲过,那么netty也有,看过老师相关课程的也应该知道。这里不多说了。 R…

    Linux 2023年5月28日
    0105
  • ThinkPHP5浏览器关闭,继续执行php脚本

    ignore_user_abort(); //即使Client断开(如关掉浏览器),PHP脚本也可以继续执行. set_time_limit(0); //执行时间为无限制,php默…

    Linux 2023年6月7日
    094
  • nslookup:command not found的解决办法

    nslookup:command not found的解决办法 通过nslookup查看DNS记录,在这里遇到了一个小插曲,nslookup:command not found(未…

    Linux 2023年6月7日
    085
  • Linux 配置Git

    前言:请各大网友尊重本人原创知识分享,谨记本人博客: 南国以南i 一、用git –version命令检查是否已经安装 二、下载git源码并解压 wget https:/…

    Linux 2023年6月14日
    096
  • 多线程执行同一任务,不共享局部变量

    多线程执行同一任务,不共享局部变量 一、 代码展示 import threading import time 多线程执行同一任务时,局部变量是不共享的 def sum_num():…

    Linux 2023年6月14日
    093
  • 常见Git命令汇总

    前言 最近,有朋友私信让我就 git 使用做篇文章分享,分享一下我在日常工作中是如何使用 git的。我当场就收费两包辣条,最后讨价还价,…

    Linux 2023年6月13日
    099
  • n的阶乘前100项。Table of n! for n = 1..100

    n的阶乘前100项 {1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,871782…

    Linux 2023年6月6日
    086
  • kafka-部署

    kafka部署 版本选择: Scala 2.12-kafka_2.12-2.7.0.tgz #版本格式 kafka_scala版本_kafka版本 部署三台服务器的高可用kafka…

    Linux 2023年6月14日
    0108
  • 【Python | opencv+PIL】常见操作(创建、添加帧、绘图、读取等)的效率对比及其优化

    本人准备用python做图像和视频编辑的操作,却发现opencv和PIL的效率并不是很理想,并且同样的需求有多种不同的写法并有着不同的效率。见全网并无较完整的效率对比文档,遂决定自…

    Linux 2023年6月13日
    097
  • pod(一):Kubernetes(k8s)创建pod的两种方式

    服务器版本 docker软件版本 CPU架构 CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 x86_64…

    Linux 2023年6月7日
    091
  • Redis缓存穿透、缓存击穿、缓存雪崩

    Redis缓存穿透、缓存击穿缓存雪崩 redis常被用于作为后台数据库的缓存,缓存一些热点访问数据,根据局部性原理,缓存能够处理大部分请求。当请求数据未命中缓存时,才会引起对数据库…

    Linux 2023年6月13日
    0108
  • short, int, long, long long各个类型的范围

    类型名称 字节数 取值范围 signed char 1 -2^7 ~ 2^7-1 -128~+127 short int 2 -2^14 ~ 2^14-1 -32768~+3276…

    Linux 2023年6月8日
    089
  • 尤娜故事-迷雾-springboot扮酷小技巧

    前情回顾 从前,有一个简单的通道系统叫尤娜…… 尤娜系统的第一次飞行中换引擎的架构垂直拆分改造 四种常用的微服务架构拆分方式 尤娜,我去面试了 正文 我回到…

    Linux 2023年6月14日
    086
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球