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 使用一个寄存器编码会带走一个通用整数寄存器。
你可以直接用jmp
或call
写IP
,但你只能通过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
来达到您想要的符号。这适用于可执行文件或动态库,其中代码和数据之间的距离是恒定的,即使库加载到不同的地址也是如此。
我想知道我是否可以在 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 使用一个寄存器编码会带走一个通用整数寄存器。
你可以直接用jmp
或call
写IP
,但你只能通过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 say4020h
. 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
来达到您想要的符号。这适用于可执行文件或动态库,其中代码和数据之间的距离是恒定的,即使库加载到不同的地址也是如此。