XNU加载Mach-O和dyld

我们知道,操作系统是电脑、手机上最基本的软件,任何其他的软件都必须在操作系统的支持下才能够运行。同理,软件的启动也必须在操作系统的支持下才能够运行。对于iOS系统来说,操作系统内核是XNU(X is not Unix),那么在一个app的启动过程中,XNU发挥了什么作用呢?本篇文章,我们来探究一下这个问题。

XNU启动launchd

XNU的代码是开源的,可以从苹果开源代码平台上下载XNU的代码,通过分析XNU的源码,可以大致了解XNU是如何加载Mach-O文件以及dyld的。

XNU内核启动后,启动的第一个进程是launchd,launchd启动之后会启动其他的守护进程。XNU启动launchd的过程是 load_init_program() -> load_init_program_at_path()。可以看一下这两个函数的源码。

load_init_program()部分代码:

void load_init_program(proc_t p)
{
    ……

    error = ENOENT;
    // 核心代码在这里,加载初始化程序,对 init_programs数组遍历
    for (i = 0; i < sizeof(init_programs)/sizeof(init_programs[0]); i++) {
        // 调用load_init_program_at_path方法
        error = load_init_program_at_path(p, (user_addr_t)scratch_addr, init_programs[i]);
        if (!error)
            return;
    }

    panic("Process 1 exec of %s failed, errno %d", ((i == 0) ? "<null>" : init_programs[i-1]), error);
}

init_programs是一个数组,可以看一下该数组的定义:

// 内核的debug模式下可以加载供调试的launchd,非debug模式下,只加载launchd
// launchd负责进程管理
static const char * init_programs[] = {
#if DEBUG
    "/usr/local/sbin/launchd.debug",
#endif
#if DEVELOPMENT || DEBUG
    "/usr/local/sbin/launchd.development",
#endif
    "/sbin/launchd",
};

可以看出,load_init_program的作用就是加载launchd,加载launchd使用的方法是load_init_program_at_path函数。load_init_program_at_path的部分代码如下:

static int load_init_program_at_path(proc_t p, user_addr_t scratch_addr, const char* path)
{
    ……
    /*
     * Set up argument block for fake call to execve.
     */
    init_exec_args.fname = argv0;
    init_exec_args.argp = scratch_addr;
    init_exec_args.envp = USER_ADDR_NULL;

    /*
     * So that init task is set with uid,gid 0 token
     */
    set_security_token(p);
    // 会调用execve方法
    return execve(p, &init_exec_args, retval);
}

load_init_program_at_path调用了execve()函数,实际上,execve是加载Mach-O文件流程的入口函数。因为launchd进程比较特殊,所以多了两个方法。因此,接下来我们就从execve()函数开始分析。

XNU加载Mach-O
execve()函数

上面说到了,execve()函数是加载Mach-O文件的入口,看一下execve()函数做了哪些事情:

/*
 uap是对可执行文件的封装,uap->fname可以得到执行文件的文件名
 uap->argp 可以得到执行文件的参数列表
 uap->envp 可以得到执行文件的环境变量列表
 */
int execve(proc_t p, struct execve_args *uap, int32_t *retval)
{
    struct __mac_execve_args muap;

    muap.fname = uap->fname;
    muap.argp = uap->argp;
    muap.envp = uap->envp;
    muap.mac_p = USER_ADDR_NULL;
    // 调用了__mac_execve方法
    err = __mac_execve(p, &muap, retval);

    return(err);
}

可以看到,execve()函数的作用主要是进行了一些赋值,然后调用了__mac_execve()函数,看来核心操作在__max_execve()函数中。

__mac_execve()函数

__mac_execve()函数会使用fork_create_child()函数启动新进程,之后使用新的进程,生成新的task。__mac_execve()函数的主要功能就是干这个,之后就调用了exec_activate_image()函数。

__mac_execve()函数的部分代码:

int __mac_execve(proc_t p, struct __mac_execve_args *uap, int32_t *retval)
{
    // 新的task定义
    task_t new_task = NULL;
    boolean_t should_release_proc_ref = FALSE;
    boolean_t exec_done = FALSE;
    boolean_t in_vfexec = FALSE;
    void *inherit = NULL;

    context.vc_thread = current_thread();
    context.vc_ucred = kauth_cred_proc_ref(p);  /* XXX must NOT be kauth_cred_get() */

    /* Initialize the common data in the image_params structure */
    // 使用uap初始化imgp结构体中的一些通用数据
    imgp->ip_user_fname = uap->fname;
    imgp->ip_user_argv = uap->argp;
    imgp->ip_user_envv = uap->envp;
    imgp->ip_vattr = vap;
    imgp->ip_origvattr = origvap;
    imgp->ip_vfs_context = &context;
    imgp-
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值