(2023-2024-1)20232830《Linux内核原理分析与设计》第二周作业
文章目录
1. 《庖丁解牛Linux分析》第1, 2章
1.1 系统调用与过程调用
系统调用作为操作系统发展的一个重要里程碑,将系统操作与用户操作分离开,确保了最底层核心部位得到保护。针对此点,提出疑问:
系统调用具体是如何绕开过程调用从而保护底层核心?
总结:权限分割,用户程序通常运行在非特权模式下,而内核部分运行在特权模式下,在一定程度上使得一些病毒程序无法调用特殊权利对系统底层进行篡改,达到保护效果。
区分sudo特权命令和系统调用特权模式。
注意,sudo命令不能用来获取系统调用的权限,它被用于在用户级别的命令行界面中执行其它命令;而系统调用的使用对象是应用程序,通过编程接口进行调用权限功能,如文件操作、进程管理、网络通信等,而不是直接在命令行运行的可执行文件。
1.2 x86汇编语言
32位的x86汇编语言与64位的x86汇编语言有何不同?
举个例子
2. 关注豆列《Linux内核及安全》
3. 实验楼配套实验一
《反汇编一个简单的C程序》
-
创建main.c文件
-
执行代码
源文件及生成的 main.s 汇编文件:
-
汇编代码分析
为了更加方便地分析汇编代码及其工作过程,在本地虚拟机Ubuntu系统重新执行上述命令,生成汇编代码文件并添加注释如下:
.file "main.c" # 源代码文件名为 "main.c"
.text
.globl g # 声明函数 g 具有全局可见性
.type g, @function # 声明函数 g 的类型为函数
g:
.LFB0:
.cfi_startproc
pushl %ebp # 保存旧的基址寄存器的值
.cfi_def_cfa_offset 8 # 定义CFA偏移
.cfi_offset 5, -8 # 声明寄存器偏移
movl %esp, %ebp # 设置新的基址寄存器
.cfi_def_cfa_register 5 # 定义CFA寄存器
call __x86.get_pc_thunk.ax # 调用函数 __x86.get_pc_thunk.ax 获取程序计数器偏移
addl $_GLOBAL_OFFSET_TABLE_, %eax # 添加全局偏移表偏移量到 eax 中
movl 8(%ebp), %eax # 将参数值加载到 eax 中
addl $99, %eax # 将 99 添加到 eax 中
popl %ebp # 恢复旧的基址寄存器值
.cfi_restore 5 # 恢复寄存器偏移
.cfi_def_cfa 4, 4 # 定义新的CFA
ret # 返回
.cfi_endproc
.LFE0:
.size g, .-g # 计算函数 g 的大小
.globl f # 声明函数 f 具有全局可见性
.type f, @function # 声明函数 f 的类型为函数
f:
.LFB1:
.cfi_startproc
pushl %ebp # 保存旧的基址寄存器的值
.cfi_def_cfa_offset 8 # 定义CFA偏移
.cfi_offset 5, -8 # 声明寄存器偏移
movl %esp, %ebp # 设置新的基址寄存器
.cfi_def_cfa_register 5 # 定义CFA寄存器
call __x86.get_pc_thunk.ax # 调用函数 __x86.get_pc_thunk.ax 获取程序计数器偏移
addl $_GLOBAL_OFFSET_TABLE_, %eax # 添加全局偏移表偏移量到 eax 中
pushl 8(%ebp) # 将参数值推入堆栈
call g # 调用函数 g
addl $4, %esp # 调整堆栈指针
leave # 释放堆栈帧
.cfi_restore 5 # 恢复寄存器偏移
.cfi_def_cfa 4, 4 # 定义新的CFA
ret # 返回
.cfi_endproc
.LFE1:
.size f, .-f # 计算函数 f 的大小
.globl main # 声明函数 main 具有全局可见性
.type main, @function # 声明函数 main 的类型为函数
main:
.LFB2:
.cfi_startproc
pushl %ebp # 保存旧的基址寄存器的值
.cfi_def_cfa_offset 8 # 定义CFA偏移
.cfi_offset 5, -8 # 声明寄存器偏移
movl %esp, %ebp # 设置新的基址寄存器
.cfi_def_cfa_register 5 # 定义CFA寄存器
call __x86.get_pc_thunk.ax # 调用函数 __x86.get_pc_thunk.ax 获取程序计数器偏移
addl $_GLOBAL_OFFSET_TABLE_, %eax # 添加全局偏移表偏移量到 eax 中
pushl $9 # 将参数值 9 推入堆栈
call f # 调用函数 f
addl $4, %esp # 调整堆栈指针
addl $1, %eax # 将结果加 1
leave # 释放堆栈帧
.cfi_restore 5 # 恢复寄存器偏移
.cfi_def_cfa 4, 4 # 定义新的CFA
ret # 返回
.cfi_endproc
.LFE2:
.size main, .-main # 计算函数 main 的大小
.section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
.globl __x86.get_pc_thunk.ax
.hidden __x86.get_pc_thunk.ax
.type __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
.LFB3:
.cfi_startproc
movl (%esp), %eax
ret
.cfi_endproc
.LFE3:
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
4. 分析汇编代码的工作过程
以上述汇编代码中 main函数 为例进行分析:
- pushl %ebp:将旧的基址寄存器的值保存到堆栈中;
- movl %esp, %ebp:建立新的基址寄存器;
- call __x86.get_pc_thunk.ax:获取程序计数器偏移,用于获取当前指令的地址或跳转到其他代码块的地址;
- addl $GLOBAL_OFFSET_TABLE, %eax:添加全局偏移表偏移量到 %eax 寄存器中,全局偏移表是一种数据结构,用于查找全局变量或函数的地址,而%eax 寄存器则是一种通用寄存器;
- pushl $9:将参数值9推入堆栈中;
- call f:调用函数 f;
- addl $4, %esp:调整堆栈指针,恢复堆栈状态;
- addl $1, %eax:将结果加 1;
- leave:释放堆栈帧;
- ret:返回,将程序控制返回给调用者。
在汇编代码堆栈的变化一般是涉及到函数调用和参数传递,每个函数都必须遵循标准的堆栈帧建立和销毁过程;在调用函数时,参数被推入堆栈,在返回时,堆栈被适当调整。
5. 总结“计算机是如何工作的”
计算机的工作,我认为最核心的就是操作系统的工作,它被分为了好几个模块,如资源管理、进程管理、内存管理、文件系统、I/O管理、网络管理等主要模块;就如课上讲的“高内聚、低耦合”一般,模块内部的元素在某种程度上共享相同的目标,功能高度统一,执行相似的任务,不包含无关的功能;另外不同模块之间应通过清晰的接口进行通信,而不是直接依赖于彼此内部的实现,也就是更改一个模块不应该影响到另一个模块。
所以,针对上述的几个模块,来讲讲计算机是如何工作的,主要是针对操作系统内核的工作;
- 资源管理:负责管理计算机的硬件资源,如CPU、内存、磁盘、网络接口等,确保资源在不同的应用程序之间得到公平和有效的分配,使得计算机任务能够多线程运行;
- 进程管理:每个进程都是一个独立的程序实例,可以通过分时调度、批处理调度、实时调度、优先级调度、多级反馈队列调度等方式进行管理,使得计算机的工作更加高效;
- 内存管理:跟踪内存的使用情况,分配和回收内存,以便应用程序可以动态分配和释放内存,同时以一种更高级的方式负责虚拟内存的管理,即将物理内存和磁盘上的交换空间结合使用,以扩展可用内存;
- 文件系统:管理文件系统,包括文件和目录的创建、读取、写入和删除,它提供了对文件的访问和组织,以及对存储设备的抽象,这个抽象性指的就是提供一个简化和标准化的方法,使得软件在访问和操作存储设备时,不必考虑它们的物理特性或细节;
- I/O管理:内核与硬件设备通信,控制输入和输出操作,例如磁盘、键盘、鼠标、显示器、打印机等,提供一种标准的接口,让应用程序能够与硬件交互。
总之,操作系统就是计算机的大脑,负责协调和管理各种计算机资源,使得计算机能够更加有效、安全地进行工作。