ToB企服应用市场:ToB评测及商务社交产业平台

标题: 哈工大2022秋计算机体系大作业——步伐人生 [打印本页]

作者: 东湖之滨    时间: 2024-9-11 19:04
标题: 哈工大2022秋计算机体系大作业——步伐人生
摘  要

本文对hello步伐的整个生命周期进行了体系的分析,分析hello步伐的过程也是对计算机体系的一次周游。Hello步伐在生命周期中履历了From Program to process 和From Zero to Zero的过程。Hello.c 通过预处理(cpp)天生hello.i,经过C编译器(ccl)天生汇编文件hello.s,然后通过汇编器(as)天生可重定位文件hello.o,最后通过链接器(ld)和库中的可重定位目的文件链接,天生可实验目的文件hello。Shell担当到./hello的指令后调用fork函数创建进程,execve加载hello进入内存,CPU控制步伐逻辑流的运行、中断、上下文切换和异常处理,最后竣事进程并由父进程进行接纳,hello的生命竣事。

关键词:预处理;编译;汇编;链接;进程;存储;IO管理                           

(择要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录


第1章 概述................................................................................... - 4 -
1.1 Hello简介............................................................................ - 4 -
1.2 环境与工具........................................................................... - 4 -
1.3 中心效果............................................................................... - 4 -
1.4 本章小结............................................................................... - 4 -
第2章 预处理............................................................................... - 5 -
2.1 预处理的概念与作用........................................................... - 5 -
2.2在Ubuntu下预处理的命令................................................ - 5 -
2.3 Hello的预处理效果解析.................................................... - 5 -
2.4 本章小结............................................................................... - 5 -
第3章 编译................................................................................... - 6 -
3.1 编译的概念与作用............................................................... - 6 -
3.2 在Ubuntu下编译的命令.................................................... - 6 -
3.3 Hello的编译效果解析........................................................ - 6 -
3.4 本章小结............................................................................... - 6 -
第4章 汇编................................................................................... - 7 -
4.1 汇编的概念与作用............................................................... - 7 -
4.2 在Ubuntu下汇编的命令.................................................... - 7 -
4.3 可重定位目的elf格式........................................................ - 7 -
4.4 Hello.o的效果解析............................................................. - 7 -
4.5 本章小结............................................................................... - 7 -
第5章 链接................................................................................... - 8 -
5.1 链接的概念与作用............................................................... - 8 -
5.2 在Ubuntu下链接的命令.................................................... - 8 -
5.3 可实验目的文件hello的格式........................................... - 8 -
5.4 hello的虚拟地址空间......................................................... - 8 -
5.5 链接的重定位过程分析....................................................... - 8 -
5.6 hello的实验流程................................................................. - 8 -
5.7 Hello的动态链接分析........................................................ - 8 -
5.8 本章小结............................................................................... - 9 -
第6章 hello进程管理.......................................................... - 10 -
6.1 进程的概念与作用............................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程........................... - 10 -
6.3 Hello的fork进程创建过程............................................ - 10 -
6.4 Hello的execve过程........................................................ - 10 -
6.5 Hello的进程实验.............................................................. - 10 -
6.6 hello的异常与信号处理................................................... - 10 -
6.7本章小结.............................................................................. - 10 -
第7章 hello的存储管理...................................................... - 11 -
7.1 hello的存储器地址空间................................................... - 11 -
7.2 Intel逻辑地址到线性地址的变更-段式管理................... - 11 -
7.3 Hello的线性地址到物理地址的变更-页式管理............. - 11 -
7.4 TLB与四级页表支持下的VA到PA的变更.................... - 11 -
7.5 三级Cache支持下的物理内存访问................................ - 11 -
7.6 hello进程fork时的内存映射......................................... - 11 -
7.7 hello进程execve时的内存映射..................................... - 11 -
7.8 缺页故障与缺页中断处理................................................. - 11 -
7.9动态存储分配管理.............................................................. - 11 -
7.10本章小结............................................................................ - 12 -
第8章 hello的IO管理....................................................... - 13 -
8.1 Linux的IO设备管理方法................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................. - 13 -
8.3 printf的实现分析.............................................................. - 13 -
8.4 getchar的实现分析.......................................................... - 13 -
8.5本章小结.............................................................................. - 13 -
结论............................................................................................... - 14 -
附件............................................................................................... - 15 -
参考文献....................................................................................... - 16 -



第1章 概述

1.1 Hello简介

1.1.1 P2P:From Program to Process
在Linux体系上,从步伐到进程必要四个阶段。
阶段1:hello.c经过C预处理cpp进行预处理天生hello.i
阶段2:hello.i通过C编译器(ccl)得到汇编语言文件hello.s
阶段3:运行汇编器(as)将hello.s翻译成可重定位目的文件hello.o
阶段4:链接器(ld)将hello.o和体系目的文件如printf.o组合起来,天生可实验目的文件hello
可实验目的文件通过shell命令./hello开始了属于它的进程。

图1:编译体系


1.1.2 020:From Zero to Zero
步伐从键盘输入到计算机中的时刻起才开始存在,在经过从步伐到进程的四个阶段后,通过shell命令./hello,shell通过fork函数创建了新的进程,之后调用execve加载并运行可实验目的文件hello,操纵体系为其分配虚拟内存空间,在物理内存和虚拟内存之间建立映射。实验过程中, 虚拟内存为进程提供独立的空间,数据从磁盘传输到CPU中,TLB、分级页表等保障了数据的高效访问,I/O管理与信号处理共同实现了hello的输入输出。
进程运行竣事后,shell父进程接纳hello进程,对应的虚拟空间以及相干数据布局被开释,hello便履历了从无到有再到无的过程。
1.2 环境与工具

硬件环境:AMDR7处理器 64位  1.9GHz 16GRAM
软件环境:Windows11家庭中文版  Vmware16.2.4  Ubuntu20.04
工具:        Vim+gcc,gdb,objdump,Hexedit
1.3 中心效果

hello.c :源步伐
hello.i :hello.c预处理后的源步伐文件
hello.s :hello.i编译后得到的汇编步伐
hello.o :hello.s汇编后得到的可重定位目的文件
hello :可实验目的文件
hello_o_elf :hello.o的elf文件
hello_elf :hello的elf文件
hello_o_asm :hello.o的反汇编文件
hello_asm :hello的反汇编文件
1.4 本章小结

本章简朴介绍了hello的从步伐到进程,从无到有的过程,给出了进行实验的软硬件环境以及实验过程中天生的文件。

(第1章0.5分)




第2章 预处理

2.1 预处理的概念与作用

预处理的概念:
预处理器(cpp)根据以字符#开头的命令,修改原始的C步伐。ISO C/C++要求支持的包括#if、 #ifdef、 #ifndef、 #else、 #elif、 #endif(条件编译)、 #define(宏定义)、 #include(源文件包含)、 #line(行控制)、 #error(错误指令)、 #pragma(和实现相干的杂注)以及单独的#(空指令)。预处理指令一般被用来使源代码在不同的实验环境中被方便地修改大概编译。
预处理的作用:
1.将头文件的内容插入步伐中,比如把stdio.h中的内容插入到步伐中
2.进行宏替换,用现实值替换用#define定义的字符串
3.删除注释
4.根据#if后面的条件决定必要编译的代码
2.2在Ubuntu下预处理的命令

       预处理指令:gcc -E hello.c -o hello.i

图2  Ubuntu下预处理的命令

2.3 Hello的预处理效果解析

通过上述的指令得到了hello.i文本文件。通过gedit打开hello.i文件,我们可以瞥见经过预处理,文本文件已经扩展到了3060行。
Hello.c的源代码在3037行开始,前面的是插入的库文件的内容,比如stdio.h, unistd.h, stdlib.h。以stdio.h为例,我们到/usr/include/的目次下打开stdio.h文件,可以看到这个文件中也有大量的#define,预处理器对其进行递归展开,最终的hello.i中是没有#define的。stdio.h中声明白很多标准函数,此中包括常用的printf和scanf。
此外预处理器cpp将hello.c中的注释删除了。


图3:hello.i的源代码部分



图4:标准库stdio.h中的部分内容



图5:hello.i的部分函数声明


2.4 本章小结


本章介绍了步伐预处理的概念、作用,在Ubuntu操纵体系中进行了实操演示,并且通过和hello.c步伐进行比较,分析了预处理得到的hello.i中的内容,进一步展示了预处理的作用。
(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译的概念:
编译步伐的工作是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译为等价的汇编代码。编译器(ccl)将hello.i文件翻译成hello.s文件。
编译的作用:
编译通过5个阶段,将高级语言步伐转化为计算机能解读、运行的低级语言。这5个步骤分别是——词法分析、语法分析、语义检查和中心代码天生、代码优化、目的代码天生。
1.词法分析:对由字符构成的单词进行处理,从左至右逐个字符地对源步伐进行扫描,产生一个个的单词符号,把作为字符串的源步伐改造成为单词符号串的中心步伐。
2.语法分析:以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单元,如表达式、赋值、循环等,最后看是否构成一个符合要求的步伐,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑布局,步伐是最终的一个语法单元。
3. 语义检查和中心代码天生:由语义分析器完成,指示判断是否正当,并不判断对错。中心代码的作用是使编译步伐的布局在逻辑上更为简朴明确,特别是可使目的代码的优化比较容易实现中心代码。
4.代码优化:对步伐进行多种等价变更,使得从变更后的步伐出发能天生更有效的目的代码。
5.目的代码天生:把语法分析后或优化后的中心代码变更成目的代码。此处指汇编语言代码,需经过汇编步伐汇编后,称为可实验的机器语言代码。

3.2 在Ubuntu下编译的命令

编译命令:gcc -S hello.i -o hello.s
 图6:Ubuntu下的编译命令

3.3 Hello的编译效果解析

3.3.1数据
(1)数字常量:
在if语句 if(argc!=4)中的数字常量4生存在.text段中,作为指令的一部分。

图7:hello.s中数据常量4作为指令的一部分

同理for循环中的0,9,1,2,3也被存储在.text段中。
(2)字符串常量:
在printf和scanf中出现的字符串常量被存储在rodata节中。
例如:printf("用法: Hello 学号 姓名 秒数!\n");     这条语句中的字符串生存在rodata节部分

图8:hello.s中printf中的字符串常量

(3)局部变量
局部变量生存在栈中或是寄存器中。比如循环中的局部变量i,在循环开始的时候给i赋值为0。i被存储在栈上,地址为%rbp – 4。

图9:hello.s中循环变量i的初始化

(4)全局变量
全局变量被存储在.data节或是.bss节。已经赋初值的全局变量存储在.data节中,为赋值或是初值赋为0的全局变量在.bss节中。

3.3.2 算数操纵
在循环中,循环变量i在每次循环竣事后会进行i++的自增操纵,在机器指令中这是通过对add指令对栈中的变量i进行加1操纵,指令如下:
addl      $1, -4(%rbp)


3.3.3关系操纵和控制转移

(1)相当判断

C步伐中第13行判断命令行参数个数是否是4个,假如不是,则打印命令行输入的正确格式。

汇编代码是通过cmpl来是实现的,cmpl s1 s2指令是判断s2-s1,只修改条件码,不修改操纵数。Je基于操纵码,判断是否进行跳转,假如操纵码的ZF为1,体现两数相当,则进行跳转。


图10:hello.s中的相当判断和控制转移

(2)小于判断
类似的,在for循环中必要判断循环变量i是否满意循环继续的条件。For循环中的小于9,转换为和常数8进行比较,若小于等于8则跳转到循环的操纵部分。

图11:for循环中循环中断条件的判断


3.3.4数组/指针操纵
下面的汇编指令将主函数读取的命令行参数argc,argv从寄存器中转移到栈中。
此中argv是一个指针数组的首地址,可以在这个基址上加特定的值,对数组的内容进行访问。

图12:hello.s中命令行参数的读取

在调用printf函数之前,汇编代码将必要打印的内容移到相应的寄存器中,%rbp-32+16 = %rbp-16存储的是argv[2],即指向姓名字符串的指针,作为printf的第三个参数,放在rdx中。同理,%rbx-8存储的是argv[1],存放的是指向学号字符串的指针,作为printf的第二个参数,放在%rsi中。

图13:hello.s中基于基址的数组操纵


3.3.5 函数操纵
X86-64位机中,函数的前6个参数通过寄存器通报,后面的参数通过栈来进行通报。6个寄存器分别是rdi,rsi,rdx,rcx,r8,r9。
1main函数:
参数通报:传入参数argc和argv[],分别用寄存器%rdi和%rsi存储。
函数调用:被体系启动函数调用。
函数返回:设置%eax为0并且返回,对应return 0 。
源代码:
int main(int argc, char *argv[])

汇编代码:
main:

.LFB6:

    .cfi_startproc

    pushq   %rbp

    .cfi_def_cfa_offset 16

    .cfi_offset 6, -16

    movq    %rsp, %rbp

    .cfi_def_cfa_register 6

    subq    $32, %rsp

    movl    %edi, -20(%rbp)

    movq    %rsi, -32(%rbp)

可见argc存储在%edi中,argv存储在%rsi中;

(2)printf函数
参数通报:call puts时只传入了字符串参数首地址;for循环中call  printf时传入了 argv[1]和argc[2]的地址。
函数调用:if判断满意条件后调用,与for循环中被调用。
源代码1:
printf("用法: Hello 学号 姓名 秒数!\n");

汇编代码1:
.LFB6:

    cmpl    $4, -20(%rbp)

    je  .L2

    leaq    .LC0(%rip), %rdi

    call    puts@PLT

源代码2:
    printf("Hello %s %s\n", argv[1], argv[2]);

汇编代码2:
.L4:

    movq    -32(%rbp), %rax

    addq    $16, %rax

    movq    (%rax), %rdx

    movq    -32(%rbp), %rax

    addq    $8, %rax

    movq    (%rax), %rax

    movq    %rax, %rsi

    leaq    .LC1(%rip), %rdi

    movl    $0, %eax

    call    printf@PLT


(3)exit函数:
参数通报:传入的参数为1,再实验退出命令
函数调用:if判断条件满意后被调用.
源代码:
    exit(1);

汇编代码:
.LFB6:

    movl    $1, %edi

    call    exit@PLT


(4)sleep函数:
参数通报:传入参数atoi(argv[3]),
函数调用:for循环下被调用,call sleep
源代码:
    sleep(atoi(argv[3]));

汇编代码:
.L4:

    movq    -32(%rbp), %rax

    addq    $24, %rax

    movq    (%rax), %rax

    movq    %rax, %rdi

    call    atoi@PLT

    movl    %eax, %edi

    call    sleep@PLT


(5)getchar函数:
函数调用:在main中被调用,汇编指令为call getchar
源代码:
    getchar();

汇编代码:
.L3:

    call    getchar@PLT


3.4 本章小结

本章首先介绍了编译的概念、步骤以及进行编译的命令行。然后联合hello.c和hello.s文件分析了编译器是如何把高级语言转化为汇编指令的。
在3.3.1中我们分析了数据的不同的类型,以及它们的生存位置。常数生存在.text中,字符串生存在rodata中,数据的赋值可以使用move指令完成。3.3.2节中介绍了算数操纵,比如add,sub等,编译器能够对数据进行一定的运算。3.3.3节中介绍了控制转移,包括条件判断、for循环,通过标记为进行一定的跳转实现步伐逻辑的实现。3.3.4中介绍了数组和指针,对于数组的方位可以通过基址加上偏移量的方式进行访问。3.3.5中介绍了hello.c中的不同函数,函数的参数可以通过寄存器和栈进行通报。
(第32分)


第4章 汇编

4.1 汇编的概念与作用

概念
驱动步伐运行汇编器as,将汇编语言(这里是hello.s)翻译成机器语言(hello.o)的过程称为汇编。这个机器语言文件是可重定位目的文件(二进制)。
作用
汇编就是将高级语言转化为机器可直接识别实验的代码文件的过程,汇编器将hello.s 汇编步伐翻译成机器语言指令,把这些指令打包成可重定位目的步伐的格式,并将效果生存在hello.o 目的文件中,hello.o 文件是一个二进制文件,它包含步伐的指令编码。
4.2 在Ubuntu下汇编的命令

汇编指令:as hello.s -o hello.o


图14:汇编指令

4.3 可重定位目的elf格式

4.3.1 输出elf文件的指令
输出hello_o_elf.txt的指令:readelf -a hello.o > ./hello_o_elf.txt


图15:输出elf文件的指令

4.3.2 ELF
包含了魔数、文件类别,数据体现方式,elf头大小,节的大小和数量、节头表在字符表中的下标。Elf头内容如下:


图16:hello_o_elf.txt的elf头部分

4.3.2节头表
节头表中描述了hello.o中各个节的信息,包括节的大小,读写属性,偏移量等信息。通过节头表能够找到相应的节,比如.text节,.data,.bss节等。
节头表的信息如下:
 图17:hello_o_elf.txt中的节头表

4.3.3 重定位节
因为hello.o中存在对外部符号的引用,在hello.o中没有定义,所有必要重定位节把这些符号的位置以及寻址方式(绝对寻址照旧PC相对寻址)标记出来,如许在毗连的时候可以根据重定位节,对空缺的符号进行添补。
重定位节的内容如下:

图18:hello_o_elf.txt中的重定位节

4.3.4 符号表
.symtab符号中,记录的是步伐中定义大概引用的函数、全局变量、常量的信息。比如main,puts,exit等函数,1,3,4等数据常量。


图19:hello_o_elf.txt中的符号表


4.4 Hello.o的效果解析

4.4.1反汇编指令
反汇编指令:objdump -d -r hello.o >hello_o_asm
4.4.2 反汇编文件
反汇编文件的内容如下:
hello.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:    f3 0f 1e fa                endbr64
   4:    55                          push   %rbp
   5:    48 89 e5               mov    %rsp,%rbp
   8:    48 83 ec 20              sub    $0x20,%rsp
   c:    89 7d ec                mov    %edi,-0x14(%rbp)
   f:     48 89 75 e0             mov    %rsi,-0x20(%rbp)
  13:    83 7d ec 04              cmpl   $0x4,-0x14(%rbp)
  17:    74 16                je     2f <main+0x2f>
  19:    48 8d 3d 00 00 00 00       lea    0x0(%rip),%rdi        # 20 <main+0x20>
                    1c: R_X86_64_PC32 .rodata-0x4
  20:    e8 00 00 00 00           callq  25 <main+0x25>
                    21: R_X86_64_PLT32      puts-0x4
  25:    bf 01 00 00 00            mov    $0x1,%edi
  2a:    e8 00 00 00 00           callq  2f <main+0x2f>
                    2b: R_X86_64_PLT32      exit-0x4
  2f:     c7 45 fc 00 00 00 00 movl   $0x0,-0x4(%rbp)
  36:    eb 48                jmp    80 <main+0x80>
  38:    48 8b 45 e0             mov    -0x20(%rbp),%rax
  3c:    48 83 c0 10             add    $0x10,%rax
  40:    48 8b 10               mov    (%rax),%rdx
  43:    48 8b 45 e0             mov    -0x20(%rbp),%rax
  47:    48 83 c0 08             add    $0x8,%rax
  4b:    48 8b 00               mov    (%rax),%rax
  4e:    48 89 c6               mov    %rax,%rsi
  51:    48 8d 3d 00 00 00 00       lea    0x0(%rip),%rdi        # 58 <main+0x58>
                    54: R_X86_64_PC32 .rodata+0x22
  58:    b8 00 00 00 00           mov    $0x0,%eax
  5d:    e8 00 00 00 00           callq  62 <main+0x62>
                    5e: R_X86_64_PLT32      printf-0x4
  62:    48 8b 45 e0             mov    -0x20(%rbp),%rax
  66:    48 83 c0 18             add    $0x18,%rax
  6a:    48 8b 00               mov    (%rax),%rax
  6d:    48 89 c7               mov    %rax,%rdi
  70:    e8 00 00 00 00           callq  75 <main+0x75>
                    71: R_X86_64_PLT32      atoi-0x4
  75:    89 c7                mov    %eax,%edi
  77:    e8 00 00 00 00           callq  7c <main+0x7c>
                    78: R_X86_64_PLT32      sleep-0x4
  7c:    83 45 fc 01              addl   $0x1,-0x4(%rbp)
  80:    83 7d fc 08              cmpl   $0x8,-0x4(%rbp)
  84:    7e b2                jle    38 <main+0x38>
  86:    e8 00 00 00 00           callq  8b <main+0x8b>
                    87: R_X86_64_PLT32      getchar-0x4
  8b:    b8 00 00 00 00           mov    $0x0,%eax
  90:    c9                   leaveq
  91:     c3                   retq  
对照反汇编的代码和hello.s的指令,我们可以发现以下几点区别:
1.数字的体现不同:hello.s中的数据体现使用的是十进制,而hello.o的反汇编代码的操纵数使用的是16进制。
2.分支转移:hello.s中的分支使用的是符号,比如.L1, .L3等,而反汇编代码中使用的是相对偏移的地址,比如jle 38<main+0x38>,使用的是相对于main函数的偏移量。
3.函数调用:hello.s中函数调用使用的是函数的名称,比如call printf@PLT。
而hello.o反汇编代码中函数使用相对main函数的偏移量来体现应该填入函数地址的位置。这个位置目前是空的,必要链接时才气确定所需函数的位置。同时在反汇编代码中给出了函数的寻址方式:PC相对寻址。
4.5 本章小结

本章介绍了汇编这一操纵。有编译得到的hello.s文件是文本文件,机器照旧无法识别,而汇编将文本文件转化为二进制的可重定位目的文件hello.o,如许机器就可以识别了。一个.c文件会天生一个.o的可重定位目的文件。
Elf中记录着可重定位目的文件的很多信息,包括节的信息、符号的信息等,这些为链接天生可实验目的文件做了准备。别的,我们将hello.s与hello.o的反汇编代码进行了对比,发现两者有很大的相似之处,但是在数字的体现、跳转和函数调用等方面存在一定的区别。
(第41分)


第5章 链接

5.1 链接的概念与作用

链接的概念:
链接(linking)是将各种代码和数据片断收集和组合成为一个单一文件的过程,这个文件可悲加载(复制)到内存并实验。链接可以实验与编译时,也就是在源代码被翻译成机器代码时;也可以实验于加载时,也就是步伐被加载器加载到内存并实验时;甚至实验于运行时,也就是有应用步伐来实验。在早期的计算机体系中,链接时手动实验的。在现代体系中,链接是由叫做链接器 的步伐自动实验的。
链接器的作用
将源步伐节省空间而未编入的常用函数文件(如printf.o)进行归并,天生可以正常工作的可实验文件。这令分离编译成为可能,节省了大量的工作空间。
5.2 在Ubuntu下链接的命令

链接命令:
ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
 图20:链接命令

5.3 可实验目的文件hello的格式

命令:readelf -a hello>hello_elf

(1)ELF文件头
可实验目的文件的elf头和可重定位目的文件的elf头非常类似。Elf头中包含了魔数,文件类别,数据体现方式,版本号,步伐头出发点,elf头的大小,节的大小、数量,节头表的索引等信息。
Hello_elf的ELF如下:


图21:hello_elf的文件头

(2)节头
描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段归并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。

图22:hello_elf的节头


(3)步伐头表
步伐头表是一个布局数组,反映可实验文件的一连的片被映射到一连的内存段的映射关系。

图23:hello_elf的步伐头表

(4)节段映射
节段映射,说明白在链接过程中,将多个代码段与数据段分别归并成一个单独的代码段和数据段,并根据段的大小以及偏移量重新设置各个符号的地址。

图24:hello_elf的节段映射


(5)重定位节
对于可实验目的文件来说,仍然会存在重定位信息,因为有些必要动态链接的块还没有被链接,重定位节中就给出了这些符号的相干信息。
(6)符号表
对于可实验目的文件来说,包含两个符号表,一个符号表的名称为.dynsym, 从名称和符号表中的内容来看应该是还没有动态链接的一些未知符号。另一张符 号表就是熟知的.symtab,内里生存了步伐中定义和引用的函数以及全局变量等的 信息。
5.4 hello的虚拟地址空间

在5.3节中我们分析了hello_elf,hello_elf的文件头中指出步伐的出发点是4010f0,我们使用edb打开可实验目的文件hello,在Data Dump中我们可以看到4010f0的位置处存储着有效信息。

图25:edb中Data Dump显示的步伐出发点

类似的,在hello_elf的节头中我们可以看到rodata从402000开始,在edb中我们可以看到printf的格式输入字符串“Hello %s %s\n”在地址为40202e的地址处。

图26:printf的格式输入串

进程的虚拟映射空间用下面这张图可以很好地体现


图27:Linux进程的虚拟地址空间

5.5 链接的重定位过程分析

命令:objdump -d -r hello > hello_asm
分析hello和hello.o的区别:
1.链接增长新的函数:
在hello中链接加入了在hello.c中用到的库函数,如exit、printf、sleep、getchar等函数。因此hello反汇编出的文件内容更多。


图28:hello_asm中plt节中的函数

2.增长的节:
hello中增长了.init和.plt节。此中init节会对步伐进行初始化。


图29:hello_asm中init节

3.函数调用:
对于hello.o的反汇编代码,函数只有在链接之后才气确定运行实验的地址,因此在.rela.text节中为其添加了重定位条目。函数调用call后面都是用0来添补。而hello中没有hello.o中的重定位条目,并且跳转和函数调用的地址都变成了虚拟内存地址。
4.地址访问:
hello.o中的相对偏移地址变成了hello中的虚拟内存地址。hello.o文件中对于某些地址的定位是不明确的,其地址也是在运行时才确定的,因此访问也必要重定位,在汇编成机器语言时,将操纵数全部置为0,并且添加重定位条目。

链接的过程:
根据hello和hello.o的不同,分析出链接的过程为:
链接就是链接器(ld)将各个目的文件(各种.o文件)组装在一起,文件中的各个函数段按照一定规则归并在一起。
5.6 hello的实验流程

使用edb实验hello,说明从加载hello到_start,到call main,以及步伐停止的所有过程。请列出其调用与跳转的各个子步伐名或步伐地址。
子函数名和地址(后6位)
401000 <_init>

401020 <.plt>

401030 <puts@plt>

401040 <printf@plt>

401050 <getchar@plt>

401060 <atoi@plt>

401070 <exit@plt>

401080 <sleep@plt>

401090 <_start>

4010c0 <_dl_relocate_static_pie>

4010c1 <main>

401150 <__libc_csu_init>

4011b0 <__libc_csu_fini>

4011b4 <_fini>



图30:edb运行步伐

5.7 Hello的动态链接分析

书上的解释是如许的:假设步伐调用一个有共享库定义的函数。编译器无法猜测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到恣意位置。正常的方法时为该引用天生一条重定位记录,然后动态链接器在步伐加载的时候再解析它。GNU通过一种叫做延长绑定(lazy binding)的技能来将地址的绑定推迟到第一次调用该过程。
动态链接器使用过程链接表PLT和全局偏移量表GOT实现函数的动态链接。此中GOT中存放函数目的地址,PLT使用GOT中地址跳转到目的函数。

图31:实验init之前PLT中的内容



图32:实验init之后PLT中的内容

初始时GOT内里存的都是PLT的第二条指令,随后链接器修改GOT,下一次再调用PLT时,指向的就是正确的内存地址。PLT就能跳转到正确的区域。

5.8 本章小结

本章通过hello步伐简朴介绍了链接。链接主要两个步骤是符号解析和重定位,将不同的可重定位目的文件链接成一个完整的可以载入内存实验的步伐。通过分析hello.o和hello的反汇编代码,更好地理解了链接中的重定位和以及虚拟内存映射。通过edb对步伐的调试,相识了在hello步伐实验前、后必要运行的体系函数,以及动态链接所要做的工作。
(第51分)



第6章 hello进程管理

6.1 进程的概念与作用

6.1.1进程的概念:
进程是实验中步伐的抽象。
6.1.2进程的作用:
(1)每次运行步伐时,shell创建一新进程,在这个进程的上下文切换中运行这个可实验目的文件。应用步伐也能够创建新进程,并且在新进程的上下文中运行它们自己的代码或其他应用步伐。
(2)进程提供给应用步伐的关键抽象:一个独立的逻辑控制流,犹如步伐独占处理器;一个私有的地址空间,犹如步伐独占内存体系。
6.2 简述壳Shell-bash的作用与处理流程

6.2.1 Shell的概念
shell是一个交互型的应用级步伐,它代表客户运行其他步伐。
6.2.2 Shell的作用
Shell毗连了用户和Linux内核,让用户能够更加高效、安全、低本钱地使用Linux内核,能够接收用户输入的命令,并对命令进行处理,处理完毕后再将效果反馈给用户,比如输出到显示器、写入到文件等。
在 Shell 中输入的命令,有一部分是 Shell本身自带的,这叫做内置命令;有一部分是其它的应用步伐(一个步伐就是一个命令),这叫做外部命令。
Shell 本身支持的命令并不多,功能也有限,但是 Shell可以调用其他的步伐,每个步伐就是一个命令,这使得 Shell命令的数量可以无限扩展,其效果就是 Shell 的功能非常强大,完万能够胜任 Linux的一样平常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动摆设、更改体系设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。
6.2.3 Shell的处理流程
Shell先分词,判断命令是否为内部命令,假如不是,则寻找可实验文件进行实验,重复这个流程:
6.3 Hello的fork进程创建过程

首先Shell会检查hello步伐是否一个内置命令。当发现不是内置命令时,则实验这个步伐。父进程通过fork函数创建进程,子进程得到一份和父进程虚拟空间相同但是相互独立的副本——包括数据段、代码段、共享库、堆、用户栈等。子进程和父进程的差异在于两者的PID不同。
Fork函数是一个比较特殊的函数,它调用1次,返回2次,在父进程中fork返回子进程的PID,在子进程中fork返回0。
6.4 Hello的execve过程

execve函数加载并运行可实验目的文件Hello,且带列表argv和环境变量列表envp。该函数的作用是在当前进程的上下文中加载并运行一个新的步伐。
execve函数正常运行不返回,只有当出现错误时,例如找不到Hello时,execve才会返回到调用步伐。
在execve加载了Hello之后,它调用启动代码。启动代码设置栈,并将控制通报给新步伐的主函数,该主函数有如下的原型:
int main(int argc , char **argv , char *envp);

联合虚拟内存和内存映射过程,可以更具体地说明exceve函数现实上是如何加载和实验步伐Hello:

6.5 Hello的进程实验

(1)逻辑控制流
一系列步伐计数器 PC 的值的序列叫做逻辑控制流。由于进程是轮替使用处理器的,同一个处理器每个进程实验它的流的一部分后被抢占,然后轮到其他进程。
(2)用户模式和内核模式
处理器使用一个寄存器提供两种模式的区分:用户模式和内核模式。
用户模式的进程不允许实验特殊指令,不允许直接引用地址空间中内核区的代码和数据。
内核模式进程可以实验指令集中的任何命令,并且可以访问体系中的任何内存位置。
(3)上下文
上下文就是内核重新启动一个被抢占的进程所必要规复的原来的状态,由寄存器、步伐计数器、用户栈、内核栈和内核数据布局等对象的值构成。


图33 进程的上下文切换

以上图为例,进程在实验到read指令时必要磁盘的读写,而磁盘的读写的时间是毫秒级的,假如不进行任何操纵的话会浪费几十万个时钟周期,因此处理器会进行进程的上下文切换,将进程A的上下文生存起来,在磁盘读取的同时,转到进程B中去实验操纵,当磁盘的读写完成的时候再转回到进程A中,规复之前生存的上下文,继续之前未实验的操纵。
(4)调理的过程
在进程实验的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决议就叫做调理,是由内核中称为调理器的代码处理的。当内核选择一个新的进程运行,我们说内核调理了这个进程。在内核调理了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
(5)用户态与核心态转换
为了能让处理器安全运行,不至于损坏操纵体系,必然必要先知应用步伐可实验指令所能访问的地址空间范围。因此,就存在了用户态与核心态的分别,核心态拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入体系调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,包管了体系的安全性。
6.6 hello的异常与信号处理

6.6.1正常运行状态:


图34:Hello正常运行的状态

6.6.2异常的类型
类别

原因
异步/同步
返回行为
中断

来自I/O设备的信号
异步
总是返回到下一条指令
陷阱

有意的异常
同步
总是返回到下一条指令
故障

潜伏可规复的错误
同步
可能返回到当前指令
停止

不可规复的错误
同步
不会返回
6.6.3异常的处理方式


图35 中断处理方式



图 36 陷阱处理方式



图 37 故障处理方式



图 38 停止处理方式

6.6.4 Hello步伐的异常处理
(1)Ctrl+C
在步伐运行的时候从键盘输入Ctrl+C,步伐会受到SIGINT信号,从而进程会停止。


图39:进程被Ctrl+C停止

(2)中途乱按
在步伐运行的时候乱按键盘,从键盘输入的字符会在屏幕上显示出来。在bash中乱按,bash会检查输入是否是内置命令或是可实验的步伐,假如不是会显示这不是内置命令。


图40:中途乱按键盘

(3)Ctrl+Z
在步伐运行的时候键入ctrl+Z,进程会收到SIGTSTP的信号,hello进程因此被挂起。可以使用ps命令来查看hello进程的PID。
ps能列出当前的所有进程,pstree能列出进程树,jobs能显示当前暂停的进程,fg使hello在前台继续进行,kill能给hello发送指定的信号。


图41:键入Ctrl+Z后使用ps和pstree操纵



图42:hello进程挂起后实验jobs和fg命令


Kill命令可以给指定的进程发送信号
Kill -9 87682 能够将PID为87682的hello进程停止。


图43:kill命令发送信号

6.7本章小结

之前的章节我们相识到是步伐,步伐是静态的,而进程是实验的步伐,进程是动态的。进程通过父进程使用fork函数得到了生命,使用execve函数实现了进程的加载。计算机中会有很多进程同时在运行,内核调理步伐会进行进程的调理,实现体系效率的优化。在进程调理的过程中,会有用户模式和内核模式的切换,上下文的切换、生存。
同时我们介绍了四种异常类型,以及信号机制。不同的异常对应不同的处理机制。同理对于不同的信号,体系会有不同的处理步伐。我们可以通过给进程发送信号的方式来对进程进行管理操纵。
(第61分)


第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址(Logical Address)是指由步伐hello产生的与段相干的偏移地址部分(hello.o)。
线性地址(Linear Address)是逻辑地址到物理地址变更之间的中心层。步伐hello的代码会产生逻辑地址,大概说是(即hello步伐)段中的偏移地址,它加上相应段的基地址就天生了一个线性地址。
偶然我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与现实物理内存容量无关的,是hello中的虚拟地址。
物理地址(Physical Address)是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变更的最终效果地址。假如启用了分页机制,那么hello的线性地址会使用页目次和页表中的项变更成hello的物理地址;假如没有启用分页机制,那么hello的线性地址就直接成为物理地址了。
7.2 Intel逻辑地址到线性地址的变更-段式管理

一个逻辑地址由两部分构成,段标识符,段内偏移量。段标识符是一个16位长的字段构成,称为段选择符,此中前13位是一个索引号。后面三位包含一些硬件细节。
    索引号,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
    这内里,我们只用关心Base字段,它描述了一个段的开始位置的线性地址。
    全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
    GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。
   给定一个完整的逻辑地址段选择符+段内偏移地址,
   看段选择符的T1=0照旧1,知道当前要转换是GDT中的段,照旧LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
   拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,如许,它了Base,即基地址就知道了。
把Base + offset,就是要转换的线性地址了
7.3 Hello的线性地址到物理地址的变更-页式管理

页式管理是一种内存空间存储管理的技能,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间分别成若干个长度相当的页(page),页式管理把内存空间按页的大小分别成片大概页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变更机构,来办理离散地址变更题目。页式管理接纳请求调页或预调页技能实现了表里存存储器的统一管理。


图 44 页式管理流程图

长处:
缺点:
7.4 TLB与四级页表支持下的VA到PA的变更

每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。假如PTE可巧缓存在L1中,那么开销就会下降1或2个周期。然而,很多体系都试图消除即使是如许的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。
多级页表:
将虚拟地址的VPN分别为相当大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。


图 45 使用k级页表进行翻译

解析VA,利用前m位vpn1寻找一级页表位置,接着一次重复k次,在第k级页表得到了页表条目,将PPN与VPO组合得到PA
7.5 三级Cache支持下的物理内存访问

CPU发送一条虚拟地址,随后MMU按照上述操纵得到了物理地址PA。根据cache大小组数的要求,将PA分为CT(标记位)CS(组号),CO(偏移量)。根据CS寻找到正确的组,比较每一个cacheline是否标记位有效以及CT是否相当。假如命中就直接返追念要的数据,假如不命中,就依次去L2,L3,主存判断是否命中,当命中时,将数据传给CPU同时更新各级cache的cacheline(假如cache已满则要接纳换入换出计谋)。


图46 3级Cache

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建各种数据布局,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存。
它创建了当前进程的mm_struct、区域布局和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域布局都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操纵时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。
7.7 hello进程execve时的内存映射

1)在bash中的进程中实验了如下的execve调用:execve("hello",NULL,NULL);
2)execve函数在当前进程中加载并运行包含在可实验文件hello中的步伐,用hello替换了当前bash中的步伐。
下面是加载并运行hello的几个步骤:
3)删除已存在的用户区域。
4)映射私有区域
5)映射共享区域
6)设置步伐计数器(PC)
exceve做的最后一件事是设置当前进程的上下文中的步伐计数器,是指指向代码区域的入口点。而下一次调理这个进程时,他将从这个入口点开始实验。Linux将根据必要换入代码和数据页面。
7.8 缺页故障与缺页中断处理

页面命中完全是由硬件完成的,而处理缺页是由硬件和操纵体系内核协作完成的:


图 47 缺页中断处理

整体的处理流程:
7.9动态存储分配管理

动态储存分配管理使用动态内存分配器来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个一连的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用步伐使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被开释,这种开释要么是应用步伐显式实验的,要么是内存分配器自身隐式实验的。动态内存分配主要有两种根本方法与计谋:
带边界标签的隐式空闲链表分配器管理

带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外添补以及一个字的尾部构成的。
隐式空闲链表:在隐式空闲链表中,因为空闲块是通过头部中的大小字段隐含地毗连着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。此中,一个设置了已分配的位而大小为零的停止头部将作为特殊标记的竣事块。
当一个应用请求一个k字节的块时,分配器搜刮空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置计谋:初次适配、下一次适共同最佳适配。分配完后可以分割空闲块减少内部碎片。同时分配器在面对开释一个已分配块时,可以归并空闲块,此中便利用隐式空闲链表的边界标记来进行归并。
显示空间链表管理
显式空闲链表是将空闲块组织为某种形式的显式数据布局。因为根据定义,步伐不必要一个空闲块的主体,以是实现这个数据布局的指针可以存放在这些空闲块的主体内里。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。
显式空闲链表:在显式空闲链表中。可以接纳后进先出的序次维护链表,将最新开释的块放置在链表的开始处,也可以接纳按照地址序次来维护链表,此中链表中每个块的地址都小于它的后继地址,在这种情况下,开释一个块必要线性时间的搜刮来定位符合的前驱。
7.10本章小结

本章主要介绍了 hello 的存储器地址空间、 intel 的段式管理、 hello 的页式管理,在指定环境下介绍了 VA 到 PA 的变更、物理内存访问,还介绍 hello 进程 fork 时的内存映射、 execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7 2分)


第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模子化:文件
文件(所有的I/O设备都被模子化为文件,甚至内核也被映射为文件)
设备管理:unix io接口
这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简朴、低级的应用接口,称为Unix I/O。
我们可以对文件的操纵有:打开关闭操纵open和close;读写操纵read和write;改变当前文件位置lseek等
8.2 简述Unix IO接口及其函数

Unix IO接口:
打开文件:内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操纵。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准堕落(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。
改变当前的文件位置,文件开始位置为文件偏移量,应用步伐通过seek操纵,可设置文件的当前位置为k。
读写文件,读操纵:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增长到k+n;写操纵:从内存复制n个字节到文件,当前文件位置为k,然后更新k
关闭文件:当应用完成对文件的访问后,关照内核关闭这个文件。内核会开释文件打开时创建的数据布局,将描述符规复到描述符池中
Unix IO函数:
1. open()函数
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
函数原型:int open(const char *pathname,int flags,int perms)
参数:pathname:被打开的文件名(可包括路径名如"dev/ttyS0")flags:文件打开方式,
返回值:成功:返回文件描述符;失败:返回-1
2. close()函数
功能描述:用于关闭一个被打开的的文件
所需头文件: #include <unistd.h>
函数原型:int close(int fd)
参数:fd文件描述符
函数返回值:0成功,-1堕落
3. read()函数
功能描述: 从文件读取数据。
所需头文件: #include <unistd.h>
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 体现调用一次read操纵,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(堕落)。
4. write()函数
功能描述: 向文件写入数据。
所需头文件: #include <unistd.h>
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(堕落)
5. lseek()函数
功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include <unistd.h>,#include <sys/types.h>
函数原型:off_t lseek(int fd, off_t offset,int whence);
参数:fd;文件描述符。offset:偏移量,每一个读写操纵所必要移动的隔断,单元是字节,可正可负(向前移,向后移)
返回值:成功:返回当前位移;失败:返回-1
8.3 printf的实现分析

printf函数:
int printf(const char *fmt, ...)

{

    int i;

    va_list arg = (va_list)((char *)(&fmt) + 4);

    i = vsprintf(buf, fmt, arg);

    write(buf, i);

    return i;

}

所引用的vsprintf函数
int vsprintf(char *buf, const char *fmt, va_list args)

{

    char *p;

    chartmp[256];

    va_listp_next_arg = args;

    for (p = buf; *fmt; fmt++)

    {

        if (*fmt != '%')

        {

            *p++ = *fmt;

            continue;

        }

        fmt++;

        switch (*fmt)

        {

        case 'x':

            itoa(tmp, *((int *)p_next_arg));

            strcpy(p, tmp);

            p_next_arg += 4;

            p += strlen(tmp);

            break;

        case 's':

            break;

        default:

            break;

        }

        return (p - buf);

    }

}

vsprintf函数将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf天生显示信息,到write体系函数,到陷阱-体系调用 int 0x80或syscall.字符显示驱动子步伐:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

getchar有一个int型的返回值。当步伐调用getchar时,步伐就等着用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。
当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如堕落返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步异常-键盘中断的处理:键盘中断处理子步伐。担当按键扫描码转成ascii码,生存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到担当到回车键才返回。
8.5本章小结

本章介绍了 Linux 的 I/O 设备的根本概念和管理方法,以及Unix I/O 接口及其函数。最后分析了printf 函数和 getchar 函数的工作过程。
(第81分)

结论

我们最早打仗的hello步伐从高级语言C步伐到可见的实验效果履历的步骤远比我们想象中的要复杂。对hello一生的周游,就是对计算机体系的一次周游。
通过本学期对计算机体系的学习和实验的实践,我对计算机体系中一些底层的实现有了更加清晰的熟悉,比如数据的体现、步伐的机器织体现、流水线的原理、编译原理、链接、信号处理等。CSAPP一书深入浅出,在阅读过程中,自己的头脑得到了提升。

在以后计算机的学习中,动手实践和理论学习中应当相互促进,一起精进。


(结论0分,缺失 -1分,根据内容酌情加分)


附件

文件的作用

文件名

源文件

hello.c

预处理后的文件

hello.i

编译之后的汇编文件

hello.s

汇编之后的可重定位目的文件

hello.o

链接之后的可实验目的文件

hello

hello.o 的 ELF 格式

hello_o_elf.txt

hello.o 的反汇编代码

hello_o_asm

hello的ELF 格式

hello_elf

hello 的反汇编代码

hello_asm


列出所有的中心产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)


参考文献

为完本钱次大作业你翻阅的册本与网站等

[1]  深入理解计算机体系 Randal E. Bryant  David R. O’Hallaron
[2]  CSDN博客 Ubuntu体系预处理、汇编、链接指令
[3]  CSDN博客 ELF可重定位目的文件格式
[4]  博客园 shell命令实验过程
[5] 博客园 [转]printf 函数实现的深入剖析 
(参考文献0分,缺失 -1分)



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4