考虑到指令的长度不同,CPU如何知道下一条指令应读取多少字节? [英] How does the CPU know how many bytes it should read for the next instruction, considering instructions have different lenghts?

查看:216
本文介绍了考虑到指令的长度不同,CPU如何知道下一条指令应读取多少字节?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我正在阅读一篇论文,在那儿,他们说静态分解二进制代码是不确定的,因为一系列字节可以用图片(其x86)中所示的尽可能多的方式表示.

So i was reading a paper, and in it, they said that statically disassembling the code of a binary is undecidable, because a series of bytes could be represented as many possible ways as shown in picture ( its x86 )

所以我的问题是:

  1. 然后,CPU如何执行此操作?例如在图片中,当我们到达C3之后时,它如何知道应该为下一条指令读取多少字节?

  1. how does the CPU execute this then? for example in the picture, when we reach after C3, how does it know how many bytes it should read for the next instruction?

在执行一条指令后,CPU如何知道应该递增PC多少?它会以某种方式存储当前指令的大小并在想要增加PC时添加它吗?

how does the CPU know how much it should increment the PC after executing one instruction? does it somehow store the size of the current instruction and add that when it wants to increment PC?

如果CPU可以某种方式知道应该为下一条指令读取多少字节,或者基本上是如何解释下一条指令的,为什么我们不能静态地执行呢?

if the CPU can somehow know how many bytes it should read for the next instruction or basically how to interpret the next instruction, why cant we do it statically?

推荐答案

简单的方法是只读取一个字节,对其进行解码,然后确定它是否为完整的指令.如果没有读取另一个字节,则在必要时对其进行解码,然后确定是否已读取完整的指令.如果没有,请继续读取/解码字节,直到读取完整的指令为止.

The simple way is to just read one byte, decode it and then determine if it's a complete instruction. If not read another byte, decode it if necessary and then determine if a complete instruction has been read. If not continue reading/decoding bytes until the complete instruction is read.

这意味着,如果指令指针指向给定的字节序列,则只有一种方法可以解码该字节序列的第一条指令.唯一的歧义是因为下一条要执行的指令可能不位于紧随第一条指令之后的字节处.这是因为字节序列中的第一条指令可能会更改指令指针,因此将执行除后一条指令以外的其他指令.

This means that if the instruction pointer is pointing at a given sequence of bytes there's only possible way to decode that first instruction of that sequence of bytes. The ambiguity only comes because the next instruction to be executed might not be located at the bytes immediately that follow the first instruction. That's because the first instruction in the sequence of bytes may change the instruction pointer so some other instruction other than the following one gets executed.

您的示例中的RET(retn)指令可能是函数的结尾.函数通常以e RET指令结尾,但不一定如此.一个函数有可能具有多个RET指令,而这些指令都不在该函数的末尾.相反,最后一条指令将是某种JMP指令,它会跳回到函数中的某个位置,或完全跳回到另一个函数.

The RET (retn) instruction in your example could be the end of a function. Functions often end with e RET instruction, but not necessarily so. It's possible for a function to have multiple RET instructions, none of which are at the end of the function. Instead the last instruction will be some sort of JMP instruction that jumps back to some location in the function, or to another function entirely.

这意味着在您的示例代码中,如果没有更多上下文,就不可能知道是否会执行RET指令之后的任何字节,如果是,则哪个字节将成为以下函数的第一条指令.函数之间可能有数据,或者此RET指令可能是程序中最后一个函数的结尾.

That means in your example code, without more context, it's impossible to know if any of the bytes following the RET instruction will be ever be executed, and if so, which of the bytes would be the first instruction of the following function. There could be data in between functions or this RET instruction could be the end of the last function in the program.

特别是x86指令集具有相当复杂的格式,包括可选的前缀字节,一个或多个操作码字节,一个或两个可能的寻址形式字节,以及可能的位移和立即数字节.前缀字节几乎可以加在任何指令之前.操作码字节确定存在多少操作码字节,以及指令是否可以具有操作数字节和立即数字节.该操作码还可以指示存在位移字节.第一个操作数字节确定是否存在第二个操作数字节以及位移字节.

The x86 instruction set in particular has a pretty complex format of optional prefix bytes, one or more opcode bytes, one or two possible addressing form bytes, and then possible displacement and immediate bytes. The prefix bytes can be prepended to just about any instruction. The opcode bytes determine how many opcode bytes there are and whether the instruction can have a operand bytes and immediate bytes. The opcode may also indicate that there's displacement bytes. The first operand byte determines if there's a second operand byte and if there's displacement bytes.

《英特尔64和IA-32架构软件开发人员手册》中的该图显示了x86指令的格式:

The Intel 64 and IA-32 Architectures Software Developer's Manual has this figure showing the format of x86 instructions:

用于解码x86指令的类似Python的伪代码如下所示:

Python-like pseudo-code for decoding x86 instructions would look something like this:

# read possible prefixes

prefixes = []
while is_prefix(memory[IP]):
    prefixes.append(memory[IP))
    IP += 1

# read the opcode 

opcode = [memory[IP]]
IP += 1
while not is_opcode_complete(opcode):
    opcode.append(memory[IP])
    IP += 1

# read addressing form bytes, if any

modrm = None
addressing_form = []    
if opcode_has_modrm_byte(opcode):
    modrm = memory[IP]
    IP += 1
    if modrm_has_sib_byte(modrm):
        addressing_form = [modrm, memory[IP]]
        IP += 1
    else:
        addressing_form = [modrm]

# read displacement bytes, if any

displacement = []
if (opcode_has_displacement_bytes(opcode)
    or modrm_has_displacement_bytes(modrm)):
    length = determine_displacement_length(prefixes, opcode, modrm)
    displacement = memory[IP : IP + length]
    IP += length

# read immediate bytes, if any

immediate = []
if opcode_has_immediate_bytes(opcode):
    length = determine_immediate_length(prefixes, opcode)
    immediate = memory[IP : IP + length]
    IP += length

# the full instruction

instruction = prefixes + opcode + addressing_form + displacement + immediate

上面的伪代码遗漏的一个重要细节是指令的长度限制为15个字节.可以构造16字节或更长的其他有效x86指令,但是如果执行,则此类指令将生成未定义的操作码CPU异常. (我遗漏了其他细节,例如如何在Mod R/M字节内部编码部分操作码,但我认为这不会影响指令的长度.)

One important detail left out of the pseudo-code above is that instructions are limited to 15 bytes in length. It's possible to construct otherwise valid x86 instructions that are 16 bytes or longer, but such instruction will generate an undefined opcode CPU exception if executed. (There are other details I've left out, like how part of the opcode can be encoded inside of the Mod R/M byte, but I don't think this affects the length of instructions.)

但是,x86 CPU实际上并没有像我上面描述的那样对指令进行解码,它们仅对指令进行解码,就好像它们一次读取一个字节一样.取而代之的是,现代CPU会将整个15个字节读入缓冲区,然后通常在单个周期内并行解码字节.当它完全解码该指令,确定其长度,并准备读取下一条指令时,它将移入缓冲区中不属于该指令的其余字节.然后,它读取更多字节以再次将缓冲区填充为15个字节,并开始解码下一条指令.

However x86 CPUs don't actually decode instructions like how I've described above, they only decode instructions as if they read each byte one at a time. Instead modern CPUs will read an entire 15 bytes in to a buffer and then decode bytes in parallel, usually in a single cycle. When it fully decodes the instruction, determining its length, and is ready to read the next instruction it shifts over the remaining bytes in the buffer that weren't part of the instruction. It then reads more bytes to fill the buffer to 15 bytes again and starts decoding the next instruction.

我上面写的内容并没有暗示现代CPU可以做的另一件事,就是推测性地执行指令.这意味着CPU会解码指令,并在尝试完成之前的指令之前尝试尝试执行它们.反过来,这意味着CPU可能最终只能在RET指令之后对指令进行解码,但前提是无法确定RET将返回到何处.由于尝试解码和临时执行不打算执行的随机数据可能会导致性能下降,因此编译器通常不会在函数之间放入数据.尽管它们可能会用NOP指令填充此空间,但出于性能原因,它们将永远不会执行以对齐功能.

Another thing modern CPUs will do that's isn't implied by what I've written above, is speculatively execute instructions. This means the CPU will decode instructions and tentatively try to execute them even before it has finished executing the previous instructions. This in turn means that the CPU may end up decoding the instructions following the RET instruction, but only if it can't determine where the RET will return to. Because there can be performance penalties from trying to decode and tentatively execute random data that's not intended to be executed, compilers don't normally put data in between functions. Though they may pad this space with NOP instructions that will never be executed in order to align functions for performance reasons.

(很久以前,他们曾经在功能之间放置只读数据,但这是在可以推测性执行指令的x86 CPU普及之前.)

(They used to put read-only data in between functions long ago, but this was before x86 CPUs that could speculatively execute instructions became common place.)

这篇关于考虑到指令的长度不同,CPU如何知道下一条指令应读取多少字节?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆