为什么“H”/72/0x48 是可执行文件中第二个最常见的字节?

为什么“H”/72/0x48 是可执行文件中第二个最常见的字节?

(如果本题得分为72分,请不要投票!)

我跑了这个:

cat /usr/bin/* |
  perl -ne 'map {$a{$_}++} split//; END{print map { "$a{$_}\t$_\n" } keys %a}' |
  grep --text . | sort -n | plotpipe --log y {1}

并得到这个:

字节值出现的次数

(即使使用对数 y 轴,它看起来仍然是指数的!顶部和底部之间的距离超过 100 倍)

看一下数字:

:
31919597        ^H
32983719        ^B
33943030        ^O
39130281        \213
39893389        $
52237360        \211
53229196        ^A
76884442        \377
100776756       H
746405320       ^@

^@ (NUL) 是可执行文件中最常见的字节,这并不奇怪。 \377 (255) 和 ^A (1) 对我来说也具有直观意义。

但是,是什么导致“H”(72)成为可执行文件中第二常见的字节——比 255 和 1 更常见呢?

背景

对于 Perl 脚本,我需要找到 Perl 脚本中最不常见的字节。意外的是,我没有只 grep 出 Perl 脚本,而是对所有二进制文件运行了该命令。我预计有几个字节会脱颖而出,例如 NUL、1 和 255,但绝不是“H”。

该图的输入是每个字节的计数(已排序)。 y 轴代表计数,x 轴代表行号(1-256,因为一个字节只能采用 256 个不同的值)。 y 轴是对数刻度,因此差异大于指数。

答案1

那将是64 位操作数大小前缀amd64 机器代码指令。

您会注意到它只发生在 amd64 可执行文件上。

/bin/*如果你比较http://ftp.debian.org/debian/pool/main/c/coreutils/coreutils_9.1-1_arm64.deb, http://ftp.debian.org/debian/pool/main/c/coreutils/coreutils_9.1-1_amd64.debhttp://ftp.debian.org/debian/pool/main/c/coreutils/coreutils_9.1-1_i386.deb, 你会看到的:

$ for f (coreutils_9.1-1_*.deb) bsdtar xOf $f da\* | bsdtar xO ./bin/\* | xxd -p -c1 | sort | uniq -c | sort -rn | head -n 5 | grep -H --label="${${f:r}##*_}" .
amd64: 692417 00
amd64: 145689 ff
amd64:  81911 48
amd64:  48006 89
amd64:  45331 0f
arm64:1409826 00
arm64:  70391 ff
arm64:  67915 03
arm64:  49380 20
arm64:  41655 40
i386: 515346 00
i386: 171643 ff
i386:  78361 0e
i386:  69317 24
i386:  50497 83

0x48(72,'H')仅位于 amd64 上的前 3 名中。

ls我的 amd64 Debian 系统上:

$ xxd -p -c1 =ls | sort | uniq -c | sort -rn | head -n 5
  39187 00
   7827 ff
   5565 48
   4181 20
   3393 0f

如果我们反汇编该可执行文件中的代码,我们会在指令中发现大量 0x48 字节:

$ objdump -d =ls | grep -cw 48
5353

其中大多数都处于第一位置:

$ objdump -d =ls | grep -wm10 48
    4000:       48 83 ec 08             sub    $0x8,%rsp
    4004:       48 8b 05 ad ff 01 00    mov    0x1ffad(%rip),%rax        # 23fb8 <__gmon_start__@Base>
    400b:       48 85 c0                test   %rax,%rax
    4012:       48 83 c4 08             add    $0x8,%rsp
    44b6:       68 48 00 00 00          push   $0x48
    4751:       48 89 f3                mov    %rsi,%rbx
    4754:       48 83 ec 68             sub    $0x68,%rsp
    4758:       48 8b 3e                mov    (%rsi),%rdi
    475b:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    4764:       48 89 44 24 58          mov    %rax,0x58(%rsp)
$ objdump -d =ls | grep -Pc '^\s*[\da-f]+:\s+48'
5113

根据http://ref.x86asm.net/geek.html#x48,0x48 是64 位操作数大小 REX.W操作码前缀,指定对 64 位操作数进行操作,而不是默认操作数。

$ objdump -d =ls | pcregrep -o1 -o2 '^\s*[\da-f]+:\s+(48 .. ).*?\t(\S+)' | sort | uniq -c  | sort -rn | head
   1512 48 89 mov
   1040 48 8b mov
    630 48 8d lea
    372 48 85 test
    326 48 83 add
    198 48 39 cmp
    158 48 83 sub
     79 48 01 add
     72 48 83 cmp
     69 48 c7 movq

所有指令均在 64 位操作数上完成。

相关内容