ch11汇编语言与高级语言溷合程序设计Word格式文档下载.docx
《ch11汇编语言与高级语言溷合程序设计Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《ch11汇编语言与高级语言溷合程序设计Word格式文档下载.docx(12页珍藏版)》请在冰豆网上搜索。
(4)编译方法。
有两种混合编程方式,一种是嵌入汇编指令的格式;
另一种是独立汇编模块格式。
7.3.1存储模式的约定
存储模式用于处理程序、数据、堆栈在主存中的分配和存取,决定代码和数据的默认指针类型,例如段寄存器CS、DS、SS、ES的设置就与所采用的存储模式有关。
存储模式在C语言中也称为编译模式或主存模式。
TurboC提供了六种存储模式,分别是:
微型模式(tiny)、小型模式(small)、紧凑模式(compact)、中型模式(medium)、大型模式(large)和巨型模式(huge)。
混合编程时必须保持汇编语言和C语言的存储模式一致。
在汇编语言中使用伪指令.model***,表示在汇编语言中采用的存储模式,其中***表示上述6种模式中的一种。
若C采用小模式,则在汇编语言中要使用.modelsmall。
7.3.2函数调用约定
要想实现高级语言与汇编语言的混合编程,必须知道这两种语言之间的调用约定。
当高级语言函数被编译成机器码时,有一个问题就必须解决:
因为CPU没有办法知道一个函数调用需要多少个、什么样的参数。
即计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用与者和函数本身来协调。
为此,计算机提供了一种被称为栈的数据结构来支持参数传递。
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。
函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。
在参数传递中,必须明确说明以下三个方面:
1)当参数个数多于一个时,按照什么顺序把参数压入堆栈;
2)函数调用后,由谁来把堆栈恢复原样;
3)调用函数返回值放在什么地方。
在高级语言中,通过函数调用约定来说明这个问题。
如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题。
常见的调用约定有如下5种:
1)stdcall---pascal调用约定;
2)cdecl---C调用约定;
3)fastcall---与stdcall类似,速度更快;
4)thiscall---C++类成员函数缺省的调用约定;
5)nakedcall---很少见的调用约定,不建议使用。
1.stdcall调用约定
stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定是stdcall。
在MicrosoftC++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。
stdcall是Win32API函数的调用约定。
stdcall调用约定声明的语法为:
int__stdcallfunction(inta,intb)/*pascal调用约定*/
stdcall的调用约定:
1)参数从右向左压入堆栈;
2)被调用函数自动清理堆栈;
3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。
以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:
push2;
第二个参数入栈
push1;
第一个参数入栈
callfunction;
调用函数,自动把cs:
eip入栈
而对于函数自身开始处,则翻译为:
pushebp;
保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
movebp,esp;
保存堆栈指针
moveax,[ebp+8H];
堆栈中ebp指向位置之前依次保存有ebp,cs:
eip,a,b,ebp+8指向a
addeax,[ebp+0CH];
堆栈中ebp+0CH处保存了b,实现加法功能
movesp,ebp;
恢复esp
popebp
ret8;
自动恢复堆栈,返回值在eax中。
在编译时,函数名被翻译成_function@8。
注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。
其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。
从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。
函数结束后,ret8表示清理8个字节的堆栈,函数自己恢复了堆栈。
__stdcall是在windows程序设计中出现的最多的调用规则,所有的不可变参数的API调用都使用这个规则。
2.cdecl调用约定
cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:
intfunction(inta,intb)/*不加修饰就是C调用约定*/
int__cdeclfunction(inta,intb)/*明确指出C调用约定*/
cdecl调用约定:
2)函数本身不清理堆栈,调用者负责清理堆栈;
3)函数名自动加前导的下划线_function。
cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由右向左压入堆栈。
所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。
由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。
对于前面的function函数,使用cdecl后的汇编码变成:
调用处
push2;
addesp,8;
注意:
由调用者恢复堆栈
被调用函数_function处
保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针
moveax,[ebp+8H];
堆栈中ebp+12处保存了b
ret;
注意,这里没有完全恢复堆栈,返回值在eax中。
由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数。
cdecl调用约定最大好处在于由于是调用者清理栈,它可以处理可变参数,缺点则在于它增加了程序的大小,因为在每个调用返回的时候,需要多执行一条清理栈的指令。
3.fastcall调用约定
fastcall调用约定和stdcall类似,
fastcall调用约定:
1)函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ECX和EDX传递,其他参数通过从右向左的顺序压栈;
2)被调用函数自动清理堆栈;
3)函数名修改规则同stdcall。
其声明语法为:
int__fastcallfunction(inta,intb)
__fastcall在windows内核设计中被广泛的使用,由于两个参数由寄存器直接传递,采用这种规则的函数效率要比以上两种规则高。
4.thiscall调用约定
thiscall是C++成员函数的默认调用约定,编译期间,这种调用会根据函数是否支持可变参数表来决定采用什么方式清理堆栈。
如果成员函数不支持可变参数,那么它就是用参数入栈,ECX保存this指针的方式进行调用,如果成员函数支持可变参数,那么它的调用和__cdecl类似,唯一不同的是将this指针最后压入栈中进行传递。
5.调用函数返回值
以上4种调用约定中,返回值保存方式是一样的。
被调用函数的返回值,按下列规则传递给调用者:
1)如果返回值小于或等于16位,则将其存放在AX寄存器中;
2)如果返回值是32位,则存放在DX,AX寄存器对中,其中DX存储高16位,AX存储低16位;
3)如果返回值大于32位,则存放在静态变量存储区;
AX寄存器存放指向这个存储区的偏移地址;
对于32位far指针,则还利用DX存放段地址。
由此可见,汇编语言子程序向C程序返回处理结果时,是通过AX和DX完成的;
但对于不同长度的返回数据,使用寄存器的情况也不同。
当返回值为char、short、int类型,仅需要使用寄存器AX;
当返回值类型为long时,低16位在AX中,高16位在DX中;
而当返回值类型为float、double时,AX传送存储地址的偏移量;
如果是far指针,DX传送段地址,偏移地址仍然在AX中。
7.4.嵌入汇编指令格式
嵌入汇编方式把插入的汇编语言语句作为C语言的组成部分,不使用完全独立的汇编模块,所以比调用汇编子程序更方便、快捷,并且在大存储模式、小存储模式下都能正常编译通过。
C语言程序中含有嵌入式汇编语言语句时,C编译器首先将C代码的源程序(.c)编译成汇编语言源文件(.asm),然后激活汇编程序TurboAssembler将产生的汇编语言源文件编译成目标文件(.obj),最后激活Tlink将目标文件链接成可执行文件(.exe)。
若要在C语言源程序中嵌入一条汇编语句,则用保留了asm声明,格式如下:
asmmovax,bx
若要在C语言源程序中嵌入一组汇编语句,则需要用括号'
{'
和'
}'
把它们括起来。
asm{
movax,data1
movbx,data2
movdata1,bx
movdata2,ax /*实现整型变量data1和data2之值的交换*/
}
例1:
将字符串中的小写字母转变为大写字母显示
/*upcase.c*/
#include<
stdlib.h>
voidupper(char*dest,char*src)
{asmmovsi,src/*dest和src是地址指针*/
asmmovdi,dest
asmcld
loop:
asmlodsb/*C语言定义的标号*/
asmcmpal,'
a'
asmjbcopy/*转移到copy标号*/
z'
asmjacopy/*不是’a’到’z’之间的字符原样复制*/
asmsubal,20h/*是小写字母转换成大写字母*/
copy:
asmstosb
asmandal,al/*C语言中,字符串用NULL(0)结尾*/
asmjnzloop
}
main()/*主程序*/
{
charstr[]="
ThisStartedOutAsLowercase!
"
;
charchr[100];
upper(chr,str);
/*调用汇编函数*/
printf("
Originstring:
\n%s\n"
str);
UppercaseString:
chr);
编辑完成后,在命令行输入如下编译命令,选项-I和-L分别指定头文件和库函数的所在目录:
TCC-B-Iinclude-Llibupcase.c
生成可执行文件upcase.exe,程序运行后输出的结果将是:
Originstring:
UppercaseString:
THISSTARTEDOUTASLOWERCASE!
7.5.独立汇编模块格式
模块划分:
有的模块用C语言实现,有的模块用汇编语言实现。
每个模块源程序(C源程序或汇编源程序)独立编译为.obj目标文件,之后用链接工具把所有的obj整合到一个exe可执行文件。
模块之间的调用有两种情况,一种是C调用汇编;
另一种是汇编调用C。
对于C语言模块调用汇编语言模块中函数(或使用变量)的情况,声明如下:
1)在C语言模块中,用extern说明要调用汇编模块中的函数(或使用变量)。
其格式如下:
extern
返回值类型
函数名(参数类型表);
变量类型
变量名;
2)在汇编语言模块中,用PUBLIC声明被C语言模块调用的函数(或使用变量)。
PUBLIC_程序标识符(函数名或变量名)
对于汇编语言模块调用C语言模块中函数(或使用变量)的情况,声明如下:
1)在汇编语言模块中,用extern说明要调用C语言模块中的函数(或使用的变量)。
EXTRN
_函数名:
类型(NEAR,FAR)
_变量名:
类型
(BYTE,WORD,DWORD)
2)在C语言模块中,用PUBLIC声明被汇编语言模块调用的函数(或使用的变量)。
函数名/*在C语言模块中,要求全局可见*/
变量名/*在C语言模块中,要求全局可见*/
例2:
C语言程序模块调用汇编语言子程序模块,显示一段信息
/*C语言程序:
main.c*/
externvoiddisplay(void);
/*说明display是外部函数*/
main()
{
display();
汇编语言子程序模块:
display.asm
.modelsmall,c;
存储模式,采用小型存储模式和C语言类型
.data;
数据段
msgdb’Hello,CandAssembly!
’,’$’
.code;
代码段
PUBLIC_display;
外部C模块可以使用本过程
_displayproc;
采用了一致的命名约定
movah,9;
小型模式只有一个数据段,所以不必设置DS
movdx,offsetmsg;
寄存器AX和DX无须保护
int21h
ret
_displayendp
end
例3:
uC/OSII中汇编语言部分。
注意本例中函数和变量的定义格式及程序中的使用。
1.全局和外部引用定义部分
1.1本汇编模块中实现的4个函数(C模块会调用下边四个汇编实现的函数)
PUBLIC_OSTickISR;
定时中断汇编函数
PUBLIC_OSStartHighRdy;
开始执行最高级任务汇编函数
PUBLIC_OSCtxSw;
任务切换汇编函数
PUBLIC_OSIntCtxSw;
中断中任务切换汇编函数
1.2C模块中声明和实现的函数和变量(本汇编模块中要引用它们)
EXTRN_OSIntExit:
FAR;
函数3个
EXTRN_OSTimeTick:
FAR
EXTRN_OSTaskSwHook:
EXTRN_OSIntNesting:
BYTE;
变量7个
EXTRN_OSTickDOSCtr:
BYTE
EXTRN_OSPrioHighRdy:
EXTRN_OSPrioCur:
EXTRN_OSRunning:
EXTRN_OSTCBCur:
DWORD
EXTRN_OSTCBHighRdy:
.MODELLARGE;
存储模式
.CODE;
代码开始
2.1开始执行最高级任务汇编函数实现部分
_OSStartHighRdyPROCFAR
…
_OSStartHighRdyENDP
2.2多任务切换汇编函数实现部分
_OSCtxSwPROCFAR
PUSHA;
保存当前旧任务环境到堆栈中
PUSHES
PUSHDS
MOVAX,SEG_OSTCBCur;
引用C中声明的变量
MOVDS,AX;
LESBX,DWORDPTRDS:
_OSTCBCur;
保存当前旧任务的SS:
SP到当前任务的TCB中,OSTCBCur->
OSTCBStkPtr=SS:
SP
MOVES:
[BX+2],SS
[BX+0],SP
CALLFARPTR_OSTaskSwHook;
调用C中函数
MOVAX,WORDPTRDS:
_OSTCBHighRdy+2;
_OSTCBCur=_OSTCBHighRdy
MOVDX,WORDPTRDS:
_OSTCBHighRdy
MOVWORDPTRDS:
_OSTCBCur+2,AX
_OSTCBCur,DX
MOVAL,BYTEPTRDS:
_OSPrioHighRdy
MOVBYTEPTRDS:
_OSPrioCur,AL
_OSTCBHighRdy;
取出新任务的堆栈指针,SS:
SP=OSTCBHighRdy->
OSTCBStkPtr
MOVSS,ES:
[BX+2]
MOVSP,ES:
[BX]
POPDS;
弹出新任务的环境
POPES
POPA
IRET;
跳到新任务执行
_OSCtxSwENDP
其它汇编函数实现略,参考uC/OSII源程序中OS_CPU_A.ASM文件。
例4.C模块与汇编模块混合调用
/*****main.c模块*****/
externintmaxx(int,int);
/*引用汇编实现的函数*/
intmain(intargc,char*argv[]){
intx;
{_asmmovah,1/*嵌入汇编指令,等待键盘输入*/
_asmint21h
}
x=maxx(8,9);
/*调用汇编函数maxx()*/
printf("
%d"
x);
/*显示返回值*/
return1;
*****sub.asm模块*****;
存储模式为large
.CODE
public_maxx;
外部可见函数_maxx
_maxxprocfar;
_maxx函数实现
pushbp
movbp,sp
movax,[bp+6]
cmpax,[bp+8]
jgeexit
movax,[bp+8]
exit:
popbp
ret
_maxxendp
End
7.6.编译、汇编和连接的方法
步骤一,用MASM汇编ASM语言源程序,生成汇编语言目标文件(.OBJ)。
步骤二,在TURBOC集成环境下建立一个工程文件,该文件包括需要编译连接的C语言源程序和汇编语言目标文件名(.OBJ)。
步骤三,在TURBOC集成环境下,按F9功能键,制作出工程,生成可执行文件(.EXE)。