How The Computer Works

SA420 马林

【实验一】计算机是怎样工作的?

  • 请使用Example的c代码分别生成.cpp,.s,.o和ELF可执行文件,并加载运行,分析.s汇编代码在CPU上的执行过程
  • 实验报告要求:通过实验解释单任务计算机是怎样工作的,并在此基础上讨论分析多任务计算机是怎样工作的。

Example:

int g(int x)

{

   return x+3;

}

int f(int x)

{

   return g(x);

}

int main(void)

{

   returnf(8)+1;

}


一、预编译

    将example.c预编译成example.cpp

    指令:gcc -E -o example.cpp example.c

example.cpp:

# 1 "example.c"

# 1 "<built-in>"

# 1 "<command-line>"

# 1 "example.c"

int g(int x)

{

   return x+3;

}

int f(int x)

{

   return g(x);

}

int main(void)

{

   returnf(8)+1;

}

可以看到,预编译将原本的#include进行了替换,讲呗包含的未见插入到了该预编译指令的位置,并且添加了行号和文件名标志。如:

# 1 "example.c"

# 1 "<built-in>"

# 1 "<command-line>"

# 1 "example.c"

里的#1表示#include语句所在的行数为1,文件名为example.c

 

二、编译成汇编代码

   将example.cpp编译成汇编代码example.s

   指令:gcc -x cpp-output -S -o lab1.slab1.cpp

example.s:

.file   "example.c"

.text

.globl  g

.type   g, @function

g:                  #g函数入口

.LFB0:

.cfi_startproc

pushl   %ebp #将ebp寄存器中的地址压栈

.cfi_def_cfa_offset8

.cfi_offset 5,-8

movl    %esp, %ebp #将栈顶地址赋给栈底

.cfi_def_cfa_register5

movl    8(%ebp), %eax #ebp寄存器的加8后的内容付给eax寄存器

addl    $3, %eax #将立即数3加上eax寄存器中内容再赋给eax寄存器

popl    %ebp 出栈内容付给ebp寄存器

.cfi_def_cfa 4,4

.cfi_restore 5

ret      # 将栈顶内容赋给eip寄存器

.cfi_endproc

.LFE0:

.size   g, .-g

.globl  f

.type   f, @function

f:                   #f函数入口

.LFB1:

.cfi_startproc

pushl   %ebp  #将ebp寄存器的内容压栈

.cfi_def_cfa_offset8

.cfi_offset 5,-8

movl    %esp, %ebp #将esp寄存器的内容赋给ebp寄存器

.cfi_def_cfa_register5

subl    $4, %esp  #将esp寄存器内容减4

movl    8(%ebp), %eax #ebp寄存器内容加8后付给eax寄存器

movl    %eax, (%esp) #将eax寄存器内容赋给esp寄存器内容指向的地址

call    g      #等价于pushl %eip;move g %eip

leave           #等价于movel %ebp %esp; popl %ebp

.cfi_restore 5

.cfi_def_cfa 4,4

ret             #等价于popl %eip

.cfi_endproc

.LFE1:

.size   f, .-f

.globl  main

.type   main, @function

main:                  #main函数入口

.LFB2:

.cfi_startproc

pushl   %ebp

.cfi_def_cfa_offset8

.cfi_offset 5,-8

movl    %esp, %ebp

.cfi_def_cfa_register5

subl    $4, %esp   #将esp寄存器里的地址减少4

movl    $8, (%esp) #将立即数8压栈

call    f

addl    $1, %eax   #eax寄存器里的值+1

leave

.cfi_restore 5

.cfi_def_cfa 4,4

ret

.cfi_endproc

.LFE2:

.size   main, .-main

.ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5)4.6.3"

.section    .note.GNU-stack,"",@progbits



三、生成目标文件example.o

    指令:gcc -x assembler -c example.s  -o example.o

   首先用指令:gdpdump -h example.o将目标文件的各个段基本信息打印出来。

Sections:

Idx Name         Size      VMA       LMA      File off  Algn

  0 .text         00000035  00000000 00000000  00000034  2**2

                 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

  1 .data         00000000  00000000 00000000  0000006c  2**2

                 CONTENTS, ALLOC, LOAD, DATA

  2 .bss          00000000  00000000 00000000  0000006c  2**2

                 ALLOC

  3.comment      0000002b  00000000 00000000  0000006c  2**0

                 CONTENTS, READONLY

  4.note.GNU-stack 00000000  00000000  00000000 00000097  2**0

                 CONTENTS, READONLY

  5.eh_frame     00000078  00000000 00000000  00000098  2**2

                 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

其中,SIZE为段的大小,FileOff为段在文件中的位置。由此我们可以了解到此文件的段分布情况。

再由指令:objdump -d example.o可以查看代码段汇编。

 

example.o:    file format elf32-i386

 

 

Disassembly of section .text:

 

00000000 <g>:

   0:    55                     push   %ebp

   1:    89 e5                  mov    %esp,%ebp

   3:    8b 45 08               mov    0x8(%ebp),%eax

   6:    83 c0 03               add    $0x3,%eax

   9:    5d                    pop    %ebp

   a:    c3                     ret   

 

0000000b <f>:

   b:    55                     push   %ebp

   c:    89 e5                  mov    %esp,%ebp

   e:    83 ec 04               sub    $0x4,%esp

  11:    8b 45 08               mov    0x8(%ebp),%eax

  14:    89 04 24               mov    %eax,(%esp)

  17:    e8 fc ff ff ff         call   18 <f+0xd>

  1c:    c9                     leave 

  1d:    c3                     ret   

 

0000001e <main>:

  1e:    55                     push   %ebp

  1f:    89 e5                  mov    %esp,%ebp

  21:    83 ec 04               sub    $0x4,%esp

  24:    c7 04 24 08 00 00 00   movl   $0x8,(%esp)

  2b:    e8 fc ff ff ff         call   2c <main+0xe>

  30:    83 c0 01               add    $0x1,%eax

  33:    c9                     leave 

  34:    c3                     ret  


                                           图1


                               图2


                图3


                  图4


                   图5


               

四、生成ELF可执行文件

   指令:gcc -o example example.c

总结:从C代码到可执行的ELF文件之间,经历了各个阶段。

           在预编译阶段,编译器将#include文件插入到代码相应的位置中;去掉了注释;展开了宏;为生产汇编代码做好了准备。

           在编译阶段,编译器将预处理后的代码文件生成了相应的汇编代码文件。

           在汇编阶段,编译器将汇编代码变成机器可以执行的指令,每一个汇编语句

          在汇编阶段,编译器将汇编代码变成机器可以执行的指令,每一个汇编语句几乎对应相应的机器语言,生成.o的目标文件

         最后,生成ELF文件。

        对于单任务的计算机而言,计算机按照最终生成的ELF文件循环进行“取指,执行”的过程即可。函数调用call的过程是逐级压栈,开辟新栈,保存现场,将当前eip压栈,将目标地址放入eip寄存器中。函数返回ret过程是逐级出栈,将原本的eip地址出栈放入eip中。

       对于多任务的计算机而言,其实本质也并没有太大的区别。计算机依旧循环进行“取指,执行”。只是每个进程信息由PCB(进程控制模块)保管。进程与进程之间分时切换,切换前需要保存现场信息(如pid,eip,ebp等)内容更多,同时读取下一个时间片被执行进程的重要信息。



 

 

 

 

 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值