x86使用execve执行一个elf文件并传递参数--代码思路分析

execve(const char *filename, char *const argv[ ], char *const envp[ ])

视频教程以及实际代码可以看这一个教程
其他的需要的知识
GDT表
GDT表虚拟内存
页表
任务切换
fork实现
elf文件加载

这一个是一个Linux下面的标准接口

这一个的实际作用的是执行一个可执行文件

把当前程序替换成要执行的程序, 而同时保留原程序运行的方法是,fork+exec

第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。

函数执行成功时没有返回值,执行失败时的返回值为-1.

#include<unistd.h>   
main()   
{   
  char *argv[ ]={"ls", "-al", "/etc/passwd", NULL};   
  char *envp[ ]={"PATH=/bin", NULL}   
  execve("/bin/ls", argv, envp);   
}  

实现的思路

CPU寄存器的初始化

在使用的时候需要一个新的页表, 记录这一个新的进程使用的地址信息

由于使用系调用的时候会记录进入的时候的CPU寄存器信息, 返回的时候弹出信息, 这一个新的进程再返回的时候, 首先使用是栈里面的记录的信息, 而不是tss表里面的信息

image-20240225191306270

返回的时候这里面的信息也需要进行更改, 如果不改变的话会返回之前的页表对应的位置

该变信息的时候可以使用tss里面的记录的esp0的值进行计算, 使用结构体syscall_frame_t(一个记录压栈时候寄存器顺序的结构体, 定位到对应的位置)

栈的初始化(参数传递)

在使用新的栈的时候, 设置esp需要减去一下系统调用的参数的位置(系统调用返回的时候使用retf会把栈里面的参数弹出来, 新的任务进入的时候栈里面没有这几个参数, 需要预留空间), 以及初始化一下main函数的参数在栈里面

image-20240225202348872

这里可以使用把所有的信息放在栈里面

实际的实现

//执行一个可执行文件
int sys_execve(char *name, char ** argv, char ** env){
    task_t *task = task_current();//获取当前的任务
    uint32_t new_page_dir = memory_create_uvm();//获取一个新的页表给任务使用
    uint32_t old_page_dir = task->tss.cr3;//记录一下现在使用的页表
    //使用这一个新的文件的名字初始化任务名字
    kernel_strncpy(task->name, get_file_name(name), TASK_NAME_SIZE);
    if(! new_page_dir)
    {
        goto exec_failed;
    }
    //获取这一个的入口, 以及加载这一个文件到新的页表里面
    //这里实际是加载一个elf文件, 以及从文件头获取他的入口地址
    //这里需要注意的是实际使用的虚拟地址是还未使用的页表里面的
    //实际加载的时候需要对使用的内存申请, 映射, 复制
    uint32_t entry = load_elf_file(task, name, new_page_dir);
    if(entry == 0){
        goto exec_failed;
    } 
    //预留一段空间放参数(main函数的参数)
    uint32_t stack_top = 栈的顶部虚拟地址 - 预留的参数保存地址;
    //为这一个任务的新页表申请一下栈空间
    int err = memory_alloc_for_page_dir(new_page_dir, 
                MEM_TASK_STACK_TOP - MEM_TASK_STACK_SIZE(实际的虚拟地址最小值), MEM_TASK_STACK_SIZE()大小, 权限(用户可使用, 可写));
    if(err < 0){
        goto exec_failed;
    }
    int argc = strings_count(argv);//获取参数的个数
    //把这一个参数按照之前图里面的格式复制到栈里面预留的空间
    //之后main函数可以直接使用
    err = copy_args((char *)stack_top, new_page_dir, argc, argv);
    if(err < 0)
    {
        goto exec_failed;
    }
    //获取记录了栈里信息的地址
    syscall_frame_t * frame = (syscall_frame_t *)(系统调用的时候记录的特权级esp - sizeof(syscall_frame_t)(实际压入的信息的大小));
    //改变特权级0的栈里面的信息用于返回
    frame->eip = entry;
    frame->eax = frame->ebx = frame->ecx = frame->edx = 0;
    frame->esi = frame->edi = frame->ebp = 0;
    frame->eflags = EFLAGS_DEFAULT | EFLAGS_IF; 
    //预留一下栈里面参数的位置
    frame->esp = stack_top - sizeof(uint32_t) * SYSCALL_PARAM_COUNT;
    //栈里面需要有初始的参数的值

    task->tss.cr3 = new_page_dir;
    mmu_set_page_dir(new_page_dir);
    //销毁之前的页表
    memory_destroy_uvm(old_page_dir);
    return 0;

exec_failed:
    if(new_page_dir){
		//错误处理
    }
    return -1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值