8086汇编中是否可以操作指令指针?

Is it possible to manipulate the instruction pointer in 8086 assembly?

我想知道我是否可以在 8086 汇编中操作(读取和更改其值)指令指针 (IP)。

例如,

说 IP 当前正在存储 0200h。我想读取这个值并将其更改为其他值,比如 4020h。我该怎么做?

如果您想将指令指针设置为已知值,例如十六进制值 4020h,您可以直接跳转到该地址:

jmp 4020h

或者如果某个内存位置 myVariable 保存了您想存储在 IP 中的值,您可以进行间接跳转:

jmp [myVariable]

jmp(间接或直接)的结果修改了指令指针。

读取指令指针有问题。 Linux 上的位置独立代码曾经通过使用一组代码来工作,例如:

 call getIP

 :getIP
 mov bx, [sp] ; Read the return address into BX.
 ret

其他读取IP的方法见Stack Overflow: reading IP

相关:Reading program counter directly(我更新了那里接受的答案,使其不那么糟糕,并涵盖 32 位与 64 位,因为它是阅读 IP 的规范问答。没有提到写 IP,因为这是一种概念上的理解:编写 IP 是一个跳跃,但您的代码可能 运行 不知道它被加载到哪里,所以用例完全不同。)

也是一个近乎重复的问题:Why can't you set the instruction pointer directly? 问为什么 RIP/EIP/IP 没有直接公开用于在像 AX 这样的整数寄存器上工作的指令。 (即为什么 add IP, AX 不能用作间接跳转。) TL:DR:一些 ISA,如 ARM do 将程序计数器公开为整数寄存器之一,但是x86 的寄存器很少,在机器代码中对 IP 使用一个寄存器编码会带走一个通用整数寄存器。


你可以直接用jmpcallIP,但你只能通过call推送来阅读它。

(从技术上讲,call 不是读取 IP 的唯一选择。您可以使用 int 或其他一些中断,并让中断处理程序在 iret,但这与 call 的想法相同,只是更复杂、更慢。)


在位置相关代码中,每条指令的地址在link时已知。您可以将任何标签的地址用作立即数或寻址模式的一部分。例如

mov ax, $         ; ax = address of the start of the MOV instruction (NASM syntax)

mov  ax, label   ; or MASM:  mov ax, OFFSET label

label:

Say IP is currently storing 0200h i would like to read this value & change it to something else say 4020h. how could i do that ?

call 4020h

汇编程序将根据当前 IP 计算出要使用的 rel16 位移。 (或者你可以把 4020h 放在一个寄存器中,然后 call ax,如果你想要一种与位置无关的方式跳转到一个固定的 IP 值(相对于 cs 的偏移量,所以仍然不是绝对地址。为此你需要 far call,并且可以使用 ptr16:16 绝对直接地址作为立即数。)

旧值(+调用指令的长度)将在堆栈上,4020h 处的代码可以用 pop 弹出它(或使用 ret 弹回 IP),或使用 mov.

加载它

一般来说,避免错配call / ret。 (即不要只是 pop 将 return 地址放入寄存器中,然后将 return 与 jmp 一起使用)。这将导致分支预测错误,因为您不平衡 return-地址预测器堆栈。 (http://agner.org/optimize/ and Return address prediction stack buffer vs stack-stored return address?)

在比 PIII 更新的 CPU 上,call next_insn / pop ax 是有效的,因为 call rel32=0 是特殊情况并且不会破坏 return -地址预测器堆栈。参见 Reading program counter directly

@mksteve 建议调用执行 mov bx, [sp] / ret 而不是 call next_instruction / pop bx 的函数,这对早期的英特尔 P6 系列 CPU(如 PPro)来说很好.但请注意 [sp] 不是有效的 16 位寻址模式,因此这在 16 位中显得格外笨重。如果你真的想用 16 位代码来做,也许 pop ax / push ax / ret 会少一些。


在64位代码中,可以更直接的读取RIP的当前值:lea rax, [rip]。这更常用于静态数据的位置无关寻址。例如lea rax, [rel my_table]add dword [rel global_counter], 2 将告诉汇编程序+linker 找出要使用什么 rel32 来达到您想要的符号。这适用于可执行文件或动态库,其中代码和数据之间的距离是恒定的,即使库加载到不同的地址也是如此。