庖丁解牛linux内核
堆栈寄存器
- esp, 堆栈指针
- ebp,基址指针
利用堆栈实现函数的调用和返回
- cs:eip总是指向下一条指令的地址,在跳转时cs:eip的值会根据程序需要被修改
- call将当前cs:eip的值压入栈顶,cs:eip指向被调用函数的入口
- ret从栈顶弹出原来保存在这里的cs:eip的值,放入cs:eip中
函数调用框架
- 调用者:
call func
- call把
call func
指令的下一条指令保存在栈顶,eip指向函数开始处
- call把
- 被调用者:储存在某块地址
1
2
3
4
5
6
7
8
9
10
11// 建立被调用者函数的堆栈框架
push %ebp
mov %esp,%ebp
函数体
// 拆除被调用者函数的堆栈框架
movl %ebp,%esp
pop %ebp
ret- call被调用前程序可能就有使用堆栈,esp到ebp之间的内容被占用
- call执行之后
- 当前cs:eip入栈,然后cs:eip指向函数开始,然后向下执行
push
和mov
把ebp(原栈底)入栈,然后ebp指向esp,即开辟一段新的栈,供函数使用- 函数调用结束后
mov
和pop
恢复原来的栈顶和栈底 - ret把栈顶元素(cs:eip)出栈,赋给cs:eip,即恢复原cs:eip
参数传入
如调用一个两个参数的函数func(x, y);
1 | pushl 0xfffffff8(%ebp) |
一个函数也会有自己的局部变量,所以函数框架创建后,编译器会扫描整个函数,然后计算需要的空间,位局部变量预留空间。以main函数为例
1 | int main(){ |
中断
中断信号发生时,cpu把当前的eip、ebp等都压到 内核堆栈 保存现场。然后把eip指向中断处理程序入口,执行中断处理程序。
嵌入汇编语法
内嵌汇编语法
1 | __asm__( |
看个例子
1 |
|
内嵌汇编常用修饰符(限定符)
限定符 | 描述 | |
---|---|---|
寄存器 | “a” | 将输入变量放入eax |
“b” | 将输入变量放入ebx | |
“c” | 将输入变量放入ecx | |
“d” | 将输入变量放入edx | |
“s” | 将输入变量放入esi | |
“D” | 将输入变量放入edi | |
“q” | 将输入变量放入eax、ebx、ecx、edx中的一个 | |
“r” | 将输入变量放入通用寄存器中的一个 | |
——- | “A” | 放入eax和edx,把eax和edx,合成一个64位的寄存器 |
内存 | “m” | 内存变量 |
“o” | 操作数为内存变量,但其寻址方式是偏移量类型 | |
“V” | 操作数为内存变量,但其寻址方式不是偏移量类型 | |
——- | “.” | 操作数为内存变量,但其寻址方式为自增量 |
寄存器或内存 | “g” | 将输入放入eax、ebx、ecx、edx中的一个或者作为内存变量 |
——- | “X” | 操作数可以是任何类型 |
立即数 | “I” | 0-31之间的立即数(用于32位移位指令) |
“J” | 0-63之间的立即数(用于64位移位指令) | |
“N” | 0-255之间的立即数(用于out指令) | |
“i” | 立即数 | |
——- | “n” | 立即数,有些系统不支持除字以外的立即数,这些系统应该使用”n” |
操作数类型 | “=” | 操作数在指令中是只写的(输出操作数) |
——- | “+” | 操作数在指令中是读写的(输入输出操作数) |
浮点数 | “f” | 浮点数 |
“t” | 第一个浮点数寄存器 | |
“u” | 第二个浮点数寄存器 | |
“G” | 标准的80387 | |
% | 该操作数可以和下一个操作数交换位置 | |
# | 部分注释,注释从该字符到气候的逗号之间的内容 | |
* | 表述如果选中寄存器,则其后的字母被忽略 |
用户态、内核态和中断处理过程
CPU一般都有几种不同的指令执行级别。在高级级别下,代码可以执行 特权指令,访问访问任意物理地址 ,这种CPU执行级别对应内核态,类似的用户态。
linux中如何区分用户态和内核态:一般来说地址空间是一个显著特征(cs:eip),0xc0000000以上的地址空间只有在内核态下可以访问,0x00000000-0xbfffffff的地址空间两种状态都可以访问。
用户态可以传递系统调用号,系统调用根据系统调用号跳到内核态进行相应的处理。