环境:ubuntu64位 vmware虚拟机环境中运行html
gdblinux
edbshell
ida pro编程
visual studioubuntu
hexedit数组
notepad++缓存
objdumpbash
hello.c源程序app
hello.i预处理后的源程序异步
hello.s汇编代码
hello.o目标程序
hello可执行文件
elfo.txt连接前的elf文件信息
elfe.txt连接后的elf文件信息
asmo.txt hello.o反编译结果
asme.txt hello反编译结果
题太多了,太累了QAQ
(第1章0.5分)
概念:在编译以前进行的处理。
做用:1.宏定义2.文件包含3.条件编译
gcc hello.c -E
hello.c程序中只包含三条预处理指令
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
做用是文件包含,即包含stdio.h,unistd.h,stdlib.h三个文件
经过-E指令,只激活预处理指令,能够看到执行完这三条文件包含指令的结果:将三个文件的内容引入,代码量提高至约800行(详见hello.i)
预处理命令,能够在编译器编译以前,提早进行一些操做,好比定义常量,还能够进行条件编译以方便调试,能够进行文件引入来导入一些预先写好的模块,便于程序的组织和调试和一些特殊的编程技巧的实现,是一项很是有用的功能。
(第2章0.5分)
编译(compilation , compile)
利用编译程序从源语言编写的源程序产生目标程序的过程。
做用:
把用高级程序设计语言书写的源程序,翻译成等价的计算机汇编语言
gcc hello.i -S
该句定义一个int类型全局变量sleepsecs,其值为2(向整数向下取整后的结果),在伪汇编文件中,先定义一个全局符号sleepsecs,用于标识和链接。
.globl sleepsecs
在.data指令后,描述一些该变量的详细信息
.type sleepsecs, @object 将sleepsecs定义为对象类型
.size sleepsecs, 4 占用4个字节
sleepsecs: 定义sleepsecs对应的标签
.long 2 定义为长整型数2
.text 在text节中定义
.globl main 声明全局符号global
.type main, @function 将main声明为函数
随后定义main:标签,后跟main函数伪汇编指令
这是一个未初始化的局部变量,它存放在栈中,位置是-4(%rbp)
cmpl $3, -20(%rbp)
je .L2
argc做为main的参数,存放在-20(%rbp)的位置,将它与3比较,若是相等就不执行后面大括号的部分
movl $.LC0, %edi
call puts
将.LC0的部分传入参数,执行printf
.LC0定义在.rodata节中,是一个字符常量
.LC0:
.string "Usage:Hello\345\255\246\345\217\267\345\247\223\345\220\215\357\274\201"
movl $1, %edi
call exit
参数为1,执行exit函数
.L2:
movl $0, -4(%rbp)
jmp .L3
该节初始化i为0跳转到判断节
.L3:
cmpl $9, -4(%rbp)
jle .L4
若是知足条件就跳转、继续执行
addl $1, -4(%rbp)
循环变量递增
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
将argv[2]传给%rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
将argv[1]传给%rsi
movl $.LC1, %edi
将字符串常量"Hello %s %s\n"传给%edi
movl $0, %eax
将%eax赋0
call printf
执行printf
(如下格式自行编排,编辑时删除)
movl sleepsecs(%rip), %eax
movl %eax, %edi
call sleep
将sleep传给%edi做为参数,执行sleep函数
call getchar
执行getchar函数
leave
释放堆栈空间
ret
返回
本节涉及到的指令所有为gun汇编程序(gas)的伪汇编指令,相比最后的汇编指令内容更为精简,方便阅读、分析。程序将常量放入.rodata节,初始化全局变量放入.data节,经过标签订义和跳转等方式定义许多操做,为后序的汇编和连接生成可执行文件准备。
(第3章2分)
概念:
把汇编语言翻译成机器语言的过程称为汇编
做用:
将汇编语言翻译成机器语言
gcc -c hello.s
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
各Section基本信息:
Name:名称
Type:类型
Address:地址
Offset:地址偏移量
Size:大小
EntSize:全体大小
Flag:旗标
Link:被重定位的符号所在的符号表的section index
Info:须要被重定位的section的index
Align:对齐信息
包含重定位信息的节
offset表示该符号在被重定位的section中的偏移
info的高4个字节表示该符号在.symtab中的index,低4字节表示重定位的类型
type表示符号类型
sym.value表示连接过程当中将要写入地址的位置
sym.name表示符号名称
append追加地址
机器语言是直接用二进制代码指令表达的计算机语言,指令是用0和1组成的一串代码,它们有必定的位数,并分红若干段,各段的编码表示不一样的含义。
每一条汇编语句被映射为若干二进制指令码,将机器语言的每一条指令符号化:指令码代之以记忆符号,地址码代之以符号地址。
在汇编语言中,操做数用十进制表示,而在机器语言中,用十六进制表示,如hello.o中:
机器语言中命令
4: 48 83 ec 20 sub $0x20,%rsp
在汇编语言hello.s中对应为
subq $32, %rsp
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
在汇编语言中,分支跳转、函数调用,使用标签订位位置,而在机器语言中使用地址+偏移量计算要跳转到的实际地址。
如hello.o中:
6f: 7e c1 jle 32 <main+0x32>
71: e8 00 00 00 00 callq 76 <main+0x76>
在hello.s中对应为:
jle .L4
call getchar
汇编是将计算机不能读懂的汇编语言翻译成计算机能读懂的机器语言的不可缺乏的重要步骤.
(第4章1分)
概念:
连接是将各类代码和数据片断收集并组合成一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
做用:
连接在软件开发中扮演着一个关键的角色,由于它使分离编译成为可能。
ld -o hello -dynamic-linker /lib/ld-linker.so.2 /usr/lib/crt1.o /usr/lib/crti.o -l hello.o /usr/lib/ctrn.o
offset:偏移量
virtaddr:虚拟地址
phyaddr:物理地址
filesiz:文件中的大小
memsiz:内存中的大小
flags:旗标
align:对齐
实际运行中,全部虚拟地址空间段大小都为0x1000,且一段中包含一个或多个5.3中的程序段。动态连接库中的文件映射到内存的内容,与hello文件中映射到内存的内容地址间隔较大。程序中还包括[stack],[vvar],[vdso],[vsyscall]等特殊用途的地址段。
不一样:
hello中包含一些外部文件的宏定义、变量、库函数和操做系统的启动代码等,且.o文件.text节从0开始,而可执行文件.text节并不是从0开始。
过程:分为符号解析和重定位两步
重定位:
hello.o的文件中包含一些重定位条目
这些重定位条目告诉连接器32位PC相对地址或32位绝对地址进行重定位,这些重定位条目经过计算地址或直接调用保存的绝对地址,达到重定位的目的。
不管什么时候汇编器遇到对最终位置未知的目标引用,会生成一个重定位条目,告诉连接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中,已初始化数据的条目放在.rel.data中
_start
__libc_start_main
__GI___cxa_atexit
__internal_atexit
__GI___cxa_atexit
__internal_atexit
__new_exitfn
__internal_atexit
__GI___cxa_atexit
__libc_start_main
_setjmp
__sigsetjmp
__sigjmp_save
__libc_start_main
__GI_exit
__run_exit_handlers
__GI___call_tls_dtors
__run_exit_handlers
__do_global_dtors_aux
deregister_tm_clones
__do_global_dtors_aux
_fini
__run_exit_handlers
_IO_cleanup
_IO_unbuffer_all
_IO_cleanup
_IO_flush_all_lockp
_IO_cleanup
_IO_unbuffer_all
_IO_cleanup
_IO_unbuffer_all
_IO_new_file_setbuf
_IO_default_setbuf
_IO_new_file_sync
_IO_default_setbuf
__GI__IO_setb
_IO_default_setbuf
__GI__IO_setb
_IO_default_setbuf
_IO_new_file_setbuf
_IO_unbuffer_all
_IO_new_file_setbuf
_IO_default_setbuf
_IO_new_file_sync
_IO_default_setbuf
__GI__IO_setb
_IO_default_setbuf
__GI__IO_setb
_IO_default_setbuf
_IO_new_file_setbuf
_IO_unbuffer_all
_IO_cleanup
__run_exit_handlers
__GI__exit
动态连接项目以下图(主要为两个.so文件相关内容)
分析GOT的变化
dl_init前:
dl_init后
0x6010208~0x601020d字节发生了变化
(我的认为,在全书中,“连接”这一章的难度比全部其余章节之和还要大,搞清楚动态连接的全过程十分困难,课本上也有意的跳过了一些部分,致使一些细节问题十分费解。)
连接是组建大型程序和团队编程不可缺乏的重要部分,掌握连接器的一些原理和动态连接是很是有必要的,也是学习库打桩等强大机制的基础。虽然hello.c很简单,可是也须要和标准库进行连接。了解hello.c连接的前因后果,对掌握连接技术颇有帮助。
(第5章1分)
概念:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操做系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
做用:
因为程序是静态的,咱们看到的程序是存储在存储介质上的,它没法反映出程序执行过程当中的动态特性,并且程序在执行过程当中是不断申请资源,程序做为共享资源的基本单位是不合适的,因此须要引入一个概念,它能描述程序的执行过程并且能够做为共享资源的基本单位,这个概念就是进程。进程解决了系统资源调度等一系列问题。
因此Shell-bash的“做用”是linux系统的一个命令行解释器
流程:
shell调用fork函数,造成自身的一个拷贝(子进程),为运行hello作准备
在shell的子进程中执行execve函数,将参数传给Hello程序,并执行Hello
一开始,Hello运行在用户模式,当程序收到一个信号时,进入内核模式,运行信号处理程序,以后再返回用户模式。在Hello运行的过程当中,cpu不断切换上下文,使Hello程序运行过程被切分红时间片,与其余进程交替占用cpu,实现进程的调度。
1.输入Ctrl-C时,程序终止
处理过程是向程序发送SIGINT信号,程序执行默认行为:中止执行
2.输入Ctrl-C时,程序挂起
处理过程是向程序发送SIGSTP信号,程序执行默认行为:挂起程序,以后会返回shell中
输入的内容会被留在缓冲区中,当hello执行结束,返回shell中,shell会从缓冲区读取并尝试解析这些内容
ps:显示当前进程的状态
jobs:查看后台运行的进程
fg:恢复一个后台进程
pstree:显示进程树
kill:结束一个进程
可能会产生IO中断、时钟中断、系统调用等等,会产生SIGINT、SIGSTP等信号。
。
linux命令行shell是一个很是强大的工具,用它能够更方便的执行Hello和发送各类命令请求。经过信号等方式能够实现异常处理,让Hello在顺序执行者也能处理一些突发情况和实现一些功能。进程调度实现了各个进程计算资源合理分配,互不干扰,提升了系统稳定性和效率。
(第6章1分)
物理地址(physical address)
用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。
逻辑地址(logical address)
逻辑地址指的是机器语言指令中,用来指定一个操做数或者是一条指令的地址。如Hello中sleepsecs这个操做数的地址。
线性地址(linear address)或也叫虚拟地址(virtual address)
跟逻辑地址相似,它也是一个不真实的地址,若是逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。
在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息没法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。
为了节约页表占用的内存空间,x86将线性地址经过页目录表和页表两级查找转换成物理地址。32位的线性地址被分红3个部分:最高10位 Directory 页目录表偏移量,中间10位 Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。页目录表的大小为4k(恰好是一个页的大小),包含1024项,每一个项4字节(32位),项目里存储的内容就是页表的物理地址。若是页目录表中的页表还没有分配,则物理地址填0。页表的大小也是4k,一样包含1024项,每一个项4字节,内容为最终物理页的物理内存起始地址。
每一个活动的任务,必需要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器。页表能够提早分配好,也能够在用到的时候再分配。
先访问一级缓存,不命中时访问二级缓存,再不命中访问三级缓存,再不命中访问主存,若是主存缺页则访问硬盘
执行新进程(hello)时,为这个新进程建立虚拟内存
在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存, 随后的写操做经过写时复制机制建立新页面
(如下格式自行编排,编辑时删除)
缺页故障:须要访问的页不在主存,须要操做系统将其调入后才能访问。
有三种状况:
只有正常缺页时,系统才会调入须要访问的页,并再次执行访问该页的命令。
基本方法:维护一个虚拟内存区域“堆”,将堆视为一组不一样大小的 块(blocks)的集合来维护,每一个块要么是已分配的,要么是空闲的,须要时选择一个合适的内存块进行分配。
经过高速缓存、虚拟内存、动态内存分配,能够实现快速、高校、利用率高的储存空间管理。能够经过内存映射等方式实现文件共享。储存管理是一个至关重要、值得研究的机制。
(第7章 2分)
设备的模型化:将设备抽象成文件
设备管理:经过unix io接口管理
1.从vsprintf生成显示信息到write系统函数,到陷阱-系统调用 int 0x80或syscall.
2.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每个点的RGB颜色信息)。
3.显示芯片按照刷新频率逐行读取vram,并经过信号线向液晶显示器传输每个点(RGB份量)。
1.异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
2.getchar等调用read系统函数,经过系统调用读取按键ascii码,直到接受到回车键才返回。
输入输出看似简单,实际是一个很是精巧的过程,从程序发出请求到系统函数调用到设备相应,须要执行许多步骤,每每也是拖慢程序的主要因素和一些崩溃异常的高发地,须要谨慎选用函数、命令实现目的。
(第8章1分)
hello的源码hello.c文件,要生成可执行文件,首先要进行预处理,其次要进行编译生成汇编代码,接着进行汇编处理生成目标文件,目标文件经过连接器造成一个可执行文件,可执行文件须要一个执行环境,它能够在linux下经过shell进行运行,与计算机其余常常文件同步运行,并经过异常处理机制相应信号。在运行的过程当中,程序经过Intel内存管理机制一步步访问逻辑地址、虚拟地址、物理地址,从而进行数据交换,还能够经过IO机制进行输入输出交互。
经过学习ics这门课程,深感计算机这个庞大致系的复杂、精巧,从电路到电路组合,再到硬件集成、软件调配,每一处都层次分明、随处显现着前人的智慧。很多复杂概念的学习,经过这门课感受仅仅只是入了个门,距离熟练运用甚至涉及差距仍然较大,但不妨碍进行一些思惟上的创新。
目前的电子计算机创建在二进制基础上,未来基础物理突破以后,可能会实现三进制、四进制的计算机,没准能够实现质的飞越。或许神经科学有了巨大飞越,让“电脑”真正构建起一个相似于人脑的神经结构,实现一个强的人工智能。
为完成本次大做业你翻阅的书籍与网站等
[1]《深刻理解计算机系统》
[2] 哈工大计算机系统课程讲课ppt
[3] [转]printf 函数实现的深刻剖析http://www.javashuo.com/article/p-fbeaelqg-y.html
[4] 【不周山之读薄 CSAPP】肆 连接 https://wdxtub.com/2016/04/16/thin-csapp-4/
[5] Linux下库函数动态连接过程分析-结合glibc-2.11源码 https://blog.csdn.net/lzshlzsh/article/details/6066628
[6] Linux下 可视化 反汇编工具 EDB 基本操做知识 http://www.javashuo.com/article/p-fpntsgri-gm.html
[7] gdb基本命令(很是详细)
https://blog.csdn.net/z15818264727/article/details/69668820
[8] Linux中查看进程的虚拟地址空间内存布局 https://blog.csdn.net/ASJBFJSB/article/details/81429576
[9] GNU ARM 汇编伪指令(Assembler Directives) https://blog.csdn.net/liuzq/article/details/83615085
[10] GCC经常使用参数详解 https://www.cnblogs.com/zhangsir6/articles/2956798.html