比如,我们在终端上输入
ls --version
就会出现如下结果。ps 在此处,我们可以人为ls为可执行程序的名称,--version 是该程序需要的参数。
ls (GNU coreutils) 8.4
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Richard M. Stallman and David MacKenzie.
首先,我们从shell的主函数开始,该函数在shell.c文件中。在主函数执行之前,主要做了以下准备工作:
在做完上述完整性检查之后,最终会执行reader_loop函数,该函数,定义在eval.c中,主要作用是读取给定的程序名称和参数。然后从execute_cmd.c调用execute_command函数,依次调用以下函数链, 不同的检查,例如我们是否需要启动subshell,是否内置bash函数等等。
reader_loop
-> execute_command
--> execute_command_internal
----> execute_simple_command
------> execute_disk_command
--------> shell_execve
众所周知,Linux的实现语言是c,shell也是其一个应用,也有自己的main函数。进入main函数后,在基本的初始化操作之后,最终进入reader_loop函数。reader_loop会调用execute_command来等待用户输入命令行参数,在用户输入参数之后,将调用execute_command_internal函数。execute_command_internal函数是shell源码中执行命令的实际操作函数。他需要对作为操作参数传入的具体命令结构的value成员进行分析,并针对不同的value类型,再调用具体类型的命令执行函数进行具体命令的解释执行工作。
具体来说:如果value是simple,则直接调用execute_simple_command函数进行执行,execute_simple_command再根据命令是内部命令或磁盘外部命令分别调用execute_builtin和execute_disk_command来执行,其中,execute_disk_command在执行外部命令的时候调用make_child函数fork子进程执行外部命令。
如果value是其他类型,则调用对应类型的函数进行分支控制。举例来说,如果是value是for_commmand,即这是一个for循环控制结构命令,则调用execute_for_command函数。在该函数中,将枚举每一个操作域中的元素,对其再次调用execute_command函数进行分析。即execute_for_command这一类函数实现的是一个命令的展开以及流程控制以及递归调用execute_command的功能。在上述整个调用流程串的最后一步是shell_execve。该函数最终会调用系统函数execve,其声明如下:
int execve(const char *filename, char *const argv [], char *const envp[]);
在该函数中,有三个参数,分别是:
该函数定义在fs/exec.c中,其声明如下:
SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
return do_execve(getname(filename), argv, envp);
}
execve的实现在这里非常简单,只调用了do_execve函数,其参数为execve的参数。而do_execve函数的定义如下:
int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
通过上述代码,我们可以看到,在do_execve中,最终调用了do_execveat_common,其除了使用do_execve中的参数之外,还有额外的两个参数。下面是do_execveat_common的具体代码(此处我们去掉了一些不必要放入判断代码)
static int do_execveat_common(int fd, struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp,
int flags)
{
struct linux_binprm *bprm;
int retval;
if (IS_ERR(filename))
return PTR_ERR(filename);
...
current->flags &= ~PF_NPROC_EXCEEDED;
bprm = alloc_bprm(fd, filename);
if (IS_ERR(bprm)) {
retval = PTR_ERR(bprm);
goto out_ret;
}
retval = count(argv, MAX_ARG_STRINGS);
bprm->argc = retval;
retval = count(envp, MAX_ARG_STRINGS);
bprm->envc = retval;
retval = bprm_stack_limits(bprm);
retval = copy_string_kernel(bprm->filename, bprm);
bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
retval = copy_strings(bprm->argc, argv, bprm);
retval = bprm_execve(bprm, fd, filename, flags);
putname(filename);
return retval;
}
第一个参数AT_FDCWD是当前目录的文件描述符,第五个参数是标志。我们稍后会看到。do_execveat_common函数检查文件名指针并返回它是否为NULL。在此之后,它检查当前进程的标志,表明未超出正在运行的进程的限制:
if (IS_ERR(filename))
return PTR_ERR(filename);
if ((current->flags & PF_NPROC_EXCEEDED) &&
atomic_read(¤t_user()->processes) > rlimit(RLIMIT_NPROC)) {
retval = -EAGAIN;
goto out_ret;
}
current->flags &= ~PF_NPROC_EXCEEDED;
如果这两项检查成功,我们将在当前进程的标志中取消设置PF_NPROC_EXCEEDED标志,以防止执行程序失败。在下一步中,我们调用在kernel/fork.c中定义的unshare_files函数,并取消共享当前任务的文件,并检查此函数的结果:
retval = unshare_files(&displaced);
if (retval)
goto out_ret;
调用此函数的目的旨在消除执行二进制文件的文件描述符的潜在泄漏。在下一步中,我们开始准备由struct linux_binprm结构(在include/linux/binfmts.h头文件中定义)表示的bprm。
linux_binprm结构用于保存加载二进制文件时使用的参数。例如,它包含vm_area_struct,表示将在给定地址空间中连续间隔内的单个内存区域,将在该空间中加载应用程序。mm字段,它是二进制文件的内存描述符,指向内存顶部的指针以及许多其他不同的字段。
在do_execveat_common函数中,执行alloc_bprm函数,最终会调用如下:
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
goto out_files;
retval = prepare_bprm_creds(bprm);
if (retval)
goto out_free;
check_unsafe_exec(bprm);
current->in_execve = 1;
初始化linux_binprm中的cred结构变量,该结构变量中包含任务的实际uid,任务的实际guid,虚拟文件系统操作的uid和guid等。然后,对check_unsafe_exec函数的调用将当前进程设置为in_execve状态。
bprm->argc = count(argv, MAX_ARG_STRINGS);
if ((retval = bprm->argc) < 0)
goto out;
bprm->envc = count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < 0)
goto out;
在上述代码中,MAX_ARG_STRINGS是头文件中定义的上限宏,它表示传递给execve系统调用的最大字符串数。MAX_ARG_STRINGS的值:
`#define MAX_ARG_STRINGS 0x7FFFFFFF`
完成所有这些操作后,我们调用do_open_execat函数,该函数
file = do_open_execat(fd, filename, flags);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_unmark;
sched_exec();
之后,我们需要检查给出可执行二进制文件的文件描述符。我们尝试检查二进制文件的名称是否从/符号开始,或者给定的可执行二进制文件的路径是否相对于调用进程的当前工作目录进行了解释,或者文件描述符为AT_FDCWD。如果这些检查之一成功,我们将设置二进制参数文件名:
bprm->file = file;
if (fd == AT_FDCWD || filename->name[0] == '/') {
bprm->filename = filename->name;
}
否则,如果文件名称为空,则将文件名设置为/dev/fd/%d (即/dev/fd/文件描述符),否则将文件名重新设置为/dev/fd/%d/文件名(其中,fd指向可执行文件的文件描述符)
} else {
if (filename->name[0] == '\0')
pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d", fd);
else
pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d/%s", fd, filename->name);
if (!pathbuf) {
retval = -ENOMEM;
goto out_unmark;
}
bprm->filename = pathbuf;
}
bprm->interp = bprm->filename;
需要注意的是,我们不仅设置了bprm-> filename,还设置了bprm-> interp,它将包含程序解释器的名称。现在,我们只是在此处写相同的名称,但是稍后将使用程序解释器的真实名称对其进行更新,其具体取决于程序的二进制格式。
retval = bprm_mm_init(bprm);
if (retval)
goto out_unmark;
其中,bprm_mm_init的定义如下:
static int bprm_mm_init(struct linux_binprm *bprm)
{
int err;
struct mm_struct *mm = NULL;
bprm->mm = mm = mm_alloc();
err = -ENOMEM;
if (!mm)
goto err;
/* Save current stack limit for all calculations made during exec. */
task_lock(current->group_leader);
bprm->rlim_stack = current->signal->rlim[RLIMIT_STACK];
task_unlock(current->group_leader);
err = __bprm_mm_init(bprm);
if (err)
goto err;
return 0;
err:
if (mm) {
bprm->mm = NULL;
mmdrop(mm);
}
return err;
}
在函数bprm_mm_init中,其功能主要是初始化mm_struct 和 vm_area_struct结构。
调用prepare_binprm函数将inode的uid填充到linux_binprm结构中,并从二进制可执行文件中读取128个字节。我们只从可执行文件中读取前128个,因为我们需要检查可执行文件的类型。我们将在后续步骤中阅读可执行文件的其余部分。
retval = prepare_binprm(bprm);
if (retval < 0)
goto out;
准备好linux_bprm结构后,我们通过调用copy_strings_kernel函数将可执行二进制文件的文件名,命令行参数和环境变量从内核复制到linux_bprm:
retval = copy_strings_kernel(1, &bprm->filename, bprm);
if (retval < 0)
goto out;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < 0)
goto out;
retval = copy_strings(bprm->argc, argv, bprm);
if (retval < 0)
goto out;
并将指针设置为我们在bprm_mm_init函数中设置的新程序堆栈的顶部bprm-> exec = bprm-> p; 堆栈的顶部将包含程序文件名,我们将该文件名存储到linux_bprm结构的exec字段中。
通过调用exec_binprm函数来存储当前当前任务所在进程的pid
retval = exec_binprm(bprm);
if (retval < 0)
goto out;
在exec_binprm函数中,也会调用search_binary_handler。当前,Linux内核支持以下二进制格式:
int search_binary_handler(struct linux_binprm *bprm)
{
...
...
...
list_for_each_entry(fmt, &formats, lh) {
retval = fmt->load_binary(bprm);
if (retval < 0 && !bprm->mm) {
force_sigsegv(SIGSEGV, current);
return retval;
}
}
return retval;
在load_binary中检查linux_bprm缓冲区中的魔数(每个elf二进制文件的头中都包含魔数,我们从可执行二进制文件中读取了前128个字节),如果不是elf二进制,则退出。
如果给定的可执行文件为elf格式,则load_elf_binary继续并检查可执行文件的体系结构和类型,并在体系结构错误且可执行文件不可执行,不可共享时退出:
if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
goto out;
if (!elf_check_arch(&loc->elf_ex))
goto out;
尝试加载描述段的程序头表。从磁盘上读取与我们的可执行二进制文件链接的程序解释器和库,并将其加载到内存中。
elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);
if (!elf_phdata)
goto out;
程序解释器指定在可执行文件的.interp部分(在大多数情况下,对于x86_64,链接器为– /lib64/ld-linux-x86-64.so.2)。它设置堆栈并将elf二进制文件映射到内存中的正确位置,映射了bss和brk部分,并做了许多其他不同的事情来准备要执行的可执行文件。在执行load_elf_binary的最后,我们调用start_thread函数并将三个参数传递给该函数:
start_thread(regs, elf_entry, bprm->p);
retval = 0;
out:
kfree(loc);
out_ret:
return retval;
这些参数是:
void
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
start_thread_common(regs, new_ip, new_sp,
__USER_CS, __USER_DS, 0);
}
通过上面代码,我们能够看到,在start_thread函数中,最终还是调用了start_thread_common函数。
static void
start_thread_common(struct pt_regs *regs, unsigned long new_ip,
unsigned long new_sp,
unsigned int _cs, unsigned int _ss, unsigned int _ds)
{
loadsegment(fs, 0);
loadsegment(es, _ds);
loadsegment(ds, _ds);
load_gs_index(0);
regs->ip = new_ip;
regs->sp = new_sp;
regs->cs = _cs;
regs->ss = _ss;
regs->flags = X86_EFLAGS_IF;
force_iret();
}
start_thread_common函数将fs段寄存器填充为零,并将es&ds填充数据段寄存器的值。之后,我们将新值设置为指令指针,cs段等。在start_thread_common函数的末尾,我们可以看到force_iret宏,该宏通过iret指令强制返回系统调用。
然后,创建了在用户空间中运行的新线程,随后可以从exec_binprm返回,再次处于do_execveat_common中。exec_binprm完成执行后,释放之前分配的结构的内存,然后返回。
从execve系统调用处理程序返回后,将开始执行程序。之所以可以这样做,是因为之前配置了所有与上下文相关的信息。
如我们所见,execve系统调用不会将控制权返回给进程,但是调用者进程的代码,数据和其他段只是被程序段所覆盖。应用程序的退出将通过退出系统调用实现。
至此,整个程序从开始运行到退出,整个流程完。
END
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/5cMFr7fAUsBPEWNi9bqtvg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。