惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
小众软件
小众软件
博客园_首页
博客园 - 聂微东
V
V2EX
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
S
SegmentFault 最新的问题
J
Java Code Geeks
Last Week in AI
Last Week in AI
The Cloudflare Blog
月光博客
月光博客
雷峰网
雷峰网
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hugging Face - Blog
Hugging Face - Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
博客园 - Franky
腾讯CDC
Jina AI
Jina AI
博客园 - 叶小钗
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
量子位
爱范儿
爱范儿
美团技术团队
T
Tailwind CSS Blog
博客园 - 【当耐特】
D
Docker
IT之家
IT之家
V
Visual Studio Blog
P
Proofpoint News Feed
L
LangChain Blog
Engineering at Meta
Engineering at Meta
C
Check Point Blog
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog RSS Feed
Recorded Future
Recorded Future

博客园 - woodfish

偏序集的Dilworth定理 XJOJ历经2个月终于完成啦~~ fork()调用的一个趣题 实现google那种输入框提示的功能 POJ 3691 安徽第二题 有限状态自动机+DP 哈尔滨赛区网络预选赛总结 [计算几何]点集中的点能组成多少个正方形 [计算几何]POJ 1375 点对圆的切线+线段重叠 [计算几何]POJ 1556 判断线段相交+Dijkstra [计算几何] POJ 1873 暴力+凸包 [计算几何]POJ 1266 三角形的外接圆 圆的参数方程 POJ 1026 置换群 [计算几何]POJ 1031 计算点对多边形的偏转角度 [计算几何]POJ2079 求点集中面积最大的三角形 [计算几何]POJ3608 求2个不相交凸包的最短距离 [计算几何]凸包的旋转卡壳算法 C++禁止一个类被继承的技术 计算位数的3种方法 避免使用条件分支
C与汇编的接口技术
woodfish · 2008-03-01 · via 博客园 - woodfish

1.保存寄存器
  首先,C假定子程序保存了下面这几个寄存器的值:EBX,ESI,EDI,EBP,CS,DS,SS,ES。这并不意味着不能在子程序内部修改他们。相反,它表示如果子程序改变了它们的值,那么子程序返回之前必须恢复它们的原始值。EBX,ESI和EDI的值不能被改变,因为C将这些寄存器用于寄存器变量。通常都是使用堆栈来保存这些寄存器的原始值。

2.函数名
  大多数C编译器都在函数名和全局或静态变量前附加一个下划线字符。例如,函数名f将指定为_f,而不是f。Linux gcc编译器并不附加任何字符。在可执行的Linux ELF下,对于C函数f,你只需要简单使用函数名f即可。但是,DJGPP的gcc却附加了一个下划线。

3.传递参数
  按照C调用约定,一个函数的参数将以一定顺序压入栈中,这个顺序与他们出现在函数里的顺序相反。
  考虑这条C语句:printf("x=%d\n",x);下图上展示了如何编译这条语句(用等价的NASM格式)。下图下展示了执行完printf函数的开始部分后,堆栈的状态。printf函数是一个可以携带任意个参数的C语言库函数。C调用约定的规则就是专门为允许这些类型的函数而规定的。因为format字符串最后压入堆栈,所以不管有多少参数传递到函数,它在堆栈里的位置将总是EBP+8。然后printf代码就可以通过看format字符串的位置来决定需要传递多少参数和堆栈上如何找到它们。
  当然,如果有错误发生,printf("x=%d\n"),printf代码仍然会将[EBP+12]中的双字输出,而这并不是x的值。

segment .data
x        dd    
0
format   db    
"x=%d\n",0

segment .text

    
push dword [x]    ;将x的值压入栈中
    push dword format ;将format字符串的地址压入栈中
    call _printf      ;注意下划线!
    add esp,8         ;从栈中移除参数


4.计算局部变量的地址
  找到定义在data或bss段的变量地址是非常容易的。基本上,连接程序做的就是这件事情。但是,要计算出在堆栈上的一个局部变量(或参数)的地址就不简单了。可是,当调用子程序时,这种需求是非常普通的。考虑传递一个变量(让我们称它为x)的地址到一个函数(让我们称它为foo)的情况。如果x处在堆栈的EBP-8的位置,你不可以这样使用:
mov  eax,ebp-8
为什么?因为指令MOV储存到EAX里的值必须能有汇编器计算出来(也就是说,它最后必须是一个常量)。但是,有一条指令能做这种需求。它就是LEA(Load Effective Address)。下面的代码就能计算出x的地址并将它储存到EAX中:
lead  eax,[ebp-8]
现在EAX中有了x的地址,而且当调用函数foo的时候,就可以将其压入到栈中。

5.返回值
    返回值不为空的C函数执行完后会返回一个值。C调用约定规定了这个要如何去做。返回值需要通过寄存器传递。所有的整形类型(char,int,enum等)通过EAX寄存器返回。如果他们小于32位,那么储存到EAX的时候,他们将被扩展成32位。64位的值通过EDX:EAX寄存器对返回。浮点数出巡在数学协处理器的ST0寄存器中。

6.其它调用约定
    所有的80x86 C编译器中都支持上面描述的标准C调用约定的规则。通常编译器也支持其它调用约定。挡雨汇编语言进行接口时,知道编译器调用你的函数时使用的是什么调用约定是非常重要的。通常,缺省时,使用的是标准的调用约定;但是,并不总是这一种情况。使用多种约定的编译器都拥有可以用来改变缺省约定的命令行开关。他们同样提供扩展的C语法来为单个函数指定调用约定。但是,各个编译器的这些扩展标准可以是不一样的。
    GCC编译器允许不同的调用约定。一个函数的调用约定可以通过扩展语法__attribute__明确指定。例如,要声明一个返回值为空的函数f,它带有一个int参数,使用标准调用约定,需要使用下面的语法来声明它的圆形:
    void f(int)  __attribute__((cdecl));
GCC同样支持标准call调用约定。通过把cdecl替换成stdcall,上面的函数可以指定为使用这种约定。stdcall约定和cdecl约定的不同点是stdcall要求子程序将参数移除栈(和Pascal调用约定一样)。因此,stdcall调用约定只能使用在带有固定参数的函数上。
    Borland和Microsoft使用一样语法来声明调用约定。他们在C代码中加上关键字_cdecl和_stdcall。这些关键字来修饰函数。在原型声明中,他们出现在函数名的前面:
       void _cdecl f(int);
    每种调用约定都有自己的优缺点。cdecl调用约定的主要有点是它非常简单而且灵活。它可以用于任何类型的C函数和C编译器。使用其它约定会限制子程序的可移植性。他的主要缺点是与其他约定相比它的执行较慢而且使用更多的内存。
    stdcall调用约定的主要优点是相比于cdecl它使用较少的内存。在CALL指令之后,不需要清理堆栈。它的主要缺点是它不能用于可变参数的函数。