• C++中嵌入汇编语言的方法(这个方法被证明在64位电脑上使用visual studio没有用)


    C语言和汇编语言相互调用
    不同的语言就像一座孤岛,似乎毫不相干,但是所有的代码最终都要编译成机器指令,他们本质上也是一样的,最终都是变成指令给CPU下达命令。

    1. C语言的链接过程
    我们知道一个C语言源文件变成可执行文件,需要经过一下几个步骤:

    预处理。(hello.c -> hello.i)把头文件包含起来。
    编译。(hello.i -> hello.s)编译成汇编代码。
    汇编。(hello.s -> hello.o)生成目标文件。
    链接。(hello.o -> hello)生成可执行文件。
    汇编代码变成可执行文件,也要经过汇编、链接。

    比如main.c调用了add.c中的add()函数:

    // main.c
    #include
    int add(int a, int b);
    int main(void)
    {
        printf("%d\n", add(1,2));
        return 0;
    }

    // add.c
    int add(int a, int b)
    {
        return a + b;
    }
    然后分别编译,再共同链接:

    # 编译main.c,生成目标文件main.o
    gcc -c main.c -o main.o
    # 编译add.c,生成目标文件add.o
    gcc -c add.c -o add.o
    # 将目标文件main.o和add.o链接生成可执行文件main
    gcc main.c add.o -o main
    # 执行可执行文件main
    ./main
    结果如下图:

    链接过程中有一项重要工作是重定位,把多个目标文件链接在一起,需要确定源文件中函数的内存地址。

    比如上面的main.c中调用了add.c中的add()函数,那么在main.c编译成目标文件时,编译器并不会检测add()函数是否存在。但是在链接时,需要确定add()函数的地址。由于main.o和add.o是一起参与链接生成可执行文件的,因此add()函数的地址会在add.o中找到。所以两个目标文件(main.o和add.o)完美地合成一个可执行文件。

    所以把汇编源代码经过汇编生成的目标文件,和C语言生成的目标文件,链接成一个可执行文件,应该是可以相互调用的。

    2. 函数调用约定
    函数在调用时,需要传递参数,可是调用者传递的参数,被调用者怎么知道去哪里找到参数呢?

    他们肯定要有一个约定,比如参数是去某个特定寄存器中取,还是在某个栈中取?压栈的顺序又是怎样的?以及,函数调用完后,栈空间谁来回收?

    2.1 C语言的调用约定
    C语言的调用约定使用的是:cdecl(C Declaration),函数参数是从右到左的顺序入栈的。GNU/Linux GCC,把这一约定作为事实上的标准,x86 架构上的许多 C 编译器也都使用这个约定。在 cdecl 中,参数是在栈中传递的。EAX、ECX 和 EDX 寄存器是由调用者保存的,其余的寄存器由被调用者保存。函数的返回值存储在 EAX 寄存器。由调用者清理栈空间。

    总结下约定有下面几点:

    参数使用栈传递
    参数从右到左入栈
    由调用者回收栈空间
    2.2 函数调用时栈空间回收
    2.1.2 什么是栈空间回收
    栈空间回收是什么情况?如下例子,调用者调用add(1,2),被调用者把1 + 2算出来:

    调用者传入参数:

    push 1
    push 2
    call add
    当上述代码执行完,栈空间应该是如下图所示的:

    被调用者取出参数并执行add(1,2):

    push ebp            ; 备份ebp,因为取参数时要用ebp来寻址
    mov ebp, esp         ; 把栈顶地址给ebp,因为栈顶不能随便动
    mov eax, [ebp + 8]  ; 取出参数2
    add eax, [ebp + 12] ; 取出参数1
    pop ebp                ; 还原ebp
    ret
    当被调用者的代码执行完毕,栈空间应该这样的情况:

    所以此时,参数还在栈里面,但是函数都调用完了,栈里面的参数应该要清理。所以在调用函数有个栈空间回收。

    2.1.3 如何栈空间回收
    栈空间回收很简单,只不过就是把栈顶指针esp移动到调用函数之前的状态即可。

    那么这里有个问题,谁来负责栈空间回收?调用者还是被调用者?

    2.1.3.1 被调用者回收栈空间
    如果是被调用者回收栈空间,那么被调用者应该负责还原esp,代码应该如下:

    ; 调用者
    push 1
    push 2
    call add
    ; 被调用者
    push ebp            ; 备份ebp,因为取参数时要用ebp来寻址
    mov ebp, esp         ; 把栈顶地址给ebp,因为栈顶不能随便动
    mov eax, [ebp + 8]  ; 取出参数2
    add eax, [ebp + 12] ; 取出参数1
    pop ebp                ; 还原ebp
    ret 8                ; 表示esp + 8,清理栈空间
    2.1.3.2 调用者回收栈空间
    如果是调用者回收栈空间,那么调用者要负责还原esp,代码应该如下:

    ; 调用者
    push 1
    push 2
    call add
    add esp, 8            ; 还原esp栈顶指针
    ; 被调用者
    push ebp            ; 备份ebp,因为取参数时要用ebp来寻址
    mov ebp, esp         ; 把栈顶地址给ebp,因为栈顶不能随便动
    mov eax, [ebp + 8]  ; 取出参数2
    add eax, [ebp + 12] ; 取出参数1
    pop ebp                ; 还原ebp
    ret                 
    3. C语言调用汇编语言
    这里调用者是C语言,被调用者是汇编语言。

    这里是main.c中调用print.asm中的print()函数:

    // main.c
    extern void print(char*, int); // 表示print函数不在本文件内,使用extern声明
    int main(void)
    {
        print("hello\n", 6);
        return 0;
    }
    ; print.asm
    global print                 ; 设置print为全局可见
    print:
        push ebp
        mov ebp, esp
        mov eax, 4              ; 发起系统调用
        mov ebx, 1              ; ebx表示stdout
        mov ecx, [ebp + 8]      ; 取出传入的第二个参数,表示字符串的地址
        mov edx, [ebp + 12]     ; 取出传入的第一个参数,表示字符的个数
        int 0x80                ; int 0x80表示系统调用

        pop ebp
        ret
    然后分别生成目标文件,然后链接成一个可执行文件:

    # 编译汇编 main.c,生成目标文件main.o
    gcc -m32 -c main.c -o main.o
    # 汇编 print.asm,生成目标文件print.o
    nasm -f elf print.asm -o print.o
    # 链接两个目标文件main.o和print.o,生成可执行文件hello
    ld -m elf_i386 -s -o hello main.o print.o -e main
    # 执行可执行文件hello
    ./hello
    注意:

    ​ print.asm汇编生成的目标文件是32位的

    ​ 如果使用gcc直接编译汇编生成的目标文件是64位的,会无法链接

    ​ 因此在使用gcc编译时要使用参数-m32指定生成32位的目标文件

    输出效果如下:

    字符串“hello”是成功输出了,然后后面报错了Segmentation fault (core dumped) ,也不知道是为什么报错,最终也没解决。

    错误原因我猜测是在main.c中传递字符串时,没有字符串结尾标志吧,但是传入’\0’似乎在汇编中无法识别?

    4. 汇编语言调用C语言
    使用汇编语言调用C语言,为了避免使用库函数,因此C语言还需使用调用汇编语言,但是C语言调用汇编语言上面讲过了。

    这里的例子依然是输出字符串,文件之间的函数调用关系如下图:

    ; 文件名:main.asm
    extern print_c ; print_c来自外部的C语言源程序

    section .data
        str: db "fuck", 0xa, 0
        str_len equ $ - str
    section .text
    ;---调用print_c.c中的print_c函数---;
    global _start
    _start:
        push str_len   ; 传入参数,表示字符的个数
        push str       ; 传入参数,表示字符串的地址
        call print_c
        add esp, 8     ;栈空间回收

    ;---退出程序---;
        mov eax, 1     ; 系统调用的第1号子程序是exit
        int 0x80       ; 相当于return 0;

    // 文件名:print_c.c
    extern void print_asm(char*, int);
    // print_c()函数调用print_asm.asm文件中的print_asm函数
    void print_c(char* s, int count)
    {
        print_asm(s, count);
    }
    ; 文件名:print_asm.asm
    global print_asm                 ; 设置print_asm函数为全局可见
    print_asm:
        push ebp
        mov ebp, esp
        mov eax, 4              ; 发起系统调用
        mov ebx, 1              ; ebx表示stdout
        mov ecx, [ebp + 8]      ; 取出传入的第二个参数,表示字符串的地址
        mov edx, [ebp + 12]     ; 取出传入的第一个参数,表示字符的个数
        int 0x80                ; int 0x80表示系统调用

        pop ebp
        ret
    然后分别编译汇编,然后把这三个文件链接成一个可执行文件:

    # 汇编 main.asm生成目标文件 main.o
    nasm -f elf main.asm -o main.o
    # 编译汇编print_c.c 生成目标文件 print_c.o
    gcc -m32 -c print_c.c -o print_c.o
    # 汇编print_asm.asm生成目标文件print_asm.o
    nasm -f elf print_asm.asm -o print_asm.o
    # 把上述的三个文件链接成一个可执行文件,名为fuck
    ld -m elf_i386 main.o print_c.o print_asm.o -o fuck
    ./fuck
    运行结果如下图:

    这里就没有出现Segmentation fault这样的错误了。

    一开始,这里的main.asm中定义字符串,是没有添加0作为结尾标志的,然后出现了Segmentation fault
    ————————————————
    版权声明:本文为CSDN博主「Freestyle Coding」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/m0_55708805/article/details/115296625

  • 相关阅读:
    [工业自动化-11]:西门子S7-15xxx编程 - PLC从站 - 分布式IO从站/从机
    Java毕业设计项目源码Java基于springboot的高校专业实习管理系统的设计和开发
    新美域杂志新美域杂志社新美域编辑部2022年第6期目录
    iPhone开发--Xcode15下载iOS 17.0.1 Simulator Runtime失败解决方案
    香港服务器的速度为什么比较快
    传输层协议-UDP协议
    Arcpy新增随机高程点、空间插值及批量制图
    pxe网络装机
    C++面试八股文:了解sizeof操作符吗?
    微信小程序数据监听器小案例
  • 原文地址:https://blog.csdn.net/geniusChinaHN/article/details/132915699