call 地址计算

博客介绍了CALL地址与E8后面硬编码及下一条指令地址的计算关系,即要CALL的地址减去下一条指令地址等于E8后面的硬编码,E8后面的硬编码加上下一条指令地址等于CALL的地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

要CALL的地址 - 下一条指令地址 = E8 后面的硬编码

E8 后面的硬编码 + 下一条指令地址 = CALL的地址

<think>我们正在讨论CALL指令中转移地址计算方法。根据提供的引用,特别是引用[2]和引用[3],CALL指令有多种形式,其中转移地址可以位于内存、寄存器或直接包含在指令中。用户特别询问如何计算转移地址,因此我们将重点放在CALL指令的不同寻址模式上。引用[2]指出:“转移地址在内存中的call(1)callwordptr内存单元地址相当于:pushIPjmpwordptr内存单元地址”。这表示当CALL指令的操作数是内存单元时,转移地址从该内存单元中取出。引用[3]中列出了多种CALL指令的类型:4.转移的目的地址在指令中的call指令5.转移地址在寄存器中的call指令6.转移地址在内存中的call指令因此,我们需要针对这三种情况分别说明转移地址计算方法。另外,引用[1]中关于jmp指令的示例也适用于call指令的跳转部分,因为call指令实际上可以分解为两个操作:保存返回地址(压栈)和跳转到目标地址(类似于jmp)。具体步骤:1.**转移的目的地址在指令中(直接调用)**:-指令中直接给出目标地址(包括段地址和偏移地址或仅偏移地址)。-例如:`call4000:1234`(这是远调用,目标地址为4000:1234)。-计算:直接将指令中给出的地址作为目标地址。2.**转移地址在寄存器中**:-例如:`callax`(这是近调用,只改变IP)。-计算:目标地址的偏移量(IP)来自寄存器的值,段地址不变(CS不变)。3.**转移地址在内存中**:-根据内存单元中存放的是字(16位,仅偏移地址)还是双字(32位,包括段地址和偏移地址),分为近调用和远调用。-`callwordptr[bx]`(近调用):从内存单元中取出一个字作为目标偏移地址,CS不变。相当于:pushIPjmpwordptr[bx]-`calldwordptr[bx]`(远调用):从内存单元中取出两个字,高位字为段地址,低位字为偏移地址。相当于:pushCSpushIPjmpdwordptr[bx]注意:在实模式下,地址计算通常涉及段基址和偏移地址的合成。但在CALL指令中,我们主要关注如何获取目标地址,而目标地址的获取方式取决于操作数的类型。下面我们详细说明每种情况的计算公式:###1.直接调用(目标地址在指令中)-**近调用**(段内转移):-指令格式:`call目标偏移地址`-操作:将当前IP(指向CALL指令的下一条指令)压栈,然后将目标偏移地址赋给IP。-目标地址计算:$目标IP=指令中给出的偏移地址$-示例:`call0123h`,假设当前CS=1000H,当前IP=2000H(CALL指令的下一条指令地址),则执行后:-栈顶压入2000H(返回地址的偏移量)-IP变为0123H,所以下一条指令地址为1000:0123。-**远调用**(段间转移):-指令格式:`call地址:偏移地址`-操作:将当前CS和IP(指向CALL指令的下一条指令)依次压栈,然后将指令中给出的段地址赋给CS,偏移地址赋给IP。-目标地址计算:$目标CS=指令中给出的段地址$,$目标IP=指令中给出的偏移地址$-示例:`call4000:1234`,假设当前CS=1000H,当前IP=2000H,则执行后:-栈顶先压入CS(1000H),再压入IP(2000H)-CS变为4000H,IP变为1234H,所以下一条指令地址为4000:1234。###2.转移地址在寄存器中(只能是近调用)-指令格式:`call16位寄存器`(如`callax`)-操作:将当前IP压栈,然后将寄存器的值赋给IP。-目标地址计算:$目标IP=寄存器的值$-示例:`movax,1234h`,`callax`,假设当前CS=1000H,当前IP=3000H(CALL指令的下一条指令地址),则执行后:-栈顶压入3000H-IP变为1234H,所以下一条指令地址为1000:1234。###3.转移地址在内存中-**近调用**(段内转移):-指令格式:`callwordptr内存单元地址`-操作:将当前IP压栈,然后从指定的内存单元地址中取出一个字(16位)作为目标偏移地址赋给IP。-目标地址计算:$目标IP=\text{内存单元地址处存放的字}$-示例:引用[2]中的例子:```assemblymovsp,10hmovax,0123hmovds:[0],axcallwordptrds:[0]```-执行后,IP=0123H,同时将原来的IP(即CALL指令的下一条指令的地址)压栈,所以(sp)=0EH(因为栈指针sp在压栈时减2,初始为10h,压栈后变为0Eh)。-**远调用**(段间转移):-指令格式:`calldwordptr内存单元地址`-操作:将当前CS和IP依次压栈,然后从指定的内存单元地址处读取两个字:低位字(低地址)作为目标IP,高位字(高地址)作为目标CS。-目标地址计算:$目标IP=\text{内存单元地址处存放的低位字}$$目标CS=\text{内存单元地址+2处存放的高位字}$-示例:假设内存单元[1000:0]处存放双字:低地址字为5678H(IP),高地址字为2000H(CS)。执行`calldwordptr[1000:0]`:-首先将当前CS和IP压栈(假设当前CS=3000H,IP=4000H),则栈中先压入CS(3000H),再压入IP(4000H),栈指针减4。-然后从[1000:0]处取IP=5678H,从[1000:2]处取CS=2000H,所以下一条指令地址为2000:5678。###注意事项-在实模式下,所有地址都是物理地址,由段基址(左移4位)加偏移地址得到。但在CALL指令中,我们计算的是逻辑地址(段:偏移),物理地址计算在跳转时由CPU自动完成。-CALL指令执行时,返回地址(即CALL指令的下一条指令的地址)的保存方式:对于近调用,只保存IP;对于远调用,先保存CS再保存IP。###示例代码分析引用[2]中的例子:```assemblymovsp,10h;设置栈指针为10hmovax,0123h;ax=0123hmovds:[0],ax;将ax的值存入内存地址ds:0处(即偏移地址0处存放0123h)callwordptrds:[0];近调用:将当前IP(下一条指令地址)压栈,然后从ds:0处取出一个字(0123h)赋给IP```执行过程:1.初始sp=10h。2.执行call指令时,首先将IP(假设此时IP指向call指令的下一条指令,假设该指令地址为2000:100,则IP=100+指令长度?)实际上,在取出call指令后,IP已经指向下一条指令。假设call指令长度为3字节,则下一条指令地址为当前IP+3。假设当前CS:IP=1000:0100,则call指令执行时,压栈的IP为0103h(因为call指令本身占3字节?这里需要具体指令长度,但通常我们关注操作)。3.压栈操作:sp=sp-2,将IP(0103h)压入栈(地址为ss:sp,即ss:000E)。4.然后从ds:0处读取一个字(0123h)赋给IP,所以IP变为0123h。5.因此,程序跳转到CS:0123h(CS不变)执行,同时栈顶保存了返回地址0103h,sp=0Eh。###总结计算CALL指令的转移地址,关键看操作数的类型:-立即数(直接地址):直接使用该地址(近调用为偏移地址,远调用为段:偏移)。-寄存器:使用寄存器的值作为偏移地址(近调用)。-内存:根据wordptr(近调用,取一个字作为偏移地址)或dwordptr(远调用,取双字,低位为偏移地址,高位为段地址)来获取目标地址。另外,CALL指令执行时,会先将返回地址(下一条指令的地址)压栈,然后跳转到目标地址。§§相关问题§§1.CALL指令和RET指令如何配合实现子程序调用?2.在实模式下,CALL指令的远调用和近调用在压栈操作上有何区别?3.如何用CALL指令实现函数指针调用?</think>### CALL指令中转移地址计算方法 在汇编语言中,CALL指令用于实现子程序调用,其核心操作是计算**转移地址**(目标地址)。计算方法取决于寻址模式,以下是详细分类说明(基于x86架构): #### 1. **直接转移地址(指令中直接给出)** - **段内直接调用**(近调用) 指令格式:`CALL 立即数` 计算过程: $$目标IP = 当前IP + 指令长度 + 立即数偏移量$$ 示例: ```assembly CALL 0123H ; 假设当前IP=1000H,指令长度=3字节 ``` 转移地址计算: $目标IP = 1000H + 3 + 0123H = 1126H$ (CS段寄存器不变) - **段间直接调用**(远调用) 指令格式:`CALL地址:偏移地址` 计算过程: $$目标地址 = 指令中指定的段地址 \times 16 + 偏移地址$$ 示例: ```assembly CALL 2000H:0100H ``` 转移地址: $2000H \times 16 + 0100H = 20000H + 100H = 20100H$ #### 2. **寄存器间接转移地址** - **段内调用** 指令格式:`CALL 16位寄存器` 计算过程: $$目标IP = 寄存器值$$ 示例(引用[^3]): ```assembly MOV AX, 0123H CALL AX ; (IP) = 0123H ``` #### 3. **内存单元中的转移地址** - **段内转移**(`word ptr`) 指令格式:`CALL WORD PTR [内存地址]` 计算过程: $$目标IP = 内存单元中的字数据$$ 等效操作(引用[^2]): ```assembly PUSH IP JMP WORD PTR [内存地址] ``` 示例: ```assembly MOV AX, 0123H MOV [BX], AX CALL WORD PTR [BX] ; (IP) = 0123H ``` - **段间转移**(`dword ptr`) 指令格式:`CALL DWORD PTR [内存地址]` 计算过程: $$目标地址 = 内存单元中的双字数据$$ (低位字为IP,高位字为CS) 等效操作: ```assembly PUSH CS PUSH IP JMP DWORD PTR [内存地址] ``` 示例: ```assembly MOV AX, 0123H ; 偏移地址 MOV DX, 2000H ; 段地址 MOV [BX], AX ; 低地址存IP MOV [BX+2], DX ; 高地址存CS CALL DWORD PTR [BX] ; 跳转到2000H:0123H ``` #### 4. **相对寻址转移地址** - **基于位移的调用** 指令格式:`CALL 标号` 计算过程: $$目标IP = 当前IP + 指令长度 + 标号位移量$$ 示例: ```assembly CALL SUB_ROUTINE ; 假设标号距离当前IP的位移=50H ``` #### 关键注意事项 1. **地址计算时机**: CPU在执行CALL指令时自动完成地址计算,但需注意: - **当前IP**指向CALL指令的**下一条指令** - **指令长度**因操作数类型不同而变化(通常3-7字节) 2. **堆栈操作**: 所有CALL指令都会压入返回地址: - 段内调用:压入IP - 段间调用:先压入CS,再压入IP 3. **模式区别**: ```mermaid graph LR A[CALL类型] --> B[段内调用] A --> C[段间调用] B --> D[改变IP] C --> E[改变CS:IP] ``` #### 调试技巧 1. 使用调试器(如Debug或GDB)查看: - 执行CALL前的IP/CS值 - 内存单元内容(对间接调用) - 堆栈变化 2. 常见错误: - 内存单元未初始化(间接调用跳转到随机地址) - 混淆近调用/远调用(导致堆栈错误)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值