#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <a.out.h> // 定义了a.out 执行文件格式和一些宏
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/segment.h>
extern int sys_exit(int exit_code);
extern int sys_close(int fd);
#define MAX_ARG_PAGES 32
static unsigned long * create_tables(char * p,int argc,int envc)
{
unsigned long *argv,*envp;
unsigned long * sp;
// 堆栈指针是以4 字节(1 节)为边界寻址的,因此这里让sp 为4 的整数倍
sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
// sp 向下移动,空出环境参数占用的空间个数,并让环境参数指针envp 指向该处
sp -= envc+1;
envp = sp;
// sp 向下移动,空出命令行参数指针占用的空间个数,并让argv 指针指向该处
sp -= argc+1;
argv = sp;
// 将环境参数指针envp 和命令行参数指针以及命令行参数个数压入堆栈
put_fs_long((unsigned long)envp,--sp);
put_fs_long((unsigned long)argv,--sp);
put_fs_long((unsigned long)argc,--sp);
// argc array
while (argc-->0) {
put_fs_long((unsigned long) p,argv++);
while (get_fs_byte(p++)) ;
}
// NULL
put_fs_long(0,argv);
// envc array
while (envc-->0) {
put_fs_long((unsigned long) p,envp++);
while (get_fs_byte(p++)) ;
}
// NULL
put_fs_long(0,envp);
return sp;
}
static int count(char ** argv)
{
int i=0;
char ** tmp;
if (tmp = argv)
while (get_fs_long((unsigned long *) (tmp++)))
i++;
return i;
}
static unsigned long copy_strings(int argc,char ** argv,unsigned long *page,
unsigned long p, int from_kmem)
{
// 参数:argc - 欲添加的参数个数;argv - 参数指针数组;page - 参数和环境空间页面指针数组。
// p -在参数表空间中的偏移指针,始终指向已复制串的头部;from_kmem - 字符串来源标志。
// 在do_execve()函数中,p 初始化为指向参数表(128kB)空间的最后一个长字处,参数字符串
// 是以堆栈操作方式逆向往其中复制存放的,因此p 指针会始终指向参数字符串的头部。
// 返回:参数和环境空间当前头部指针
char *tmp, *pag;
int len, offset = 0;
unsigned long old_fs, new_fs;
if (!p)
return 0;
// 取ds 寄存器值到new_fs,并保存原fs 寄存器值到old_fs
new_fs = get_ds();
old_fs = get_fs();
// 如果字符串和字符串数组来自内核空间,则设置fs 段寄存器指向内核数据段(ds)
if (from_kmem==2)
set_fs(new_fs);
// 循环处理各个参数,从最后一个参数逆向开始复制,复制到指定偏移地址处
while (argc-- > 0)
{
// 如果字符串在用户空间而字符串数组在内核空间,则设置fs 段寄存器指向内核数据段(ds)
if (from_kmem == 1)
set_fs(new_fs);
// 从最后一个参数开始逆向操作,取fs 段中最后一参数指针到tmp,如果为空,则出错死机
// tmp指向的是一个字符串,char ** argv
if (!(tmp = (char *)get_fs_long(((unsigned long *)argv)+argc)))
panic("argc is wrong");
// 如果字符串在用户空间而字符串数组在内核空间,则恢复fs 段寄存器原值
if (from_kmem == 1)
set_fs(old_fs);
// 得到tmp指向的字符串的长度
len=0;
do {
len++;
} while (get_fs_byte(tmp++));
// 如果该字符串长度超过此时参数和环境空间中还剩余的空闲长度
if (p-len < 0) {
set_fs(old_fs);
return 0;
}
// 复制fs 段中当前指定的参数字符串,是从该字符串尾逆向开始复制
while (len)
{
--p; --tmp; --len;
// 函数刚开始执行时,偏移变量offset 被初始化为0,因此若offset-1<0,说明是首次复制字符串,
// 则令其等于p 指针在页面内的偏移值,并申请空闲页面
if (--offset < 0)
{
offset = p % PAGE_SIZE;
// 如果字符串和字符串数组在内核空间,则恢复fs 段寄存器原值
if (from_kmem==2)
set_fs(old_fs);
// 如果当前偏移值p 所在的串空间页面指针数组项page[p/PAGE_SIZE]==0,表示相应页面还不存在,
// 则需申请新的内存空闲页面,将该页面指针填入指针数组,并且也使pag 指向该新页面,若申请不
// 到空闲页面则返回
if (!(pag = (char *) page[p/PAGE_SIZE]) &&
!(pag = (char *) page[p/PAGE_SIZE] =
(unsigned long *) get_free_page()))
return 0;
// 如果字符串和字符串数组来自内核空间,则设置fs 段寄存器指向内核数据段(ds)
if (from_kmem==2)
set_fs(new_fs);
}
// 从fs 段中复制参数字符串中一字节到pag+offset 处
*(pag + offset) = get_fs_byte(tmp);
}
}
// 如果字符串和字符串数组在内核空间,则恢复fs 段寄存器原值
if (from_kmem==2)
set_fs(old_fs);
// 最后,返回参数和环境空间中已复制参数信息的头部偏移值
return p;
}
static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
unsigned long code_limit,data_limit,code_base,data_base;
int i;
// 根据执行文件头部a_text 值,计算以页面长度为边界的代码段限长
code_limit = text_size+PAGE_SIZE -1;
code_limit &= 0xFFFFF000;
// 设置数据段长度为64MB
data_limit = 0x4000000;
// 取当前进程中局部描述符表代码段描述符中代码段基址,代码段基址与数据段基址相同
code_base = get_base(current->ldt[1]);
data_base = code_base;
// 重新设置局部表中代码段和数据段描述符的基址和段限长
set_base(current->ldt[1],code_base);
set_limit(current->ldt[1],code_limit);
set_base(current->ldt[2],data_base);
set_limit(current->ldt[2],data_limit);
// fs 段寄存器中放入局部表数据段描述符的选择符(0x17)
__asm__("pushl $0x17/n/tpop %%fs"::);
// 将参数和环境空间已存放数据的页面(共可有MAX_ARG_PAGES 页,128kB)放到数据段线性地址的
// 末端。是调用函数put_page()进行操作的
data_base += data_limit;
for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--)
{
data_base -= PAGE_SIZE;
if (page[i]) // 如果该页面存在
put_page(page[i],data_base); // 就放置该页面
}
return data_limit; // 最后返回数据段限长(64MB)
}
int do_execve(unsigned long * eip,long tmp,char * filename,
char ** argv, char ** envp)
{
struct m_inode * inode; // 可执行文件在内存中的i节点指针
struct buffer_head * bh;
struct exec ex; // 执行文件头部数据结构变量
unsigned long page[MAX_ARG_PAGES]; // 32,足够
int i,argc,envc;
int e_uid, e_gid; // 有效用户id 和有效组id
int retval; // 返回值
int sh_bang = 0; // 控制是否需要执行脚本处理代码
// 参数和环境字符串空间中的偏移指针,初始化为指向该空间的最后一个长字处
unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;
// 内核不能调用本函数
if ((0xffff & eip[1]) != 0x000f)
panic("execve called from supervisor mode");
// 初始化程序参数和环境串空间的页面指针数组(表)
for (i=0 ; i<MAX_ARG_PAGES ; i++)
page[i]=0;
if (!(inode=namei(filename)))
return -ENOENT;
argc = count(argv); // 可执行文件参数数组长度
envc = count(envp); // 环境指针数组长度
restart_interp: // 执行文件必须是常规文件
if (!S_ISREG(inode->i_mode))
{
retval = -EACCES;
goto exec_error2;
}
// 根据其属性(对应i 节点的uid 和gid),看本进程是否有权执行它
i = inode->i_mode;
e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
if (current->euid == inode->i_uid)
i >>= 6;
else if (current->egid == inode->i_gid)
i >>= 3;
if (!(i & 1) &&
!((inode->i_mode & 0111) && suser())) { // 没有权限?
retval = -ENOEXEC;
goto exec_error2;
}
// 读取执行文件的第一块数据到高速缓冲区
if (!(bh = bread(inode->i_dev,inode->i_zone[0])))
{
retval = -EACCES;
goto exec_error2;
}
ex = *((struct exec *) bh->b_data);
// 如果可执行文件的头部是'#!',并且sh_bang没有设置,执行脚本程序
if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang))
{
char buf[1023], *cp, *interp, *i_name, *i_arg;
unsigned long old_fs;
strncpy(buf, bh->b_data+2, 1022); // 将可执行程序复制到buf中
brelse(bh);
iput(inode); // 释放该执行文件i 节点
// 取第一行内容,并删除开始的空格、制表符
buf[1022] = '/0';
if (cp = strchr(buf, '/n')) {
*cp = '/0';
for (cp = buf; (*cp == ' ') || (*cp == '/t'); cp++);
}
// 若该行没有其它内容,则出错
if (!cp || *cp == '/0') {
retval = -ENOEXEC;
goto exec_error1;
}
interp = i_name = cp;
i_arg = 0;
// 首先取第一个字符串,其应该是脚本解释程序名,iname 指向该名称
for ( ; *cp && (*cp != ' ') && (*cp != '/t'); cp++) {
if (*cp == '/')
i_name = cp+1;
}
// 若文件名后还有字符,则应该是参数串,令i_arg 指向该串
if (*cp) {
*cp++ = '/0';
i_arg = cp;
}
// // 若sh_bang 标志没有设置,则设置它
if (sh_bang++ == 0)
{
// 复制指定个数的环境变量串和参数串到参数和环境空间中(page中)
p = copy_strings(envc, envp, page, p, 0);
p = copy_strings(--argc, argv+1, page, p, 0);
}
// 复制脚本程序文件名到参数和环境空间中
p = copy_strings(1, &filename, page, p, 1);
argc++;
// 复制解释程序的参数到参数和环境空间中
if (i_arg) {
p = copy_strings(1, &i_arg, page, p, 2);
argc++;
}
// 复制解释程序文件名到参数和环境空间中
p = copy_strings(1, &i_name, page, p, 2);
argc++;
if (!p) {
retval = -ENOMEM;
goto exec_error1;
}
old_fs = get_fs(); // 保留原fs 段寄存器
set_fs(get_ds()); // 现置其指向内核数据段
if (!(inode=namei(interp))) {
set_fs(old_fs);
retval = -ENOENT;
goto exec_error1;
}
set_fs(old_fs);
// 跳转到restart_interp 处重新处理
goto restart_interp;
}
brelse(bh);
// 对于下列情况,将不执行程序:如果执行文件不是需求页可执行文件(ZMAGIC)、或者代码重定位部分
// 长度a_trsize 不等于0、或者数据重定位信息长度不等于0、或者代码段+数据段+堆段长度超过50MB、
// 或者i 节点表明的该执行文件长度小于代码段+数据段+符号表长度+执行头部分长度的总和
if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
retval = -ENOEXEC;
goto exec_error2;
}
// 如果执行文件执行头部分长度不等于一个内存块大小
if (N_TXTOFF(ex) != BLOCK_SIZE) {
printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
retval = -ENOEXEC;
goto exec_error2;
}
// 如果sh_bang 标志没有设置,则复制指定个数的环境变量字符串和参数到参数和环境空间中。
// 若sh_bang 标志已经设置,则表明是将运行脚本程序,此时环境变量页面已经复制,无须再复制
if (!sh_bang) {
p = copy_strings(envc,envp,page,p,0);
p = copy_strings(argc,argv,page,p,0);
// 如果p=0,则表示环境变量与参数空间页面已经被占满,容纳不下了。转至出错处理处
if (!p) {
retval = -ENOMEM;
goto exec_error2;
}
}
if (current->executable) // 释放当前进程所占有的i节点
iput(current->executable);
current->executable = inode; // 设置成指向inode(需要执行程序的i节点)
// 清复位所有信号处理句柄
for (i=0 ; i<32 ; i++)
current->sigaction[i].sa_handler = NULL;
// 根据执行时关闭(close_on_exec)文件句柄位图标志
for (i=0 ; i<NR_OPEN ; i++)
if ((current->close_on_exec>>i)&1)
sys_close(i);
current->close_on_exec = 0;
// 释放原来程序代码段和数据段所对应的内存页表指定的内存块及页表本身
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
// 重新设置全局变量(是否使用协处理器)last_task_used_math
if (last_task_used_math == current)
last_task_used_math = NULL;
current->used_math = 0;
// 根据a_text 修改局部表中描述符基址和段限长,并将参数和环境空间页面放置在数据段末端。
// 执行下面语句之后,p 此时是以数据段起始处为原点的偏移值,仍指向参数和环境空间数据开始处,
// 也即转换成为堆栈的指针
p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
// create_tables()在新用户堆栈中创建环境和参数变量指针表,并返回该堆栈指针
p = (unsigned long) create_tables((char *)p,argc,envc);
// 修改当前进程各字段为新执行程序的信息
current->brk = ex.a_bss +
(current->end_data = ex.a_data +
(current->end_code = ex.a_text));
// 设置进程堆栈开始字段为堆栈指针所在的页面
current->start_stack = p & 0xfffff000;
// 重新设置进程的用户id 和组id
current->euid = e_uid;
current->egid = e_gid;
// 初始化一页bss 段数据,全为零
i = ex.a_text+ex.a_data;
while (i&0xfff)
put_fs_byte(0,(char *) (i++));
// 将原调用系统中断的程序在堆栈上的代码指针替换为指向新执行程序的入口点
eip[0] = ex.a_entry;
// 并将堆栈指针替换为新执行程序的堆栈指针
eip[3] = p;
// 返回指令将弹出这些堆栈数据并使得CPU 去执行新的执行程序
return 0;
exec_error2: // 如果加载的文件不是常规文件的话,跳转到这里执行
iput(inode); // 将该i节点信息重新写入设备
exec_error1:
for (i=0 ; i<MAX_ARG_PAGES ; i++)
free_page(page[i]);
return(retval);
}
参考《linux内核完全注释》和网上相关资料