进程—异常控制流之陷阱篇

一、Exceptions(异常) and System Call(系统调用)

1.1 陷阱

陷阱是有意为之的异常,是处理器执行程序的一条指令的结果。陷阱最重要的用途是提供用户程序和内核之间一个像普通过程调用似的接口,名曰:系统调用。用户程序经常需要向内核请求服务,比如读一个文件(read) 、创建一个新的进程(fork) 、加载一个新的程序(execv),或者终止当前进程(exit) 。为了允许对这些内核服务的受控的访问,处理器提供了一条特殊的 “syscall n” 指令,当用户程序想要请求服务n时,可以执行这条指令。执行syscall 指令会导致一个到异常处理程序的陷阱,这个处理程序对参数解码,并调用适当的内核程序。

1.1.1 System Call N

系统调用运行在内核模式中,内核模式允许执行系统调用函数的指令,并访问定义在内核中的栈。

Examples of popular system calls:

Linux provides hundreds of system calls.

Source: /usr/include/sys/syscall.h.

1.1.2 实现

1.在IA32 系统上,系统调用是通过一条称为int n1 的陷阱指令来提供的,其中n可能是IA32 异常表 中256 个条目中任何一个的索引。在历史上,系统调用是通过异常128 (Ox80) 提供的。

2.所有的传到Linux系统调用服务程序的参数都是通过寄存器传递的。按照惯例,寄存器%eax包含系统调用号,寄存器%ebx 、%ecx 、%edx 、%esi 、%edi和%ebp 包含最多六个任意的参数。栈指针%esp不能使用,因为当进入内核模式时,内核会覆盖它。

3.C程序用syscall函数可以直接调用任何存在于系统调用表中的系统调用。然而,实际中几乎没必要这么做。对于大多数系统调用,标准C库提供了一组方便的包装函数。这些包装函数将参数打包到一起,以适当的系统调用号陷入内核,然后将系统调用的返回状态传递回调用程序。我们将系统调用和与它们相关联的包装函数称为系统级函数

4.内核记录了系统调用表中的所有已注册过的系统调用的列表,存储在sys_call_table中(sys_call_table是一张由指向实现各种系统调用的内核函数的函数指针组成的表)。它与体系结构有关,一般在entry.s中定义。这个表中为每一个有效的系统调用指定了惟一的系统调用号

1.1.2.1 int 指令

有必要说一下int指令。

中断信息可以来自CPU的外部,例如I/O设备的中断请求,还可以来自CPU的内部,通过执行到某条特定的指令引发内部中断。int n指令就是产生内部中断的指令之一,其中n的取值范围一般是0~256 。

—The INT n instruction uses an interrupt vector as an argument, which allows a program to call any interrupt handler.

CPU执行int n指令,相当于引发一个n号中断的中断过程,可以在程序中使用int指令调用任何一个中断的中断处理程序。

1.1.2.2 int Ox80(陷入内核)

当进程执行系统调用时,先调用系统调用库中定义的某个函数,该函数通常被展开成_syscallN的形式通过INT 0x80陷入内核,其参数将通过寄存器传往内核。 INT 0x80的中断处理程序名为system_call。为了保证在内核执行完系统调用后能够返回用户态的调用点继续执行用户代码,必须在进入内核态时往内核栈中压入一个上下文层,在从内核返回时会弹出一个上下文层,这样用户进程就可以继续运行。

Stack Switch on a Call to a Different Privilege level:

在执行INT指令时,实际完成了以下几条操作:

(1) 由于INT指令发生了不同优先级之间的控制转移,所以首先从TSS(任务状态段)中获取高优先级的内核堆栈信息(SS和ESP);

(2) 把低优先级堆栈信息(SS和ESP)保留到高优先级堆栈(即内核栈)中;

(3) 把EFLAGS,外层CS,EIP推入高优先级堆栈(内核栈)中。

(4) 通过IDT加载CS,EIP(控制转移至中断处理函数)

note2

然后就进入了中断0x80的中断处理函数system_call了,在该函数中首先使用了一个宏SAVE_ALL,该宏的定义如下所示:

# define SAVE_ALL 

cld;
pushl %es;
pushl %ds;
pushl %eax;
pushl %ebp;
pushl %edi;
pushl %esi;
pushl %edx;
pushl %ecx;
pushl %ebx;
movl $(__KERNEL_DS), %edx;  //设置内核数据段地址
movl %edx, %ds;         //%ds是数据段寄存器
movl %edx, %es;  

该宏的功能一方面是将寄存器上下文压入到内核栈中以供系统调用返回恢复用户态使用,另一方面将用户模式下传递给系统调用的参数压入内核栈中以供系统调用函数内部使用。在用户陷入内核之前会把参数指定到各个寄存器中,然后在陷入核心之后使用SAVE_ALL把这些保存在寄存器中的参数依次压入内核栈,这样内核才能使用用户传入的参数。

system_call 的部分源代码:

//在system_call之前,还在用户模式下的时候,就已经将系统调用函数要用到的参数放在了寄存器中

ENTRY(system_call)
pushl %eax # save orig_eax  //为返回值保存%eax
SAVE_ALL    //调用SAVE_ALL
GET_CURRENT(%ebx)  

cmpl $(NR_syscalls),%eax  //比较%eax中的系统调用号,看是否合法。

jae badsys 

testb $0x20,flags(%ebx) # PF_TRACESYS  

jne tracesys  

call *SYMBOL_NAME(sys_call_table)(,%eax,4)
//根据系统调用号在sys_call_table中找系统调用函数指针,并跳入系统调用函数中去。
. . . . . .
//汇编指令相关的知识可以参考《深入理解操作系统》第3章。

在这里所做的所有工作是:

1.保存%eax寄存器。

因为%eax是用来保存系统调用返回值的默认寄存器,里面的内容会被返回值覆盖。

2.调用SAVE_ALL保存寄存器上下文。

3.判断当前调用是否是合法系统调用(%eax中存放着系统调用号,它应该小于NR_syscalls,在unistd.h中,有宏定义#define NR_syscalls 294)。

4.如果设置了PF_TRACESYS标志(PF_TRACESYS是Linux的进程标志Process flag, 表示正在跟踪),则跳转到tracesys,在那里将会把当前进程挂起并向其父进程发送SIGTRAP。

5.如果没有设置PF_TRACESYS标志,则调用查找系统调用函数指针的处理函数 ,然后处理函数以%eax*4作为偏移(%eax存放了系统调用号),在系统调用表sys_call_table中查找系统调用函数的入口地址,并跳转到该入口地址。

1.1.3 实例

1) open

2) write

int main{

  write(1, "hello, world\n", 13)
  //第一个参数"1"指示写到stdout;第二个参数是要写的字节序列;第三个参数给出要写的字节数。

    exit(0);

}

code/ecflhello-asm.sa

.section .data
string:
.ascii "hello, world\n"
string_end:
.equ len, string_end - string
.section .text
.globl main
main:

?                   //First, call write(1, "hello, world\n", 13)
movl $4, %eax          //System call number 4
movl $1, %ebx          //stdout has descriptor 1
movl $string, %ecx     // Hello world string
movl $len, %edx        // String length
int $0x80              //System call code

?                   //Next, call exit(0)
movl $1, %eax          //System call number 1
movl $0, %ebx          //Argument is 0
int $0x80              //System call code

  1. 这里的编号n就是一个异常号。它满足异常号的概念 —-定位异常表中相应的异常向量。 ?
  2. ESP是作为指针的寄存器,常用于堆栈操作。在这里,ESP被当作栈顶偏移指针,SS 是它的默认段寄存器(栈基地址)。EIP存放指令的偏移地址,同CS一同指向即将执行的那条指令的地址。CS是默认的代码段寄存器,同EIP寄存器一同指向当前正在执行的那个地址。IDT是中断描述表,里面包含有各个异常号及其对应的中断处理程序的入口地址。DS数据段寄存器,这个寄存器连同ESI一同指向指令将要处理的内存,同时,所有的内存操作指令默认情况下都用它指定操作段。ESI:通常在内存中作为“源地址指针”使用,DS是其默认数据段寄存器。 获取更多信息 ?
时间: 2024-10-17 06:46:10

进程—异常控制流之陷阱篇的相关文章

进程—异常控制流之中断篇

从给处理器加电开始,直到断电为止,PC(程序计数器)都在不间断的读取并执行指令. 最简单的一种控制流是一个"平滑的"序列,其中每个instk和instk+1 在存储器中都是相邻的.典型地,这种平滑流的突变,也就是instk?和instk+1? 不相邻,是由诸如跳转(jump).调用(call)和返回(ret)这样一些熟悉的程序指令造成的.这样一些指令都是必要的机制,使得程序能够对由程序变量表示的内部程序状态中的变化做出反应. 但是系统也必须能够对系统状态的变化做出反应,这些系统状态不是

进程—异常控制流之故障、终止篇

进程-异常控制流之故障.终止篇 一.Exceptions(异常) and System Call(系统调用) 1.1 故障 故障由错误情况引起,它可能能够被故障处理程序修正.当故障发生时,处理器将控制转移给故障处理程序.如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它.否则,处理程序返回到内核中的abort例程, abort 例程会终止引起故障的应用程序. 故障处理.根据故障是否能够被修复,故障处理程序要么重新执行引起故障的指令,要么终止. 一个经典的故障示例是缺

异常控制流 第十周11.15~11.22

第八章 异常控制流 控制流:控制转移序列. 控制转移:从一条指令到下一条指令. 异常控制流:现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流. 作为程序员,理解ECF很重要,这有很多原因: 理解ECF将帮助你理解重要的系统概念.ECF是操作系统用来实现I/O.进程和虚拟存储器的基本机制,在能够真正理解这些重要概念之前,你必须须理解ECF. 理解ECF将帮助你理解应用程序是如何与操作系统交互的.应用程序通过一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务. 理解

第8章 异常控制流

控制转移序列叫做处理器的控制流 现代系统通过使控制流发生突变来对这些情况做出反应,这些突变被称为异常控制流(ECF).异常控制流发生在操作系统的各个层次. 8.1异常 异常是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现. 异常就是控制流中的突变,用来响应处理器状态中的某些变化.在处理器中,状态变化称为事件. 任何情况下,当处理器检测到有事件发生时,他就会通过一张叫做异常表的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序). 8.

异常控制流

异常 异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现. 异常就是控制流中的突变,用来响应处理器状态中的某些变化. 处理器发现异常时会进行一个间接过程调用,到一个操作系统子程序,即异常处理程序. 异常处理程序完成处理后会依据异常类型,产生以下三种情况: 将控制返回当前指令,即正在执行的指令. 返回给下一条指令. 终止被中断的程序. 异常处理 异常号:系统为每种类型的异常分配的唯一的非负整数. 异常表:系统启动时操作系统就会初始化一张条转变,使得条目k包含异常k的处理程序的地址

第八章 异常控制流

第八章 异常控制流 ECF:(异常控制流)突变集合 平滑:顺序结构的指令 突变:跳转.调用.和返回等指令,不在同一栈 基本机制:ECF是操作系统用来实现I/O.进程和虚拟存器的基本机制   ECF是计算机系统中实现并发的基本机制 异常 控制流的突变 异常号: 系统为每种类型的异常分配的唯一的非负整数 异常表: 系统启动时操作系统就会初始化一张条转变,使得条目k包含异常k的处理程序的地址 用法: 从异常号到异常表的索引 类别: 中断:来自处理器外部的I/O设备的信号的结果,返回下一条指令 陷阱:陷

[CSAPP笔记][第八章异常控制流]

异常控制流 控制转移 控制流 系统必须能对系统状态的变化做出反应,这些系统状态不是被内部程序变量捕获,也不一定和程序的执行相关. 现代系统通过使控制流 发生突变对这些情况做出反应.我们称这种突变为异常控制流( Exceptional Control Flow,ECF) 异常控制流发生在系统的各个层次. 理解ECF很重要 理解ECF将帮助你理解重要的系统概念. 理解ECF将帮助你理解应用程序如何与操作系统交互 通过陷阱(trap)或者系统调用(system call)的ECF形式,向操作系统请求服

谈异常控制流

引子 Cpu/内核是怎么处理各种异常的? 用户态程序怎样调用系统函数,与操作系统交互的? 并发是怎样实现的? Try catch 使怎样跳转的? ............. 异常控制流是这些问题的根基,想更多的理解计算机系统,必须对这个问题有一定的了解. 首先,必须清楚什么是控制流? cpu有一个处理序列a1,a2-ak,ak+1.. 这就是一个控制流,从ak到ak+1就是控制转移.但是很多时候不是按顺序处理的,比如突然插上一个u盘,就要对它进行处理,这种突变叫做异常控制流. 那么都有哪些异常呢

CSAPP:第八章 异常控制流1

CSAPP:第八章 异常控制流1 关键点:异常 8.1 异常8.2 进程 ??现代系统通过使控制流发生突变来对这些情况做出反应,一般而言,我们把这些突变称为异常控制流(Exceptional Control Flow,ECF).异常控制流发生在计算机系统的各个层次. 8.1 异常 ??异常是异常控制流的一种形式,它的一部分由硬件实现,一部分由操作系统实现.异常就是控制流中的突变,用来响应处理器中的一些变化.如图所示,当处理器状态发生一个重要的变化时,处理器正在执行某个当前指令Icurr.在处理器