汇编语言与C语言混合编程
创始人
2025-05-31 17:25:12
0

函数调用规约

在c语言中有这样的代码

int subtract (int a,int b) {return a-b;
}

我们可以用这样的形式调用它

int sub = subtract(3,2)

这样我们就完成了一次函数调用,这是C语言最常见的函数调用手法,可是大家想过没有,计算机是如何知道我们传入的两个参数3和2在哪里的呢?

我们可以保存在寄存器中,但是寄存器的数量是有限的,我们也可以放在内存栈中,调用的时候传入栈的地址,放在栈内存中有几个好处

  • 每个进程都有自己的栈,这就是每个内存自己的专用内存空间。
  • 保存参数的内存地址不用再花精力维护,已经有栈机制来维护地址变化了。

保存在内存栈中,我们第一个问题解决了,怎么找到这两个传入值。那第二个问题,谁来负责收回这部分内存,参数很多的情况下,主调函数将参数以什么样的顺序传递呢?

这两个问题其实也好解决?我自己规定好了,是调用者负责将栈回收到之前的位置还是被调用者将栈回收到之前的位置,参数是从左往右压栈还是从右往左压栈。

实际上高级语言也是这么规定的,不同的高级语言之间也有些许不同,下面就详细罗列出了这些调用规约。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hPbJoAvy-1679378163605)(C:\Users\LoveSS\Desktop\linux内核\picture\image-20230319185501582.png)]

其实我们这里讲的是C语言与汇编语言的混合编程,所以我们只需要关注有关c语言的 cdecl 调用规约就好了。

cdecl调用约定由于起源于C语言,所以又称为C调用约定,是C语言默认的调用约定。cdecl的调用约定意味着

  • 调用者将所有参数从右向左入栈。
  • 调用者清理参数所占的栈空间。

我们将之前的sutract变成汇编看一下

主调用者

push 2
push 3
call subtract
add esp,8

被调用者

push ebp
mov  ebp,esp
mov  eax,[ebp+0x8]
add  eax,[ebp+0xc]
mov  esp,ebp
pop  ebp
ret

看到了嘛,主调用者负责将参数从右到左入栈,主调用者负责将栈顶指针的前八个字节弹出

c库函数与系统调用

我们先学习下Linux系统调用,利用系统调用能够帮助简化演示的模型。

系统调用是Linux内核提供的一套子程序,它和Windows的动态链接库dll文件的功能一样,用来实现一系列在用户态不能或不易实现的功能,比如最常见的读写硬盘文件,只有操作系统有权限去访问硬件,用户程序是没有权限的,用户程序只能向操作系统寻求帮助,故系统调用是供用户程序来使用的,操作系统权利至高无上,不需要使用自己对外发布的功能接口,即系统调用。

系统调用很像BIOS中断调用,只不过系统调用的入口只有一个,即第0x80号中断,具体的子功能在寄存器eax中单独指定。在Linux系统中,系统调用是定义在/usr/include/asm/unistd.h文件中,在asm目录下提供了这两个版本,文件名分别是unistd_32.h 和unistd_64.h,这个很显然一个对应32位一个对应64位,我在32位的版本中找到了435个系统调用,我们摘录前10个看一看

#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9

如果不知道某个系统调用的用法的话,可以用man命令来看一看

man 2 #系统调用名
man 2 write

系统调用的两种方式

调用“系统调用”有两种方式

  • 将系统调用指令封装为c库函数,通过库函数进行系统调用,操作简单。
  • 不依赖任何库函数,直接通过汇编指令int与操作系统通信。

库函数系统调用

write的功能是把buf指向的缓冲区中的count个字节写入fd指向的文件描述符,执行成功后返回写入的字节数,失败则返回-1。我们试一试

#include int main() {write(1,"Hello C\n",8);return 0;
}

我们编译执行后发现我们的命令终端输出了这个字符串,因为在Linux中,1号文件是 stdout 也就是标准输出。

直接系统调用

我们要看看系统调用输入参数的传递方式。

当输入的参数小于等于5个时,Linux用寄存器传递参数。当参数个数大于5个时,把参数按照顺序放入连续的内存区域,并将该区域的首地址放到ebx寄存器。这里我们只演示参数小于等于5个的情况。

(1)ebx存储第1个参数。
(2)ecx存储第2个参数。
(3)edx存储第3个参数。
(4)esi存储第4个参数。
(5)edi存储第5个参数。

我们实践一下

section .data
str_c_lib: db "C library says: hello c!",0xa;
str_c_lib_len equ $-str_c_libstr_syscall: db "syscall says: hello c!",0xa;
str_syscall_len equ $-str_syscallsection .text
global _start
_start:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;模拟C语言的调用形式push str_c_lib_lenpush str_c_libpush 1call simu_writeadd  esp,12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;直接进行系统调用mov eax,4mov ebx,1mov ecx,str_syscallmov edx,str_syscall_lenint 0x80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;退出程序mov eax,1int 0x80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;自己定义的函数模拟c语言调用形式
simu_write:push ebpmov ebp,espmov eax,4mov ebx,[ebp+8]mov ecx,[ebp+12]mov edx,[ebp+16]int 0x80pop ebpret

编译

nasm -f elf32 -o wr.o wr.s

链接

ld -m elf_i386 -o wr.bin wr.o

这里我们需要 -m 指定我们链接的文件格式

./wr.bin

执行就会打印下面的字符串

C library says: hello c!
syscall says: hello c!

c与汇编的相互调用

我们这里准备了两个例子

c语言

extern void asm_print(char*,int);void c_print(char *str) {int len = 0;while(str[len++]);asm_print(str,len);
}

汇编

section .data
str:db "asm_print says hello c",0xa,0
; 0xa指出字符串是ascii编码,0是手动加上的\n
str_len equ $-strsection .text
extern c_printglobal _start
_start:push strcall c_printadd esp,4mov eax,1  ; 子功能号1是exitint 0x80   ; 发起中断通知Linux完成请求的功能global asm_print
asm_print:push ebpmov ebp,espmov eax,4mov ebx,1mov,ecx,[ebp+8]mov edx,[ebp+12]int 0x80pop ebpret

c中的函数c_print是被汇编代码调用的,c_print又是调用汇编代码中的asm_print实现的。在C语言中的第一行是申明外部函数asm_print,通知编译器这个函数并不在当前文件中定义,我们知道这个函数是在汇编中定义的,但是编译器并不知道,所以只能在链接阶段将此函数重新定位,编排地址。

由于我们并不想在链接的时候把C语言标准库也链接进来,所以这里我们是手动的在统计字符串的长度,这也是为什么我们会在汇编中这个字符串的结尾手动加一个0的原因

为了在汇编文件中引用外部的函数(未必是C代码中的),需要用extern来声明所需要的函数名。

在汇编语言中导出符号名用global关键字,这在之前说_start时大伙已有所耳闻,global将符号导出为全局属性,对程序中的所有文件可见,这样其他外部文件中也可以引用被global导出的符号啦,无论该符号是函数,还是变量。

我们可以把这两段代码编译链接一下

编译C文件

gcc -c -m32 -o C_with_S.o C_with_S.c

编译汇编文件

nasm -f elf32 -o S_with_C.o S_with_C.s 

链接这两个文件

ld C_with_S.o S_with_C.o -o CS.bin -m elf_i386

执行

./ CS.bin

得到输出结果

lovetzp@ubuntu:~/temp$ ./CS.bin 
asm_print says hello c
c -m32 -o C_with_S.o C_with_S.c

编译汇编文件

nasm -f elf32 -o S_with_C.o S_with_C.s 

链接这两个文件

ld C_with_S.o S_with_C.o -o CS.bin -m elf_i386

执行

./ CS.bin

得到输出结果

lovetzp@ubuntu:~/temp$ ./CS.bin 
asm_print says hello c

相关内容

热门资讯

打架斗殴检讨书通用版三篇 打架... 我们之所以要写检讨书是为了自我反省,这才是写检讨书的真正目的,以下是小编为大家搜集整理的打架斗殴检讨...
认识最深刻的检讨书多篇 深刻认...   篇一  尊敬的老师:  我是很认真得的写这篇检讨书,没有任何搞笑色彩,也没有任何的其他杂念,我是...
放学去网吧检讨书范文3篇 学生...  放学去网吧检讨书范文一  本人,XXX(姓名),XX中学高中X年级X班学生,学号为XX。  前天放...
写给父母的检讨书多篇 顶撞父母... 父母总是爱着自己的子女的,也许他们会对你很严厉,让你不能理解,但是你要理解父母的苦心,以下是小编推荐...
做错事的检讨书精选5篇 做错事...  做错事的检讨书(一)  尊敬的老师:  你好!  开学至今,我本人做错的事很多,如迟到、爬墙等。但...
最新或2023(历届)普通党员...   教师党员四风对照检查材料  根据走访调研和召开不同类型座谈会收集到的意见,梳理出管理(服务)岗、...
因期中考试成绩不好检讨书三篇 ... 因中考成绩不好检讨书(一)  尊敬的老师们:  您们好!现在我怀着愧疚的心情,写下这份检查.以向您表...
喝酒检讨书,学生和上班喝酒检讨...  喝酒检讨书(一)  尊敬的老师:  你好,我知错了,我不该喝酒,我下次再也不敢喝酒了  我这次喝酒...
最新或2023(历届)学校三严... 学校三严三实班子对照检查材料一  按照学校的总体部署,根据学院《学院“三严三实”专题民主生活会工作方...
值日偷懒检讨书,没做值日检讨书...   值日偷懒检讨书(一)  尊敬的老师:  非常抱歉,我在值日工作当中偷懒了,基本没有完成值日工作就...
最新或2023(历届)最新三严... “凡是有利于党和人民事业的,就坚决干、加油干、一刻不停歇地干;凡是不利于党和人民事业的,就坚决改、彻...
党员干部四风对照检查材料范文【...  党员干部四风对照检查材料范文一  市局总队启动党的群众路线教育实践活动以来,按照机关党委工作方案的...
最新或2023(历届)教师党员...  教师党员反四风对照检查材料篇一  这次党的群众路线教育实践活动,对个人来说,既是一次用群众观点、群...
最新小学美德少年事迹材料范文 ...  “美德少年”事迹材料一:  这次,我非常荣幸地被老师推荐为我们班的“美德少年”侯选人,这是让我意想...
最新或2023(历届)评选劳模...  劳模先进事迹材料一  郝建峰同志,是有着多年党龄的优秀共产党员,现在负责通风区行政管理工作,负责组...
最新或2023(历届)最新学校... 学校优秀团干部事迹材料篇一  在团市委领导的指导和我校党支部的关心、支持下,本人在思想上积极上进,工...
最新或2023(历届)女劳模先...   女劳模先进事迹材料_第1篇  最近,XXX正在为自己的几个婴幼儿护理项目多方奔走,由一名从业10...
最新或2023(历届)最新孝心...   材料一:最美孝心少年事迹材料  李云宽,男,13岁,家住武安市南通乐村,目前就读于武安市第九中学...
学校关于美德少年事迹材料简写 ...  【小学生美德少年事迹材料简写一】  我是一名孤儿,在我呱呱坠地不久,父亲就去世了,母亲不堪生活的压...
最新或2023(历届)企业优秀...  材料一:优秀员工先进事迹材料  我在xx有限公司生产车间担任手xx组组长,参加工作已有两年,在这两...