对于这些个指令的细究,也是在看ULK中的 switch_to的宏实现时候
在切换进程A到进程B,调用__switch_to函数时,不直接使用 call,而是采用先 push 进程B的eip,然后调用 jmp,最后在__switch_to中返回时候通过 ret指令来切换到 进程B中eip标记位置的指令来执行(以上是题外话)
对于 call和 jmp,都可以进行完成指令的跳转目的,区别在于
call指令相当于执行了:
1. push eip
2. jmp
比单单的 jmp多一步 push eip的动作
实际上,无论是 call还是 jmp,都会有短跳和长跳的区别
jmp: 无论是长跳还是短跳,不会有任何区别,而仅仅在于目的不同而已。长跳对应于段间,短跳则是在段内
call:长跳和短跳是有区别的,这个对应于后续的 ret和 retf指令。同上所述,call指令在执行前会进行push eip的动作。实际上,如果是长跳,在 push eip之前,会有 push cs的动作,会将代码段寄存器同时压栈;如果是短跳,仅仅只对 eip压栈(对于长跳,记作 call far ptr标号;对于短跳,记作 call near ptr标号)
对应的,ret和 retf命令就和call是遥相呼应之势
ret指令,在返回时候,会有 pop eip的动作
而对于 retf指令,在执行返回时候,首先会 pop cs,然后 pop eip,最后才真正返回到调用前 eip指向的指令位置开始执行
所以,对于
call far ptr1
在 ptr1执行完返回时,应该使用 retf来返回。
对于
call near ptr2
在 ptr2执行完返回时,应该使用 ret来返回。
而对于
jmp ptr3
理论上,在 ptr3执行完后,应该也使用 jmp来跳回到原先的位置来执行,而不能直接使用 ret,更不能直接使用 retf
比如
-
.ptr2:
-
push %ebp;
-
movl %esp, %ebp;
-
...
-
...
-
jmp ptr3;
-
// End of .ptr2
-
...
-
.ptr1:
-
jmp ptr2;
-
.ptr3:
-
mov %edx, %ebx;
-
...
-
...
-
...
但是实际上,如果按照上述的实现,在 ptr2中使用 jmp ptr3来跳回到 ptr3指令继续执行,或许会隐含有一个你不希望的结果:
使用 jmp ptr3跳回后,不会再通过 eax寄存器来返回被调用函数的返回值
而这就涉及到我们将要讨论的第二点,函数的参数和返回值
对于函数参数的传递和返回值,
一种是直接通过寄存器来传递
一种是通过栈数据来进行传递
我们知道,cpu的寄存器的个数是有限制的,而对于函数的参数,理论上可以有多个(至少可以有超过8个);而对于返回值,不仅可以返回一个 int数据,同时也可以返回一个大型结构体数据(在这种情形下,就不能可能使用有限的寄存器来完成返回值的传递)
对于函数参数,寄存器和栈数据都会被使用到用来实现参数传递动作。
如果对于参数数量较多的情形下
1. 寄存器中不会存放所有的参数,(由于寄存器的复用,仅仅会保存有限个参数)(通常 eax保存第一个参数,edx保存第二个参数)
2. 栈数据中存放的是所有的参数
对于函数的返回值
1. 对于 int型数据,直接使用 eax返回函数返回值
2. 如果是大型结构体数据,会使用栈来传递
因此,对于上述的代码中,在使用 “jmp ptr2"后,保存在 eax和 edx等等寄存器中的函数参数是没有问题的
但是如果在 ptr2中使用 jmp ptr3跳回到 ptr3继续执行时, eax中保存的不在是 ptr2中你想返回的那个函数值。
比如对于以下函数
-
#include <string.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
-
int
-
mul3(int ia)
-
{
-
int ib = 0, ret = 0;
-
__asm__(
-
"movl %%eax, %0;"
-
"movl %2, %%eax;"
-
"addl %2, %%eax;"
-
"addl %2, %%eax;"
-
"movl %%eax, %1;"
-
:"=m" (ib), "=m" (ret)
-
:"m" (ia)
-
);
-
-
printf("%s, eax: %x\n", __FUNCTION__, ib);
-
-
return ret;
-
}
-
-
int
-
main(void)
-
{
-
int ia = 4, ret = 0;
-
-
__asm__(
-
"movl %%eax, %0;"
-
:"=m" (ret)
-
:
-
);
-
-
printf("%s, Before eax: %x\n", __FUNCTION__, ret);
-
-
ret = mul3(ia);
-
-
__asm__(
-
"movl %%eax, %0;"
-
:"=m" (ia)
-
:
-
);
-
printf("%s, After mul3, ret: %x, eax: %x\n", __FUNCTION__, ret, ia);
-
}
其产生的汇编代码为:
-
[martin@assemble]$ cat call_ret.s
-
.file "call_ret.c"
-
.section .rodata
-
.LC0:
-
.string "%s, eax: %x\n"
-
.text
-
.globl mul3
-
.type mul3, @function
-
mul3:
-
.LFB2:
-
.cfi_startproc
-
pushl %ebp
-
.cfi_def_cfa_offset 8
-
.cfi_offset 5, -8
-
movl %esp, %ebp
-
.cfi_def_cfa_register 5
-
subl $40, %esp
-
movl $0, -16(%ebp)
-
movl $0, -12(%ebp)
-
#APP
-
# 9 "call_ret.c" 1
-
movl %eax, -16(%ebp);movl 8(%ebp), %eax;addl 8(%ebp), %eax;addl 8(%ebp), %eax;movl %eax, -12(%ebp);
-
# 0 "" 2
-
#NO_APP
-
movl -16(%ebp), %eax
-
movl %eax, 8(%esp)
-
movl $__FUNCTION__.2508, 4(%esp)
-
movl $.LC0, (%esp)
-
call printf
-
movl -12(%ebp), %eax
-
leave
-
.cfi_restore 5
-
.cfi_def_cfa 4, 4
-
ret
-
.cfi_endproc
-
.LFE2:
-
.size mul3, .-mul3
-
.section .rodata
-
.LC1:
-
.string "%s, Before eax: %x\n"
-
.align 4
-
.LC2:
-
.string "%s, After mul3, ret: %x, eax: %x\n"
-
.text
-
.globl main
-
.type main, @function
-
main:
-
.LFB3:
-
.cfi_startproc
-
pushl %ebp
-
.cfi_def_cfa_offset 8
-
.cfi_offset 5, -8
-
movl %esp, %ebp
-
.cfi_def_cfa_register 5
-
andl $-16, %esp
-
subl $32, %esp
-
movl $4, 24(%esp)
-
movl $0, 28(%esp)
-
#APP
-
# 29 "call_ret.c" 1
-
movl %eax, 28(%esp);
-
# 0 "" 2
-
#NO_APP
-
movl 28(%esp), %eax
-
movl %eax, 8(%esp)
-
movl $__FUNCTION__.2514, 4(%esp)
-
movl $.LC1, (%esp)
-
call printf
-
movl 24(%esp), %eax
-
movl %eax, (%esp)
-
call mul3
-
movl %eax, 28(%esp)
-
#APP
-
# 39 "call_ret.c" 1
-
movl %eax, 24(%esp);
-
# 0 "" 2
-
#NO_APP
-
movl 24(%esp), %edx
-
movl 28(%esp), %eax
-
movl %edx, 12(%esp)
-
movl %eax, 8(%esp)
-
movl $__FUNCTION__.2514, 4(%esp)
-
movl $.LC2, (%esp)
-
call printf
-
leave
-
.cfi_restore 5
-
.cfi_def_cfa 4, 4
-
ret
-
.cfi_endproc
-
.LFE3:
-
.size main, .-main
-
.section .rodata
-
.type __FUNCTION__.2508, @object
-
.size __FUNCTION__.2508, 5
-
__FUNCTION__.2508:
-
.string "mul3"
-
.type __FUNCTION__.2514, @object
-
.size __FUNCTION__.2514, 5
-
__FUNCTION__.2514:
-
.string "main"
-
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
-
.section .note.GNU-stack,"",@progbits
-
[martin@assemble]$
从中可以看到,在 main中调用 mul3函数时候使用的是 call(71行所示),而不是 jmp
同样在 mul3函数执行完后,使用ret指令来返回(34行所示),而不是直接使用jmp方式
该函数中实现:
1. 在main中
1.1 调用 mul3之前,打印 eax的值来确认后续在调用 mul3后,eax有被修改成参数值
1.2 在调用 mul3后,打印 ret(函数的返回值)和 eax的值,来对比两者是否相同
2. 在 mul3中
2.1 首先打印 eax的值确认是否已经被改编成参数值
2.2 然后在计算后,保存 eax的值到 ret后,返回 ret
该函数执行结果:
-
[martin@assemble]$ ./a.out
-
main, Before eax: 1
-
mul3, eax: 4
-
main, After mul3, ret: c, eax: c
-
[martin@assemble]$
可以看到,如我们所预期
1. 到 mul3函数后, eax的值被设置成参数值 4
2. mul3返回后, eax的值和 ret的值相同,都是 12
现在如果我们来做少许改动:
1. 将71行的 call mul3该成 jmp mul3(该处改完后,需要同步修改下面第2步骤后,在编译执行,否则会出现segment fault,段错误。因为ret指令返回后,pop eip时候将不再能获取到 72行的指令地址,因为现在使用了 jmp,而不是call,eip没有被压栈)
2. 在 mul3中的34行,使用 jmp .Martin来替换 ret(在 72行添加 .Martin的标记,以便在 mul3中可以直接 jmp过来)。
修改后的汇编源码如下:
-
[martin@assemble]$ cat call_ret.s
-
.file "call_ret.c"
-
.section .rodata
-
.LC0:
-
.string "%s, eax: %x\n"
-
.text
-
.globl mul3
-
.type mul3, @function
-
mul3:
-
.LFB2:
-
.cfi_startproc
-
pushl %ebp
-
.cfi_def_cfa_offset 8
-
.cfi_offset 5, -8
-
movl %esp, %ebp
-
.cfi_def_cfa_register 5
-
subl $40, %esp
-
movl $0, -16(%ebp)
-
movl $0, -12(%ebp)
-
#APP
-
# 9 "call_ret.c" 1
-
movl %eax, -16(%ebp);movl 8(%ebp), %eax;addl 8(%ebp), %eax;addl 8(%ebp), %eax;movl %eax, -12(%ebp);
-
# 0 "" 2
-
#NO_APP
-
movl -16(%ebp), %eax
-
movl %eax, 8(%esp)
-
movl $__FUNCTION__.2508, 4(%esp)
-
movl $.LC0, (%esp)
-
call printf
-
movl -12(%ebp), %eax
-
leave
-
.cfi_restore 5
-
.cfi_def_cfa 4, 4
-
jmp .Martin
-
.cfi_endproc
-
.LFE2:
-
.size mul3, .-mul3
-
.section .rodata
-
.LC1:
-
.string "%s, Before eax: %x\n"
-
.align 4
-
.LC2:
-
.string "%s, After mul3, ret: %x, eax: %x\n"
-
.text
-
.globl main
-
.type main, @function
-
main:
-
.LFB3:
-
.cfi_startproc
-
pushl %ebp
-
.cfi_def_cfa_offset 8
-
.cfi_offset 5, -8
-
movl %esp, %ebp
-
.cfi_def_cfa_register 5
-
andl $-16, %esp
-
subl $32, %esp
-
movl $4, 24(%esp)
-
movl $0, 28(%esp)
-
#APP
-
# 29 "call_ret.c" 1
-
movl %eax, 28(%esp);
-
# 0 "" 2
-
#NO_APP
-
movl 28(%esp), %eax
-
movl %eax, 8(%esp)
-
movl $__FUNCTION__.2514, 4(%esp)
-
movl $.LC1, (%esp)
-
call printf
-
movl 24(%esp), %eax
-
movl %eax, (%esp)
-
jmp mul3
-
.Martin:
-
movl %eax, 28(%esp)
-
#APP
-
# 39 "call_ret.c" 1
-
movl %eax, 24(%esp);
-
# 0 "" 2
-
#NO_APP
-
movl 24(%esp), %edx
-
movl 28(%esp), %eax
-
movl %edx, 12(%esp)
-
movl %eax, 8(%esp)
-
movl $__FUNCTION__.2514, 4(%esp)
-
movl $.LC2, (%esp)
-
call printf
-
leave
-
.cfi_restore 5
-
.cfi_def_cfa 4, 4
-
ret
-
.cfi_endproc
-
.LFE3:
-
.size main, .-main
-
.section .rodata
-
.type __FUNCTION__.2508, @object
-
.size __FUNCTION__.2508, 5
-
__FUNCTION__.2508:
-
.string "mul3"
-
.type __FUNCTION__.2514, @object
-
.size __FUNCTION__.2514, 5
-
__FUNCTION__.2514:
-
.string "main"
-
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
-
.section .note.GNU-stack,"",@progbits
-
[martin@assemble]$
该函数执行后,结果是
-
[martin@assemble]$ ./a.out
-
main, Before eax: 1
-
mul3, eax: 4
-
main, After mul3, ret: 180d9131, eax: 180d9131
-
[martin@assemble]$
可以看到
1. mul3 中 eax代表的参数值依旧是 4,这个没有问题
2. main中调用 mul3后的返回值
2.1 虽然现在 ret和 eax的值依旧是相同的
2.2 但是看得出来,这个结果并非是 mul3的计算结果12,而是一个随机的垃圾值,无任何意义
从以上可以看得出来
1. 通常我们需要配对使用 call和 ret/retf
2. 如果在特殊情况下,需要配对使用 jmp和 ret。那么千万注意,在 jmp fun1之前,应该 push一个你希望在fun1返回时候转去执行的指令的地址(某eip值)。当然这种情况比较少见,但是 kernel的 switch_to就是这样的一个例子
最后说明下,在汇编中如果直接使用 eip寄存器,会提示错误信息。 Error
: bad register
name `%
eip'
-
[martin@assemble]$ gcc call_ret.s
-
call_ret.s: Assembler messages:
-
call_ret.s:70: Error: bad register name `%eip
如果你想查看 eip的值,可以通过先调用call,然后 pop ebx的方式,然后查看 ebx的值。因为这样,ebx中 pop出来的就是 call指令 push进去的 eip的值
-
...
-
call .TestPop
-
. TestPop
-
pop %ebx
-
...
---------------------------------------参考内容----------------------------------------
跳转指令 jmp、call、ret、retf
汇编语言--call和ret指令
阅读(7044) | 评论(0) | 转发(0) |