荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: igmp (igmp), 信区: Security
标  题: 论文集(三)
发信站: 荔园晨风BBS站 (Wed Jun 27 23:13:55 2001), 转信

Linux系统调用与ptrace分析
概述
1.Linux的系统结构
在Linux系统结构中,最核心的是计算机硬件,它提供对Linux软件的支持,靠近硬
件的内层是Linux内核程序(即操作系统)。内核直接和硬件打交道是程序和硬件
之间的接口或界面。它对一切外层程序提供公共服务,把外部程序同硬件隔离开。
内核程序大致可分为文件系统管理,进程管理,内存管理等几部分。进程管理又分
为低级进程管理和高级进程管理。低级进程管理主要包括:进程调度分配,控制占
用处理器的程序和基本的进程通信。高级进程管理主要包括:进程的创建,终止,
进程间通信,进程在内存和外存之间的转储,信号机构和进程间跟踪控制等。内核
程序的外层是实用程序,内核提供对实用程序的支持,两层之间的界面是系统调用
。内核外的实用程序通过系统调用来和内核打交道。实现的过程是通过一种特殊的
指令(陷入指令)进入内核,然后转入相应的系统调用处理程序。这也是本文将主
要讨论的问题。

2.80386体系结构
80386的体系结构承认两类事件。

1.      异常(exceptions)
2.      中断(interrupts)
他们两都会引起"上下文转换"同时建立一个过程或任务,中断可以随时随地发生(
包括在执行程序时)所以用来响应硬件信号。而异常则由指令内部错误引起。
        每一个异常或中断都有一个唯一的标识符,在linux中被称为向量。
指令内部异常和NMI(不可屏蔽中断)的中断向量的范围从0-31。32-255的任何向
量都可以用做
1.      可屏蔽中断
2.      编程(调试)异常
        至于可屏蔽中断则取决于该系统的硬件配置。外部中断控制器在中断响应周期把
中断向量放到总线上。

3.     Linux系统调用流程概述
        Linux系统调用的流程非常简单,它由0x80号中断进入系统调用入口,通过使用系
统调用表保存系统调用服务函数的入口地址来实现,本文首先分析一般Linux系统调
用的流程,然后再分析Linux系统调用sys_ptrace().






一.      Linux系统调用的流程分析
1.1     设定0x80号中断
系统启动后,先进行初始化,其中一部分重要的工作在start_kernel()函数(
main.c中定义)中进行,在该函数中先做必要的初始化工作(setup_arch()与
paging_init()),各种trap入口就在该函数中通过调用trap_init()(traps.c
)被设置,其中与系统调用有关的是:set_system_gate(0x80,&system_call);
"set_system_gate()"是一宏,它在"system.h"中被定义:

#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)
            ……中断描述表结构(head.s)
其中"_set_gate()"也是在该文件中定义的宏:

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ __volatile__ ("movw %%dx,%%ax\n\t" \
        "movw %2,%%dx\n\t" \
        "movl %%eax,%0\n\t" \
        "movl %%edx,%1" \
        :"=m" (*((long *) (gate_addr))), \
         "=m" (*(1+(long *) (gate_addr))) \
        :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
         "d" ((char *) (addr)),"a" (KERNEL_CS << 16) \
        :"ax","dx")


调用该宏,将使addr地址值置入gate_addr中的地址值所指向的内存单元中,以上
过程,使中断向量描述表中的第128项(16进制第80项)保存了0x80号中断的中断
服务程序,即system_call。

而中断描述表结构则定义在head.s中.
1.2     系统调用的数据结构
系统调用所用到的数据结构也非常简单,主要有两种,系统调用表和寄存器帧结构

"entry.S"中定义了系统调用表,该表保存了Linux基于Intel x86系列体系结构的
计算机的166个系统调用入口地址(其中3个保留,Linux开辟的系统调用表可容纳
256项),其中每项都被说明成 long型。
ENTRY(sys_call_table)
                .long SYMBOL_NAME(sys_setup)            /* 0 */
        .long SYMBOL_NAME(sys_exit)
        .long SYMBOL_NAME(sys_fork)
        …………
        .long SYMBOL_NAME(sys_setitimer)                /* 104 */
        …………
        .long SYMBOL_NAME(sys_select)           /* 142*/
        …………
        .long 0,0
        .long SYMBOL_NAME(sys_vm86)             /* 166 */
        .space (NR_syscalls-166)*4
NR_syscalls是在"sys.h"文件中定义的宏,表示x86微机上最多可容纳的系统调用
个数。
#define NR_syscalls 256
在文件"ptrace.h"中定义了一种寄存器帧结构:pt_regs,该帧结构与系统调用时
压入堆栈的寄存器的顺序保持一致,用来在系统调用时传递参数。
struct pt_regs {
        long ebx;
        long ecx;
        long edx;
        long esi;
        long edi;
        long ebp;
        long eax;
        int  xds;
        int  xes;
        long orig_eax;
        long eip;
        int  xcs;
        long eflags;
        long esp;
        int  xss;
};


        这样,如果pt_regs结构体的首地址(设为regs)是该帧的帧顶(栈顶),在
entry.s中压入堆栈的一帧将和pt_regs结构体中的字段对应。

1.3     系统调用的入口
在头文件"unistd.h"中,定义了一系列的与系统调用有关的宏,包括系统调用序号
,如:
#define __NR_exit                 1
还定义了设置系统调用入口的宏,_syscallX(type,name, type1,arg1,type2,
arg2……),其中X表示系统调用的参数个数,Linux定义的各种系统调用的参数个
数不超过5个,因此,在该文件中,共定义了6个宏,"_syscallX"宏,分别对应X个
参数,下面以X=2即两个参数为例,解释该宏:
#define _syscall2(type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
if (__res >= 0) \
        return (type) __res; \
errno = -__res; \
return -1; \
}
该宏的第一个参数是一类类型参数,它指明系统调用返回值的类型,第二个参数指
明系统调用的名称。参数列表中若还有参数,则第2i个参数是系统调用函数的第i
个参数的类型,第2i+1个参数是系统调用函数的第i个参数
该宏的主体部分是一内联汇编,在内联汇编中只有一条扩展汇编指令,即"int
$0x80",该语句两个冒号后的语句设置输入和输出寄存器。
第一个冒号后的语句指明返回参数(即__res)使用eax寄存器。
第二个冒号后面指定接受输入的寄存器,""0" (__NR_##name),"将参数name与
"__NR_"串接起来,形成的标志符存入eax寄存器,作为区别系统调用类型的唯一参
数,例如设置name为"ptrace",那么,gcc编译器将把"__NR_"与之串接,被视为标
志符"__NR_ptrace",由于在文件"include/asm-i386/unistd.h"中已定义其为26,
那么,传给eax的值将为26。
后面的语句将参数arg1,arg2分别传给寄存器ebx和ecx,在"_syscallX"宏中,有如
下约定:
arg1值存入寄存器ebx;
arg2值存入寄存器ecx;
arg3值存入寄存器edx;
arg4值存入寄存器esi;
arg5值存入寄存器edi;
在该宏的最后,判断返回值"__res"是否合法,若为负数,表明在系统调用中出错
,将其绝对值作为出错号赋给全局变量"errno",并返回-1,否则返回"__res"。
该宏的唯一一条汇编指令"int $0x80"使程序流程转入"system_call"。
1.4     转入system_call
system_call是在汇编语言文件"entry.S"中定义的一入口,在Linux中,所有的系
统调用都是通过中断"int &0x80"语句来实现的,因而,system_call是所有系统调
用的入口。下面解释关于它的一些重要指令,以清晰它的流程:
1.     首先,pushl %eax,保存原来的eax寄存器,然后调用宏"SAVE_ALL"将现有通用
寄存器保存,寄存器的保存不但避免影响原来的寄存器数据,而且提供了一种传递
参数的方法。正如在2.2节所指出的,这样保存的一帧寄存器,与该过程所要传递
的pt_regs结构相对应。在该宏中,还使ds和es指向内核的数据段,使fs指向用户
的数据段。
  #define SAVE_ALL \
        cld; \
        push %gs; \
        push %fs; \
        push %es; \
        push %ds; \
        pushl %eax; \
        pushl %ebp; \
        pushl %edi; \
        pushl %esi; \
        pushl %edx; \
        pushl %ecx; \
        pushl %ebx; \
        movl $(KERNEL_DS),%edx; \
        mov %dx,%ds; \
        mov %dx,%es; \
        movl $(USER_DS),%edx; \
            mov %dx,%fs;

2.     语句"cmpl $(NR_syscalls),%eax"比较NR_syscalls与eax的大小,如果eax大
于或等于NR_syscalls,表明指定的系统调用函数错误,"jae
ret_from_sys_call"使系统调用直接返回。
3 .  流程进入ret_from_sys_call,该过程内处理一些系统调用返回前应该处理的
事情,如检
    测bottom half缓冲区,判断CPU是否需要重新调度等.
   先注意全局变量intr_count,它虽然不是信号量,但也部分的具有了信号量的
作用,表
   示已有进程进入bottom_half,它在系统处理bottom_half时增1,则其为非零。

   语句"cmpl $0,SYMBOL_NAME(intr_count)"就是进行上述判断,若非零,处理
   bottom     half 缓冲区。("jne handle_bottom_half")。
  下面两条语句判断CPU是否需要重新调度:
        cmpl $0,SYMBOL_NAME(need_resched)
                jne reschedule
其中,need_resched是一全程量,它置位,表示CPU需要重新调度,程序转向过程
reschedule,进而,转向schedule()函数,在该函数中,将其重新置零。
注意,handle_bottom_half和reschedule并不是必需的,只不过在系统运行过程中
,随时都有可能出现需要处理bottom half缓冲区或重新调度CPU,放在系统调用返
回前,有利于它们被及时处理。但这也说明,Linux 不是一个硬实时的操作系统,
它可能会产生延误。
4.  如果eax小于NR_syscalls,system_call过程接下去执行语句:
    movl  SYMBOL_NAME(sys_call_table)(,%eax,4),%eax
该语句以        sys_call_table为基地址,eax寄存器中的内容(即系统调用的序号)乘
以4为偏移量(因为long型为4字节),即得到所需调用的系统调用函数的入口地址
,将其存入寄存器eax。
testl %eax,%eax
接着判断寄存器eax值是否为0,若是,表明出错,直接返回,je
ret_from_sys_call。
  #ifdef __SMP__
        GET_PROCESSOR_OFFSET(%edx)
                        movl SYMBOL_NAME(current_set)(,%edx),%ebx
#else
movl SYMBOL_NAME(current_set),%ebx
        以上语句首先判断是否为多处理器结构,若是,得到当前处理器的偏移值,当前
的进
    程控制块的指针为current_set[smp_processor_id()],否则,
current_set[0]即为当前进程
    控制块的指针,这样,ebx寄存器指向当前进程。
        movl %db6,%edx
                movl %edx,dbgreg6(%ebx)
    以上两条语句用来保存当前调试信息,在进程控制块task_struct结构中,第
8项是
    debugreg[8],用来指示硬件调试信息。在entry.S中,定义了一系列宏作为偏
移量,用
    来得到当前进程的信息,它们是:
state                   = 0
counter                 = 4
priority                        = 8
signal                  = 12
blocked                 = 16
flags                   = 20
dbgreg6                 = 52
dbgreg7                 = 56
exec_domain             = 60
这样,在当前进程的task_struct结构中,保存了当前的调试信息。
5.语句"testb $0x20,flags(%ebx)"检测当前进程是否正跟踪系统调用,如果不是
的话,直
 接调用所选系统调用函数,相关语句为:
                                                call
*%eax

     如判断当前进程正处于跟踪系统调用状态(
current->flags&PF_TRACESYS==0),调用函
     数体"syscall_trace()"(在ptrace.c中定义),使当前进程状态转为
TASK_STOPPED,即
     转入睡眠状态。
asmlinkage void syscall_trace(void)
{
        if ((current->flags & (PF_PTRACED|PF_TRACESYS))
                        != (PF_PTRACED|PF_TRACESYS))
                return;
        current->exit_code = SIGTRAP;
        current->state = TASK_STOPPED;
        notify_parent(current, SIGCHLD);
        schedule();
        if (current->exit_code) {
                send_sig(current->exit_code, current, 1);
                current->exit_code = 0;
        }
}
然后从压入寄存器的堆栈中重新找到原来的eax值,再重新设置系统调用函数的偏
移量,调用实现相应系统调用的函数,语句为:
call SYMBOL_NAME(syscall_trace)
                                        movl ORIG_EAX(%esp),
%eax

call *SYMBOL_NAME(sys_call_table)(,%eax,4)
做完以上工作后,将返回值保存在eax寄存器中。
         movl %eax,EAX(%esp)
最后进入ret_from_sys_call,作一些处理工作。





















二.     系统调用实例分析:ptrace系统调用
2.1     跟踪及ptrace()简述
Linux提供的跟踪功能,即父进程对子进程的跟踪,使得父进程可以对自己的子进
程进行监督与控制。具体包括读写子进程用户空间的程序,数据,或user结构中的
变量,向它们发送软中断,以及命令它们自我终止等。系统提供了两种系统调用
waitpid()和ptrace(),一实现跟踪功能。这种父子进程间的关联可由子进程发
ptrace(0)请求,也可由父进程发attach请求来实现.
本文第二部分先利用第一部分的知识阐述ptrace系统调用的设置,然后再简单解释
ptrace系统调用的流程。
2.2     预备知识
1.     标识"错误"的宏,定义在linux\include\asm_i386\errno.h
ptrace()中涉及下面几个宏:
#define EPERM            1      /* 操作不被允许 */
#define ESRCH            3      /* 不存在这样的进程 */
#define EIO                      5  /*  I/O错误  */
2.     下面是一些用来帮助标识具体跟踪命令或状态的宏:
   (定义在linux\include\linux\ptrace.h中)
#define PTRACE_TRACEME             0
/*  说明是子进程调用该程序,请求父进程跟踪  */
#define PTRACE_PEEKTEXT            1
/*  在指定的位置读一个字  */
#define PTRACE_PEEKDATA            2
#define PTRACE_PEEKUSR             3
/*  在USER结构指定的位置读一个字  */
#define PTRACE_POKETEXT            4
/*  在指定的位置写一个字  */
#define PTRACE_POKEDATA            5
#define PTRACE_POKEUSR             6
/*  在USER结构指定的位置写一个字  */
#define PTRACE_CONT                7
/*   子进程接受信号后,RESTART  */
#define PTRACE_KILL                8
/*  终止子进程  */
#define PTRACE_SINGLESTEP          9
/*  置TRAP标志  */
#define PTRACE_ATTACH           0x10
#define PTRACE_DETACH           0x11

    #define PTRACE_SYSCALL                24
        (定义在linux\include\asm_i386\ptrace.h中)
        #define PTRACE_GETREGS            12
                /*  在子进程中获得所有的GP寄存器内容  */
#define PTRACE_SETREGS            13
/*  设置子进程中所有的的GP寄存器  */
#define PTRACE_GETFPREGS          14
/*  Get the child FPU state.  */
#define PTRACE_SETFPREGS          15
/* Set the child FPU state. */
3.     其他(待解释)
#define PAGE_SHIFT      12
#define PAGE_SIZE       (1UL << PAGE_SHIFT)
#define PAGE_MASK       (~(PAGE_SIZE-1))
#define VM_GROWSDOWN    0x0100
#define _NSIG           64
2.3     ptrace系统调用的设置
系统调用的设置均在文件"include/asm-i386/unistd.h"中进行.ptrace系统调用的
对应函数带4个参数,因此该系统调用的设置应使用的宏应为:
_syscall4(int,ptrace,long,request,long,pid,long,addr,long,data)
     (文件"include/asm-i386/unistd.h"中),
 #define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,
arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
          "d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}
这样,在调用ptrace时,系统将调用宏指令_syscall4,进而,调用0x80号中断,
寄存器eax中的值为__NR_ptrace, request值存入寄存器ebx;pid值存入寄存器
ecx;addr值存入寄存器edx;data值存入寄存器esi;调用中断"int $0x80"以后,
在汇编过程"system_call"中,将通过eax中的值__NR_ptrace(即26)与4的乘积作
为相对于系统调用表(sys_call_table)偏移量,找到入口:
.long SYMBOL_NAME(sys_ptrace)
于是,系统流程转向函数sys_ptrace()。sys_ptrace()是在文件
"arch/i386/kernel/ptrace.c"中定义的,程序如下:
下面一节介绍该函数的大概流程。
2.4     ptrace()的流程
ptrace()函数一开始就"作最坏的打算",将可能返回的值    ret初始值置为
-EPERM,它意味着,操作被拒绝。然后,才进入主流程。
首先,函数根据request参数判断是否是子进程发出的要求,如果request为1,则
表示是子进程要求父进程进行跟踪,再判断是否已被跟踪,如是则返回,此步可用
指向当前进程的current的flags字段与PF_PTRACED进行求与,如结果非零表示子进
程已被跟踪。如未被跟踪,则将PF_PTRACED与flags求并,ret置为0,最后返回。

if (request ==PTRACE_TRACEME) {
                if (current->flags & PF_PTRACED)
                        goto out;
                /* set the ptrace bit in the process flags. */
                current->flags |= PF_PTRACED;
                ret = 0;
                goto out;
        }
如request不为1,则表示父进程发给子进程的命令类型。
接下去如果子进程的pid等于1(1号进程是初始进程,是除0号进程外所有进程的祖
先),则带出错信息返回。然后函数将从tasklist中找到子进程,先做好最坏的准
备,即找不到pid所表示的子进程,将ret置为   -ESRCH,接着调用宏read_lock和
read_unlock(定义在spinlock.h中)
#define read_lock(lock)         do { } while(0)
#define read_unlock(lock)               do { } while(0)
在它们中间调用find_task_by_pid(),根据进程的id号从hash表中找到指向子进
程的指针,赋给child指针。如该指针为null,则带前设出错信息返回。
        下面将判断是否是当前进程发出命令要求跟踪指定的进程,为此函数有做好最坏的
准备,将ret置为-EPERM。
        接着函数将准备处理attach的请求,此请求表示当前进程想跟踪指定的进程,以便
后面各种命令的实现.若找到的进程就是当前进程,带设好的出错信息返回.而要想
绑定一个进程,必须向它发送信号(signal),又因为除了内核和超级用户,不是每一
个进程都能向别的进程发送信号,而且一般的进程只能向同用户和同组的进程发送
信号,所以如果当前进程不满足这些条件,或者子进程不能进行进程交换,并且当前
进程所属用户也不是超级用户的话,那么该操作也将拒绝而返回.如果该进程正在被
当前进程所跟踪,操作也将失败.这一系列的判断有下面的语句完成:
                if ((!child->dumpable ||
                    (current->uid != child->euid) ||
                    (current->uid != child->suid) ||
                    (current->uid != child->uid) ||
                    (current->gid != child->egid) ||
                    (current->gid != child->sgid) ||
                    (current->gid != child->gid)) && !suser())
                        goto out;
                if (child->flags & PF_PTRACED)
                        goto out;
        接着将current的flags字段与PF_PTRACED求并.如果当前进程是child进程的父进
程,则直接调用send_sig()函数,向它发送SIGSTOP信号,使子进程进入
TASK_STOPPED状态,等待父进程的命令.父进程则返回准备下一次调用.
                send_sig(SIGSTOP, child, 1);
        如果当前进程不是child的父进程,而是属于超级用户,则需要修改进程的p_pptr字
段,以使当前进程为父进程,为对共享数据进行读写,须先保存必要的信息到flags中
然后关中断,以保证数据的一致性和完整性,修改完数据后从flags中恢复信息.此工
作有下面几个宏完成,分别定义在spin_lock.h,system.h中.
#define save_flags(x) \
__asm__ __volatile__("pushfl ; popl %0":"=g" (x): /* no input */ :
"memory")
#define restore_flags(x) \
__asm__ __volatile__("pushl %0 ; popfl": /* no output */ :"g" (x):
"memory")
#define cli() __asm__ __volatile__ ("cli": : :"memory")
                #define write_lock_irqsave(lock, flags) \
                do { save_flags(flags); cli(); } while (0)
                #define write_unlock_irqrestore(lock, flags) \
                restore_flags(flags)
        在REMOVE_LINKS和SET_LINKS两个宏中间,将child的p_pptr指针指向当前进程.然
后就可以像父进程一样直接向child发信号了.其中,REMOVE_LINKS和 SET_LINKS
定义在sched.h中,REMOVE_LINKS宏把进程从上相链表中删除,并把连在其上的父
兄进程指针移开。而SET_LINKS宏则重新设置该进程的相关指针。
        如果收到的请求不是PTRACE_ATTACH,表示子进程已被当前进程绑定,处于
TASK_STOPPED状态,等待父进程接下来的命令,或者父进程将KILL子进程.接着函数
有将对子进程作一些判断,于是先将ret置为-ESRCH,做好出错的准备.首先,如果该
进程未被跟踪
则出错返回,表明一个进程不能对任意的进程跟踪,子进程须先做请求,或者父进程
先要提出跟踪的要求.如果子进程不处于TASK_STOPPED状态,父进程也未发kill命令
,出错返回.如果child的父进程不是当前进程,表明此跟踪乃子进程首先申请,但不
应由当前进程处理,返回.
        下面进入各种命令的具体实现部分,由一个switch...case...组成.共分下面几种
情况处理:
        1.PTRACE_PEEKTEXT;
        2.PTRACE_PEEKDATA;
        3.PTRACE_PEEKUSR;
        4.PTRACE_POKETEXT:
        5.PTRACE_POKEDATA:
        6.PTRACE_POKEUSR:
        7.PTRACE_SYSCALL:
        8.PTRACE_CONT:
        9.PTRACE_KILL:
        10.PTRACE_SINGLESTEP:
        11.PTRACE_DETACH:
        12.PTRACE_GETREGS:
        13.PTRACE_SETREGS:
        14.PTRACE_GETFPREGS:
        15.PTRACE_SETFPREGS:
        16.default:
        由于笔者时间有限,将试着做一些简单的分析.
        如果收到的要求是PTRACE_PEEKDATA,即要求从指定的地址读数据。首先定义一个
无符号长整型变量tmp,用来存放中间结果。然后如下调用函数:
        ret = read_long(child, addr, &tmp);
该函数也定义在ptrace.h中,该函数首先调用find_extend_vma函数(ptrace.h),
    这里需要讲一下vm_area_struct这个结构。当一个进程映像被执行的时候,可
执行的进程映像内容须被引入到进程的虚地址空间去,任何与进程映像相联系的函
数库也一样。可执行文件实际上并没有引入内存,相反,它仅仅只是被关联到进程
的虚地址空间。这样,作为正运行的应用程序访问的程序的一部分,进程映像就通
过可执行的映像引入到内存中。这个过程就叫内存映射。每一个进程的虚存由
mm_struct数据结构代表,它包括了进程的映像的信息和一个指向一系列
vm_area_struct结构的指针。每一个vm_area_struct结构描述一个虚存区的启始地
址和结束地址,进程进入内存的权限,和一些相关的操作。
在find_extend_vma函数中调用find_vma函数,从进程的vm_area_struct中找到第
一个满足addr<vm_end的虚存区,首先检查cache,然后再在双项链表中找到相应的虚
存区.如果找不到则返回 NULL,找到,则返回一个满足条件的vm_area_struct指针
。如果该指针为null,
返回null,如果它的vm_start<addr,则返回这个指针,下面的代码待解释:

        if (!(vma->vm_flags & VM_GROWSDOWN))
                return NULL;
if (vma->vm_end - addr > tsk->rlim[RLIMIT_STACK].rlim_cur)
                return NULL;
        如果都不是,则将vm_start和vm_offset重新设置,返回。如果返回值为null,则
返回  -EIO给ret,这个例程将通过页表从进程区获得一个长整型数。注意:你必须
自己检查该长整型是否在页的边界,并且在调用它前它已在task aera中,例程不
会替你检查。通过下面的判断可知是否在边界:
if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long))
如果不是则直接调用get_long()即可,不然则需用两个long来获得该数。
这个long存在tmp中,如果发现ret>=0,则调用put_user()宏。该宏允许设备在用
户区写数据。注意,这个函数可能导致I/O冲突,如果被访问的内存已被换出,所
以此时抢占可能发生。即使临界区已被cli()和sti()保护起来,也不要在临界区中
使用这个函数,因为I/O冲突将破坏cli()/sti()对的完整性。如果你想到达用户空
间内存,需在进入临界区前把它拷到内核区内存中。(ret为什么会大于零呢?)

如果请求是PTRACE_KILL,则首先判断子进程是否已为僵死状态,如是则直接退出
。如否则调用一个内联函数wake_up_process(),置进程状态为运行态,然后将
SIGKILL赋给child->exit_code,作为子进程强行退出时的出错代码。然后调用
get_stack_long 和put_stack_long确保禁止单步跟踪,返回。(其中,对tss.
esp0不了解,tss是一个thread_struct结构,也是task_struct中的一个字段)
如果请求是PTRACE_DETACH,则将断开父子进程的关联。首先做好最坏准备,置
ret为-EIO。因为参数data将作为子进程强行退出时的出错代码,故如果大于64则
出错返回,然后置flags的相关位,使子进程不被跟踪,接着唤醒子进程,将data
赋给child->exit_ccode,安全的修改child的p_pptr指针使其指向起原始父进程,
p_opptr.在通过调用get_stack_long和put_stack_long,确保单步中断被禁止。最
后返回。
三.     总结
本实验报告组要有两部分组成,先分析了系统调用的陷入与返回过程,然后结合
ptrace系统调用具体的分析了其中的流程.在分析过程中也遇到了不少的困难,比如
,刚开始对那些内联的汇编语句不能理解,通过与同学的互相讨论,在结合Brennan
UnderWood   "Brennan's Guide to Inline Assembly",终于能看明白这些陌生的
格式.在具体分析ptrace这个系统调用时,更是碰到了麻烦,再看了一本关于UNIX的
相应的流程之后,对这个程序才有了些眉目.在UNIX SYSTEM V中,只有子进程能发
ptrace(0)命令,要求父进程进行跟踪,而父进程则消极等待,在linux中,不仅支持了
上面的功能,还允许父进程或超级用户主动提出要求,无疑大大方便了进程的监督与
控制.然而由于时间和本人能力的关系,不能对其中所有的命令一一分析下来,即使
分析过的,也还存在不少疑问,这也在分析过程中提过.但我仍感觉受益非浅,通过这
学期操作系统课的学习,尤其是对linux系统的分析工作,我对计算机的认识又深了
一层,
以前总认为能编出好的程序就足够了,可现在却觉得这还远远不够,因为任何程序的
执行都依托于操作系统.我想利用寒假的时间对linux进行进一步研究, 也深深希望
得到您的指点.






四.     附录
1.参考资料
(1)   Linux kernel 2.1.99
(2)   李善平 《LINUX进程管理》
(3)   David A Rusling "The Linux Kernel"
(4)   Brennan UnderWood   "Brennan's Guide to Inline Assembly

--

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.43.46]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店