程序为什么无法同时在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:

  1. 首先是.text Section,也叫作代码段或者指令段(Code Section),用来保存程序的代码和指令;
  2. 接着是.data Section,也叫作数据段(Data Section),用来保存程序里面设置好的初始化数据信息
  3. 然后就是.rel.text Secion,叫作重定位表(Relocation Table)。重定位表里,保留的是当前的文件里面,哪些跳转地址其实是我们不知道的。比如上面的 link_example.o 里面,我们在 main 函数里面调用了 add 和 printf 这两个函数,但是在链接发生之前,我们并不知道该跳转到哪里,这些信息就会存储在重定位表里;
  4. 最后是.symtab Section,叫作符号表(Symbol Table)。符号表保留了我们所说的当前文件里面定义的函数名称和对应地址的地址簿。

所链接器的作用就是会扫描所有的目标文件,之后将符号表中的数据收集起来,构成一个全局的符号表,将重定向表中所有不确定的要跳转地址的代码,根据储存的地址进行一次修正,最后把所有目标文件合并,生成可执行的代码在这里插入图片描述
最后再由装载器将指令和命令装载到内存中就好

总结

到这里我们就可以说说为什么不能同时在linux和windows下同时运行的原因了,因为他们的可执行程序的格式不相同,在linux 是ELF格式,而在windows下则是PE格式

当然,在今天,我们在Linux也可以通过wine在Linux下运行PE格式的可执行程序,还有微软的WSL也就是 Windows Subsystem for Linux,可以解析和加载 ELF 格式的文件。

我们再写代码的时候,也不是将所有的代码放到一个文件里去运行,而是拆分为不同的函数库,最后通过静态链接的功能,是文件既可以分工来完成事情,也可以合作去完成。
对应ELF格式的文件,他其中并不只是单纯的指令,还包含这重定向表和符号表。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值