函数调用过程
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
│ │
0012F640├─┄┄┄┄─┤
│ │
0012F644├──────┤← ebp被赋值后指向该单元,此时ebp=0012F644
│AC F6 12 00 │ebp赋值为esp之前的值
0012F648├──────┤
注意:
1.堆栈指针ESP指向栈顶的新入栈数据的最低位。
2.MOV指令中偏移指针指向被“MOV”的数据的最低位。如下面指令是将ebp+8到ebp+11四个字节的内容传到eax寄存器中。
00402048 mov eax,dword ptr [ebp+8]
一个例子如下:
c.堆栈指针ESP减少一个值,如44H,向上移动一个距离,留出一个空间给该函数作为临时存储区。
{
// 以上准备工作做好后,函数正式被执行,如下所示。
d.将其他指针或寄存器中的值入栈,以便在函数中使用这些寄存器。
e.执行代码。
f.执行return()返回执行结果,将要返回的值存入EAX中。
00401FBC call @ILT+10(t1) (0040100f) ;函数调用,下一行地址00401FC1入栈
00401FC1 add esp,4 ;函数返回,堆栈指针加4,复原为00401FB8时的值
00401FC4 mov dword ptr [ebp-10h],eax ;从eax中取出高级语言中的函数返回值,放入bR变量中
00402053 mov esp,ebp ;esp复原
00402055 pop ebp ;ebp出栈,它的值也复原了
00402056 ret ;返回到此时栈顶存储的代码地址:00401FC1
这里给出一个示例(linux/include/asm/unistd.h):
#define _syscallN(type, name, type1, arg1, type2, arg2, . . . ) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
;故而如果不幸被修改了返回地址,程序ห้องสมุดไป่ตู้会出现意外
以上汇编代码由VC++6.0编译得到。
堆栈在EBP入栈后的情况:
低位 高位
↓ ↓
内存地址 堆栈
┆ ┆
0012F600├──────┤← edi = 0012F600
1.将函数参数入栈,第一个参数在栈顶,最后一个参数在栈底。
2.执行CALL指令,调用该函数,进入该函数代码空间。
a.执行CALL指令,将CALL指令下一行代码的地址入栈。
b.进入函数代码空间后,将基址指针EBP入栈,然后让基址指针EBP指向当前堆栈栈顶,并使用它访问存在堆栈中的函数输入参数及堆栈中的其他数据。
高级语言代码中的函数调用如下:
117: bR = t1(p);
汇编代码如下:
00401FB8 mov ecx,dword ptr [ebp-8] ;将参数放入ecx寄存器
00401FBB push ecx ;参数入栈
00402046 rep stos dword ptr [edi] ;
127: int* q = (int*)p; ;
00402048 mov eax,dword ptr [ebp+8] ;ebp+8指向函数输入参数的最低位地址;
……
__asm__ __volatile__(\
"int $0x80\n\t"\
:"=a"(retval)\
:"0"(__NR_ioctl),\
"b"(fd),\
"c"(cmd),\
"d"(arg));
}
这两个函数在Linux/x86上运行的结果应该是一样的。
下面将示例说明:
int func1()
{
int fd, retval;
fd = open(filename, ……);
……
ioctl(fd, cmd, arg);
. . .
}
func2()
{
int fd, retval;
fd = open(filename, ……);
若干个库函数可以映射到同一个系统调用入口点。系统调用入口点对每个系统调用定义其真正的语法和语义,但库函数通常提供一个更方便的接口。如系统调用exec有集中不同的调用方式:execl, execle,等,它们实际上只是同一系统调用的不同接口而已。对于这些调用,它们的库函数对它们各自的参数加以处理,来实现各自的特点,但是最终都被映射到同一个核心入口点。
00402033 sub esp,44h ;esp减少一个值,空出一段存储区
00402036 push ebx ;将三个寄存器的值入栈,以便在函数中使用它
00402037 push esi ;
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
. . . . . .
__syscall_return(type,__res); \
;如果是ebp+4则指向函数返回地址00401FC1的最低位,值为C1
0040204B mov dword ptr [ebp-4],eax ;
128: return 0; ;
0040204E xor eax,eax ;返回值放入eax寄存器中
129: }
00402050 pop edi ;三个寄存器出栈
00402051 pop esi ;
00402052 pop ebx ;
│C1 1F 40 00 │返回地址
0012F64C├──────┤← ebp + 8
│A0 F6 12 00 │函数实参p的值
0012F650├──────┤
│ │
├──────┤
┆ ┆
g.步骤2.d中的指针出栈。
}
h.将EBP的值传给堆栈指针ESP,使ESP复原为2.c之前的值。此时进入函数时EBP的值在栈顶。
i.基址指针EBP出栈,复原为2.b之前的EBP的值。
j.执行RET指令,“调用函数”的地址出栈,本函数返回到CALL指令的下一行。
3.函数返回到CALL指令下一行,将堆栈指针加一个数值,以使堆栈指针恢复到以上步骤1执行之前的值。该数值是上面第一步入栈参数的总长度。
│ │
0012F604├─┄┄┄┄─┤
│ │
│ │
┆ 44h的空间 ┆
┆ ┆
│ │
高级语言反汇编程序的函数调用过程
减小字体 增大字体 作者:佚名 来源: 发布时间:2006-7-19 6:34:27
Jim Chan
摘要:本文说明高级语言编译成汇编语言后,高级语言中函数调用的汇编程序过程。
正文:高级语言编译成汇编程序以后,在高级语言中的函数调用的汇编程序过程如下:
注:存储器存储空间堆栈按从高到低的排列,左边标注的地址是其右下方存储单元的最低位地址。如0012F644指向0012F6AC的AC字节,AC在栈顶。图中存储器中的内容按从低到高位书写,“AC F6 12 00”= 0x0012F6AC
系统调用及参数传递过程
00402038 push edi ;
00402039 lea edi,[ebp-44h] ;
0040203C mov ecx,11h ;
00402041 mov eax,0CCCCCCCCh ;
其中t1函数如下:
125: BOOL t1(void* p)
126: {
00402030 push ebp ;ebp入栈
00402031 mov ebp,esp ;ebp指向此时堆栈的栈顶
}
在执行一个系统调用库中定义的系统调用入口函数时,实际执行的是类似如上的一段代码。这里牵涉到一些gcc的嵌入式汇编语言,不做详细的介绍,只简单说明其意义:
其中__NR_##name是系统调用号,如name == ioctl,则为__NR_ioctl,它将被放在寄存器eax中作为参数传递给中断0x80的处理函数。而系统调用的其它参数arg1, arg2, …则依次被放入ebx, ecx, . . .等通用寄存器中,并作为系统调用处理函数的参数,这些参数是怎样传入核心的将会在后面介绍。
责任编辑:admin 更新日期:2005-8-6
1.系统调用函数接口是如何转化为陷入命令
系统调用是通过一条陷入指令进入核心态,然后根据传给核心的系统调用号为索引在系统调用表中找到相映的处理函数入口地址。这里将详细介绍这一过程。
我们以x86为例说明:
由于陷入指令是一条特殊指令,而且依赖与操作系统实现的平台,如在x86中,这条指令是int 0x80,这显然不是用户在编程时应该使用的语句,因为这将使得用户程序难于移植。所以在操作系统的上层需要实现一个对应的系统调用库,每个系统调用都在该库中包含了一个入口点(如我们看到的fork, open, close等等),这些函数对程序员是可见的,而这些库函数的工作是以对应系统调用号作为参数,执行陷入指令int 0x80,以陷入核心执行真正的系统调用处理函数。当一个进程调用一个特定的系统调用库的入口点,正如同它调用任何函数一样,对于库函数也要创建一个栈帧。而当进程执行陷入指令时,它将处理机状态转换到核心态,并且在核心栈执行核心代码。
2. 系统调用陷入内核后作的参数传递过程
当进程执行系统调用时,先调用系统调用库中定义某个函数,该函数通常被展开成前面提到的_syscallN的形式通过INT 0x80来陷入核心,其参数也将被通过寄存器传往核心。
在这一部分,我们将介绍INT 0x80的处理函数system_call。
思考一下就会发现,在调用前和调用后执行态完全不相同:前者是在用户栈上执行用户态程序,后者在核心栈上执行核心态代码。那么,为了保证在核心内部执行完系统调用后能够返回调用点继续执行用户代码,必须在进入核心态时保存时往核心中压入一个上下文层;在从核心返回时会弹出一个上下文层,这样用户进程就可以继续运行。
0012F640├─┄┄┄┄─┤
│ │
0012F644├──────┤← ebp被赋值后指向该单元,此时ebp=0012F644
│AC F6 12 00 │ebp赋值为esp之前的值
0012F648├──────┤
注意:
1.堆栈指针ESP指向栈顶的新入栈数据的最低位。
2.MOV指令中偏移指针指向被“MOV”的数据的最低位。如下面指令是将ebp+8到ebp+11四个字节的内容传到eax寄存器中。
00402048 mov eax,dword ptr [ebp+8]
一个例子如下:
c.堆栈指针ESP减少一个值,如44H,向上移动一个距离,留出一个空间给该函数作为临时存储区。
{
// 以上准备工作做好后,函数正式被执行,如下所示。
d.将其他指针或寄存器中的值入栈,以便在函数中使用这些寄存器。
e.执行代码。
f.执行return()返回执行结果,将要返回的值存入EAX中。
00401FBC call @ILT+10(t1) (0040100f) ;函数调用,下一行地址00401FC1入栈
00401FC1 add esp,4 ;函数返回,堆栈指针加4,复原为00401FB8时的值
00401FC4 mov dword ptr [ebp-10h],eax ;从eax中取出高级语言中的函数返回值,放入bR变量中
00402053 mov esp,ebp ;esp复原
00402055 pop ebp ;ebp出栈,它的值也复原了
00402056 ret ;返回到此时栈顶存储的代码地址:00401FC1
这里给出一个示例(linux/include/asm/unistd.h):
#define _syscallN(type, name, type1, arg1, type2, arg2, . . . ) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
;故而如果不幸被修改了返回地址,程序ห้องสมุดไป่ตู้会出现意外
以上汇编代码由VC++6.0编译得到。
堆栈在EBP入栈后的情况:
低位 高位
↓ ↓
内存地址 堆栈
┆ ┆
0012F600├──────┤← edi = 0012F600
1.将函数参数入栈,第一个参数在栈顶,最后一个参数在栈底。
2.执行CALL指令,调用该函数,进入该函数代码空间。
a.执行CALL指令,将CALL指令下一行代码的地址入栈。
b.进入函数代码空间后,将基址指针EBP入栈,然后让基址指针EBP指向当前堆栈栈顶,并使用它访问存在堆栈中的函数输入参数及堆栈中的其他数据。
高级语言代码中的函数调用如下:
117: bR = t1(p);
汇编代码如下:
00401FB8 mov ecx,dword ptr [ebp-8] ;将参数放入ecx寄存器
00401FBB push ecx ;参数入栈
00402046 rep stos dword ptr [edi] ;
127: int* q = (int*)p; ;
00402048 mov eax,dword ptr [ebp+8] ;ebp+8指向函数输入参数的最低位地址;
……
__asm__ __volatile__(\
"int $0x80\n\t"\
:"=a"(retval)\
:"0"(__NR_ioctl),\
"b"(fd),\
"c"(cmd),\
"d"(arg));
}
这两个函数在Linux/x86上运行的结果应该是一样的。
下面将示例说明:
int func1()
{
int fd, retval;
fd = open(filename, ……);
……
ioctl(fd, cmd, arg);
. . .
}
func2()
{
int fd, retval;
fd = open(filename, ……);
若干个库函数可以映射到同一个系统调用入口点。系统调用入口点对每个系统调用定义其真正的语法和语义,但库函数通常提供一个更方便的接口。如系统调用exec有集中不同的调用方式:execl, execle,等,它们实际上只是同一系统调用的不同接口而已。对于这些调用,它们的库函数对它们各自的参数加以处理,来实现各自的特点,但是最终都被映射到同一个核心入口点。
00402033 sub esp,44h ;esp减少一个值,空出一段存储区
00402036 push ebx ;将三个寄存器的值入栈,以便在函数中使用它
00402037 push esi ;
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
. . . . . .
__syscall_return(type,__res); \
;如果是ebp+4则指向函数返回地址00401FC1的最低位,值为C1
0040204B mov dword ptr [ebp-4],eax ;
128: return 0; ;
0040204E xor eax,eax ;返回值放入eax寄存器中
129: }
00402050 pop edi ;三个寄存器出栈
00402051 pop esi ;
00402052 pop ebx ;
│C1 1F 40 00 │返回地址
0012F64C├──────┤← ebp + 8
│A0 F6 12 00 │函数实参p的值
0012F650├──────┤
│ │
├──────┤
┆ ┆
g.步骤2.d中的指针出栈。
}
h.将EBP的值传给堆栈指针ESP,使ESP复原为2.c之前的值。此时进入函数时EBP的值在栈顶。
i.基址指针EBP出栈,复原为2.b之前的EBP的值。
j.执行RET指令,“调用函数”的地址出栈,本函数返回到CALL指令的下一行。
3.函数返回到CALL指令下一行,将堆栈指针加一个数值,以使堆栈指针恢复到以上步骤1执行之前的值。该数值是上面第一步入栈参数的总长度。
│ │
0012F604├─┄┄┄┄─┤
│ │
│ │
┆ 44h的空间 ┆
┆ ┆
│ │
高级语言反汇编程序的函数调用过程
减小字体 增大字体 作者:佚名 来源: 发布时间:2006-7-19 6:34:27
Jim Chan
摘要:本文说明高级语言编译成汇编语言后,高级语言中函数调用的汇编程序过程。
正文:高级语言编译成汇编程序以后,在高级语言中的函数调用的汇编程序过程如下:
注:存储器存储空间堆栈按从高到低的排列,左边标注的地址是其右下方存储单元的最低位地址。如0012F644指向0012F6AC的AC字节,AC在栈顶。图中存储器中的内容按从低到高位书写,“AC F6 12 00”= 0x0012F6AC
系统调用及参数传递过程
00402038 push edi ;
00402039 lea edi,[ebp-44h] ;
0040203C mov ecx,11h ;
00402041 mov eax,0CCCCCCCCh ;
其中t1函数如下:
125: BOOL t1(void* p)
126: {
00402030 push ebp ;ebp入栈
00402031 mov ebp,esp ;ebp指向此时堆栈的栈顶
}
在执行一个系统调用库中定义的系统调用入口函数时,实际执行的是类似如上的一段代码。这里牵涉到一些gcc的嵌入式汇编语言,不做详细的介绍,只简单说明其意义:
其中__NR_##name是系统调用号,如name == ioctl,则为__NR_ioctl,它将被放在寄存器eax中作为参数传递给中断0x80的处理函数。而系统调用的其它参数arg1, arg2, …则依次被放入ebx, ecx, . . .等通用寄存器中,并作为系统调用处理函数的参数,这些参数是怎样传入核心的将会在后面介绍。
责任编辑:admin 更新日期:2005-8-6
1.系统调用函数接口是如何转化为陷入命令
系统调用是通过一条陷入指令进入核心态,然后根据传给核心的系统调用号为索引在系统调用表中找到相映的处理函数入口地址。这里将详细介绍这一过程。
我们以x86为例说明:
由于陷入指令是一条特殊指令,而且依赖与操作系统实现的平台,如在x86中,这条指令是int 0x80,这显然不是用户在编程时应该使用的语句,因为这将使得用户程序难于移植。所以在操作系统的上层需要实现一个对应的系统调用库,每个系统调用都在该库中包含了一个入口点(如我们看到的fork, open, close等等),这些函数对程序员是可见的,而这些库函数的工作是以对应系统调用号作为参数,执行陷入指令int 0x80,以陷入核心执行真正的系统调用处理函数。当一个进程调用一个特定的系统调用库的入口点,正如同它调用任何函数一样,对于库函数也要创建一个栈帧。而当进程执行陷入指令时,它将处理机状态转换到核心态,并且在核心栈执行核心代码。
2. 系统调用陷入内核后作的参数传递过程
当进程执行系统调用时,先调用系统调用库中定义某个函数,该函数通常被展开成前面提到的_syscallN的形式通过INT 0x80来陷入核心,其参数也将被通过寄存器传往核心。
在这一部分,我们将介绍INT 0x80的处理函数system_call。
思考一下就会发现,在调用前和调用后执行态完全不相同:前者是在用户栈上执行用户态程序,后者在核心栈上执行核心态代码。那么,为了保证在核心内部执行完系统调用后能够返回调用点继续执行用户代码,必须在进入核心态时保存时往核心中压入一个上下文层;在从核心返回时会弹出一个上下文层,这样用户进程就可以继续运行。