原作者姓名 沟通无限
正文
汇编,c,c++接口
一、 汇编就这么简单
(1)c语言下,我们说数据,放在数据段,代码放在代码段,非静态的局部变量放在堆栈段,但是我们谁也看不见代码段,数据段和堆栈段,到底它是什么样的呢?我们在c语言源程序是看不到的,那么是不是没有这些段呢?非也,而是编译器为我们生成一种标准的段结构,当我们选定一种模式时编译器就为我们生成某一种标准的段结构。那么段是什么东东呢?汇编语言会帮助你揭开它的神秘的面纱。
(2)汇编语言在编程时可以用两种方式定义段的结构,一种是标准的段定义结构,一种是简化段定义结构,要想完全了解段的结构,最好学标准的段定义结构,当然我们用汇编编写程序的时候就要用简化段定义了,因为标准结构太麻烦。
(3)1.标准段定义格式
段名 SEGMENT 边界类型 结合类型 USE ’类型’
语句
段名 ENDS
段名:顾名思义就是段的名称,名称可以是唯一的也可以和程序中的其他段同名,链接程序把同名的段当作一个段
边界类型:就是段的开始地址位于何处,dos下有五种
byte------表示段的开始地址为字节地址
word-----表示段的开始地址为字地址,也就是从2、4、6字节地址开始dword----
para-------缺省类型,段的开始地址为16字节倍数的地址
page------表示段的开始地址为256的倍数。
结合类型:就是该段与其他段的关系。链接程序可以将不同模块的同名段进行结合,怎么样结合,就要看结合类型。主要有以下几种
none----表明该段是一个独立的段,当结合类型省略时就是这种类型。
Public---表明该段和其他同名的段链接成一个连续的段,公用一个段开始地址
Stack---表明该段为堆栈段,当进行链接是同名的堆栈段链接成一个连续的段,链接方式同public,但不同的是连续的段地址放在,ss段寄存器,当stack类型说明后,ss就自动初始化为堆栈段的段地址。
Common---表示所以该类型的同名段都有相同的段地址,这些同名的段可以相互覆盖,段长度为其中最长的同名段长度。比较一下public就明白和我们c种的联合差不多,其实主要是为了共享。
At地址表达式---表示该段地址以地址表达式的地址为段地址。但是这种方式不能用于代码段。
Use:use16表示段的容量是64k,use32表示段容量为4M。
类型:是用但引号括起来的字符串,以表示该段的类型,代码段(code)数据段(data),堆栈段(stack)等,我们也可以自己定义名字,比如c语言生成的汇编里的_bss、const等,这样链接程序进行链接时,把同类型的段(不一定同名)放在连续的存储区。一般情况下,我们的程序只有一个模块,所以除了stack需要用stack说明外,其他段的结合类型,类型都可以省掉
2 过程的的定义,过程其实就相当于c中函数。
汇编中时这样定义函数的:
过程名 proc [类型]
过程指令
ret
过程名 endp
类型表示过程时32 far位调用,还时16 near位调用,ret表示返回的意思。
代码段可以看成由很多过程组成。
这样一个标准的c程序翻译成汇编程序就是这样
Stack segment stack
….
Stack ends
Data segment
…..
data ends
code segment
main proc far
assme cs:code,ds:data,ss:stack
mov ax,data
mov ds,ax
…….
……
ret
main endp
code ends
end main//定义函数入口;
注意:这里assme表示意思是段寄存器约定标准,比如这里规定code段要放进cs寄存器,其实并没有放进去后来不需要放code到cs是因为系统为我们把code和stack的放入了cs和,ss。
汇编语言工作原理:
程序初始化以后,cs寄存器指向程序的代码段地址, ss指向堆栈段,ip指向定义的函数入口。这样程序执行入口那条指令,然后ip+1执行第二条指令,如果遇到不是立即数的数据,就寻址也就是找到数据所在的段地址和偏移地址把数据读出来进行处理,如果是通过堆栈传递的参数,就通过bx,和sp进行读取。所以讲学了c学汇编其实就是理解寻址,参数传递,寄存器的使用,和记汇编指令,汇编语言就这么简单。如果你没有学过汇编而且想在你的程序中嵌入汇编,那么不进行一番学习是不行的,以上只是告诉你汇编并不是很难,并不是说看了以上就会汇编语言程序设计。我用了不到一个月就学明白了,我想任何人也能做到,甚至会做的更快,更好。
(4)简化段定义程序结构
首先要明白,为什么要出现简化段定义呢?是不是仅仅为了简化呢?用标准定义也可以省略大部分选项。简化段定义主要目的是为了兼容性问题,当c语言刚出现的时候很多用汇编语言一般情况下c语言可以处理大部分问题,但是有些问题,必须用汇编来处理。这样就涉及到c和汇编的接口性问题。选定了了一种内存模式,那么c就产生某一种段的结构,如果想在c中调用汇编,那么汇编的段结构(段名,边界类型,结合类型,类型等)也必须与c编译系统产生的段结构一致。这才是简化段出现的真正目的。
简化段定义伪指令
.stack
.data,.data?
.fardata,.fardata?
.const
.stack他们表示一个段的开始,同时说明前一个段的结束,若这个段是程序的最后一个段,则该段以end伪指令结束。这些伪指令的使用格式如下:
.stack 长度
.code 名字
.data
.data?
.fardata 名字
.fardata? 名字
.const
用伪指令.stack
.const
.data
.data?存放在一个叫dgroup的段组中,所谓的段组就是这些段共同使用一个开始地址,
用
.fardata 名字
.fardata? 名字
定义的段中的数据不放在任何段组中。
段组定义伪指令
名字 group 段名1,段名2,…….
段组中各段间不一定是连续的,也可能中间擦入不是该段组中的其他段。段组中的第一个段的首字节到最后一个段的末尾字节其距离不能超过65535个字节,即不能超过64k。
模式定义伪指令
定义了六种模式:
tiny,
small,
medium,
compact,
large,
huge
使用简化段定义伪指令时,每个段都有一个缺省名。当整个程序都使用简化段定义时,段的名字没有必要知道。若标准的段定义和简化的段定义在程序中都出现,则可能需要知道各个段在不同内存模式下的缺省名字(name)。边界类型,结合类型,类型,及段组名。从表中可以看出,在中内存模式和大内存模式下,.code伪指令表示的缺省名字是name_text,fardata和fardata?使用的缺省名在各种模式下可以替换。
在使用简化段定义的时候,实际上已经完成了如下的语句:
小和紧凑模式下
assume cs:_text,ds:dgroup,ss:dgroup
中,大,巨的时候
assume cs:name_text,ds:dgroup,ss:dgroup
当一个程序用到所有的段时
则完成
dgoup group _data,const,_bss,stack
各种内存模式下的缺省段名及类型和tc编译模式下缺省段名及类型是一致的。
段次序定义伪指令:
.alpha-----按字母序对段排序
.seq-------按段出现的顺序
.dossseg―按dos定义的段序来排序段
1. 类型为“code”的段
2. 不是“code”的段,且不是dgroup段组中的独立段
3. 属于dgroup中的段,他们次序是1)begdata的段(微软保留的段类型名)2)不是begdata,bas,stack的段3)bss类型段4)stack段。一般只需要dosseg就可以拉
所以上面的标准汇编程序可以翻译成如下简化汇编程序
dosseg
.model small
.386
.stack 100h
.data
………
.code
main proc far
push ds
sub ax,ax
push ax
mov ax, @data
mov ds,ax
……..
ret
main endp
end main
二.变量和函数的相互使用、参数的传递与寄存器的使用
1.c语言可以调用汇编的子程序,变量汇编语言也可以调用c中的函数和变量,只要它们以合适的方式声明.
由于c语言源程序中的变量和函数在编译成目标文件时,编译器自动在函数和变量前边加上了下划线.所以汇编语言调用c语言中的函数和变量时,应该在函数和变量前加下划线并且在汇编语言的开始部分,对调用的函数和变量用
extern _函数名:函数类型(32位下没有远近之分)
extern _变量名:变量类型
c语言和汇编语言的数据类型对应关系
char db byte 1
int dw word 2
long dd dword 4
float double dq dword 8
例如
int mothed(void);
long m;
如果在汇编中要使用result和调用mothed
需要这样声明
extern _mothed: 类型(near或far 32位下不用)
extern _m:dword
若c中要调用汇编语言的过程和变量,在汇编中要用public进行说明,函数名和变量名要带有下划线
例如:
public _mothed
public _m
在c中应该将其说明为extern
例如
extern int mothed(void);
extern m;
需要的是在汇编中是大小写不分的,而在c中是区分的.
2.参数的传递
程序设计中一般有两种参数传递类型寄存器和堆栈,一般情况主程序传递参数用堆栈,有pascal,c,stdcal三种类型堆栈参数传递标准.c和stdcal传递参数是先把右边第一个的参数压堆栈依次到左边第一个,pascal方式正好相反.一般c编译系统用的是stdcal这种方式和c的区别主要是堆栈平衡由子程序还是主程序来处理,pascal和stdcal规定堆栈平衡由子程序来处理,但是c的方式刚好相反,一般c编译系统参数传递方式都是stdcal但是堆栈平衡的工作一般由编译器处理,我们只需要ret就可以拉.在进行压堆栈时一般要进行转化,char转化成整型,无符号字符转化成无符号整型,浮点数转化成双精度,结构是按成员反向压入,树组压入指向数组的指针.
函数的返回值放在ax,dx或者eax,edx中返回值可能是值,也可能是指针
寄存器的使用:
汇编下要用bx来保存堆栈基址,用来访问传递来的参数,ax,eax和dx,edx用来返回值.cx一般可以任意使用,至于其他的寄存器最好使用的时候push把原始值保存起来.返回时pop.
以上主要是讲在turboc下的调用,
在32位下嵌入汇编简单多了,因为win32程序只有一种模式flat,与16位最简单模式小模式一个样.
test.cpp
#include<stdio.h>
extern "C" int mothed(int m);
void main()
{
int m =0;
mothed(m);
return;
}
*************************************************************
test.asm
.386
public _mothed
_text segment byte public 'code'
assume cs:_text
_mothed proc near
push ebp
mov ebp,esp
mov eax,[ebp+8] ;此处ebp+8为传入的参数m
pop ebp
ret
_mothed endp
_text ends
end
首先建立一个空vc控制台程序test
然后建立一个汇编原文件test.asm
找到32位masm汇编编译器(网上有masm32开发包里就有)
编译出test.obj文件
ml /c /coff test.asm
把生成的test.obj复制到工程目录下
然后打开vc工程菜单,点击设置/link选项下的project options 加入 test.obj,就可以编译成功拉!
发几个可以在tc下调用的汇编程序
.model small
.code
public _min_num
_min_num proc near
push bp
mov bp,sp
mov ax,0
mov cx,[bp+4]
cmp cx,ax
jle exit
mov ax,[bp+6]
jmp ltest
comp: cmp ax,[bp+6]
jle ltest
mov ax,[bp+6]
ltest: add bp,2
loop comp
exit: pop bp
ret
_min_num endp
end
#include<stdio.h>
int extern min_num(int count,int v1,int v2,int v3,int v4,int v5);
main()
{
int i=0;
i=min_num(5,7,0,6,1,12);
printf("the mininum of five is %d\n",i);
}
2。
.model large
public _num
.code
_num proc far
push bp
mov bp,sp
mov ax,[bp+8]
mov es,ax
mov bx,[bp+6]
mov ax,[bp+10]
cmp ax,9
ja exit
add ax,30h
mov es:[bx],ax
mov ax,0
exit: pop bp
ret
_num endp
end
#include<stdio.h>
int far num(char far *ch,int k);
main()
{
int in;
char ch;
do
{
printf("\nplease input");
scanf("%d",&in);
getchar();
}while(num(&ch,in));
printf("\n%c",ch);
}
3
.model medium
public _sort_num
.code
_sort_num proc far
push bp
mov bp,sp
push ds
push si
push bx
mov ds,[bp+8]
mov bl,0ffh
a1: cmp bl,0
je a4
xor bl,bl
mov cx,word ptr[bp+10]
dec cx
mov si,word ptr[bp+6]
a2: mov ax,word ptr[si]
cmp ax,word ptr[si+2]
jbe a3
xchg word ptr[si+2],ax
mov word ptr[si],ax
mov bl,0ffh
a3: inc si
inc si
loop a2
jmp a1
a4: pop bx
pop si
mov dx,word ptr[bp+8]
mov ax,word ptr[bp+6]
pop ds
mov sp,bp
pop bp
ret
_sort_num endp
#include<stdio.h>
int far *sort_num(int far *,int);
void main(void)
{
register int i;
int a[10];
int far *b;
for(i=0;i<10;i++)a[i]=10-i;
for(i=0;i<10;i++)
printf("%d",a[i]);
b=sort_num((int far *)a,10);
printf("\n");
for(i=0;i<10;i++)
printf("%d",*b++);
b--;
}
tc下调用汇编最好最简单就是集成开发环境中进行编译,一般先建立c源程序和汇编子程序把汇编语言用masm编译成xxx.obj文件,然后重新建立一个新文件,选择project下的project name写入一个新的项目文件名,然后选择file下的write to 中写入我们在project name中写入的项目名,还要选择option下compiler中选择适当的模式,linker下把最后一项选择off关闭大小写相同.在项目文件中写入c源文件名,和编译好的目标文件名.就可以编译成功了.
-- 作者: Bison
-- 发布时间: 2002/07/12 05:12pm
用VC写汇编有两种方式,1是传统的用MASM32的ML.exe做编译器汇编纯粹ASM文件,比如VC工程里有几个ASM文件,你在file view面板里右键点击一个ASM文件,在弹出菜单里选Custom
Build选项,弹出Custom Build对话筐,在里面输入Ml的命令行就可以了,这中方式写ASM文件的语法和用MASM32完全一样(你可以得到一个程序使用多个ASM的好处),即用关键字标示语句、语句块等和你在MSAM里完全一样, include 、includelib头文件是用MASM原带的头文件(这种编译方式也适用与C++和ASM文件混合编译)
2是CPP文件里用汇编,这种方式include是用VC带的h头文件,定义变量是用C++的方式,采用内嵌汇编
例子: void MyWinMain()
{ char* msg="Hello World!" //代替asm的字符定义方式
_asm{ push 0
push msg ;注意,asm里可以使用c++的变量
push 0
push 0
call dowrd ptr[MessageBoxA]
}
}link的编译选项里把entry设置为MyWinMain
用vc内嵌汇编写的好处是调用API可以直接比如MessageBox(0,msg,0,0);效果和用汇编完全一样,而且API有自动弹出提示
用tcc下进行编译和连接的时候,经常出现这样那样的问题。这是由于我们一般情况下用的tc开发系统是直接解压缩的。没有进行安装。如果要用tcc进行编译,连接一定要下载安装版的,在命令行下进行安装之后就可以用了。用tcc自动生成中间汇编的时候,哪个-S是大写字母。
一、汇编调用c
1。对调用的函数和变量用extern 说明,当是near时,extern说明可以放在代码段中,若是far型的要放在所有段之前。exrern mothed:near
2。对汇编语言中使用的c函数中的变量也要extern说明例如:int i;extern _:word
3.汇编语言中的调用c函数,要加下划线;同理当用巨模式时候要把声明放到所有段之前。
call _mothed
4.参数传递。可以有两种一种是值传递,一种是堆栈传递。
二、
c与c++的接口
本质上c++和c接口表现在它们产生的函数名称不一样,这个问题比较好理解,c++允许函数名重载,这样肯定是函数名和参数一起产生一个标号。因为编译器不会像人一样进行判断。
那么我们要在c中调用c++就让c++中的函数按c的方式生成。在c++中的函数加extern "C"就、可以拉。同样在c++对调用c中的函数用extern “c”进行声明就可以了。
理解了c与c++接口,也就理解了c++和汇编的接口
这样我们可以在汇编中调用c,c中调用汇编,c中调用c++,c++中调用c,c++中调用汇编。
是不是觉得很灵活呢?
总算说完了。过两天回老家过年。一个月可能不上论坛,大家新年快乐。
#include<stdio.h>
同一个程序,按c方式与c++方式产生的汇编代码
void mothed(void);
void main()
{
mothed();
}
void mothed(void)
{
printf(" hello c and c++");
}
按照c方式产生的汇编代码
TITLE g:\test.c
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC _mothed
PUBLIC _main
_TEXT SEGMENT
_main PROC NEAR
; File g:\test.c
; Line 5
push ebp
mov ebp, esp
; Line 6
call _mothed
; Line 8
pop ebp
ret 0
_main ENDP
_TEXT ENDS
EXTRN _printf:NEAR
_DATA SEGMENT
$SG340 DB ' hello c and c++', 00H
_DATA ENDS
_TEXT SEGMENT
_mothed PROC NEAR
; Line 10
push ebp
mov ebp, esp
; Line 11
push OFFSET FLAT:$SG340
call _printf
add esp, 4
; Line 12
pop ebp
ret 0
_mothed ENDP
_TEXT ENDS
END
按c++方式产生的汇编代码
TITLE g:\test1.cpp
.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
FLAT GROUP _DATA, CONST, _BSS
ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC ?mothed@@YAXXZ ; mothed
PUBLIC _main
_TEXT SEGMENT
_main PROC NEAR
; File g:\test1.cpp
; Line 5
push ebp
mov ebp, esp
; Line 6
call ?mothed@@YAXXZ ; mothed
; Line 8
pop ebp
ret 0
_main ENDP
_TEXT ENDS
EXTRN _printf:NEAR
_DATA SEGMENT
$SG531 DB ' hello c and c++', 00H
_DATA ENDS
_TEXT SEGMENT
?mothed@@YAXXZ PROC NEAR ; mothed
; Line 10
push ebp
mov ebp, esp
; Line 11
push OFFSET FLAT:$SG531
call _printf
add esp, 4
; Line 12
pop ebp
ret 0
?mothed@@YAXXZ ENDP ; mothed
_TEXT ENDS
END
从上面c和c++产生的汇编代码分析可以看出,函数标号被改变了。
c中mothed变成
; Line 6
call _mothed
c++中则变成
;Line 6
call ?mothed@@YAXXZ
正文完
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)