程序为什么无法同时在windows的linux下同时运行
Posted ᝰFour Years
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序为什么无法同时在windows的linux下同时运行相关的知识,希望对你有一定的参考价值。
前言
前面我们说过,一个程序是通过编译,汇编最后变成我们需要的机器码的,同样不同的CPU会有不同的机器码,但是这时候我们就有了疑问,既然是相同的CPU,为什么我们生成的机器码不能同时在linux和windows下同时运行呢。这就要说到我们的最后一个环节了,就是链接。
编译,链接和装载
上面说过程序是通过汇编,编译,链接,最后变成可执行程序的,但是我们仔细的去看他的汇编代码,就会发现一些不同,我们来看一下,下面是两个.c文件
// add_lib.c
int add(int a, int b)
{
return a+b;
}
// link_example.c
#include <stdio.h>
int main()
{
int a = 10;
int b = 5;
int c = add(a, b);
printf("c = %d\\n", c);
}
通过objdump命令来查看一下生成的机器码
$ gcc -g -c add_lib.c link_example.c
$ objdump -d -M intel -S add_lib.o
$ objdump -d -M intel -S link_example.o
add_lib.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <add>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
10: 01 d0 add eax,edx
12: 5d pop rbp
13: c3 ret
link_example.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 10 sub rsp,0x10
8: c7 45 fc 0a 00 00 00 mov DWORD PTR [rbp-0x4],0xa
f: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5
16: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
19: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
1c: 89 d6 mov esi,edx
1e: 89 c7 mov edi,eax
20: b8 00 00 00 00 mov eax,0x0
25: e8 00 00 00 00 call 2a <main+0x2a>
2a: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
2d: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc]
30: 89 c6 mov esi,eax
32: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 39 <main+0x39>
39: b8 00 00 00 00 mov eax,0x0
3e: e8 00 00 00 00 call 43 <main+0x43>
43: b8 00 00 00 00 mov eax,0x0
48: c9 leave
49: c3 ret
我们仔细地去看上面的机器指令就会发现,这两条机器指令的地址是重复的,所以但你试图去运行他们的时候,系统拒绝给你运行的。这是因为他并不是一个可执行文件而是一个目标文件。需要通过链接器去链接才能能得到一个可执行文件
所以我们通过gcc的-o操作,可以生成对应的执行文件,就可以得到这个简单加法执行结果。
实际上,C语言代码-汇编代码-机器码,这个过程在我们计算中的过程中是两个部分组成的
第一部分,是汇编-编译-链接,在完成这三个部分后,我们就得到了一个可执行文件
第二部分,是我们通过装载器把可执行文件装载到内存中,CPU从内存指令中读取指令和数据,来开始真正执行程序
ELF格式和链接:理解链接的过程
下来我们将可执行程序来进行一次反汇编,我们可以发现,在可执行程序中,不是只有机器码,还有一些有趣的东西,同样还是objdump命令
link_example: file format elf64-x86-64
Disassembly of section .init:
...
Disassembly of section .plt:
...
Disassembly of section .plt.got:
...
Disassembly of section .text:
...
6b0: 55 push rbp
6b1: 48 89 e5 mov rbp,rsp
6b4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
6b7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
6ba: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
6bd: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
6c0: 01 d0 add eax,edx
6c2: 5d pop rbp
6c3: c3 ret
00000000000006c4 <main>:
6c4: 55 push rbp
6c5: 48 89 e5 mov rbp,rsp
6c8: 48 83 ec 10 sub rsp,0x10
6cc: c7 45 fc 0a 00 00 00 mov DWORD PTR [rbp-0x4],0xa
6d3: c7 45 f8 05 00 00 00 mov DWORD PTR [rbp-0x8],0x5
6da: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
6dd: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
6e0: 89 d6 mov esi,edx
6e2: 89 c7 mov edi,eax
6e4: b8 00 00 00 00 mov eax,0x0
6e9: e8 c2 ff ff ff call 6b0 <add>
6ee: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
6f1: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc]
6f4: 89 c6 mov esi,eax
6f6: 48 8d 3d 97 00 00 00 lea rdi,[rip+0x97] # 794 <_IO_stdin_used+0x4>
6fd: b8 00 00 00 00 mov eax,0x0
702: e8 59 fe ff ff call 560 <printf@plt>
707: b8 00 00 00 00 mov eax,0x0
70c: c9 leave
70d: c3 ret
70e: 66 90 xchg ax,ax
...
Disassembly of section .fini:
...
你可以发现在我们dump的内容中多了一些内容,我们在linux把他叫做ELF的文件格式,中文名字叫做可执行与可链接文件格式,这里面不仅存放了被编译成的汇编指令,还包含很多别的数据
观察上面的代码,我们可以发现我们定义的很多函数名称例如add都存放到了这个ELF文件中,这些名字和他们对应的地址存放在了一个叫符号表的位置,它相当于一个地址薄,把名字和地址关联了起来。这样我们在main函中调用add函数就是真实的地址了,这个就是ELF格式和链接器的功劳
ELF 文件格式把各种信息,分成一个一个的 Section 保存起来。ELF 有一个基本的文件头(File Header),用来表示这个文件的基本属性,比如是否是可执行文件,对应的 CPU、操作系统等等。除了这些基本属性之外,大部分程序还有这么一些 Section:
- 首先是.text Section,也叫作代码段或者指令段(Code Section),用来保存程序的代码和指令;
- 接着是.data Section,也叫作数据段(Data Section),用来保存程序里面设置好的初始化数据信息
- 然后就是.rel.text Secion,叫作重定位表(Relocation Table)。重定位表里,保留的是当前的文件里面,哪些跳转地址其实是我们不知道的。比如上面的 link_example.o 里面,我们在 main 函数里面调用了 add 和 printf 这两个函数,但是在链接发生之前,我们并不知道该跳转到哪里,这些信息就会存储在重定位表里;
- 最后是.symtab Section,叫作符号表(Symbol Table)。符号表保留了我们所说的当前文件里面定义的函数名称和对应地址的地址簿。
所链接器的作用就是会扫描所有的目标文件,之后将符号表中的数据收集起来,构成一个全局的符号表,将重定向表中所有不确定的要跳转地址的代码,根据储存的地址进行一次修正,最后把所有目标文件合并,生成可执行的代码
最后再由装载器将指令和命令装载到内存中就好
总结
到这里我们就可以说说为什么不能同时在linux和windows下同时运行的原因了,因为他们的可执行程序的格式不相同,在linux 是ELF格式,而在windows下则是PE格式
当然,在今天,我们在Linux也可以通过wine在Linux下运行PE格式的可执行程序,还有微软的WSL也就是 Windows Subsystem for Linux,可以解析和加载 ELF 格式的文件。
我们再写代码的时候,也不是将所有的代码放到一个文件里去运行,而是拆分为不同的函数库,最后通过静态链接的功能,是文件既可以分工来完成事情,也可以合作去完成。
对应ELF格式的文件,他其中并不只是单纯的指令,还包含这重定向表和符号表。
以上是关于程序为什么无法同时在windows的linux下同时运行的主要内容,如果未能解决你的问题,请参考以下文章
深入计算机组成原理ELF和静态链接:为什么程序无法同时在Linux和Windows下运行?
windows下同时装了Python3和Python2,如何区分使用?
十年前,女:“对不起,我不会喜欢你的,你不要再坚持了,就好比让 Linux 和 Windows 同时运行在一台PC机上,可能吗?