在 notepad++ 和 Sublime Text 3 中打开一个简单的 .exe 文件会产生非常不同的结果

Opening a simple .exe file in notepad++ vs in Sublime Text 3 yields very different results

我用 GCC 为 windows 10 (mingw-64) 编译了以下 C 代码:

#include <stdio.h>
int main(){
    printf("Hello World!");
    return 0;
}

使用命令

gcc.exe -o test test.c

之所以有效,是因为当我执行生成的文件时,我确实得到了一个 Hello World!在控制台中,但是我很惊讶,因为当我在记事本++中打开 test.exe 时,它有 220 行长,其中包含一些可读文本,例如

Address %p has no image-section VirtualQuery failed for %d bytes at address %p

还有

Unknown pseudo relocation protocol version %d. Unknown pseudo relocation bit size %d.

然而,当我在 Sublime Text 3 中打开同一个文件时,我得到了超过 3300 行的一些看似随机的数字和字母,例如:

4d5a 9000 0300 0000 0400 0000 ffff 0000
b800 0000 0000 0000 4000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 8000 0000
0e1f ba0e 00b4 09cd 21b8 014c cd21 5468
6973 2070 726f 6772 616d 2063 616e 6e6f
7420 6265 2072 756e 2069 6e20 444f 5320
6d6f 6465 2e0d 0d0a 2400 0000 0000 0000
5045 0000 6486 0f00 5aca 455d 0068 0000
9304 0000 f000 2700 0b02 021e 001e 0000
0038 0000 000a 0000 e014 0000 0010 0000
0000 4000 0000 0000 0010 0000 0002 0000
0400 0000 0000 0000 0500 0200 0000 0000
0020 0100 0004 0000 0e3e 0100 0300 0000
0000 2000 0000 0000 0010 0000 0000 0000
0000 1000 0000 0000 0010 0000 0000 0000
0000 0000 1000 0000 0000 0000 0000 0000
0080 0000 6c07 0000 0000 0000 0000 0000
0050 0000 7002 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000

我也尝试获取汇编版本,这个在记事本和 sublime 中是相同的:

    .file   "test.c"
    .text
    .def    __main; .scl    2;  .type   32; .endef
    .section .rdata,"dr"
.LC0:
    .ascii "Hello World![=12=]"
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    subq    , %rsp    #,
    .seh_stackalloc 40
    .seh_endprologue
 # test.c:2: int main(){
    call    __main   #
 # test.c:3:    printf("Hello World!");
    leaq    .LC0(%rip), %rcx     #,
    call    printf   #
 # test.c:5: }
    xorl    %eax, %eax   #
    addq    , %rsp    #,
    ret 
    .seh_endproc
    .ident  "GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
    .def    printf; .scl    2;  .type   32; .endef

第一个问题:

why is the output different in sublime text and notepad ?

第二个问题:

where are the 0s and 1s , I thought machine code was only 0s and 1s ?

第三个问题:

how come it's 3300 lines for just a simple hello world, doesnt that sound grossly inefficient?

感谢您的任何见解!

.exe 文件是 binary file。其中大部分是不可打印的、非人类可读的字节。所以你的问题实际上可以归结为,为什么这两个文本编辑器对一个非文本文件做两件不同的事情,而它们一开始就不是为了操作而设计的?

二进制文件中可能隐藏着一些人类可读的字符串。首先,二进制文件中的一些字节偶然会出现在可打印集中。此外,包含 "Can't open file" 等文本字符串的计算机程序通常最终会在其二进制文件中包含这些字符串。

通常,文本编辑器将二进制文件显示为垃圾。通常,它会显示它知道的那些可打印字符,不加区别地与 "funny" 表示的不可打印字符混合在一起。 (至少在 Windows 平台上,使用到旧 MS-DOS 字符集的映射来显示非打印字符并不少见,它在许多不可打印的位置确实有特殊的图形字符。)它看起来就像记事本所做的那样。

看起来 Sublime 注意到文件是二进制的,并将其中的每个字节都转换为十六进制。这意味着您无法立即看到打印字符,但您可以统一地(十六进制)并排看到所有字符,可打印字符和不可打印字符。

为了更清楚地说明这一点,让我们看一个稍微不同的案例。考虑这个程序:

#include <stdio.h>

int main()
{
    char binary[] = "HelloWorld\x1E\x1F\x20\x21";
    fwrite(binary, 1, sizeof(binary), stdout);
}

此程序将文本和二进制字符混合打印到其标准输出。如果你编译并 运行 这个程序并将它的输出重定向到一个文件,你最终会得到一个混合了文本和二进制字符的文件,就像(在这方面)你的 .exe 文件。

如果我在我的正常环境中打印这个程序的输出,我得到:

HelloWorld !

我们可以看到我们预期的可打印字符串 HelloWorld,以及我们可能没有预期的 ! 字符。在我的正常环境中,不可打印的字符根本没有打印出来。

如果我在 MS-DOS 环境中打印该程序的输出(正如我所提到的,其中许多理论上 "unprintable" 字符确实有图形表示),我们可能会看到类似

☺☻♥Hello♦♣♠World▲▼ !

如果我运行这个程序通过一个将每个字节转换成十六进制表示的程序,我得到

01020348656C6C6F040506576f726C641E1F202100

让我们仔细看看这个。它以十六进制 010203 开头,这显然对应于字符串的前导 ""。接下来是 48656C6C6F,如果您查找它们,它是字符串 "Hello" 的十六进制 ASCII 代码。接下来是 040506,对应 "" 部分。接下来是 576F726C64,您猜对了,就是 "World"。接下来是1E1F2021,当然是最后的"\x1E\x1F\x20\x21"。最后,在最后,有 00,这是编译器自动附加到 binary 数组中字符串末尾的 '[=32=]' 字符。

你可能已经明白了,但是十六进制 2021 是 space 和 ! 字符的 ASCII 码(十六进制),所以这是这些在输出中做了什么。

如果我 运行 通过 Unix/Linux 命令 cat -v 的输出,使用 "control character" 表示 ^X 使不可打印的字符可见,我得到:

^A^B^CHello^D^E^FWorld^^^_ !^@

最后,这是输出的另一种表示形式,运行 通过一个 "hex dump" 程序,它并排显示十六进制和文本表示形式,但不可打印的字符被点替换:

01 02 03 48 65 6c 6c 6f  04 05 06 57 6f 72 6c 64   ...Hello...World
1e 1f 20 21 00                                     .. !.           

Sublime中显示的随机数就是你的程序。每四位数字是用十六进制编写的代码的 16 位。这就是您的计算机查看程序的方式。 Sublime 使它对你来说可读,因为以纯文本打开的 .exe 文件根本不可读。不幸的是,我不知道notepad++给你显示了什么。

反汇编代码时,输​​出为纯文本,因此在 Sublime 和 Notepad++ 中以相同的方式显示。

关于文件的大小,您的程序必须包含 stdlib.h。尝试编译一些更简单的东西,它不使用任何库。

而且尺寸也没有那么大。一共3300行,每行8个数,每个16位。 3300 * 16 * 8 = 422 400 位 = 52 800 B ~ 51.5 KiB。文件的权重,不是吗?

为什么输出不同?

编辑:阅读那个 wong... 第一个输出是原始字节码,第二个是实际的人类可读的汇编版本——它们的意思相同。

0 和 1 在哪里?

他们就在那里 - 你只是没有看到他们。对于您的计算机,一切都已经是 0 和 1。对于一个人来说,这是不可读的。字节码显示 0 和 1 十六进制块 (https://en.wikipedia.org/wiki/Hexadecimal)。这只是另一个 数字表示,例如 ffff 将转换为二进制形式的 1111111111111111。 前面提到的汇编文件也(为了这个简短的解释)直接转换为 0 和 1。汇编程序员使用汇编程序进行逆向工程和编写实际的机器代码。

为什么我的程序这么长?

不是。你的实际程序是这样的:

main:
subq    , %rsp    
call    __main  
leaq    .LC0(%rip), %rcx  
call    printf 
xorl    %eax, %eax 
addq    , %rsp   
ret 

我怀疑这个问题是出于好奇而问的(没有错!),但你需要 在深入反汇编和编写自己的汇编代码之前了解很多事情。尝试对此进行研究:

  • 计算机如何表示数据(整数、浮点数、字符、指针)以及十六进制表示法为何有用
  • 计算机体系结构基础知识
  • 如何存储数据(长期、RAM、寄存器)
  • ISA(InstructionSetArchitecture)的用途和功能
  • 标志寄存器、跳转和条件跳转
  • 函数调用
  • 指令、堆栈和基址指针
  • 调用约定
  • 算术和逻辑指令
  • ...

这是一个广阔的领域,有很多东西要学。这不是完整的学习指南,但我希望这些 要点 能帮助您开始拼图 - 这很有趣 :-)