相关: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 的寄存器编码将带走一个通用整数寄存器。
IP可以直接用jmp或call写,但只有call推送才能读。
(从技术上讲,call 不是读取IP 的唯一选项。您可以使用int 或其他一些中断,并让中断处理程序查看iret 之前的上下文,但这是相同的想法就像call 一样复杂和慢。)
在位置相关代码中,每条指令的地址在链接时都是已知的。您可以将任何标签的地址用作直接常量或寻址模式的一部分。例如
mov ax, $ ; ax = address of the start of the MOV instruction (NASM syntax)
或者
mov ax, label ; or MASM: mov ax, OFFSET label
label:
假设 IP 当前正在存储 0200h,我想读取此值并将其更改为其他值,例如 4020h。我该怎么做?
call 4020h
在给定当前 IP 的情况下,汇编器将计算出要使用的 rel16 位移。 (或者您可以将4020h 放入寄存器和call ax,如果您想要一种与位置无关的方式跳转到固定的IP 值(相对于cs 的偏移量,所以仍然不是绝对地址。为此您需要一个far call,并且可以使用ptr16:16 绝对直接,并将地址作为立即数。)
旧值(+调用指令的长度)将在堆栈上,4020h 处的代码可以用pop 弹出它(或用@987654351 弹出回IP @),或使用mov 加载它。
一般来说,避免不匹配的call / ret。 (即不要只将pop 的返回地址放入寄存器并返回jmp)。这将导致分支错误预测,因为您不平衡返回地址预测器堆栈。 (http://agner.org/optimize/ 和Return address prediction stack buffer vs stack-stored return address?)
在比 PIII 更新的 CPU 上,call next_insn / pop ax 是高效的,因为call rel32=0 is special-cased and doesn't break the return-address predictor stack。见Reading program counter directly。
@mksteve 建议调用一个执行mov bx, [sp] / ret 而不仅仅是call next_instruction / pop bx 的函数,这对于早期的Intel 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 将告诉汇编器+链接器找出使用什么 rel32 来达到您想要的符号。这适用于可执行文件或动态库,其中代码和数据之间的距离是恒定的,即使库被加载到不同的地址。