程序人生-Hello’s P2P

本文深入剖析了Hello程序从源代码到可执行程序的全过程,包括预处理、编译、汇编、链接等阶段,以及在Linux环境下从程序到进程的转变,涉及进程管理、存储管理、I/O管理等核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

计算机系统

大作业

题 目 程序人生-Hello’s P2P

专业 计算机科学与技术

学  号 1180300107

班  级 1803001

学生 胡云帆

指导教师 郑贵滨

计算机科学与技术学院

2019年12月


摘要

hello基本上是所有程序员最先接触到的程序,程序员通过hello接触了一门又一门的计算机语言,但很少有人能理解hello程序运行时背后的机制。本文以C程序为例,简要介绍了源程序hello.c从预处理、编译、汇编、链接成为程序的过程,并介绍了hello从程序到进程的进程管理、内存管理、Linux I/O等过程的实现细节。

关键词:编译过程;进程管理;

目录

第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简介

我是Hello,程序员用C语言创造了我,当程序员使用gcc编译我的时候,经历了预处理,编译,链接,汇编四个阶段后,我就变成了程序(program),后来,用户在shell中使用./hello ……运行了我,shell调用fork函数,并用execve函数运行了我,我便变成了进程(process)

自我出生,我就在磁盘中呆着,在shell调用execve函数运行我的时候,我才从0开始映射到了自己的虚拟内存,CPU为我分配了时间片来运行我,随着运行缺页,我被加载到了物理内存,当我运行完,shell进程便来回收我,我便回归为0

1.2 环境与工具

Intel® Core™ i5-8300 CPU 2.30GHz

Ubuntu 18.04

gcc,vim,readelf,objdump,gedit,gdb,code::block 16.01,edb……1.3 中间结果

hello.i预处理文件,hello预处理后得到的结果,为下一步汇编提供遍历

hello.s汇编代码文件,hello完成编译阶段后生成的汇编代码,机器级代码

hello.o可重定位目标文件,hello完成汇编阶段生成的二进制代码

hellodump.txt hello使用objdump反汇编后得到的汇编代码

helloelf.txt hello.o使用readelf得到的结果

helloelf_e.txt hello 使用readelf得到的结果

1.4 本章小结

本章是hello的简介,简要介绍了Hello的p2p,o2o过程,并且展示了hello运行环境,处理hello使用的工具,以及中间的结果等

(第1章0.5分)

第2章预处理

#2.1 预处理的概念与作用

预处理指的是C语言程序在编译之前所做的工作,是C语言程序编译过程中的重要的一步

预处理的作用作用

删除“#define”并展开所定义的宏

处理所有条件预编译指令,如#ifdef, #ifndef,#endif等

插入头文件到“#include”处,可以递归插入

删除所有注释

添加行号和文件名标识,以便编译时编译器产生调试用的行号信息

保留所有#pragma编译指令

预处理作用:

将.c文件转化成.i文件,方便后续编译器的工作(代码的转化)。

2.2在Ubuntu下预处理的命令
在这里插入图片描述

cpp hello.c -o hello.i

gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

删除“#define”并展开所定义的宏

处理所有条件预编译指令,如#ifdef, #ifndef,#endif等

插入头文件到“#include”处,可以递归插入

删除所有注释

添加行号和文件名标识,以便编译时编译器产生调试用的行号信息

对hello.c进行预处理,将stdio.h,unistd.h,stdlib.h三个头文件插入到#include处

处理后得到的代码行数暴增,但是仍然是可读的C代码。

2.4 本章小结 预处理阶段将hello.c处理成hello.i文件,对程序进行进一步的简化,便于后续程序的运行

(第2章0.5分)

第3章编译

3.1 编译的概念与作用

编译:将预处理文件后的文件(如hello.i)进行词法分析,语法分析,语义分析,和优化,生成汇编代码文件的过程

作用:将.i文件转换成.s文件,.s文件是机器级代码,经过简单的处理就可以转化成机器可以执行的代码

3.2 在Ubuntu下编译的命令

gcc -S hello. -o hello.s

在这里插入图片描述

3.3 Hello的编译结果解析

3.3.1常量和常量表达式

C语言中的常量和常量表达式在处理后直接放入代码段中或数据段中。如果是全局初始化的常量(不含0)会被放入数据段,函数中的常量会体现在代码段中

const常量如果是在全局范围内定义的将会放入.rodata

3.3.2全局变量

C语言中的全局变量和静态变量放在数据段中,在汇编码中可以看到.data

3.3.3局部变量

C语言中的局部变量在栈中管理,具体行为在汇编代码中体现

3.3.4类型

在汇编语言中不存在数据类型,在转化为汇编代码的过程中已经做好处理,具体可以按照指令中的b w l q确定操作数的大小其中,b代表1Byte,w代表一个字(2Byte),l代表双字(4Byte)q代表四字(8Byte)

3.3.5宏

C语言的宏在预处理阶段就已经进行了替换,不在编译阶段处理

3.3.6赋值表达式

一般用mov指令实现,具体有movb,movw,movl,movq指令,分别传送一Byte,一字,二字,四字

3.3.7逗号

定义中的逗号代表定义多个类型相同的符号

表达式间当顺序点用,结合顺序是从左至右,用来顺序求值,完毕之后整个表达式的值是最后一个表达式的值。

在汇编的层面上是多条汇编指令分别处理

3.3.8赋初值

不赋初值的全局变量当做0处理,赋初值的变量有初值,栈中为赋初值的局部变量的值不确定

3.3.9类型转换

隐式类型转换:在运算的过程中要向上进行类型转换

显示类型转换:程序员明确指明要进行类型转换

显示类型转换和隐式类型转换的过程都没有真正的进行类型转换,因为汇编中是没有类型的概念的,只是编译器在这里要按照显示规定的类型或隐式要求的类型进行处理,生成的汇编代码的根据是转换后的类型

在汇编代码里的一些条件传送指令可以支持扩展,如movzbl,movslq,z代表0扩展,s代表符号扩展,有些转换可以通过这些扩展实现。另movl指令对低位赋值的时候,高位会自动清为0

3.3.10算数操作

在C语言代码中,一元运算符++,–,-,+是右结合的,其它是左结合的,且括号优先级最大,然后是%*/,最后是±,

在汇编代码中,通过 inc,dec,neg,not,add,sub,imul,xor,or,and,sal,shl,sar,shr等等指令完成这一过程

3.3.11逻辑运算

C语言中逻辑运算是短路运算

从左向右运算,遇到条件符合的停止运算

!运算只对0作用,其它均返回0

3.3.12位运算

位运算有直接对应的汇编指令,如AND,OR,NOT,XOR,SAL,SHL,SAR,SHR

3.3.13复合操作

复合操作在这个过程中被拆解,拆解成多个普通的操作

3.3.14关系操作

关系操作如果与控制转移语句一起使用,一般由test,cmp指令与jx指令联合使用进行处理

3.3.15数组

数组可能是全局的或局部的,全局的在.data中分配相应的空间,局部的在栈上分配空间.数组操作的寻址方式一般采用的是比例变址寻址法。即I(rA,rB,d)得到的地址为rA + d * rB + I

3.3.16指针

指针一般存放的是虚拟地址的值,&指令是取变量的虚拟地址,*变量是取虚拟地址中对应的值指针可以通过取地址实现

3.3.17结构操作

用户自己定义的结构只是被看成了连续的空间,不过这里有对齐的需求,每个元素按其大小对齐,结构整体按最大元素对齐,寻址方式和数组类似。

3.3.18控制转移

由jx指令组成,有jmp无条件跳转,还有ja,jae,jb,jbe,jnbe,j,ae,jg,jl,jge,jle,jnge,jnle,je,jne等等指令组成

3.3.19函数操作

参数传递:%rdi,%rsi,%rdx,%rcx,%r8,%r9分别放置前6个参数,其余放在栈里,7~n由低地址到高地址排列

函数调用call,先将返回地址入栈,PC相对寻址,将PC的值指向调用函数的地址,调用后,压栈旧的%rbp

函数返回return,取出%rbp,根据返回地址跳转返回,%rax用来存放函数的返回值

3.4 本章小结

本章讲述了hello在编译的过程中对其各种数据与操作进行处理的策略与方法

(第3章2分)

第4章汇编

4.1 汇编的概念与作用

概念:汇编是将.s文件转化为二进制机器指令的过程

作用:汇编将.s代码转化为机器可以直接处理的机器指令以便后序链接生成可执行文件。

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

as hello.s -o hello.o

在这里插入图片描述

4.3 可重定位目标elf格式

ELF头:

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

类别: ELF64

数据: 2补码,小端序 (little endian)

版本: 1 (current)

OS/ABI: UNIX - System V

ABI版本: 0

类型: REL (可重定位文件)

系统架构: Advanced Micro Devices X86-64

版本: 0x1

入口点地址:0x0

程序头起点:0 (bytes into file)

Start of section headers: 1152 (bytes into file)

标志:0x0

本头的大小:64 (字节)

程序头大小:0 (字节)

Number of program headers: 0

节头大小:64 (字节)

节头数量:13

字符串表索引节头:12

魔数的第2~4字符是ELF的ASCII码,说明了文件的类型,说明了其为二进制补码表示,小端法

ELF头中说明了节头表位置,每项大小,项数等信息,也列出了程序头的位置,每项大小,项数等信息。

节头:

[号] 名称 类型 地址 偏移量

大小全体大小旗标链接信息对齐

[ 0] NULL 0000000000000000 00000000

   0000000000000000  0000000000000000           0     0     0

[ 1] .text PROGBITS 0000000000000000 00000040

   0000000000000081  0000000000000000  AX       0     0     1

[ 2] .rela.text RELA 0000000000000000 00000340

   00000000000000c0  0000000000000018   I      10     1     8

[ 3] .data PROGBITS 0000000000000000 000000c4

   0000000000000004  0000000000000000  WA       0     0     4

[ 4] .bss NOBITS 0000000000000000 000000c8

   0000000000000000  0000000000000000  WA       0     0     1

[ 5] .rodata PROGBITS 0000000000000000 000000c8

   000000000000002b  0000000000000000   A       0     0     1

[ 6] .comment PROGBITS 0000000000000000 000000f3

   000000000000002c  0000000000000001  MS       0     0     1

[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000011f

   0000000000000000  0000000000000000           0     0     1

[ 8] .eh_frame PROGBITS 0000000000000000 00000120

   0000000000000038  0000000000000000   A       0     0     8

[ 9] .rela.eh_frame RELA 0000000000000000 00000400

   0000000000000018  0000000000000018   I      10     8     8,

[10] .symtab SYMTAB 0000000000000000 00000158

   0000000000000198  0000000000000018          11     9     8

[11] .strtab STRTAB 0000000000000000 000002f0

   000000000000004d  0000000000000000           0     0     1

[12] .shstrtab STRTAB 0000000000000000 00000418

   0000000000000061  0000000000000000           0     0     1

通过READELF工具,得到了各个节的名称,大小,类型,偏移等信息,注意由于还没有进行重定位,地址信息尚且无法确定

偏移量信息类型符号值符号名称+加数

000000000018 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4

00000000001d 000c00000004 R_X86_64_PLT32 0000000000000000 puts - 4

000000000027 000d00000004 R_X86_64_PLT32 0000000000000000 exit - 4

000000000050 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 1a

00000000005a 000e00000004 R_X86_64_PLT32 0000000000000000 printf - 4

000000000060 000900000002 R_X86_64_PC32 0000000000000000 sleepsecs - 4

000000000067 000f00000004 R_X86_64_PLT32 0000000000000000 sleep - 4

000000000076 001000000004 R_X86_64_PLT32 0000000000000000 getchar - 4

如图为.rel.text中的信息,第一条偏移量是指的在.text节中的信息,类型是指重定位信息,名称是其在符号表中的名称,后边加数是重定位的过程中所要修改的信息,重定位的过程中需要根据符号表修改这里的部分信息,完成重定位后这个节将会被舍弃

4.4 Hello.o的结果解析

机器语言由二进制数构成,二进制数对应于指令和数据,在变长指令集中,二进制指令的长度不是固定的。每一句二进制指令和一句汇编指令构成一一对应的关系,机器语言中的操作数与汇编语言不一致,是由于采用了PC相对寻址,PC相对寻址中地址加上当前PC的值(下一条语句)就可以得到与汇编语言一样的结果

4.5 本章小结

本章讲述了汇编的过程与命令,使用readelf和objdump两种工具,对指汇编代码,机器代码,ELF格式进行了分析。

(第4章1分)

第5章链接

5.1 链接的概念与作用

链接是指将可重定位目标文件合并生成可执行目标文件的过程

作用:通过符号解析和重定位,将.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
在这里插入图片描述

5.3 可执行目标文件hello的格式

ELF 头:

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

类别: ELF64

数据: 2 补码,小端序 (little endian)

版本: 1 (current)

OS/ABI: UNIX - System V

ABI 版本: 0

类型: EXEC (可执行文件)

系统架构: Advanced Micro Devices X86-64

版本: 0x1

入口点地址:0x400500

程序头起点:64 (bytes into file)

Start of section headers: 5928 (bytes into file)

标志:0x0

本头的大小:64 (字节)

程序头大小:56 (字节)

Number of program headers: 8

节头大小:64 (字节)

节头数量:25

字符串表索引节头:24

ELF头中,魔数列出了这个文件的基本信息,列出了节头表,程序头表的位置,表项数,表项大小等信息,还列出了目标文件类型。由于是可执行文件,ELF头中列出了入口点的地址。

节头:

[号] 名称 类型 地址 偏移量

大小全体大小旗标链接信息对齐

[ 0] NULL 0000000000000000 00000000

   0000000000000000  0000000000000000           0     0     0

[ 1] .interp PROGBITS 0000000000400200 00000200

   000000000000001c  0000000000000000   A       0     0     1

[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c

   0000000000000020  0000000000000000   A       0     0     4

[ 3] .hash HASH 0000000000400240 00000240

   0000000000000034  0000000000000004   A       5     0     8

[ 4] .gnu.hash GNU_HASH 0000000000400278 00000278

   000000000000001c  0000000000000000   A       5     0     8

[ 5] .dynsym DYNSYM 0000000000400298 00000298

   00000000000000c0  0000000000000018   A       6     1     8

[ 6] .dynstr STRTAB 0000000000400358 00000358

   0000000000000057  0000000000000000   A       0     0     1

[ 7] .gnu.version VERSYM 00000000004003b0 000003b0

ps in th   0000000000000010  0000000000000002   A       5     0     2

[ 8] .gnu.version_r VERNEED 00000000004003c0 000003c0

   0000000000000020  0000000000000000   A       6     1     8

[ 9] .rela.dyn RELA 00000000004003e0 000003e0

   0000000000000030  0000000000000018   A       5     0     8

[10] .rela.plt RELA 0000000000400410 00000410

   0000000000000078  0000000000000018  AI       5    19     8

[11] .init PROGBITS 0000000000400488 00000488

   0000000000000017  0000000000000000  AX       0     0     4

[12] .plt PROGBITS 00000000004004a0 000004a0

   0000000000000060  0000000000000010  AX       0     0     16

[13] .text PROGBITS 0000000000400500 00000500

   0000000000000132  0000000000000000  AX       0     0     16

[14] .fini PROGBITS 0000000000400634 00000634

   0000000000000009  0000000000000000  AX       0     0     4

[15] .rodata PROGBITS 0000000000400640 00000640

   000000000000002f  0000000000000000   A       0     0     4

[16] .eh_frame PROGBITS 0000000000400670 00000670

   00000000000000fc  0000000000000000   A       0     0     8

[17] .dynamic DYNAMIC 0000000000600e50 00000e50

   00000000000001a0  0000000000000010  WA       6     0     8

[18] .got PROGBITS 0000000000600ff0 00000ff0

   0000000000000010  0000000000000008  WA       0     0     8

[19] .got.plt PROGBITS 0000000000601000 00001000

   0000000000000040  0000000000000008  WA       0     0     8

[20] .data PROGBITS 0000000000601040 00001040

   0000000000000008  0000000000000000  WA       0     0     4

[21] .comment PROGBITS 0000000000000000 00001048

   000000000000002b  0000000000000001  MS       0     0     1

[22] .symtab SYMTAB 0000000000000000 00001078

   0000000000000498  0000000000000018          23    28     8

[23] .strtab STRTAB 0000000000000000 00001510

   0000000000000150  0000000000000000           0     0     1

[24] .shstrtab STRTAB 0000000000000000 00001660

   00000000000000c5  0000000000000000           0     0     1

Key to Flags:

W (write), A (alloc), X (execute), M (merge), S (strings), I (info),

L (link order), O (extra OS processing required), G (group), T (TLS),

C (compressed), x (unknown), o (OS specific), E (exclude),

l (large), p (processor specific)

There are no section groups in this file.

节头表列出了各个节的信息,可以看到,多了一个.init节,少了.rel.text,.rel.data这两个节。

程序头:

Type Offset VirtAddr PhysAddr

             FileSiz            MemSiz              Flags  Align

PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040

             0x00000000000001c0 0x00000000000001c0  R      0x8

INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200

             0x000000000000001c 0x000000000000001c  R      0x1

  [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000

             0x000000000000076c 0x000000000000076c  R E    0x200000

LOAD 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50

             0x00000000000001f8 0x00000000000001f8  RW     0x200000

DYNAMIC 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50

             0x00000000000001a0 0x00000000000001a0  RW     0x8

NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c

             0x0000000000000020 0x0000000000000020  R      0x4

GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000

             0x0000000000000000 0x0000000000000000  RW     0x10

GNU_RELRO 0x0000000000000e50 0x0000000000600e50 0x0000000000600e50

             0x00000000000001b0 0x00000000000001b0  R      0x1

程序头表中,INTERP是用于动态链接的段,两个LOAD分别是代码段和数据段

Section to Segment mapping:

段节…

00

01 .interp

02 .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame

03 .dynamic .got .got.plt .data

04 .dynamic

05 .note.ABI-tag

06

07 .dynamic .got

Dynamic section at offset 0xe50 contains 21 entries:

标记类型名称/值

0x0000000000000001 (NEEDED) 共享库:[libc.so.6]

0x000000000000000c (INIT) 0x400488

0x000000000000000d (FINI) 0x400634

0x0000000000000004 (HASH) 0x400240

0x000000006ffffef5 (GNU_HASH) 0x400278

0x0000000000000005 (STRTAB) 0x400358

0x0000000000000006 (SYMTAB) 0x400298

0x000000000000000a (STRSZ) 87 (bytes)

0x000000000000000b (SYMENT) 24 (bytes)

0x0000000000000015 (DEBUG) 0x0

0x0000000000000003 (PLTGOT) 0x601000

0x0000000000000002 (PLTRELSZ) 120 (bytes)

0x0000000000000014 (PLTREL) RELA

0x0000000000000017 (JMPREL) 0x400410

0x0000000000000007 (RELA) 0x4003e0

0x0000000000000008 (RELASZ) 48 (bytes)

0x0000000000000009 (RELAENT) 24 (bytes)

0x000000006ffffffe (VERNEED) 0x4003c0

0x000000006fffffff (VERNEEDNUM) 1

0x000000006ffffff0 (VERSYM) 0x4003b0

0x0000000000000000 (NULL) 0x0

重定位节’.rela.dyn’ at offset 0x3e0 contains 2 entries:

偏移量信息类型符号值符号名称+ 加数

000000600ff0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0

000000600ff8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 gmon_start + 0

重定位节’.rela.plt’ at offset 0x410 contains 5 entries:

偏移量信息类型符号值符号名称+ 加数

000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0

000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

000000601028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0

000000601030 000600000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0

000000601038 000700000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

Symbol table ‘.dynsym’ contains 8 entries:

Num: Value Size Type Bind Vis Ndx Name

 0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

 1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)

 2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)

 3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)

 4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@GLIBC_2.2.5 (2)

 5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

 6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (2)

 7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.2.5 (2)

Symbol table ‘.symtab’ contains 49 entries:

Num: Value Size Type Bind Vis Ndx Name

 0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

 1: 0000000000400200     0 SECTION LOCAL  DEFAULT    1

 2: 000000000040021c     0 SECTION LOCAL  DEFAULT    2

 3: 0000000000400240     0 SECTION LOCAL  DEFAULT    3

 4: 0000000000400278     0 SECTION LOCAL  DEFAULT    4

 5: 0000000000400298     0 SECTION LOCAL  DEFAULT    5

 6: 0000000000400358     0 SECTION LOCAL  DEFAULT    6

 7: 00000000004003b0     0 SECTION LOCAL  DEFAULT    7

 8: 00000000004003c0     0 SECTION LOCAL  DEFAULT    8

 9: 00000000004003e0     0 SECTION LOCAL  DEFAULT    9

10: 0000000000400410     0 SECTION LOCAL  DEFAULT   10

11: 0000000000400488     0 SECTION LOCAL  DEFAULT   11

12: 00000000004004a0     0 SECTION LOCAL  DEFAULT   12

13: 0000000000400500     0 SECTION LOCAL  DEFAULT   13

14: 0000000000400634     0 SECTION LOCAL  DEFAULT   14

15: 0000000000400640     0 SECTION LOCAL  DEFAULT   15

16: 0000000000400670     0 SECTION LOCAL  DEFAULT   16

17: 0000000000600e50     0 SECTION LOCAL  DEFAULT   17

18: 0000000000600ff0     0 SECTION LOCAL  DEFAULT   18

19: 0000000000601000     0 SECTION LOCAL  DEFAULT   19

20: 0000000000601040     0 SECTION LOCAL  DEFAULT   20

21: 0000000000000000     0 SECTION LOCAL  DEFAULT   21

22: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c

23: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS

24: 0000000000600e50     0 NOTYPE  LOCAL  DEFAULT   17 __init_array_end

25: 0000000000600e50     0 OBJECT  LOCAL  DEFAULT   17 _DYNAMIC

26: 0000000000600e50     0 NOTYPE  LOCAL  DEFAULT   17 __init_array_start

27: 0000000000601000     0 OBJECT  LOCAL  DEFAULT   19 _GLOBAL_OFFSET_TABLE_

28: 0000000000400630     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini

29: 0000000000601040     0 NOTYPE  WEAK   DEFAULT   20 data_start

30: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5

31: 0000000000601044     4 OBJECT  GLOBAL DEFAULT   20 sleepsecs

32: 0000000000601048     0 NOTYPE  GLOBAL DEFAULT   20 _edata

33: 0000000000400634     0 FUNC    GLOBAL DEFAULT   14 _fini

34: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.5

35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_

36: 0000000000601040     0 NOTYPE  GLOBAL DEFAULT   20 __data_start

37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getchar@@GLIBC_2.2.5

38: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

39: 0000000000400640     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used

40: 00000000004005c0   101 FUNC    GLOBAL DEFAULT   13 __libc_csu_init

41: 0000000000601048     0 NOTYPE  GLOBAL DEFAULT   20 _end

42: 0000000000400530     2 FUNC    GLOBAL HIDDEN    13 _dl_relocate_static_pie

43: 0000000000400500    43 FUNC    GLOBAL DEFAULT   13 _start

44: 0000000000601048     0 NOTYPE  GLOBAL DEFAULT   20 __bss_start

45: 0000000000400532   129 FUNC    GLOBAL DEFAULT   13 main

46: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5

47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@@GLIBC_2.2.5

48: 0000000000400488 0 FUNC GLOBAL DEFAULT 11 _init

符号表中列出了程序中所有符号的信息,标明了符号的类型和全局/局部属性,给处所有符号的符号名和地址

Histogram for bucket list length (total of 3 buckets):

Length Number % of total Coverage

  0  0          (  0.0%)

  1  0          (  0.0%)      0.0%

  2  2          ( 66.7%)     57.1%

  3  1          ( 33.3%)    100.0%

Version symbols section ‘.gnu.version’ contains 8 entries:

地址:00000000004003b0 Offset: 0x0003b0 Link: 5 (.dynsym)

000: 0 (本地) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5)

004: 2 (GLIBC_2.2.5) 0 (本地) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5)

Version needs section ‘.gnu.version_r’ contains 1 entry:

地址:0x00000000004003c0 Offset: 0x0003c0 Link: 6 (.dynstr)

000000: Version: 1 文件:libc.so.6 计数:1

0x0010: Name: GLIBC_2.2.5 标志:无 版本:2

Displaying notes found in: .note.ABI-tag

所有者Data size Description

GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)

OS: Linux, ABI: 3.2.0

5.4 hello的虚拟地址空间

代码段从0x400000开始

数据段从0x600e50开始

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

5.5 链接的重定位过程分析

链接过程首先进行符号解析,将每个符号引用与一个符号定义唯一的关联起来,然后进行重定位过程,将hello.o与库的代码段和数据段合并,给每个定义的符号唯一确定的地址,利用重定位算法修改符号引用处的值

符号引用的值在重定位条目中有所体现,具体过程根据一下重定位算法

foreach section s

{

foreach relocation entry r

{

refptr = s+r.offset;

if(r.type == R_X86_64_PC32)

{

refaddr = ADDR(s)+r.offset;

*refptr = ADDR(r.symbol) + r.addend - refaddr;

}

if(r.type == R_X86_64_32)

*refptr = ADDR(r.symbol) + r.addend;

}

}

即:

链接过程:

符号解析:

将每一个符号引用与唯一的符号定义关联起来

重定位:

1)将所有相同的节合并,形成一个大的数据段,代码段

[if !supportLists]2) [endif]确定所有符号的地址

[if !supportLists]3) [endif]把所有符号引用处的地址填入适当的内容

5.6 hello的执行流程

ld-2.27.so! dl_start0x7ffee5aca680

ld-2.27.so!dl_init0x7f9f48629630

hello!_start0x0x400500

ld-2.27.so!_libc_start_main0x7f9f48249ab0

libc-2.27.so! cxa_atexit0x7f4523fd6af7

libc-2.27.so!lll_look_wait_private0x7f4523ff8471

libc-2.27.so!_new_exitfn0x7f87ff534220

hello!_libc_csu_init0x7f87ff512b26

libc-2.27.so!_sigsetjmp0x7f87ff52fc12

libc-2.27.so!__sigjmp_save0x7f87ff52fbc3

hello_main0x400532

exit@plt0x4004e0hello!

printf@plt0x400587hello!

sleep@plt0x400594hello!

getchar@plt0x4005a3

dl_runtime_resolve0x7f169ad84750

libc-2.27.so!exit0x7fce8c889128

5.7 Hello的动态链接分析

动态链接是由PLT和GOT合作完成的,GOT原来指向的是PLT对应表项的下一行的地址,具体行为是将调用的号码压栈,跳到PLT[0].PLT[0]将GOT[1]压栈,调用GOT2,这样控制交给了动态链接器,动态链接器完成链接后会修改相应的GOT表项,修改后再次调用是通过GOT表项可直接跳到链接后对应的地址。

首先,找到PLT段的内容,根据节头表可以确定其地址为0x4004a0

在这里插入图片描述

每个PLT表项指向一个GOT表项,在动态链接的过程中,PLT表项不会发生变化,发生改变的将是GOT表项,GOT表项原来指向的就是PLT表项的下一句,在发生动态链接后,指向的将是链接后函数的地址

在这里插入图片描述

5.8 本章小结

本章主要讲述了链接的过程,包括链接的概念,过程,重定位过程分析,虚拟地址空间的分析,执行流程的分析,动态链接过程的分析等

(第5章1分)

第6章hello进程管理

6.1 进程的概念与作用

概念:一个执行中程序的实例

作用:可以使一个CPU通过将时间分成时间片,并发完成不同任务,从而实现同时运行多个进程;使内存管理更加高效,便于内存共享等

(以下格式自行编排,编辑时删除)

6.2 简述壳Shell-bash的作用与处理流程

shell进行操作系统与用户之间的交互,解释用户的命令,将命令翻译后交由操作系统执行,

首先,当shell程序开始运行,打印shell前置的信息,等待用户输入命令

用户输入命令后,shell程序解析命令

首先判断用户输入的是不是内置命令,如果是内置命令,则直接调用相关处理函数进行处理

如果不是内置命令,那么认为用户要运行一个程序,首先解析命令行,获取用户所需要的程序,获取待创建的进程是进程还是后台进程,如果程序不存在,则报错并返回,如果程序存在,阻塞sigchld,sigint,sigtstp信号,调用fork函数,子进程调用execve函数,shell阻塞所有信号,将其加入到任务数组中,如果是前台进程,shell要等待其结束,如果是后台进程,shell不等待,直接进入下一轮循环

6.3 Hello的fork进程创建过程

当在命令行中输入./hello时,shell发现它不是内置命令,于是利用fork函数创建一个子进程。fork调用一次,返回两次,也就是说产生了一个子进程,内核为新进程创建各种数据结构,并给它分配一个唯一的pid,创建了当前进mm_struct,区域结构和页表的原样副本,将两个进程中每一个页面都标为只读,两个进程中每一个结构都标为写时复制。fork函数在父进程中返回PID,子进程中返回0

6.4 Hello的execve过程

调用execve函数时,首先清空原来的信息,删除当前进程虚拟地址的用户部分中已存在的数据结构,根据执行的程序创建新的数据结构。所有区域都是私有的、写时复制的,代码、数据区域映射到a.out文件中的.text,.data,bss映射到匿名文件,堆栈请求二进制0,初始长度为0。对于共享库区域,动态链接后映射,最后,设置当前上下文的程序计数器,指向当前进程代码段的入口处,CPU分配时间片给此进程,开始执行

6.5 Hello的进程执行

上下文:进程正确运行所需的状态,包括存放在内存中的程序代码和数据,它的栈,通用目的寄存器,程序计数器,环境变量,以及打开的文件描述符的集合

进程时间片:进程运行的一段时间

进程调度:进程被分配时间片

用户态,内核态:处理器通常用某个控制寄存器的一个模式位来提供用户模式和内核模式的功能。设置了模式位时,进程就运行在内核模式中,该进程可以执行指令集中的任何指令,可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中,用户模式中的进程不允许执行特权指令。

进程运行时,要抢占上一个进程的运行。操作系统进入内核态,检查有无待处理信号,然后CPU给进程分配时间片,就说CPU调度了hello这个进程。此时加载Hello的上下文,包括恢复其各种寄存器,PC等,然后进入用户态,hello进程开始执行

6.6 hello的异常与信号处理

类别原因同步/异步返回行为备注

中断来自外部I/O设备的信号异步

下一条

处理器芯片引脚信号,总线异常号

陷阱有意的异常

同步

系统调用

故障潜在的可恢复错误

本条

终止不可恢复错误不返回

信号有操作系统函数(内核代码)处理,如果用户绑定了信号处理代码,那么将由用户自定义的信号处理函数处理

随便乱按,不会产生异常

ctrl+c进程收到sigint信号,终止,由shell回收

ctrl+z进程收到sigtstp信号,停止,在后台

ps可以查看进程的信息

jobs可以查看作业

pstree可以查看所有进程的进程树

fg可以让后台进程到前台运行kill可以给进程发指定的信号 kill -9 发的是sigkill 信号

(以下格式自行编排,编辑时删除)

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

在这里插入图片描述
在这里插入图片描述
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

在这里插入图片描述

6.7本章小结

本章介绍的进程有关的概念,给出了进程的创建,执行,异常等机制

(第6章1分)

第7章hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:Intel为了兼容,保留了IA-32的段式管理方式。逻辑地址是由一个段标识符加上一个指定段内相对地址的偏移量组成的,表示为段标识符:段内偏移量.

线性地址:线性地址指的是连续的地址空间组成的地址

虚拟地址:虚拟地址只的是虚拟内存中的地址,从程序员的视角看到的都是虚拟地址

物理地址:物理地址指的是在主存中的真实地址,一个页只有被加载到主存中,才会有物理地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

一个逻辑地址由两部分组成,段标识符和段内偏移量

虚拟内存可以分成很多的段,每个段都有段描述符,这个描述符是记录在段描述符表里边的,段描述符记录了段的起始地址。

段描述符表有全局描述符表和局部描述符表之分,而段基址在全局描述符表中还是局部描述符表中是由段标示符中的一个字段(一位)确定的。

首先我们通过段标示符中的确定在全局描述符表还是局部描述符表的那一个字段,确定这个地址所对应的段信息是在全局描述符表中还是局部描述符表中

然后根据相应的寄存器得到表的基地址,根据偏移量确定表项,在表项中可以得到段的基地址,

然后根据段中的基地址加上相应的段内偏移量,就是线性地址。

7.3 Hello的线性地址到物理地址的变换-页式管理

虚拟内存相对与物理内存很大,虚拟内存分页,动态的载入到物理内存中。页表记录了虚拟页在不在物理内存中的信息。虚拟内存用4KB划分,每4KB为一页,物理内存也有同样的划分方式,即二者的页大小一定是一致的

有一个寄存器CR3,专门记录了页表的基址,每个进程有独立的页表,基址也就不一样,所以上下文切换的过程中CR3要动态更新。

得到虚拟地址后,根据虚拟地址的高位VPN到页表中寻找对应的项,如果页表项已经载入,那么直接得到对应的PPN与VPO拼接则得到物理地址;如果页表项没有载入,那么发生缺页故障,由缺页异常处理程序进行处理。

在这里插入图片描述

7.4 TLB与四级页表支持下的VA到PA的变换

VPN:虚拟页号

VPO:虚拟页面偏移

PPN:物理页号

PPO:物理页面偏移

在地址翻译的过程中,VPN和PPN代表的内容是相同的。

首先将VA分成VPN和VPO,用VPN到TLB中找对应的表项,过程类似于高速缓存的过程。如果TLB命中,可以直接得到PPN,如果TLB不命中,则以VPN作为索引到页表中取相应的项,将VPN分成四个部分,分别对应四级页表的索引,前边几级得到的是下一级页表的索引,最后一级得到的是相应的PPN。如果任意一级页表中有效位为0,那么这个页都不在物理内存中,此时,要交由缺页异常处理程序处理,修改页表对应的值,重新执行导致缺页异常的指令

在这里插入图片描述

在这里插入图片描述

7.5 三级Cache支持下的物理内存访问

首先,通过页表的查询,由VA可以得到PA,然后就可以通过PA访问cache,先访问L1cache,首先将PA分成CT CI CO 三个部分,通过CI找到对应的组,然后通过CT和cache中的tag匹配寻找相应的项,如果命中,则可以以CO作为偏移得到数据,如果不命中,则继续向L2L3cache发出请求,如果cache都不命中就到主存中请求相应数据

7.6 hello进程fork时的内存映射

当调用fork()时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前程序的mm_struct,区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中每个区域结构都标记为只读、私有的写时复制。当发生写的时候,会引起相应的保护故障,有操作系统函数来进行复制,并修改权限,此时再写就可以不会引发故障,这样的机制节省了宝贵的物理内存资源

7.7 hello进程execve时的内存映射

(1)删除已存在的用户区域。即当前虚拟地址用户部分已存在的结构

(2)映射私有区域。为新程序代码,数据,和堆栈区域创建新的区域结构。

(3)映射共享区域。如果与共享对象链接比如libc.so,那么这些对象都是动态链接到这个程序的,然后在映射到用户虚拟地址的共享区域。

(4)设置程序计数器。设置当前进程的PC,使之指向代码区域的入口。

7.8 缺页故障与缺页中断处理

当页表中有效位为0,引发了缺页异常,此时触发了缺页异常处理程序。异常处理程序会选择一个牺牲页,如果这个牺牲页被修改过,那么就将这个页写回内存,然后换入新的页面,更新页表中相应的内容。CPU重新运行相应的代码,就不会发生缺页异常了

7.9动态存储分配管理

动态内存分配是由内存分配器实现的。内存分配器维护着堆区域。内核维护着一个变量brk指向堆顶。分配器把堆视为一组大小不相等的块的集合。每个块都是一个连续的虚拟内存空间,是已分配的或空闲的。已分配的块显示的保留为供应程序使用。空闲块可以用来分配。一个已分配的块保持已分配状态直到它被释放。

对于隐式空闲链表的实现,动态内存分配器有三种分配策略:首次适配,下一次适配和最佳适配。首次适配就是从头开始搜索,选择第一个合适的空闲块;下一次适配是从上一次链表查询结束的地方开始搜索;最佳适配是搜索一遍找到最合适的块,时间代价比较高

7.10本章小结

本章介绍了hello的内存管理,包括地址空间、段页式存储、cache访问、内存映射、缺页故障处理、动态内存分配等内容

(第7章 2分)

第8章hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

在Linux系统中,所有对I/O设备的访问都抽象为文件操作。文件包括普通文件、目录和套接字。

设备管理:unix io接口

Linux内核引出一个简单、低级的应用接口,叫Unix I/O

打开文件:一个应用程序通过要求内核打开相应的文件,来宣告想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后序对此文件的所有操作中标示这个文件,内核记录有关这个文件的打开信息。Linux shell创建的进程每个进程开始时都有三个打开的文件:标准输入、标准输出、标准错误。

读写文件:一个读操作就是从文件复制个n个字节到内存,从当前文件位置k开始,然后将k增加到k+n。写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件

8.2 简述Unix IO接口及其函数

Unix I/O是简单、低级的应用接口,使得所有输入和输出都能通过统一且一致的方式进行。

int open(char *filename,int flags,mode_t mode);

int close(int fd);

ssize_t read(int fd,void *buf,size_t n);

ssize_t write(int fd,const void *buf,size_t n);

off_t lseek(int fd, off_t offset , int whence)

8.2.1 open

int open(char *filename,int flags,mode_t mode);

open函数将filename转换为一个文件描述符,并且返回描述符的数字,返回的描述符总是当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件,mode参数指明了访问权限位

8.2.2 close

int close(int fd);

关闭一个打开的文件,fd是文件描述符

8.2.3 read

ssize_t read(int fd,void *buf,size_t n);

read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf,返回值为-1表示错误,返回值为0表示EOF,否则,返回值表示的是实际的传送字节数量

8.2.4 write

ssize_t write(int fd,const void *buf,size_t n);

write函数从内存位置buf复制至多n个字节到描述符fd的当前位置

8.3 printf的实现分析

int printf(const char *fmt, ...)

{

int i;

char buf[256];



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

i = vsprintf(buf, fmt, arg); //返回字符串的长度

write(buf, i); //写我们的字符串



return i;

}

解析

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

由于printf函数的参数位置不确定,我们要根据格式串确定参数数量,这句语句实际上是在确定参数的地址

vsprintf(buf, fmt, arg);

vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

write(buf, i);

i是返回字符串的长度,这里是要将buf中的字符串写向终端。write是系统函数,这里使用int 0x80或syscall陷入内核

打印的过程是由字符串显示子程序运行的。字符显示驱动子程序从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

getchar函数的源代码大致如下

    int getchar(void)

    {

        static char buf[BUFSIZ];

        static char* bb=buf;

        static int n=0;

if(n==0)

{

        n=read(0,buf,BUFSIZ);

        bb=buf;

}

    return(--n>=0)?(unsigned char)*bb++:EOF;

}

用户从键盘输入后,通过CPU的引脚信号,发现键盘处的异常(异步异常)。当发现用户输入的符号是Enter的时候,getchar函数将调用系统函数read将用户输入的内容全部读到缓冲区中,并只从缓冲区中读一个元素,如果发现缓冲区中为空,则返回EOF。

8.5本章小结

本章介绍了Linux I/O管理和printf,getchar的实现方法。

(以下格式自行编排,编辑时删除)

(第8章1分)


结论

hello被程序员编写后,经过预处理、编译、汇编、链接阶段后成为程序,在用户命令下由shell程序fork并execve后成为进程。在预处理阶段,其所有的宏被替换;编译阶段,被转化成汇编指令,汇编阶段转化成机器码,链接阶段与静态库链接形成可执行程序,成为进程后,有独自的虚拟地址空间,动态链接为其链接动态库,其代码和数据在虚拟内存的机制下在物理内存中换入换出,并有cache为其寻址提供缓存。Linux的文件机制为其提供简单的I/O,运行的最后,shell提供回收机制将其回收。

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


附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.i预处理文件,hello预处理后得到的结果,为下一步汇编提供遍历

hello.s汇编代码文件,hello完成编译阶段后生成的汇编代码,机器级代码

hello.o可重定位目标文件,hello完成汇编阶段生成的二进制代码

hellodump.txt hello使用objdump反汇编后得到的汇编代码

helloelf.txt hello.o使用readelf得到的结果

helloelf_e.txt hello 使用readelf得到的结果

(附件0分,缺失 -1分)


参考文献

为完成本次大作业你翻阅的书籍与网站等

[1] Randall E.Bryant, David R. O’Hallaron Computer System A programer’s Perspective Third Edition ISBN 978-7-111-54493-7[2016]

[2] 袁春风计算机系统基础机械工业出版社2014.5

[3] https://blog.csdn.net/weixin_33868027/article/details/93492905

[4] https://www.cnblogs.com/edver/p/8419807.html

[5] https://www.cnblogs.com/pianist/p/3315801.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值