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

查看:27
本文介绍了考虑到指令具有不同的长度,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.

Intel 64 and IA-32 Architectures Software Developer's Manual 有这张图显示了 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天全站免登陆