【问题标题】:Is it possible to manipulate the instruction pointer in 8086 assembly?是否可以在 8086 汇编中操作指令指针?
【发布时间】:2018-04-28 08:31:16
【问题描述】:

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

例如,

假设 IP 当前正在存储0200h。我想读取此值并将其更改为其他值,例如4020h。我怎么能这样做?

【问题讨论】:

  • IIRC,所有 x86 指令设置/调整指令指针到/通过特定值是:jmp, call, ret, iret, int, syscall, jCC, jcxz, loop(jCC = 所有条件跳转)...所有指令当然默认递增指令指针指向下一个(如果没有像jmp 那样按特定值重置)。并且一些指令可能通过触发一些陷阱机制(除法错误,无效的内存访问,...)来设置指令指针。其中一些不适用于 x86-16(系统调用和内存访问)。 ...要读取它,您可以使用假调用技巧,从堆栈中读取它。
  • 回复:您的编辑:call 4020h。在给定当前 IP 的情况下,汇编器将计算出要使用的 rel16 位移。旧值(+call 指令的长度)将在堆栈上,4020h 处的代码可以使用pop 弹出它(或使用ret 弹出回IP),或加载它带有mov

标签: assembly x86-16


【解决方案1】:

如果你想将指令指针设置为一个已知值,比如十六进制值 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

【讨论】:

  • @Doda 没问题。调用 whereAmI: POP AX, JMP AX
  • @Doda 阅读 9086 用户手册了解架构,阅读汇编手册了解说明。然后,您将能够理解 mksteve 的答案并对其进行投票。提款机,你在得到一个好的答案后似乎在问无关紧要的问题。 JMP 指令直接更改 IP 寄存器的值 - 仅此而已。 IP 寄存器在内存映射中没有地址。 0x1234 只是一个值 mksteve 用作示例。你在问毫无意义的问题。阅读手册。在询问指令详细信息之前,您需要了解架构以及计算机的工作原理。
  • @Doda - 处理反对票的方法是发布新帖子并获得支持。 :-) 否决票的一个可能解释是,一些人知道MOV IP, X 并没有丢失,而只是拼写为JMP X,您也许可以通过阅读英特尔的手册自己发现这一点。
  • @Doda 您被否决了,因为您提出了一个可以使用 Google 或查看手册轻松回答的问题。没有办法摆脱反对票。不要删除问题,但下一次,请在写问题之前进行一些研究,并告诉我们您在问题中已经知道的所有内容,以便我们确切知道您需要帮助的地方。像你这样模糊的问题(例如,你没有具体说明“操纵指令指针”应该是什么意思)很难回答并被否决。
  • @Doda 好多了!现在这个问题可以得到合理的回答了。
【解决方案2】:

相关: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可以直接用jmpcall写,但只有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 来达到您想要的符号。这适用于可执行文件或动态库,其中代码和数据之间的距离是恒定的,即使库被加载到不同的地址。

【讨论】:

  • 酷所以我们不能直接访问8086中的IP?
  • @Doda:你可以直接用jmp写,但是你只能用call推送才能读。 (中断也会将当前的cs:ip 推送为iret 的返回上下文的一部分,包括来自int 等软件生成的中断,但是当您只想读取IP 时,唯一明智的选择是call。 )
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-06-25
  • 1970-01-01
  • 2011-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多