计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机专业与技术
学 号 7203610609
班 级 2036014
学 生 宋浩
指 导 教 师 刘宏伟
计算机科学与技术学院
2022年5月
本文介绍了hello程序预处理、编译、汇编、链接生成可执行文件的全过程,并分析了其在运行中与计算机系统进程管理、存储管理和IO管理等部件的相互作用和原理,展现了程序文件和计算机系统密不可分的底层关系。
关键词:hello,预处理,编译,汇编,链接,进程管理,存储管理,IO管理,edb;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:在Linux系统中,hello.c经过cpp的预处理成为hello.i,再由ccl的编译变成汇编文件hello.s,然后汇编其as的汇编成为hello.o,最终经过ld的链接成为可执行目标程序hello,在shell中键入启动命令后,shell为其fork产生一个子进程,hello程序就变为了进程。
020: shell为此子进程execve,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入主函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据结构。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1.2.1 硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 软件环境
Windows10 64位;
Vmware Workstation Pro 15.5;
Ubuntu 20.04 LTS 64位;
1.2.3 开发工具
CodeBlocks 64位;gedit+gcc
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
中间结果文件名字 |
文件作用 |
hello.i |
预处理得到的中间结果 |
hello.s |
hello.i编译后得到的汇编语言文本文件 |
hello.o |
hello.s汇编后得到的可重定位目标文件 |
hello.out |
链接后得到的可执行目标文件 |
hello |
可执行文件 |
helloelf.txt |
hello.o的ELF格式文本 |
helloELF.txt |
hello的ELF格式文本 |
asmhello.txt |
hello.o的反汇编文本 |
asmhelloexe.txt |
hello的反汇编文本 |
1.4 本章小结
•预处理阶段
预处理器(CPP)根据以字符 #开头的命令,修改原始的 C 程序。比如hello.c 中第1行的#include < stdio.h> 命令告诉预处理器读取系统头文件stdio.h 的内容,并把它直接插入程序文本中。结果就得到了另一个 C 程序,通常是以.i 作为文件扩展名。
•编译阶段
编译器(ccl)将文本文件 hello.i 翻译成文本文件 hello.s, 它包含一个汇编语言程序。该程序包含函数 main 的定义
定义中的每条语句都以一种文本格式描述了一条低级机器语言指令。
汇编语言是非常有用的,因为它为不同高级语言的不同编译器提供了通用的输出语言。例如,C 编译器和 Fortran 编译器产生的输出文件用的都是一样的汇编语言。
•汇编阶段
接下来,汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标文件 hello.o 中。hello.o文件是一个二进制文件,它包含的 17 个字节是函数 main的指令编码。如果我们在文本编辑器中打开 hello.o文件,将看到一堆乱码。[1]
(第1章0.5分)
图1
第2章 预处理
2.1 预处理的概念与作用
2.1.1概念
指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。
预处理也称为预编译,它为编译做准备工作,主要进行代码文本的替换工作,用于处理#开头的指令,其中预处理产生编译器的输出。下表是一些常见的预处理指令及其功能。[3]
图2
2.1.2作用
C语言的预处理主要有三个方面的内容:宏定义、文件包含、条件编译。预处理命令以符号“#”开头。预处理工作也叫做宏展开,将宏名替换为文本。宏定义只需将符号常量替换成后面对应的文本即可。文件包含可以在一个文件中包含另一个文件的内容,被包含的文件称为头文件。头文件的内容可以有函数原型、宏定义、结构体定义。条件编译是在条件满足时才编译某些语句。使用条件编译可以使目标程序变小,运行时间变短。同时有利于代码的模块化。
2.2在Ubuntu下预处理的命令
命令:
gcc hello.c -o hello.i -E
图3
图4
图5
2.3 Hello的预处理结果解析
经过预处理之后,hello.c文件转化为hello.i文件。原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。打开该文件可以发现,文件长度变为3057行。文件的内容增加,且仍为可以阅读的C语言程序文本文件。
2.4 本章小结
本阶段完成了对hello.c的预处理工作。使用Ubuntu下的预处理指令可以将其转换为.i文件。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
3.1.1概念
将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化后编译成若干个目标模块。可以理解为将高级语言翻译成计算机可以理解的二进制码,即机器语言。
3.1.2作用
编译程序的基本功能是把源程序(高级语言)翻译成目标程序。但是,作为一个具有实际应用价值的编译系统,除了基本功能之外,还应具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人机联系等重要功能。[4]
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
图6
图7
图8
图9
3.3 Hello的编译结果解析
C语言代码如下:
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main(int argc,char *argv[]){
- int i;
- if(argc!=4){
- printf(“用法: Hello 7203610609 宋浩 秒数!\n”);
- exit(1);
- }
- for(i=0;i<8;i++){
- printf(“Hello %s %s\n”,argv[1],argv[2]);
- sleep(atoi(argv[3]));
- }
- getchar();
- return 0;
- }
hello.s代码如下:
- .file “hello.c”
- .text
- .section .rodata
- .align 8
- .LC0:
- .string “\347\224\250\346\263\225: Hello 7203610609 \345\256\213\346\265\251 \347\247\222\346\225\260\357\274\201”
- .LC1:
- .string “Hello %s %s\n”
- .text
- .globl main
- .type main, @function
- main:
- .LFB6:
- .cfi_startproc //用于函数开始
- endbr64
- pushq %rbp
- .cfi_def_cfa_offset 16 //表示执行完pushq %rbp后sp与cfa偏移了16字节
- .cfi_offset 6, -16
- movq %rsp, %rbp
- .cfi_def_cfa_register 6
- subq $32, %rsp
- movl %edi, -20(%rbp) //%edi代表argc
- movq %rsi, -32(%rbp) //%rsi代表argv[]
- cmpl $4, -20(%rbp)
- je .L2 //if(argc!=4)执行下面操作,否则跳转.L2
- leaq .LC0(%rip), %rdi
- call puts@PLT
- movl $1, %edi
- call exit@PLT //exit(1)
- .L2:
- movl $0, -4(%rbp)
- jmp .L3
- .L4:
- movq -32(%rbp), %rax
- addq $16, %rax //argv[2]
- movq (%rax), %rdx
- movq -32(%rbp), %rax
- addq $8, %rax //argv[1]
- movq (%rax), %rax
- movq %rax, %rsi
- leaq .LC1(%rip), %rdi
- movl $0, %eax
- call printf@PLT
- movq -32(%rbp), %rax
- addq $24, %rax //argv[3],sleep的参数
- movq (%rax), %rax
- movq %rax, %rdi
- call atoi@PLT //调用atoi函数
- movl %eax, %edi
- call sleep@PLT //调用sleep函数
- addl $1, -4(%rbp)
- .L3:
- cmpl $7, -4(%rbp) //用来判断i是否超过8
- jle .L4 //不超过则执行L4循环体
- call getchar@PLT //调用getchar函数
- movl $0, %eax
- leave
- .cfi_def_cfa 7, 8
- ret //return 0
- .cfi_endproc
- .LFE6:
- .size main, .-main
- .ident “GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0”
- .section .note.GNU-stack,””,@progbits
- .section .note.gnu.property,”a”
- .align 8
- .long 1f – 0f
- .long 4f – 1f
- .long 5
- 0:
- .string “GNU”
- 1:
- .align 8
- .long 0xc0000002
- .long 3f – 2f
- 2:
- .long 0x3
- 3:
- .align 8
- 4:
3.3.0文件结构
.file:声明源文件
.text:声明代码段
.data:声明数据段
.rodata:只读数据
.align:数据或者指令的地址对其方式
.string:声明一个字符串
.globl:声明全局变量
.type:指定类型
- .file “hello.c”
- .text
- .section .rodata
- .align 8
- .LC0:
- .string “\347\224\250\346\263\225: Hello 7203610609 \345\256\213\346\265\251 \347\247\222\346\225\260\357\274\201”
- .LC1:
- .string “Hello %s %s\n”
- .text
- .globl main
- .type main, @function
3.3.1数据
①字符串
字符串常量被存在只读数据段(.rodata)中。
图10
②局部变量
main函数声明了一个局部变量i,编译器进行编译的时候将局部变量i会放在堆栈中。如图所示,局部变量i放在栈上-4(%rbp)的位置,且赋值为0。
图11
③常量
程序中的数字常量直接存储在程序段(.text)中
图12
④全局变量
全局函数main
图13
3.3.2赋值
赋值主要通过mov指令进行操作,如下:
图14
图15
3.3.3算数操作
加法操作,此处为i=i+1。
图16
减法操作。
图17
3.3.4关系操作
图18
图19
3.3.5数组操作
数组的起始地址存放在栈中-32(%rbp)的位置,并对相应位置进行字符串的赋值。如下图:
图20
图21
3.3.6控制转移
主要通过j类命令操作。
图22
图23
3.3.7函数操作
主要通过call命令。
图24
3.3.8类型转换
图25
调用atoi函数将字符型转换成int型
3.4 本章小结
概述了编译的概念和作用,重点详细分析了C程序编译成汇编程序的过程以及不同C语言的操作翻译成汇编语句的表示和处理方法。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
4.1.1概念
汇编是指把汇编语言翻译成机器语言的过程,在这里还包括把这些机器语言指令打包成为可重定位目标程序的过程。
4.1.2作用
把汇编语言一一对应地翻译成机器可以直接执行的机器指令。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
gcc hello.s -c -o hello.o
图26
再用gedit打开可以看到乱码:
图27
4.3 可重定位目标elf格式
(分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。)
4.3.1概览
首先ELF文件是一种用于二进制文件、可执行文件、目标代码、共享库和core转存格式文件。是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。
使用readelf -l 命令可以查看一个链接后的elf可执行文件,Section to Segment 的映射关系。
使用命令readelf -a hello.o > helloelf.txt 查看hello.o的ELF格式,并将结果重定位为文本文件,如下:
图28
图29
其完整代码如下:
- ELF Header: //ELF头↓
- Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
- Class: ELF64
- Data: 2’s complement, little endian //2补码,小端序
- Version: 1 (current)
- OS/ABI: UNIX – System V
- ABI Version: 0
- Type: REL (Relocatable file) //可重定位文件
- Machine: Advanced Micro Devices X86-64 //系统架构
- Version: 0x1
- Entry point address: 0x0 //入口点地址
- Start of program headers: 0 (bytes into file) //程序头起点
- Start of section headers: 1248 (bytes into file) //节头起点
- Flags: 0x0 //标志
- Size of this header: 64 (bytes) //本头大小64字节
- Size of program headers: 0 (bytes) //程序头大小0字节
- Number of program headers: 0
- Size of section headers: 64 (bytes) //节头大小64字节
- Number of section headers: 14 //有14个节头
- Section header string table index: 13 //字符串表索引节头
- //ELF头↑
- Section Headers: //节头表↓
- [Nr] Name Type Address Offset //Offset是偏移量
- Size EntSize Flags Link Info Align
- [ 0] NULL 0000000000000000 00000000
- 0000000000000000 0000000000000000 0 0 0
- [ 1] .text PROGBITS 0000000000000000 00000040
- 0000000000000092 0000000000000000 AX 0 0 1
- [ 2] .rela.text RELA 0000000000000000 00000390
- 00000000000000c0 0000000000000018 I 11 1 8
- [ 3] .data PROGBITS 0000000000000000 000000d2
- 0000000000000000 0000000000000000 WA 0 0 1
- [ 4] .bss NOBITS 0000000000000000 000000d2
- 0000000000000000 0000000000000000 WA 0 0 1
- [ 5] .rodata PROGBITS 0000000000000000 000000d8
- 0000000000000037 0000000000000000 A 0 0 8
- [ 6] .comment PROGBITS 0000000000000000 0000010f
- 000000000000002c 0000000000000001 MS 0 0 1
- [ 7] .note.GNU-stack PROGBITS 0000000000000000 0000013b
- 0000000000000000 0000000000000000 0 0 1
- [ 8] .note.gnu.propert NOTE 0000000000000000 00000140
- 0000000000000020 0000000000000000 A 0 0 8
- [ 9] .eh_frame PROGBITS 0000000000000000 00000160
- 0000000000000038 0000000000000000 A 0 0 8
- [10] .rela.eh_frame RELA 0000000000000000 00000450
- 0000000000000018 0000000000000018 I 11 9 8
- [11] .symtab SYMTAB 0000000000000000 00000198
- 00000000000001b0 0000000000000018 12 10 8
- [12] .strtab STRTAB 0000000000000000 00000348
- 0000000000000048 0000000000000000 0 0 1
- [13] .shstrtab STRTAB 0000000000000000 00000468
- 0000000000000074 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.
- There are no program headers in this file.
- There is no dynamic section in this file.
- Relocation section ‘.rela.text’ at offset 0x390 contains 8 entries:
- Offset Info Type Sym. Value Sym. Name + Addend
- 00000000001c 000500000002 R_X86_64_PC32 0000000000000000 .rodata – 4
- 000000000021 000c00000004 R_X86_64_PLT32 0000000000000000 puts – 4
- 00000000002b 000d00000004 R_X86_64_PLT32 0000000000000000 exit – 4
- 000000000054 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 26
- 00000000005e 000e00000004 R_X86_64_PLT32 0000000000000000 printf – 4
- 000000000071 000f00000004 R_X86_64_PLT32 0000000000000000 atoi – 4
- 000000000078 001000000004 R_X86_64_PLT32 0000000000000000 sleep – 4
- 000000000087 001100000004 R_X86_64_PLT32 0000000000000000 getchar – 4
- Relocation section ‘.rela.eh_frame’ at offset 0x450 contains 1 entry:
- Offset Info Type Sym. Value Sym. Name + Addend
- 000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
- The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
- Symbol table ‘.symtab’ contains 18 entries: //符号表↓
- Num: Value Size Type Bind Vis Ndx Name
- 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
- 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
- 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
- 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
- 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
- 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
- 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
- 7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
- 8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
- 9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
- 10: 0000000000000000 146 FUNC GLOBAL DEFAULT 1 main
- 11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
- 12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
- 13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit
- 14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
- 15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND atoi
- 16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
- 17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND getchar //符号表↑
- No version information found in this file.
- Displaying notes found in: .note.gnu.property
- Owner Data size Description
- GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0
- Properties: x86 feature: IBT, SHSTK
4.3.2分析[6]
①ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如X86-64)、节头部表的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的,其中目标文件中每一个节都有一个固定大小的条目,
可通过readelf -h hello.o读取ELF头信息,包括文件信息。[5]
图30
可见,对应代码1-20行。
其中Magic含义如下图:图来自[7]
图31
②程序头表(Program header table)列举了所有有效的段(segments)和他们的属性(执行视图)。程序头是一个结构的数组,每一个结构都表示一个段(segments)。在可执行文件或者共享链接库中所有的节(sections)都被分为不同的几个段(segments)。
可通过readelf -l hello.o读取ELF程序头信息。
图32
可见,对应代码61行,为NULL。
③节头表(Section header table) – 包含对节(sections)的描述(链接视图)。一个ELF文件中到底有哪些具体的 sections,由包含在这个ELF文件中的 section head table(SHT)决定。每个section描述了这个段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其它属性。
可通过readelf -S hello.o读取sections’ header。
图33
④系统预定的固定section
以下给出常见section:
图34
- .text代码段
可以通过objdump -d 反汇编,查看ELF文件代码段内容。
可通过命令readelf -x.text hello.o查看
图35
- .strtab 字符串表
在ELF文件中,会用到很多字符串,比如节名,变量名等。所以ELF将所有的字符串集中放到一个表里,每一个字符串以’\0’分隔,然后使用字符串在表中的偏移来引用字符串。这样在ELF中引用字符串只需要给出一个数组下标即可。字符串表在ELF也以段的形式保存, .shstrtab是专供section name的字符串表。可以通过命令readelf –x.strtab hello.o查看:
图36
- .symtab符号表
在链接过程中,我们将函数和变量统称为符号,函数名和变量名就是符号名。
每个定义的符号都有一个相应的值,叫做符号值(Symbol Value),对于变量和函数,符号值就是它们的地址。
可以使用下面命令查看:readelf -s hello.o
图37
可见对应代码82-101行。
也可以通过readelf -x.symtab hello.o查看其16进制输出:
图38
- .eh_frame / .eh_frame_hdr
在调试程序的时候经常需要进行堆栈回溯,早期使用通用寄存器(ebp)来保存每层函数调用的栈帧地址,但局限性很大。后来现代Linux操作系统在LSB(Linux Standard Base)标准中定义了一个.eh_frame section,用来描述如何去unwind the stack。gcc编译器默认打开,如果不想把.eh_frame section编入elf文件,可以通过gcc选项 -fno-asynchronous-unwind-tables 去除。
GAS(GCC Assembler)汇编编译器定义了一组伪指令来协助eh_frame生成调用栈信息CFI(Call Frame Information)。
- .relname重定位表
链接器在处理目标文件时,需要对目标文件中的某些部位进行重定位,即代码段和数据中中那些绝对地址引用的位置。对于每个需要重定位的代码段或数据段,都会有一个相应的重定位表。比如”.rel.text”就是针对”.text”的重定位表,”.rel.data”就是针对”.data”的重定位表。
可通过命令readelf -r hello.o查看
图39
信息的高4个字节表示该符号在.symtab中的index,低4个字节表示重定位的类型,加数是一个符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。
- .rodata节
可通过命令readelf -x.rodata hello.o 查看其16进制输出:
图40
4.4 Hello.o的结果解析
(objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。)
hello.s代码如下:
- .file “hello.c”
- .text
- .section .rodata
- .align 8
- .LC0:
- .string “\347\224\250\346\263\225: Hello 7203610609 \345\256\213\346\265\251 \347\247\222\346\225\260\357\274\201”
- .LC1:
- .string “Hello %s %s\n”
- .text
- .globl main
- .type main, @function
- main:
- .LFB6:
- .cfi_startproc //用于函数开始
- endbr64
- pushq %rbp
- .cfi_def_cfa_offset 16 //表示执行完pushq %rbp后sp与cfa偏移了16字节
- .cfi_offset 6, -16
- movq %rsp, %rbp
- .cfi_def_cfa_register 6
- subq $32, %rsp
- movl %edi, -20(%rbp) //%edi代表argc
- movq %rsi, -32(%rbp) //%rsi代表argv[]
- cmpl $4, -20(%rbp)
- je .L2 //if(argc!=4)执行下面操作,否则跳转.L2
- leaq .LC0(%rip), %rdi
- call puts@PLT
- movl $1, %edi
- call exit@PLT //exit(1)
- .L2:
- movl $0, -4(%rbp)
- jmp .L3
- .L4:
- movq -32(%rbp), %rax
- addq $16, %rax //argv[2]
- movq (%rax), %rdx
- movq -32(%rbp), %rax
- addq $8, %rax //argv[1]
- movq (%rax), %rax
- movq %rax, %rsi
- leaq .LC1(%rip), %rdi
- movl $0, %eax
- call printf@PLT
- movq -32(%rbp), %rax
- addq $24, %rax //argv[3],sleep的参数
- movq (%rax), %rax
- movq %rax, %rdi
- call atoi@PLT //调用atoi函数
- movl %eax, %edi
- call sleep@PLT //调用sleep函数
- addl $1, -4(%rbp)
- .L3:
- cmpl $7, -4(%rbp) //用来判断i是否超过8
- jle .L4 //不超过则执行L4循环体
- call getchar@PLT //调用getchar函数
- movl $0, %eax
- leave
- .cfi_def_cfa 7, 8
- ret //return 0
- .cfi_endproc
- .LFE6:
- .size main, .-main
- .ident “GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0”
- .section .note.GNU-stack,””,@progbits
- .section .note.gnu.property,”a”
- .align 8
- .long 1f – 0f
- .long 4f – 1f
- .long 5
- 0:
- .string “GNU”
- 1:
- .align 8
- .long 0xc0000002
- .long 3f – 2f
- 2:
- .long 0x3
- 3:
- .align 8
- 4:
通过objdump -d -r hello.o > asmhello.txt反汇编hello.o并重定向为asmhello.txt如下:
图41
hello.o反汇编代码如下:
- hello.o: file format 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+0x26
- 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 07 cmpl $0x7,-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 |
分支转移: hello.s文件中分支转移是使用段名称进行跳转的,而反汇编hello.o文件中分支转移是通过地址进行跳转的。
|
|
数的进制: 在反汇编hello.o中,立即数全部是以16进制表示的,因为16进制与2进制之间的转换比十进制更加方便,所以都转换成了16进制。 |
|
函数调用: hello.s文件中,函数调用call后跟的是函数名称,而在hello.o文件中,call后跟的是下一条指令。这是因为 hello.c中调用的函数都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,在.rela.text 节中为其添加重定位条目,等待静态链接的进一步确定。 |
|
机器语言的构成,与汇编语言的映射关系:
机器语言是一种二进制语言,每一条指令、数据都由二进制来表示。汇编语言对于很多指令用一个字符串来表示,更容易读懂。每一条汇编语言操作码都可以用机器二进制数据来表示,进而可以将所有的汇编语言和二进制机器语言建立一一映射的关系,也就是双射。
4.5 本章小结
本章对汇编结果进行了详尽的介绍,介绍了汇编的概念与作用,以及汇编的命令。本章主要部分在于对可重定位目标ELF格式进行了详细的分析,重点在重定位项目上。同时对hello.o文件进行反汇编,将反汇编代码与hello.s文件进行了对比,在分析比较的过程中加深了对二者的理解。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
5.1.1概念
链接是将关联的所有可重定位的目标文件结合到一起,形成一个具有统一地址空间的可执行目标文件的过程。
5.1.2作用
将模块化编写的程序链接起来,成为一个整体,实现程序功能。提高了空间利用率,源程序文件中无需包含共享库的所有代码,直接调用即可。可执行文件运行时的内存中只需要包含所调用的函数的代码。
注意:这儿的链接是指从 hello.o 到hello生成过程。
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
链接结果如下图所示:
(使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件)
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用命令readelf -a hello > helloELF.txt查看hello的ELF格式。
其完整代码如下:
- ELF Header: //ELF头↓
- Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
- Class: ELF64
- Data: 2’s complement, little endian
- Version: 1 (current)
- OS/ABI: UNIX – System V
- ABI Version: 0
- Type: EXEC (Executable file)
- Machine: Advanced Micro Devices X86-64
- Version: 0x1
- Entry point address: 0x4010f0
- Start of program headers: 64 (bytes into file)
- Start of section headers: 14208 (bytes into file)
- Flags: 0x0
- Size of this header: 64 (bytes)
- Size of program headers: 56 (bytes)
- Number of program headers: 12
- Size of section headers: 64 (bytes)
- Number of section headers: 27
- Section header string table index: 26 //ELF头↑
- Section Headers: //节头↓
- [Nr] Name Type Address Offset
- Size EntSize Flags Link Info Align
- [ 0] NULL 0000000000000000 00000000
- 0000000000000000 0000000000000000 0 0 0
- [ 1] .interp PROGBITS 00000000004002e0 000002e0
- 000000000000001c 0000000000000000 A 0 0 1
- [ 2] .note.gnu.propert NOTE 0000000000400300 00000300
- 0000000000000020 0000000000000000 A 0 0 8
- [ 3] .note.ABI-tag NOTE 0000000000400320 00000320
- 0000000000000020 0000000000000000 A 0 0 4
- [ 4] .hash HASH 0000000000400340 00000340
- 0000000000000038 0000000000000004 A 6 0 8
- [ 5] .gnu.hash GNU_HASH 0000000000400378 00000378
- 000000000000001c 0000000000000000 A 6 0 8
- [ 6] .dynsym DYNSYM 0000000000400398 00000398
- 00000000000000d8 0000000000000018 A 7 1 8
- [ 7] .dynstr STRTAB 0000000000400470 00000470
- 000000000000005c 0000000000000000 A 0 0 1
- [ 8] .gnu.version VERSYM 00000000004004cc 000004cc
- 0000000000000012 0000000000000002 A 6 0 2
- [ 9] .gnu.version_r VERNEED 00000000004004e0 000004e0
- 0000000000000020 0000000000000000 A 7 1 8
- [10] .rela.dyn RELA 0000000000400500 00000500
- 0000000000000030 0000000000000018 A 6 0 8
- [11] .rela.plt RELA 0000000000400530 00000530
- 0000000000000090 0000000000000018 AI 6 21 8
- [12] .init PROGBITS 0000000000401000 00001000
- 000000000000001b 0000000000000000 AX 0 0 4
- [13] .plt PROGBITS 0000000000401020 00001020
- 0000000000000070 0000000000000010 AX 0 0 16
- [14] .plt.sec PROGBITS 0000000000401090 00001090
- 0000000000000060 0000000000000010 AX 0 0 16
- [15] .text PROGBITS 00000000004010f0 000010f0
- 0000000000000145 0000000000000000 AX 0 0 16
- [16] .fini PROGBITS 0000000000401238 00001238
- 000000000000000d 0000000000000000 AX 0 0 4
- [17] .rodata PROGBITS 0000000000402000 00002000
- 000000000000003f 0000000000000000 A 0 0 8
- [18] .eh_frame PROGBITS 0000000000402040 00002040
- 00000000000000fc 0000000000000000 A 0 0 8
- [19] .dynamic DYNAMIC 0000000000403e50 00002e50
- 00000000000001a0 0000000000000010 WA 7 0 8
- [20] .got PROGBITS 0000000000403ff0 00002ff0
- 0000000000000010 0000000000000008 WA 0 0 8
- [21] .got.plt PROGBITS 0000000000404000 00003000
- 0000000000000048 0000000000000008 WA 0 0 8
- [22] .data PROGBITS 0000000000404048 00003048
- 0000000000000004 0000000000000000 WA 0 0 1
- [23] .comment PROGBITS 0000000000000000 0000304c
- 000000000000002b 0000000000000001 MS 0 0 1
- [24] .symtab SYMTAB 0000000000000000 00003078
- 00000000000004c8 0000000000000018 25 30 8
- [25] .strtab STRTAB 0000000000000000 00003540
- 0000000000000158 0000000000000000 0 0 1
- [26] .shstrtab STRTAB 0000000000000000 00003698
- 00000000000000e1 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.
- Program Headers: //程序头↓
- Type Offset VirtAddr PhysAddr
- FileSiz MemSiz Flags Align
- PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
- 0x00000000000002a0 0x00000000000002a0 R 0x8
- INTERP 0x00000000000002e0 0x00000000004002e0 0x00000000004002e0
- 0x000000000000001c 0x000000000000001c R 0x1
- [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
- LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
- 0x00000000000005c0 0x00000000000005c0 R 0x1000
- LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
- 0x0000000000000245 0x0000000000000245 R E 0x1000
- LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
- 0x000000000000013c 0x000000000000013c R 0x1000
- LOAD 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
- 0x00000000000001fc 0x00000000000001fc RW 0x1000
- DYNAMIC 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
- 0x00000000000001a0 0x00000000000001a0 RW 0x8
- NOTE 0x0000000000000300 0x0000000000400300 0x0000000000400300
- 0x0000000000000020 0x0000000000000020 R 0x8
- NOTE 0x0000000000000320 0x0000000000400320 0x0000000000400320
- 0x0000000000000020 0x0000000000000020 R 0x4
- GNU_PROPERTY 0x0000000000000300 0x0000000000400300 0x0000000000400300
- 0x0000000000000020 0x0000000000000020 R 0x8
- GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
- 0x0000000000000000 0x0000000000000000 RW 0x10
- GNU_RELRO 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
- 0x00000000000001b0 0x00000000000001b0 R 0x1
- Section to Segment mapping:
- Segment Sections…
- 00
- 01 .interp
- 02 .interp .note.gnu.property .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
- 03 .init .plt .plt.sec .text .fini
- 04 .rodata .eh_frame
- 05 .dynamic .got .got.plt .data
- 06 .dynamic
- 07 .note.gnu.property
- 08 .note.ABI-tag
- 09 .note.gnu.property
- 10
- 11 .dynamic .got //程序头↑
- Dynamic section at offset 0x2e50 contains 21 entries:
- Tag Type Name/Value
- 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
- 0x000000000000000c (INIT) 0x401000
- 0x000000000000000d (FINI) 0x401238
- 0x0000000000000004 (HASH) 0x400340
- 0x000000006ffffef5 (GNU_HASH) 0x400378
- 0x0000000000000005 (STRTAB) 0x400470
- 0x0000000000000006 (SYMTAB) 0x400398
- 0x000000000000000a (STRSZ) 92 (bytes)
- 0x000000000000000b (SYMENT) 24 (bytes)
- 0x0000000000000015 (DEBUG) 0x0
- 0x0000000000000003 (PLTGOT) 0x404000
- 0x0000000000000002 (PLTRELSZ) 144 (bytes)
- 0x0000000000000014 (PLTREL) RELA
- 0x0000000000000017 (JMPREL) 0x400530
- 0x0000000000000007 (RELA) 0x400500
- 0x0000000000000008 (RELASZ) 48 (bytes)
- 0x0000000000000009 (RELAENT) 24 (bytes)
- 0x000000006ffffffe (VERNEED) 0x4004e0
- 0x000000006fffffff (VERNEEDNUM) 1
- 0x000000006ffffff0 (VERSYM) 0x4004cc
- 0x0000000000000000 (NULL) 0x0
- Relocation section ‘.rela.dyn’ at offset 0x500 contains 2 entries:
- Offset Info Type Sym. Value Sym. Name + Addend
- 000000403ff0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
- 000000403ff8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
- Relocation section ‘.rela.plt’ at offset 0x530 contains 6 entries:
- Offset Info Type Sym. Value Sym. Name + Addend
- 000000404018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
- 000000404020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
- 000000404028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
- 000000404030 000600000007 R_X86_64_JUMP_SLO 0000000000000000 atoi@GLIBC_2.2.5 + 0
- 000000404038 000700000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
- 000000404040 000800000007 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 9 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 atoi@GLIBC_2.2.5 (2)
- 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
- 8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)
- Symbol table ‘.symtab’ contains 51 entries:
- Num: Value Size Type Bind Vis Ndx Name
- 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
- 1: 00000000004002e0 0 SECTION LOCAL DEFAULT 1
- 2: 0000000000400300 0 SECTION LOCAL DEFAULT 2
- 3: 0000000000400320 0 SECTION LOCAL DEFAULT 3
- 4: 0000000000400340 0 SECTION LOCAL DEFAULT 4
- 5: 0000000000400378 0 SECTION LOCAL DEFAULT 5
- 6: 0000000000400398 0 SECTION LOCAL DEFAULT 6
- 7: 0000000000400470 0 SECTION LOCAL DEFAULT 7
- 8: 00000000004004cc 0 SECTION LOCAL DEFAULT 8
- 9: 00000000004004e0 0 SECTION LOCAL DEFAULT 9
- 10: 0000000000400500 0 SECTION LOCAL DEFAULT 10
- 11: 0000000000400530 0 SECTION LOCAL DEFAULT 11
- 12: 0000000000401000 0 SECTION LOCAL DEFAULT 12
- 13: 0000000000401020 0 SECTION LOCAL DEFAULT 13
- 14: 0000000000401090 0 SECTION LOCAL DEFAULT 14
- 15: 00000000004010f0 0 SECTION LOCAL DEFAULT 15
- 16: 0000000000401238 0 SECTION LOCAL DEFAULT 16
- 17: 0000000000402000 0 SECTION LOCAL DEFAULT 17
- 18: 0000000000402040 0 SECTION LOCAL DEFAULT 18
- 19: 0000000000403e50 0 SECTION LOCAL DEFAULT 19
- 20: 0000000000403ff0 0 SECTION LOCAL DEFAULT 20
- 21: 0000000000404000 0 SECTION LOCAL DEFAULT 21
- 22: 0000000000404048 0 SECTION LOCAL DEFAULT 22
- 23: 0000000000000000 0 SECTION LOCAL DEFAULT 23
- 24: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
- 25: 0000000000000000 0 FILE LOCAL DEFAULT ABS
- 26: 0000000000403e50 0 NOTYPE LOCAL DEFAULT 19 __init_array_end
- 27: 0000000000403e50 0 OBJECT LOCAL DEFAULT 19 _DYNAMIC
- 28: 0000000000403e50 0 NOTYPE LOCAL DEFAULT 19 __init_array_start
- 29: 0000000000404000 0 OBJECT LOCAL DEFAULT 21 _GLOBAL_OFFSET_TABLE_
- 30: 0000000000401230 5 FUNC GLOBAL DEFAULT 15 __libc_csu_fini
- 31: 0000000000404048 0 NOTYPE WEAK DEFAULT 22 data_start
- 32: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
- 33: 000000000040404c 0 NOTYPE GLOBAL DEFAULT 22 _edata
- 34: 0000000000401238 0 FUNC GLOBAL HIDDEN 16 _fini
- 35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
- 36: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
- 37: 0000000000404048 0 NOTYPE GLOBAL DEFAULT 22 __data_start
- 38: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@@GLIBC_2.2.5
- 39: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
- 40: 0000000000402000 4 OBJECT GLOBAL DEFAULT 17 _IO_stdin_used
- 41: 00000000004011c0 101 FUNC GLOBAL DEFAULT 15 __libc_csu_init
- 42: 0000000000404050 0 NOTYPE GLOBAL DEFAULT 22 _end
- 43: 0000000000401120 5 FUNC GLOBAL HIDDEN 15 _dl_relocate_static_pie
- 44: 00000000004010f0 47 FUNC GLOBAL DEFAULT 15 _start
- 45: 000000000040404c 0 NOTYPE GLOBAL DEFAULT 22 __bss_start
- 46: 0000000000401125 146 FUNC GLOBAL DEFAULT 15 main
- 47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@@GLIBC_2.2.5
- 48: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@@GLIBC_2.2.5
- 49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
- 50: 0000000000401000 0 FUNC GLOBAL HIDDEN 12 _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 1 ( 33.3%) 25.0%
- 3 2 ( 66.7%) 100.0%
- Version symbols section ‘.gnu.version’ contains 9 entries:
- Addr: 0x00000000004004cc Offset: 0x0004cc Link: 6 (.dynsym)
- 000: 0 (*local*) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5)
- 004: 2 (GLIBC_2.2.5) 0 (*local*) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5)
- 008: 2 (GLIBC_2.2.5)
- Version needs section ‘.gnu.version_r’ contains 1 entry:
- Addr: 0x00000000004004e0 Offset: 0x0004e0 Link: 7 (.dynstr)
- 000000: Version: 1 File: libc.so.6 Cnt: 1
- 0x0010: Name: GLIBC_2.2.5 Flags: none Version: 2
- Displaying notes found in: .note.gnu.property
- Owner Data size Description
- GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0
- Properties: x86 feature: IBT, SHSTK
- Displaying notes found in: .note.ABI-tag
- Owner Data size Description
- GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
- OS: Linux, ABI: 3.2.0
①ELF头
命令:readelf -h hello
图41
对应1-20行。
②节头
命令:readelf -S hello
图42
图43
对应代码22-83行。起始地址0x3780
③程序头
命令:readelf -l hello
图45
对应87-129行。起始地址0x4010f0,有12个程序头,开始于偏移64的位置。
④重定位节
命令:readelf -r hello
图46
⑤符号表
命令:readelf -s hello
图47
图48
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
图49
edb的Data Dump窗口。窗口显示虚拟地址由0x400000开始,从开始到结束这之间的每一个节对应5.3中的每一个节头表的声明。
图50
举例如下:
①
程序入口:
图51
②
.text从0x4010f0开始
图52
图53
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
输入命令如下:
图54
得到hello的反汇编代码如下:
- hello: file format elf64-x86-64
- Disassembly of section .init:
- 0000000000401000 <_init>:
- 401000: f3 0f 1e fa endbr64
- 401004: 48 83 ec 08 sub $0x8,%rsp
- 401008: 48 8b 05 e9 2f 00 00 mov 0x2fe9(%rip),%rax # 403ff8 <__gmon_start__>
- 40100f: 48 85 c0 test %rax,%rax
- 401012: 74 02 je 401016 <_init+0x16>
- 401014: ff d0 callq *%rax
- 401016: 48 83 c4 08 add $0x8,%rsp
- 40101a: c3 retq
- Disassembly of section .plt:
- 0000000000401020 <.plt>:
- 401020: ff 35 e2 2f 00 00 pushq 0x2fe2(%rip) # 404008 <_GLOBAL_OFFSET_TABLE_+0x8>
- 401026: f2 ff 25 e3 2f 00 00 bnd jmpq *0x2fe3(%rip) # 404010 <_GLOBAL_OFFSET_TABLE_+0x10>
- 40102d: 0f 1f 00 nopl (%rax)
- 401030: f3 0f 1e fa endbr64
- 401034: 68 00 00 00 00 pushq $0x0
- 401039: f2 e9 e1 ff ff ff bnd jmpq 401020 <.plt>
- 40103f: 90 nop
- 401040: f3 0f 1e fa endbr64
- 401044: 68 01 00 00 00 pushq $0x1
- 401049: f2 e9 d1 ff ff ff bnd jmpq 401020 <.plt>
- 40104f: 90 nop
- 401050: f3 0f 1e fa endbr64
- 401054: 68 02 00 00 00 pushq $0x2
- 401059: f2 e9 c1 ff ff ff bnd jmpq 401020 <.plt>
- 40105f: 90 nop
- 401060: f3 0f 1e fa endbr64
- 401064: 68 03 00 00 00 pushq $0x3
- 401069: f2 e9 b1 ff ff ff bnd jmpq 401020 <.plt>
- 40106f: 90 nop
- 401070: f3 0f 1e fa endbr64
- 401074: 68 04 00 00 00 pushq $0x4
- 401079: f2 e9 a1 ff ff ff bnd jmpq 401020 <.plt>
- 40107f: 90 nop
- 401080: f3 0f 1e fa endbr64
- 401084: 68 05 00 00 00 pushq $0x5
- 401089: f2 e9 91 ff ff ff bnd jmpq 401020 <.plt>
- 40108f: 90 nop
- Disassembly of section .plt.sec:
- 0000000000401090 <puts@plt>:
- 401090: f3 0f 1e fa endbr64
- 401094: f2 ff 25 7d 2f 00 00 bnd jmpq *0x2f7d(%rip) # 404018 <puts@GLIBC_2.2.5>
- 40109b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- 00000000004010a0 <printf@plt>:
- 4010a0: f3 0f 1e fa endbr64
- 4010a4: f2 ff 25 75 2f 00 00 bnd jmpq *0x2f75(%rip) # 404020 <printf@GLIBC_2.2.5>
- 4010ab: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- 00000000004010b0 <getchar@plt>:
- 4010b0: f3 0f 1e fa endbr64
- 4010b4: f2 ff 25 6d 2f 00 00 bnd jmpq *0x2f6d(%rip) # 404028 <getchar@GLIBC_2.2.5>
- 4010bb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- 00000000004010c0 <atoi@plt>:
- 4010c0: f3 0f 1e fa endbr64
- 4010c4: f2 ff 25 65 2f 00 00 bnd jmpq *0x2f65(%rip) # 404030 <atoi@GLIBC_2.2.5>
- 4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- 00000000004010d0 <exit@plt>:
- 4010d0: f3 0f 1e fa endbr64
- 4010d4: f2 ff 25 5d 2f 00 00 bnd jmpq *0x2f5d(%rip) # 404038 <exit@GLIBC_2.2.5>
- 4010db: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- 00000000004010e0 <sleep@plt>:
- 4010e0: f3 0f 1e fa endbr64
- 4010e4: f2 ff 25 55 2f 00 00 bnd jmpq *0x2f55(%rip) # 404040 <sleep@GLIBC_2.2.5>
- 4010eb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- Disassembly of section .text:
- 00000000004010f0 <_start>:
- 4010f0: f3 0f 1e fa endbr64
- 4010f4: 31 ed xor %ebp,%ebp
- 4010f6: 49 89 d1 mov %rdx,%r9
- 4010f9: 5e pop %rsi
- 4010fa: 48 89 e2 mov %rsp,%rdx
- 4010fd: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
- 401101: 50 push %rax
- 401102: 54 push %rsp
- 401103: 49 c7 c0 30 12 40 00 mov $0x401230,%r8
- 40110a: 48 c7 c1 c0 11 40 00 mov $0x4011c0,%rcx
- 401111: 48 c7 c7 25 11 40 00 mov $0x401125,%rdi
- 401118: ff 15 d2 2e 00 00 callq *0x2ed2(%rip) # 403ff0 <__libc_start_main@GLIBC_2.2.5>
- 40111e: f4 hlt
- 40111f: 90 nop
- 0000000000401120 <_dl_relocate_static_pie>:
- 401120: f3 0f 1e fa endbr64
- 401124: c3 retq
- 0000000000401125 <main>:
- 401125: f3 0f 1e fa endbr64
- 401129: 55 push %rbp
- 40112a: 48 89 e5 mov %rsp,%rbp
- 40112d: 48 83 ec 20 sub $0x20,%rsp
- 401131: 89 7d ec mov %edi,-0x14(%rbp)
- 401134: 48 89 75 e0 mov %rsi,-0x20(%rbp)
- 401138: 83 7d ec 04 cmpl $0x4,-0x14(%rbp)
- 40113c: 74 16 je 401154 <main+0x2f>
- 40113e: 48 8d 3d c3 0e 00 00 lea 0xec3(%rip),%rdi # 402008 <_IO_stdin_used+0x8>
- 401145: e8 46 ff ff ff callq 401090 <puts@plt>
- 40114a: bf 01 00 00 00 mov $0x1,%edi
- 40114f: e8 7c ff ff ff callq 4010d0 <exit@plt>
- 401154: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
- 40115b: eb 48 jmp 4011a5 <main+0x80>
- 40115d: 48 8b 45 e0 mov -0x20(%rbp),%rax
- 401161: 48 83 c0 10 add $0x10,%rax
- 401165: 48 8b 10 mov (%rax),%rdx
- 401168: 48 8b 45 e0 mov -0x20(%rbp),%rax
- 40116c: 48 83 c0 08 add $0x8,%rax
- 401170: 48 8b 00 mov (%rax),%rax
- 401173: 48 89 c6 mov %rax,%rsi
- 401176: 48 8d 3d b5 0e 00 00 lea 0xeb5(%rip),%rdi # 402032 <_IO_stdin_used+0x32>
- 40117d: b8 00 00 00 00 mov $0x0,%eax
- 401182: e8 19 ff ff ff callq 4010a0 <printf@plt>
- 401187: 48 8b 45 e0 mov -0x20(%rbp),%rax
- 40118b: 48 83 c0 18 add $0x18,%rax
- 40118f: 48 8b 00 mov (%rax),%rax
- 401192: 48 89 c7 mov %rax,%rdi
- 401195: e8 26 ff ff ff callq 4010c0 <atoi@plt>
- 40119a: 89 c7 mov %eax,%edi
- 40119c: e8 3f ff ff ff callq 4010e0 <sleep@plt>
- 4011a1: 83 45 fc 01 addl $0x1,-0x4(%rbp)
- 4011a5: 83 7d fc 07 cmpl $0x7,-0x4(%rbp)
- 4011a9: 7e b2 jle 40115d <main+0x38>
- 4011ab: e8 00 ff ff ff callq 4010b0 <getchar@plt>
- 4011b0: b8 00 00 00 00 mov $0x0,%eax
- 4011b5: c9 leaveq
- 4011b6: c3 retq
- 4011b7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
- 4011be: 00 00
- 00000000004011c0 <__libc_csu_init>:
- 4011c0: f3 0f 1e fa endbr64
- 4011c4: 41 57 push %r15
- 4011c6: 4c 8d 3d 83 2c 00 00 lea 0x2c83(%rip),%r15 # 403e50 <_DYNAMIC>
- 4011cd: 41 56 push %r14
- 4011cf: 49 89 d6 mov %rdx,%r14
- 4011d2: 41 55 push %r13
- 4011d4: 49 89 f5 mov %rsi,%r13
- 4011d7: 41 54 push %r12
- 4011d9: 41 89 fc mov %edi,%r12d
- 4011dc: 55 push %rbp
- 4011dd: 48 8d 2d 6c 2c 00 00 lea 0x2c6c(%rip),%rbp # 403e50 <_DYNAMIC>
- 4011e4: 53 push %rbx
- 4011e5: 4c 29 fd sub %r15,%rbp
- 4011e8: 48 83 ec 08 sub $0x8,%rsp
- 4011ec: e8 0f fe ff ff callq 401000 <_init>
- 4011f1: 48 c1 fd 03 sar $0x3,%rbp
- 4011f5: 74 1f je 401216 <__libc_csu_init+0x56>
- 4011f7: 31 db xor %ebx,%ebx
- 4011f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
- 401200: 4c 89 f2 mov %r14,%rdx
- 401203: 4c 89 ee mov %r13,%rsi
- 401206: 44 89 e7 mov %r12d,%edi
- 401209: 41 ff 14 df callq *(%r15,%rbx,8)
- 40120d: 48 83 c3 01 add $0x1,%rbx
- 401211: 48 39 dd cmp %rbx,%rbp
- 401214: 75 ea jne 401200 <__libc_csu_init+0x40>
- 401216: 48 83 c4 08 add $0x8,%rsp
- 40121a: 5b pop %rbx
- 40121b: 5d pop %rbp
- 40121c: 41 5c pop %r12
- 40121e: 41 5d pop %r13
- 401220: 41 5e pop %r14
- 401222: 41 5f pop %r15
- 401224: c3 retq
- 401225: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
- 40122c: 00 00 00 00
- 0000000000401230 <__libc_csu_fini>:
- 401230: f3 0f 1e fa endbr64
- 401234: c3 retq
- Disassembly of section .fini:
- 0000000000401238 <_fini>:
- 401238: f3 0f 1e fa endbr64
- 40123c: 48 83 ec 08 sub $0x8,%rsp
- 401240: 48 83 c4 08 add $0x8,%rsp
- 401244: c3 retq
5.5.1 hello与hello.o的不同
①hello反汇编结果比hello.o多了许多文件节。如.init节和.plt节,hello.o反汇编得到的代码中只有.text节。
hello反汇编 |
hello.o反汇编 |
|
|
②hello反汇编文件中的地址是虚拟地址,而hello.o反汇编节中的是相对偏移地址
hello反汇编 |
hello.o反汇编 |
|
|
③hello的反汇编中增加了许多外部链接的共享库函数。如puts@plt共享库函数,printf@plt共享库函数以及getchar@plt函数等,如下图:
图55
5.5.2链接过程
查看hello.o的重定位节可知,链接过程中下列偏移量位置需要进行重定位。
图56
5.5.3分析hello,hello.o重定位
①变量重定位:例如main函数偏移量为0x16位置的变量调用,重定位后索引地址为绝对寻址,地址为.rodata节首地址-4,查看hello的ELF格式知对应地址为0x402004。
图57
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
子程序名 |
程序地址 |
正确输入时调用与跳转 |
hello!_start |
00000000004010f0 |
|
hello!__libc_csu_init |
00000000004011c0 |
|
hello!_init |
0000000000401000 |
|
hello!main |
0000000000401125 |
|
hello!printf@plt |
00000000004010a0 |
|
hello!atoi@plt |
00000000004010c0 |
|
hello!sleep@plt |
00000000004010e0 |
|
hello!getchar@plt |
00000000004010b0 |
|
hello!_fini |
0000000000401238 |
|
hello!_start |
00000000004010f0 |
错误输入时调用与跳转 |
hello!__libc_csu_fini |
0000000000401230 |
|
hello!_init |
0000000000401000 |
|
hello!main |
0000000000401125 |
|
hello!puts@plt |
0000000000401090 |
|
hello!exit@plt |
00000000004010d0 |
|
hello!_fini |
0000000000401238 |
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
在程序中动态链接是通过延迟绑定来实现的,延迟绑定的实现依赖全局偏移量表GOT和过程连接表PLT实现。GOT是数据段的一部分,PLT是代码段的一部分。编译系统将过程地址的绑定推迟到第一次调用该过程时。他通过GOT和过程链接表PLT的协作来解析函数的地址。在加载时,动态链接器会重定位GOT中的每个条目,使它包含正确的绝对地址,而PLT中的每个函数负责调用不同函数。
dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,GOT存放的是PLT中函数调用指令的下一条指令地址。
图58
如图,GOT起始位置0x404000。
0x404000后面的16个字节的变化包含了动态链接器在解析函数地址时会使用的信息,包括动态链接器在ld-linux.so模式中的入口地址。
调用前:
图59 调用前
图60 调用后
5.8 本章小结
本章主要介绍了链接的概念和作用,以及生成链接的命令,分析了hello的elf格式文件,同时也分析了hello的虚拟地址空间以及重定位过程,遍历了整个hello的执行过程,并且比较了hello.o的反汇编和hello的反汇编。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程:一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码,数据区域存储变量和进程执行期间使用的动态分配的内存,堆栈区域存储区着活动过程调用的指令和本地变量。
作用:进程为用户提供了以下假象:
(1) 我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。
(2) 处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash的作用:是一种交互型的应用级程序,是Linux的外壳,提供了一个界面,用户可以通过这界面访问操作系统内核。
处理流程:
1、从终端读入用户输入的命令
2、将输入字符串切分获得所有的参数
3、如果是内置命令则立即执行
4、否则则调用fork()创建新子进程,再调用execve()执行指定程序
6.3 Hello的fork进程创建过程
打开shell,使用 ./hello 7203610609 宋浩 1的命令来运行hello程序。
图61
由于我们输入的不是一条内置命令,因此shell会调用fork函数创建一个子进程。这样,我们的hello子进程就被创建了。
6.4 Hello的execve过程
fork生成子进程后,子进程根据传入的命令行参数调用execve函数加载并运行hello程序。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。加载器创建的内存映像如下图所示。
图62
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的权限,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换。进程上下文切换由以下4个步骤组成:
①决定是否作上下文切换,包括对进程调度原因的检查分析,以及当前执行进程的资格和CPU执行方式的检查等
②保存当前执行进程的上下文
③使用进程调度算法,选择一处于就绪状态的进程
④恢复或装配所选进程的上下文,将CPU控制权交到所选进程手中
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
- 可能出现的异常:
(1)中断:来自I/O设备的信号。比如输入CTRL -C或者CTRL-Z
(2)陷阱:有意的异常,是执行一条指令的结果,调用后也会返回到下一条指令,用来调用内核的服务进行操作。
(3)故障:是由错误情况引起的,它可能能够被故障处理程序修正。如果修正成功,则将控制返回到引起故障的指令,否则将终止程序。
(4)终止:是不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程序会将控制返回给一个abort例程,该例程会终止这个应用程序。 - 可能产生的信号:SIGINT,SIGSTP,SIGCONT,SIGWINCH等等。常见信号种类如下表所示。
ID |
名称 |
默认行为 |
相应事件 |
2 |
SIGINT |
终止 |
来自键盘的中断 |
9 |
SIGKILL |
终止 |
杀死程序(该信号不能被捕获不能被忽略) |
11 |
SIGSEGV |
终止 |
无效的内存引用(段故障) |
14 |
SIGALRM |
终止 |
来自alarm函数的定时器信号 |
17 |
SIGCHLD |
忽略 |
一个子进程停止或者终止 |
表1
- 各类处理:
①运行中按回车:
图63
②运行中按ctrl-z:
图64
Shell进程收到SIGSTP信号,Shell显示屏幕提示信息并挂起hello进程。
③运行中按ctrl-c:
图65
Shell进程收到SIGINT信号,Shell结束并回收hello进程。
④运行中按其他非信号字符(不停乱按):输入缓存到stdin输入回车符则会多打印一行提示输入。
图66
由ps和jobs命令查看,可以发现hello进程确实被挂起而非被回收,且其job代号为1。
图67
图68
图69
图70
输入fg 1则命令将hello进程再次调到前台执行。
图71
6.7本章小结
本章介绍了进程的概念与作用,简要说明了shell的作用和处理流程,介绍了hello程序的进程创建、加载、进程执行以及各种异常与信号处理的相关内容。异常控制流发生在计算机系统的各个层次,是计算机系统中提供并发的基本机制。在这一过程中加深了对信号的理解。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:源代码经过预处理、编译、汇编后出现在汇编程序中地址,包含在机器语言中用来指定一个操作数或一条指令的地址。每一个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。
线性地址:地址空间是一个非负整数的有序集合,如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间。线性地址就是线性地址空间中的地址。是hello中的虚拟内存地址。
虚拟地址:在采用虚拟内存的系统中,CPU从一个有N = 2n个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间,里面的地址就是虚拟地址。
物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。一些全局的段描述符,就放在”全局段描述符表(GDT)”中,一些局部的段描述符,就放在所谓的”局部段描述符表(LDT)”中。
7.3 Hello的线性地址到物理地址的变换-页式管理
CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。线性地址被分为以固定长度为单位的组,称为页(page),通过页表与物理页进行映射,页表中的每一个项就是一个地址一一对应的页的地址。物理页(页桢)是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。
7.4 TLB与四级页表支持下的VA到PA的变换
Core i7 MMU 使用四级的页表将虚拟地址翻译成物理地址。36位VPN 被划分成四个9 位VPN,分别用于一个页表的偏移量。
7.5 三级Cache支持下的物理内存访问
首先CPU发出一个虚拟地址,在TLB里面寻找。如果命中,那么将PTE发送给L1Cache,否则先在页表中更新PTE。然后再进行L1根据PTE寻找物理地址,检测是否命中的工作。这样就能完成Cache和TLB的配合工作。
7.6 hello进程fork时的内存映射
当fork函数被shell进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
映射共享区域, hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:当寄存器要访问虚拟内存中的某一页,而这个页又没有加载到内存中时,就会触发一个缺页故障。
缺页中断处理:
1.段错误:首先判断这个缺页的虚拟地址是否合法,遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,就返回一个段错误。
2.非法访问:查看地址的权限,判断一下进程是否有读写改这个地址的权限
3.如果不是上面两种情况那就是正常缺页,就选择一个页面换入新的页面并更新到页表。
7.9动态存储分配管理
通过维护虚拟内存(堆),一种是隐式空闲链表,一种是显式空闲链表。显式空闲链表法是malloc(size_t size)每次声明内存空间都保证至少分配size_t大小的内存,双字对齐,每次必须从空闲块中分配空间,在申请空间时将空闲的空间碎片合并,以尽量减少浪费
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
概述了存储器的地址空间,讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念以及进程fork和execve的内存映射的内容。描述了系统应对缺页异常的方法和malloc的内存分配管理机制。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
一个Linux文件就是一个m个字节的序列:B0, B1, …, Bk, …, Bm-1
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
linux 提供如下 IO 接口函数:
read 和 write–最简单的读写函数;
readn 和 writen–原子性读写操作;
recvfrom 和 sendto–增加了目标地址和地址结构长度的参数;
recv 和 send–允许从进程到内核传递标志;
readv 和 writev–允许指定往其中输入数据或从其中输出数据的缓冲区;
recvmsg 和 sendmsg–结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力。
8.3 printf的实现分析
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;
}
(char*)(&fmt) + 4) 表示的是…可变参数中的第一个参数的地址。而vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。接着从vsprintf生成显示信息,到write系统函数,直到陷阱系统调用 int 0x80或syscall。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。然后getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章通过介绍hello中包含的函数所对应的unix I/O,大致了解了I/O接口及其工作方式,同时也了解了硬件设备的使用和管理的技术方法。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
Ⅰ.hello所经历过程
①预处理
②编译
③汇编
④链接
⑤加载运行
⑥执行指令
⑦访存
⑧动态申请内存
⑨信号处理
Ⅱ.感悟
如此庞大的计算机系统底层却是由01组成,在惊叹科学知识严谨周密的同时,也佩服于科学工作者的智慧。
这门课程使我们受益匪浅,掌握计算机的底层构造应成为一种学科素养。任何功能都建立在底层之上,顶层更偏向应用,底层逻辑才是学术之本。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
中间结果文件名字 |
文件作用 |
hello.i |
预处理得到的中间结果 |
hello.s |
hello.i编译后得到的汇编语言文本文件 |
hello.o |
hello.s汇编后得到的可重定位目标文件 |
hello.out |
链接后得到的可执行目标文件 |
hello |
可执行文件 |
helloelf.txt |
hello.o的ELF格式文本 |
helloELF.txt |
hello的ELF格式文本 |
asmhello.txt |
hello.o的反汇编文本 |
asmhelloexe.txt |
hello的反汇编文本 |
表2
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] https://blog.csdn.net/qq_46470984/article/details/110885751
[2] https://blog.csdn.net/qq_39286580/article/details/106496837
[3] 预处理与编译 – noticeable – 博客园 (cnblogs.com)
[5] Randy E.Bryant,深入理解计算机系统(第3版),机械工业出版社2021.5出版
[7] (175条消息) ELF文件格式解析_mergerly的博客-CSDN博客_elf文件格式
(参考文献0分,缺失 -1分)