汇编中编写stdcall的子程序时候,不是要自己调整堆栈吗?那么最后应该写成add esp, para_num*4 ret或者ret para_num*4啊。但为什么还是直接写ret呢?那谁来调整堆栈?还有,我上面的写法对吗?望指正。
当用invoke来调用stdcall 的API时,如果用call来代替,该怎么写?
13 个解决方案
#1
这是个简单例子:
/* C语言程序:a1.c */
extern asub();
main()
{ asub(); /* 调用汇编语言子程序 */
}
csub(char * str) /* C语言函数,str是地址参数 */ { printf("%s\n",str);
}
; 汇编语言程序:a1s.asm
.model small,c
extern csub:near
.data
astring db ’OK, Assembly !’,0dh,0ah,’$’
cstring db ’Good, Turbo C 2.0 !’,0
.code
PUBLIC asub
asub proc
mov dx,offset astring ;汇编语言子程序显示信息
mov ah,09h
int 21h
mov ax,offset cstring ;得到字符串的偏移地址
push ax ;压入调用参数
call csub ;调用C函数
add sp,2 ;平衡堆栈
ret
asub endp
end
/* C语言程序:a1.c */
extern asub();
main()
{ asub(); /* 调用汇编语言子程序 */
}
csub(char * str) /* C语言函数,str是地址参数 */ { printf("%s\n",str);
}
; 汇编语言程序:a1s.asm
.model small,c
extern csub:near
.data
astring db ’OK, Assembly !’,0dh,0ah,’$’
cstring db ’Good, Turbo C 2.0 !’,0
.code
PUBLIC asub
asub proc
mov dx,offset astring ;汇编语言子程序显示信息
mov ah,09h
int 21h
mov ax,offset cstring ;得到字符串的偏移地址
push ax ;压入调用参数
call csub ;调用C函数
add sp,2 ;平衡堆栈
ret
asub endp
end
#2
对于汇编中的函数(PROC)同样有修饰符比如syscall,c,stdcall 楼主在与C语言对接时不要搞错了。
#3
谢谢楼上2位,可否进一步回答一下我的疑问?
#4
顶一下,等待解答!
#5
invoke ->call
先自己压栈然后call
主要是注意stdcall cdecl fastcall等的差异
这个google一下就知道了
先自己压栈然后call
主要是注意stdcall cdecl fastcall等的差异
这个google一下就知道了
#6
睡前顶一下,谢谢各位,现在只剩下第一个问题了。
#7
为什么没高手给解释下呢?难道标题不够吸引人?
#8
stdcall 一般情况下是不需要调整堆栈的(见下面说明),调整堆栈的概念来自于c。在使用masm宏汇编伪指令的情况下,调整堆栈一般都是通过伪指令来完成的,用户并不需要关心堆栈的平衡问题,这也就是使用宏汇编所带来的最大好处,许多人都认为使用invoke不如直接使用call来得直观,但实际上在proto、invoke、proc等伪指令自动完成了堆栈平衡。其中proc伪指令更是为使用者建立了局部变量的概念。
这是proc的语法
Syntax: label PROC [distance] [langtype] [visibility]
[<prologuearg>]
[USES reglist] [,parameter [:tag]]...
[LOCAL varlist]
statements
label ENDP
[distanc] 距离可选项 near, near16, near32, far, far16, far32
[langtype] 调用类型选项 C , syscall , stdcall , basic , fortran , pascall
[visibility] 可见性 PRIVATE, PUBLIC, EXPORT
[<prologuearg>] 框架 可选择缺省框架 , 无框架 , 用户自建框架 ,一般很少使用
[USES reglist] 使用寄存器 完成保护寄存器的作用,即在程序头 push 程序位 pop 所指定的寄存器
[,parameter [:tag]]... 入口参数和类型,对于 C , syscall , stdcall 可以使用 VARARG (不定长参数),其中 stdcall 需要根据变长参数来自己平衡堆栈
[LOCAL varlist] 局部变量
下面我们将以一个简单的冒泡排序bubble来详细说明这些伪指令具体起了什么作用:
首先我们选择远程 stdcall 调用,代码中 uses 选项指令的需要保存的寄存器,并指定了两个入口参数,local 指定了两个局部变量。因为局部变量是建立在堆栈框架内,所以子程序返回时自动清除内存。
dseg segment para 'data'
Source dw 50,90,20,80,100,10,0
Len equ ($ - Source) / 2 -1
dseg ends
public bubble
cseg segment para 'code'
assume cs:cseg
; 入口 pSource: 排序数据起始地址
; sLen: 排序数据长度
;返回 pSource: 已经排序的数据
bubble proc far stdcall uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
mov ax, dseg
mov es, ax
mov ds, ax
assume ds:dseg, es:dseg
mov di, pSource
mov start_addr, di
mov cx, sLen
mov save_cnt, cx
init:
mov bx, 1
dec save_cnt
jz sorted
mov cx, save_cnt
mov di, start_addr
next:
mov ax, es:[di]
cmp es:[di + 2], ax
jae cont
xchg es:[di + 2], ax
mov es:[di], ax
sub bx, bx
cont:
add di, 2
loop next
cmp bx,0
je init
sorted:
assume ds:nothing, es:nothing
ret
bubble endp
cseg ends
text segment para 'code'
start:
invoke bubble, addr Source, Len
int 20h
text ends
end start
将上面的代码编译链接后生成可执行文件bubble.exe。我们使用IDA来具体分析bubble.exe,看看这些伪指令具体坐了些什么事。
len = 6
dseg segment byte stack 'data' use16
assume cs:dseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
Source dw 32h, 5Ah, 14h, 50h, 64h, 0Ah, 0, 0; 0
dseg ends
; ==========================================================================
cseg segment byte public 'CODE' use16
assume cs:cseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
; *************** S U B R O U T I N E ***************************************
bubble proc far
start_addr = word ptr -4
save_cnt = word ptr -2
pSource = word ptr 6
sLen = word ptr 8
push bp ; / proc 伪指令产生的以bp位堆栈指针的堆栈框架
mov bp, sp ; | 堆栈框架的顶部
add sp, -4 ; \ 堆栈框架的深度,这里因为使用了两个局部变量,所以 -4
push ds ; /
push es ; |
push cx ; | uses 选项产生的代码 push xx
push bx ; |
push di ; \
mov ax, seg dseg
mov es, ax
assume es:dseg
mov ds, ax
assume ds:dseg
mov di, [bp+pSource] ; 入口参数 pSource,即 [bp + 6],由 mov ax, len; push ax 在堆栈上产生
mov [bp+start_addr], di ; 局部变量 start_addr,即 [bp - 4],由 sp - 4 在堆栈框架内产生
mov cx, [bp+sLen] ; 入口参数 sLen,即 [bp +8],由 lea ax, Source; push ax 在堆栈上产生
mov [bp+save_cnt], cx ; 局部变量 save_cnt,即 [bp - 2],由 sp - 4 在堆栈框架内产生
init:
mov bx, 1
dec [bp+save_cnt]
jz short sorted
mov cx, [bp+save_cnt]
mov di, [bp+start_addr]
next:
mov ax, es:[di]
cmp es:[di+2], ax
jnb short cont
xchg ax, es:[di+2]
mov es:[di], ax
sub bx, bx
cont:
add di, 2
loop next
cmp bx, 0
jz short init
sorted:
pop di ; /
pop bx ; |
pop cx ; | uses 选项产生的代码 pop xx
pop es ; |
assume es:nothing ; |
pop ds ; \
assume ds:nothing
mov sp, bp ; / 恢复堆栈指针,即清除局部变量,释放堆栈
pop bp ; \ 由endp伪指令产生的代码
retf 4 ; 由 far 选项产生 retf,由入口产生的长度决定堆栈平衡的长度 4
bubble endp
align 10h
cseg ends
; ==========================================================================
text segment byte public 'CODE' use16
assume cs:text
assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
start proc near
mov ax, len ; / 由 invoke bubble, addr Source, Len 伪指令最右面的参数
push ax ; \ Len 产生的代码
lea ax, Source ; / addr 指示符产生 lea ax, Source,即指针
push ax ; \ 和 mov ax, offset Source 效果相同,取决于编译器
; C,syscall,stdcall调用,参数入栈顺序由右至左
call bubble ; 参数入栈后开始调用,这里是远程调用,由子程序的 far 决定
; 先压段地址,再压返回地址,相当于 push cs; push ip+5, jmp bubble
int 20h
start endp
text ends
end start
待续......
#9
从上面IDA的分析可以看出,stdcall 的栈平衡是通过返回指令来完成的,即 retf 4,而堆栈平衡的深度 4,则是由这些伪指令在编译时根据入口参数的长度自动设定的,用户不需要自己计算栈平衡。
同样的代码,我们来看看 C 调用格式有什么不同,下面我们省略了和前面相同的代码:
其实整个程序只是将 stdcall -> C ,其他都保持原样。
下面通过IDA,我们来看看 C 调用和stdcall到底有什么不同:
从上面两个实例中,我们可以看出,stdcall 和 C 调用格式,参数入栈顺序完全相同,都是由右至左,顺序入栈。其中stdcall由子程序在返回指令中获得堆栈平衡,而 C 调用则由 call 返回后,直接通过 add sp, ?? 来平衡。而这些平衡过程都是由伪指令通过编译器自动完成。
接着我们来看看 pascal 调用的方法,同样只需要将 C -> pascall ,然后编译链接即可:
使用IDA分析bubble.exe结果如下:
从上面的分析,可以看出,pascal 调用和 C, stdcall调用,参数入栈顺序相反,由左至右,而堆栈平衡方法和 stdcall 相同。
至于其他的调用方式由什么不同,大家可以自己去检验,我就不多罗嗦了。
这个问题从上面的几个实例中都有显示,这里就不多说了。
前几天那个相杀人的家伙也提有类似的问题,我回答敲字手都敲痛了,结果一分不给,太令人失望了。
http://topic.csdn.net/u/20090730/20/2dc25c2f-fb3b-4048-8c03-81e730f8170b.html
同样的代码,我们来看看 C 调用格式有什么不同,下面我们省略了和前面相同的代码:
bubble proc far c uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
......
......
ret
bubble endp
text segment para 'code'
start:
; 注意这里的C调用,和前面的stdcall调用是完全一样的
invoke bubble, addr Source, Len
int 20h
text ends
end start
其实整个程序只是将 stdcall -> C ,其他都保持原样。
下面通过IDA,我们来看看 C 调用和stdcall到底有什么不同:
cseg segment byte public 'CODE' use16
assume cs:cseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt = word ptr -2
pSource = word ptr 6
sLen = word ptr 8
push bp
mov bp, sp
add sp, -4
......
......
pop di
pop bx
pop cx
pop es
assume es:nothing
pop ds
assume ds:nothing
mov sp, bp
pop bp
retf ; 由 far 指示直接生成 retf 指令,不过没有对入口参数进行堆栈平衡
bubble endp
cseg ends
test segment byte public 'CODE' use16
assume cs:test
assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
public start
start proc near
mov ax, Len
push ax
lea ax, Source
push ax
call bubble
add sp, 4 ; 在这里进行入口参数平衡,这就是 C 调用模式的堆栈平衡方法
; 这段代码由 invoke 根据C调用指引自动产生,无需用户干涉
int 20h
start endp
test ends
end start
从上面两个实例中,我们可以看出,stdcall 和 C 调用格式,参数入栈顺序完全相同,都是由右至左,顺序入栈。其中stdcall由子程序在返回指令中获得堆栈平衡,而 C 调用则由 call 返回后,直接通过 add sp, ?? 来平衡。而这些平衡过程都是由伪指令通过编译器自动完成。
接着我们来看看 pascal 调用的方法,同样只需要将 C -> pascall ,然后编译链接即可:
bubble proc far pascal uses ds es cx bx di, pSource:word, sLen:word
使用IDA分析bubble.exe结果如下:
cseg segment byte public 'CODE' use16
assume cs:cseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt = word ptr -2
sLen = word ptr 6 ; / 注意这里的次序
pSource = word ptr 8 ; \ 先 sLen 后 pSource,和前面的两个调用相反
push bp
mov bp, sp
add sp, -4
push ds
push es
push cx
push bx
push di
......
......
pop di
pop bx
pop cx
pop es
assume es:nothing
pop ds
assume ds:nothing
mov sp, bp
pop bp
retf 4 ; 栈平衡方法和 stdcall 完全相同
bubble endp
cseg ends
test segment byte public 'CODE' use16
assume cs:test
assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
public start
start proc near
lea ax, Source ; 入栈顺序由左至右,和 C,stdcall相反
push ax
mov ax, Len
push ax
call bubble
int 20h
start endp
test ends
end start
从上面的分析,可以看出,pascal 调用和 C, stdcall调用,参数入栈顺序相反,由左至右,而堆栈平衡方法和 stdcall 相同。
至于其他的调用方式由什么不同,大家可以自己去检验,我就不多罗嗦了。
这个问题从上面的几个实例中都有显示,这里就不多说了。
前几天那个相杀人的家伙也提有类似的问题,我回答敲字手都敲痛了,结果一分不给,太令人失望了。
http://topic.csdn.net/u/20090730/20/2dc25c2f-fb3b-4048-8c03-81e730f8170b.html
#10
to gzgzlxg:好长!不过非常感谢,现在对堆栈平衡还有伪指令的辅助作用总算了解了。但第一个问题还是没搞清楚:
csource.h //c头文件
...
int func_c(int);
csource.c //c源文件
extern int func_asm(int,int);
void myown()
{
if(func_asm(2,3)) //call
.....
}
asmsource.asm //汇编源文件
func_c proto c :dword
func_asm proc c arg1:dword,arg2:dword
...
call func_c //call
func_asm endp
如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。
csource.h //c头文件
...
int func_c(int);
csource.c //c源文件
extern int func_asm(int,int);
void myown()
{
if(func_asm(2,3)) //call
.....
}
asmsource.asm //汇编源文件
func_c proto c :dword
func_asm proc c arg1:dword,arg2:dword
...
call func_c //call
func_asm endp
如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。
#11
我可不可以这样理解:
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。
#12
我怎么都读不明白你到底要问什么问题,“ 如上,这样互相调用。名字都是一样的。”--什么名字是一样的,看不懂。
这里也一样,“ 从C编译成汇编,只对变量名进行转换(加下划线)......”--加下划线?那是编译器和链接器之间的协议,爱加什么加什么,你管这些干什么,不能理解。
很抱歉,无法回答你的问题。
不论是 c 调用汇编,还是汇编调用 c ,只要调用申明一致即可,即都使用一种 langtype,如最常使用的 cdecl (缩写 c) 或其他的调用申明,只要两边一致即可。1#楼给出的例子已经写的很清楚了,其中 c 使用的是缺省调用申明,即cdecl,所以没有显式申明,而汇编中在第一句
.model small, c ; 那个 c ,就是 cdecl 调用规则的申明。
#13
不知道你结了没,如果没结也不要结给我,我只是路过学习了一下,把我所了解的说一下
1、变量名字的互相引用是要加下划线的:这是因为对于外部变量,你只用extern声明,并没有声明编译语言,而如果这个变量是在C源文件里定义的,那在编译的时候就会按C命名习惯来对其重新命名如
Var---->_Var 但如果个变量是在汇编源文件里定义的,那么在编译的时候就肯定会按汇编编译器的命名习惯来对其命名
2、对于函数名却不一样,因为你在声明函数的时候已经指明了 语言类型,就是前面说的c,stdcall,pascl等,这样的话,编译器在看到这个声明的时候就会自动根据你声明的语言类型所用的命名习惯来对函数名命名
1、变量名字的互相引用是要加下划线的:这是因为对于外部变量,你只用extern声明,并没有声明编译语言,而如果这个变量是在C源文件里定义的,那在编译的时候就会按C命名习惯来对其重新命名如
Var---->_Var 但如果个变量是在汇编源文件里定义的,那么在编译的时候就肯定会按汇编编译器的命名习惯来对其命名
2、对于函数名却不一样,因为你在声明函数的时候已经指明了 语言类型,就是前面说的c,stdcall,pascl等,这样的话,编译器在看到这个声明的时候就会自动根据你声明的语言类型所用的命名习惯来对函数名命名
#1
这是个简单例子:
/* C语言程序:a1.c */
extern asub();
main()
{ asub(); /* 调用汇编语言子程序 */
}
csub(char * str) /* C语言函数,str是地址参数 */ { printf("%s\n",str);
}
; 汇编语言程序:a1s.asm
.model small,c
extern csub:near
.data
astring db ’OK, Assembly !’,0dh,0ah,’$’
cstring db ’Good, Turbo C 2.0 !’,0
.code
PUBLIC asub
asub proc
mov dx,offset astring ;汇编语言子程序显示信息
mov ah,09h
int 21h
mov ax,offset cstring ;得到字符串的偏移地址
push ax ;压入调用参数
call csub ;调用C函数
add sp,2 ;平衡堆栈
ret
asub endp
end
/* C语言程序:a1.c */
extern asub();
main()
{ asub(); /* 调用汇编语言子程序 */
}
csub(char * str) /* C语言函数,str是地址参数 */ { printf("%s\n",str);
}
; 汇编语言程序:a1s.asm
.model small,c
extern csub:near
.data
astring db ’OK, Assembly !’,0dh,0ah,’$’
cstring db ’Good, Turbo C 2.0 !’,0
.code
PUBLIC asub
asub proc
mov dx,offset astring ;汇编语言子程序显示信息
mov ah,09h
int 21h
mov ax,offset cstring ;得到字符串的偏移地址
push ax ;压入调用参数
call csub ;调用C函数
add sp,2 ;平衡堆栈
ret
asub endp
end
#2
对于汇编中的函数(PROC)同样有修饰符比如syscall,c,stdcall 楼主在与C语言对接时不要搞错了。
#3
谢谢楼上2位,可否进一步回答一下我的疑问?
#4
顶一下,等待解答!
#5
invoke ->call
先自己压栈然后call
主要是注意stdcall cdecl fastcall等的差异
这个google一下就知道了
先自己压栈然后call
主要是注意stdcall cdecl fastcall等的差异
这个google一下就知道了
#6
睡前顶一下,谢谢各位,现在只剩下第一个问题了。
#7
为什么没高手给解释下呢?难道标题不够吸引人?
#8
stdcall 一般情况下是不需要调整堆栈的(见下面说明),调整堆栈的概念来自于c。在使用masm宏汇编伪指令的情况下,调整堆栈一般都是通过伪指令来完成的,用户并不需要关心堆栈的平衡问题,这也就是使用宏汇编所带来的最大好处,许多人都认为使用invoke不如直接使用call来得直观,但实际上在proto、invoke、proc等伪指令自动完成了堆栈平衡。其中proc伪指令更是为使用者建立了局部变量的概念。
这是proc的语法
Syntax: label PROC [distance] [langtype] [visibility]
[<prologuearg>]
[USES reglist] [,parameter [:tag]]...
[LOCAL varlist]
statements
label ENDP
[distanc] 距离可选项 near, near16, near32, far, far16, far32
[langtype] 调用类型选项 C , syscall , stdcall , basic , fortran , pascall
[visibility] 可见性 PRIVATE, PUBLIC, EXPORT
[<prologuearg>] 框架 可选择缺省框架 , 无框架 , 用户自建框架 ,一般很少使用
[USES reglist] 使用寄存器 完成保护寄存器的作用,即在程序头 push 程序位 pop 所指定的寄存器
[,parameter [:tag]]... 入口参数和类型,对于 C , syscall , stdcall 可以使用 VARARG (不定长参数),其中 stdcall 需要根据变长参数来自己平衡堆栈
[LOCAL varlist] 局部变量
下面我们将以一个简单的冒泡排序bubble来详细说明这些伪指令具体起了什么作用:
首先我们选择远程 stdcall 调用,代码中 uses 选项指令的需要保存的寄存器,并指定了两个入口参数,local 指定了两个局部变量。因为局部变量是建立在堆栈框架内,所以子程序返回时自动清除内存。
dseg segment para 'data'
Source dw 50,90,20,80,100,10,0
Len equ ($ - Source) / 2 -1
dseg ends
public bubble
cseg segment para 'code'
assume cs:cseg
; 入口 pSource: 排序数据起始地址
; sLen: 排序数据长度
;返回 pSource: 已经排序的数据
bubble proc far stdcall uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
mov ax, dseg
mov es, ax
mov ds, ax
assume ds:dseg, es:dseg
mov di, pSource
mov start_addr, di
mov cx, sLen
mov save_cnt, cx
init:
mov bx, 1
dec save_cnt
jz sorted
mov cx, save_cnt
mov di, start_addr
next:
mov ax, es:[di]
cmp es:[di + 2], ax
jae cont
xchg es:[di + 2], ax
mov es:[di], ax
sub bx, bx
cont:
add di, 2
loop next
cmp bx,0
je init
sorted:
assume ds:nothing, es:nothing
ret
bubble endp
cseg ends
text segment para 'code'
start:
invoke bubble, addr Source, Len
int 20h
text ends
end start
将上面的代码编译链接后生成可执行文件bubble.exe。我们使用IDA来具体分析bubble.exe,看看这些伪指令具体坐了些什么事。
len = 6
dseg segment byte stack 'data' use16
assume cs:dseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
Source dw 32h, 5Ah, 14h, 50h, 64h, 0Ah, 0, 0; 0
dseg ends
; ==========================================================================
cseg segment byte public 'CODE' use16
assume cs:cseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
; *************** S U B R O U T I N E ***************************************
bubble proc far
start_addr = word ptr -4
save_cnt = word ptr -2
pSource = word ptr 6
sLen = word ptr 8
push bp ; / proc 伪指令产生的以bp位堆栈指针的堆栈框架
mov bp, sp ; | 堆栈框架的顶部
add sp, -4 ; \ 堆栈框架的深度,这里因为使用了两个局部变量,所以 -4
push ds ; /
push es ; |
push cx ; | uses 选项产生的代码 push xx
push bx ; |
push di ; \
mov ax, seg dseg
mov es, ax
assume es:dseg
mov ds, ax
assume ds:dseg
mov di, [bp+pSource] ; 入口参数 pSource,即 [bp + 6],由 mov ax, len; push ax 在堆栈上产生
mov [bp+start_addr], di ; 局部变量 start_addr,即 [bp - 4],由 sp - 4 在堆栈框架内产生
mov cx, [bp+sLen] ; 入口参数 sLen,即 [bp +8],由 lea ax, Source; push ax 在堆栈上产生
mov [bp+save_cnt], cx ; 局部变量 save_cnt,即 [bp - 2],由 sp - 4 在堆栈框架内产生
init:
mov bx, 1
dec [bp+save_cnt]
jz short sorted
mov cx, [bp+save_cnt]
mov di, [bp+start_addr]
next:
mov ax, es:[di]
cmp es:[di+2], ax
jnb short cont
xchg ax, es:[di+2]
mov es:[di], ax
sub bx, bx
cont:
add di, 2
loop next
cmp bx, 0
jz short init
sorted:
pop di ; /
pop bx ; |
pop cx ; | uses 选项产生的代码 pop xx
pop es ; |
assume es:nothing ; |
pop ds ; \
assume ds:nothing
mov sp, bp ; / 恢复堆栈指针,即清除局部变量,释放堆栈
pop bp ; \ 由endp伪指令产生的代码
retf 4 ; 由 far 选项产生 retf,由入口产生的长度决定堆栈平衡的长度 4
bubble endp
align 10h
cseg ends
; ==========================================================================
text segment byte public 'CODE' use16
assume cs:text
assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
start proc near
mov ax, len ; / 由 invoke bubble, addr Source, Len 伪指令最右面的参数
push ax ; \ Len 产生的代码
lea ax, Source ; / addr 指示符产生 lea ax, Source,即指针
push ax ; \ 和 mov ax, offset Source 效果相同,取决于编译器
; C,syscall,stdcall调用,参数入栈顺序由右至左
call bubble ; 参数入栈后开始调用,这里是远程调用,由子程序的 far 决定
; 先压段地址,再压返回地址,相当于 push cs; push ip+5, jmp bubble
int 20h
start endp
text ends
end start
待续......
#9
从上面IDA的分析可以看出,stdcall 的栈平衡是通过返回指令来完成的,即 retf 4,而堆栈平衡的深度 4,则是由这些伪指令在编译时根据入口参数的长度自动设定的,用户不需要自己计算栈平衡。
同样的代码,我们来看看 C 调用格式有什么不同,下面我们省略了和前面相同的代码:
其实整个程序只是将 stdcall -> C ,其他都保持原样。
下面通过IDA,我们来看看 C 调用和stdcall到底有什么不同:
从上面两个实例中,我们可以看出,stdcall 和 C 调用格式,参数入栈顺序完全相同,都是由右至左,顺序入栈。其中stdcall由子程序在返回指令中获得堆栈平衡,而 C 调用则由 call 返回后,直接通过 add sp, ?? 来平衡。而这些平衡过程都是由伪指令通过编译器自动完成。
接着我们来看看 pascal 调用的方法,同样只需要将 C -> pascall ,然后编译链接即可:
使用IDA分析bubble.exe结果如下:
从上面的分析,可以看出,pascal 调用和 C, stdcall调用,参数入栈顺序相反,由左至右,而堆栈平衡方法和 stdcall 相同。
至于其他的调用方式由什么不同,大家可以自己去检验,我就不多罗嗦了。
这个问题从上面的几个实例中都有显示,这里就不多说了。
前几天那个相杀人的家伙也提有类似的问题,我回答敲字手都敲痛了,结果一分不给,太令人失望了。
http://topic.csdn.net/u/20090730/20/2dc25c2f-fb3b-4048-8c03-81e730f8170b.html
同样的代码,我们来看看 C 调用格式有什么不同,下面我们省略了和前面相同的代码:
bubble proc far c uses ds es cx bx di, pSource:word, sLen:word
local save_cnt:word
local start_addr:word
......
......
ret
bubble endp
text segment para 'code'
start:
; 注意这里的C调用,和前面的stdcall调用是完全一样的
invoke bubble, addr Source, Len
int 20h
text ends
end start
其实整个程序只是将 stdcall -> C ,其他都保持原样。
下面通过IDA,我们来看看 C 调用和stdcall到底有什么不同:
cseg segment byte public 'CODE' use16
assume cs:cseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt = word ptr -2
pSource = word ptr 6
sLen = word ptr 8
push bp
mov bp, sp
add sp, -4
......
......
pop di
pop bx
pop cx
pop es
assume es:nothing
pop ds
assume ds:nothing
mov sp, bp
pop bp
retf ; 由 far 指示直接生成 retf 指令,不过没有对入口参数进行堆栈平衡
bubble endp
cseg ends
test segment byte public 'CODE' use16
assume cs:test
assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
public start
start proc near
mov ax, Len
push ax
lea ax, Source
push ax
call bubble
add sp, 4 ; 在这里进行入口参数平衡,这就是 C 调用模式的堆栈平衡方法
; 这段代码由 invoke 根据C调用指引自动产生,无需用户干涉
int 20h
start endp
test ends
end start
从上面两个实例中,我们可以看出,stdcall 和 C 调用格式,参数入栈顺序完全相同,都是由右至左,顺序入栈。其中stdcall由子程序在返回指令中获得堆栈平衡,而 C 调用则由 call 返回后,直接通过 add sp, ?? 来平衡。而这些平衡过程都是由伪指令通过编译器自动完成。
接着我们来看看 pascal 调用的方法,同样只需要将 C -> pascall ,然后编译链接即可:
bubble proc far pascal uses ds es cx bx di, pSource:word, sLen:word
使用IDA分析bubble.exe结果如下:
cseg segment byte public 'CODE' use16
assume cs:cseg
assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
bubble proc far
start_addr = word ptr -4
save_cnt = word ptr -2
sLen = word ptr 6 ; / 注意这里的次序
pSource = word ptr 8 ; \ 先 sLen 后 pSource,和前面的两个调用相反
push bp
mov bp, sp
add sp, -4
push ds
push es
push cx
push bx
push di
......
......
pop di
pop bx
pop cx
pop es
assume es:nothing
pop ds
assume ds:nothing
mov sp, bp
pop bp
retf 4 ; 栈平衡方法和 stdcall 完全相同
bubble endp
cseg ends
test segment byte public 'CODE' use16
assume cs:test
assume es:nothing, ss:dseg, ds:nothing, fs:nothing, gs:nothing
public start
start proc near
lea ax, Source ; 入栈顺序由左至右,和 C,stdcall相反
push ax
mov ax, Len
push ax
call bubble
int 20h
start endp
test ends
end start
从上面的分析,可以看出,pascal 调用和 C, stdcall调用,参数入栈顺序相反,由左至右,而堆栈平衡方法和 stdcall 相同。
至于其他的调用方式由什么不同,大家可以自己去检验,我就不多罗嗦了。
这个问题从上面的几个实例中都有显示,这里就不多说了。
前几天那个相杀人的家伙也提有类似的问题,我回答敲字手都敲痛了,结果一分不给,太令人失望了。
http://topic.csdn.net/u/20090730/20/2dc25c2f-fb3b-4048-8c03-81e730f8170b.html
#10
to gzgzlxg:好长!不过非常感谢,现在对堆栈平衡还有伪指令的辅助作用总算了解了。但第一个问题还是没搞清楚:
csource.h //c头文件
...
int func_c(int);
csource.c //c源文件
extern int func_asm(int,int);
void myown()
{
if(func_asm(2,3)) //call
.....
}
asmsource.asm //汇编源文件
func_c proto c :dword
func_asm proc c arg1:dword,arg2:dword
...
call func_c //call
func_asm endp
如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。
csource.h //c头文件
...
int func_c(int);
csource.c //c源文件
extern int func_asm(int,int);
void myown()
{
if(func_asm(2,3)) //call
.....
}
asmsource.asm //汇编源文件
func_c proto c :dword
func_asm proc c arg1:dword,arg2:dword
...
call func_c //call
func_asm endp
如上,这样互相调用。名字都是一样的。为什么?而变量名字的互相引用是要加下划线的。
#11
我可不可以这样理解:
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。
从C编译成汇编,只对变量名进行转换(加下划线),而函数名不转换。从汇编汇编成机器语言obj文件时,或者link时,才会发生函数名的转换。
#12
我怎么都读不明白你到底要问什么问题,“ 如上,这样互相调用。名字都是一样的。”--什么名字是一样的,看不懂。
这里也一样,“ 从C编译成汇编,只对变量名进行转换(加下划线)......”--加下划线?那是编译器和链接器之间的协议,爱加什么加什么,你管这些干什么,不能理解。
很抱歉,无法回答你的问题。
不论是 c 调用汇编,还是汇编调用 c ,只要调用申明一致即可,即都使用一种 langtype,如最常使用的 cdecl (缩写 c) 或其他的调用申明,只要两边一致即可。1#楼给出的例子已经写的很清楚了,其中 c 使用的是缺省调用申明,即cdecl,所以没有显式申明,而汇编中在第一句
.model small, c ; 那个 c ,就是 cdecl 调用规则的申明。
#13
不知道你结了没,如果没结也不要结给我,我只是路过学习了一下,把我所了解的说一下
1、变量名字的互相引用是要加下划线的:这是因为对于外部变量,你只用extern声明,并没有声明编译语言,而如果这个变量是在C源文件里定义的,那在编译的时候就会按C命名习惯来对其重新命名如
Var---->_Var 但如果个变量是在汇编源文件里定义的,那么在编译的时候就肯定会按汇编编译器的命名习惯来对其命名
2、对于函数名却不一样,因为你在声明函数的时候已经指明了 语言类型,就是前面说的c,stdcall,pascl等,这样的话,编译器在看到这个声明的时候就会自动根据你声明的语言类型所用的命名习惯来对函数名命名
1、变量名字的互相引用是要加下划线的:这是因为对于外部变量,你只用extern声明,并没有声明编译语言,而如果这个变量是在C源文件里定义的,那在编译的时候就会按C命名习惯来对其重新命名如
Var---->_Var 但如果个变量是在汇编源文件里定义的,那么在编译的时候就肯定会按汇编编译器的命名习惯来对其命名
2、对于函数名却不一样,因为你在声明函数的时候已经指明了 语言类型,就是前面说的c,stdcall,pascl等,这样的话,编译器在看到这个声明的时候就会自动根据你声明的语言类型所用的命名习惯来对函数名命名