我不时地读到,在所有的指令中,CPU只有很少的部分被使用,上一次是这里,作者写道:“只有少数几个不同的指令,占执行的所有操作的90%”。
最常用的指令是什么?我最感兴趣的是x86-64和arm64。
发布于 2022-11-18 10:35:07
这将因操作系统、编译器、编译器版本、编程语言以及您所要查看的目标的体系结构扩展而有所不同。这进一步取决于你在什么是和不是“相同”指令之间划清界线。
在arm64上,如果您认为movz w0, 0和movz w0, 1是不同的,那么您只需将任何二进制代码块分成4字节块,用十六进制打印并在sort | uniq -c中运行。
如果您认为所有的movz都是相同的指令,那么就会产生一个问题:movz w0和movz x0是否相同。如果这是“是”,那么还有其他指令,比如add,它们有不同的形式,其中第三个操作数要么是寄存器,要么是即时的。还有一些东西,比如ldr,在索引前后的模式中引入了附加效应。除此之外,还有像mov这样的伪指令,它们可以被编码为movz、movn或orr,这取决于操作数是什么。
但是让我们看一个具体的例子:/bin/bash在macOS 12.6.1上的二进制文件。
其__TEXT.__text切片的arm64e部分有118587条指令。如果我们拆解它的全部,并试图用...替换所有直接寄存器,用rN替换所有通用寄存器,用rN替换(w|x)zr和(w)sp,用fN替换所有浮点寄存器,使用以下命令:
perl -pe 's/b\.\w{2}/b.cond/g;s/\b(x[0-9]+|sp|xzr)\b/rN/g;s/\bw([0-9]+|sp|zr)/rN/g;s/\b[dq][0-9]+/fN/g;s/-?0x[0-9a-f]+/.../g;s/, -?\d+/, .../g;s/(lsl|lsr|asr|uxtw|sxtw) (\d+|0x[0-9a-f]+)/.../g;s/, \.\.\.\]/]/g'然后将结果按频率排序如下:
sort | uniq -c | perl -pe 's/^\s+//g' | sort -V然后在ret指令处选择任意的截止点,我们得到:
265 ret
268 brk ...
269 autibsp
271 eor rN, rN, rN, ...
284 sub rN, rN, rN
299 cset rN, eq
312 cmn rN, ...
322 ldur rN, [rN]
332 add rN, rN, rN, ...
363 csel rN, rN, rN, eq
392 orr rN, rN, ...
432 paciza rN
450 ldr rN, [rN, rN]
476 strb rN, [rN]
507 ldp fN, fN, [rN]
578 sxtw rN, rN
745 and rN, rN, ...
780 tbnz rN, ..., ...
805 tbz rN, ..., ...
866 stp rN, rN, [rN]!
871 add rN, rN, rN
978 ldp rN, rN, [rN], ...
1028 invalid
1141 stp fN, fN, [rN]
1170 retab
1290 pacibsp
1457 sub rN, rN, ...
1520 cbnz rN, ...
1567 cmp rN, rN
1622 ldrb rN, [rN]
1695 adrp rN, ...
2548 ldr rN, ...
3504 stp rN, rN, [rN]
3537 ldp rN, rN, [rN]
3996 adr rN, ...
4644 cmp rN, ...
4664 add rN, rN, ...
4728 cbz rN, ...
4966 b ...
5294 str rN, [rN]
5601 b.cond ...
7451 mov rN, ...
7691 nop
8270 ldr rN, [rN]
10181 bl ...
11585 mov rN, rN这相当于112015条指令,占总数的94.5%。有几件事在这里很突出:
retab都是ret,而pacibsp、autibsp和paciza则根本不存在。brk都遵循autibsp,这是苹果为了解决FEAT_PAuth体系结构扩展的结构缺陷而被迫进行的编译器更改。其余的事件很可能是源中__builtin_trap()的调用。nop是非常常见的。这是由于PC相对寻址,编译器发出像adrp x0, ...; add x0, x0, ...这样的两三条指令,如果目标地址与指令足够接近,则可以在连接到adr x0, ...; nop的时候对其进行优化。nops是否“运行”,或者它们是否刚刚从指令管道中被删除,这是值得商榷的。invalid是非常常见的。这些是在使用它们的函数之后找到的跳转表。它们是数据,而不是代码,因此永远不会实际运行。stp和ldp.编译器通常使用浮点寄存器进行memcpy和memset操作,其中一个是因为这些寄存器可以容纳128位,另一个是因为它避免了泄漏通用寄存器,这些寄存器比浮点寄存器更有可能已经被分配,至少在命令行二进制文件(如bash )中是这样。我希望在操作系统内核和嵌入式固件中出现类似的情况,但是在与图形相关的代码或机器学习中,这可能看起来非常不同。cmn指令都会检查-1值。这很可能是C中API设计的结果,而不是体系结构固有的结果。现在取决于你是否想减少这些指令中的一些。但总的来说,我认为这种模式是明确的:
占了绝大部分的指令,这是有意义的,因为有了这些积木,你基本上可以做任何事情。几乎所有其他指令都是对这些指令的某种优化,它们更适合,因此很少发生。其余的指令都是特定于体系结构的,并且公开了您本来无法得到的东西,比如brk或msr,但是这些将会更加罕见。不管你看什么样的架构,情况都很可能是这样。
https://stackoverflow.com/questions/74485971
复制相似问题