[TOC]
include/uapi/linux/elf.h
Elf32_Ehdr
(内核中通常用 struct elfhdr
): ELF文件头结构体
这个结构体是ELF文件格式的“封面”和“目录”,它必须位于任何一个ELF文件的最开头。加载器(如内核)通过读取并解析这个结构体,来了解如何处理文件的其余部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
typedef struct elf32_hdr {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx; } Elf32_Ehdr;
#define ET_NONE 0 #define ET_REL 1 #define ET_EXEC 2 #define ET_DYN 3 #define ET_CORE 4 #define ET_LOPROC 0xff00 #define ET_HIPROC 0xffff
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
#define EI_MAG0 0 #define EI_MAG1 1 #define EI_MAG2 2 #define EI_MAG3 3 #define EI_CLASS 4 #define EI_DATA 5 #define EI_VERSION 6 #define EI_OSABI 7 #define EI_PAD 8
#define ELFMAG0 0x7f #define ELFMAG1 'E' #define ELFMAG2 'L' #define ELFMAG3 'F'
#define ELFMAG "\177ELF"
#define SELFMAG 4
#define ELFCLASSNONE 0 #define ELFCLASS32 1 #define ELFCLASS64 2 #define ELFCLASSNUM 3
#define ELFDATANONE 0 #define ELFDATA2LSB 1 #define ELFDATA2MSB 2
#define EV_NONE 0 #define EV_CURRENT 1 #define EV_NUM 2
#define ELFOSABI_NONE 0 #define ELFOSABI_LINUX 3
#ifndef ELF_OSABI
#define ELF_OSABI ELFOSABI_NONE #endif
|
程序头表项类型 (p_type
)
ELF文件的程序头表是一个数组,数组中的每个元素(一个elf_phdr
结构体)都描述了文件中的一个“段”以及如何处理它。p_type
字段就是用来指明这个段的类型的。加载器(内核)会遍历这个表,并根据p_type
的值来采取不同的行动。
下面是对这些宏的逐一、详细解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
#define PT_NULL 0
#define PT_LOAD 1
#define PT_DYNAMIC 2
#define PT_INTERP 3
#define PT_NOTE 4
#define PT_SHLIB 5
#define PT_PHDR 6
#define PT_TLS 7
#define PT_LOOS 0x60000000
#define PT_HIOS 0x6fffffff
#define PT_LOPROC 0x70000000
#define PT_HIPROC 0x7fffffff
#define PT_GNU_EH_FRAME (PT_LOOS + 0x474e550)
#define PT_GNU_STACK (PT_LOOS + 0x474e551)
#define PT_GNU_RELRO (PT_LOOS + 0x474e552)
#define PT_GNU_PROPERTY (PT_LOOS + 0x474e553)
|
fs/binfmt_script.c
#!
机制的本质是把执行权委托给另一个程序。下面我们来深入了解这些常见的“受委托者”以及它们各自的特长。
1. /bin/sh
与 /bin/bash
(Bourne Shell 和 Bourne-Again Shell)
脚本作用:
这是Linux/Unix世界中最基础、最核心的“胶水语言”。它的主要作用是自动化执行一系列命令,管理文件系统,控制进程,以及配置系统环境。sh
是遵循POSIX标准的最小集,保证了极高的可移植性;bash
是sh
的超集,提供了更多便利的功能,如命令历史、更强大的数组、更复杂的条件判断等。
使用场景:
它们是嵌入式Linux系统中无可替代的工具。
- 系统启动脚本: 几乎所有的初始化脚本(位于
/etc/init.d/
或被systemd
调用的脚本)都是shell脚本。它们负责挂载文件系统、配置网络、启动后台服务(守护进程)等。
- 任务自动化 (Cron Jobs): 定时执行的任务,如夜间日志清理、数据备份、系统状态检查等,通常都由shell脚本完成。
- 设备管理: 编写一个简单的脚本来配置GPIO、设置串口参数或重启一个外设驱动。
- 简单的应用程序启动器: 编写一个包装脚本,在运行主程序前设置好必要的环境变量或检查依赖项。
示例 (/usr/local/bin/start_myapp
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #!/bin/sh
APP_BIN="/opt/myapp/my_application" PID_FILE="/var/run/myapp.pid"
if [ ! -x "$APP_BIN" ]; then echo "错误: 应用程序 '$APP_BIN' 不存在或不可执行。" exit 1 fi
echo "正在启动 my_application..."
$APP_BIN > /dev/null 2>&1 & echo $! > $PID_FILE
echo "my_application 已启动, PID 为: $(cat $PID_FILE)"
|
2. /usr/bin/python3
(Python 3)
3. /usr/bin/perl
(Perl)
4. /usr/bin/awk -f
脚本作用:
AWK是一种优秀的面向列的文本处理工具。它逐行读取文本,并能自动将每一行按分隔符(默认为空格)拆分成多个字段($1
, $2
, $3
…)。它非常适合从格式规整的文本数据中提取信息、进行计算和重新格式化输出。-f
标志告诉awk
命令,执行逻辑的脚本就写在当前文件中。
使用场景:
- 处理命令输出: 对
ls -l
, ps aux
, df -h
等命令的输出进行二次处理,只提取你关心的列。
- 数据汇总: 计算文件中某一列的总和、平均值等。
- 格式转换: 将一种格式的文本文件(如空格分隔)转换为另一种格式(如CSV逗号分隔)。
示例 (/usr/local/bin/disk_usage_report.awk
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
BEGIN { print "高使用率的文件系统 (>80%):" print "文件系统\t\t已用%" }
NR > 1 && $5 ~ /%/ { gsub(/%/, "", $5) if ($5 > 80) { printf "%-20s\t%s%%\n", $1, $5 } }
END { print "检查完毕." }
|
5. #!/usr/bin/env
的巧妙用法
这不是一个解释器, 而是一个寻找解释器的工具。env
是一个标准的系统程序, 它的作用是在当前用户的PATH
环境变量所指定的目录列表中, 查找并执行一个程序。
问题: 如果你在脚本中硬编码 #!/usr/bin/python3
, 但在另一台机器上, Python被安装在了 /usr/local/bin/python3
。那么这个脚本在那台机器上就会因为找不到解释器而执行失败。
解决方案: 使用 #!/usr/bin/env python3
工作流程:
- 内核看到
#!
行, 它执行 /usr/bin/env
程序。
- 内核将
python3
和脚本路径 /home/user/test.py
作为参数传递给 env
。
env
程序启动, 它看到第一个参数是 python3
。
env
开始在 PATH
环境变量(如/usr/local/bin:/usr/bin:/bin
)的所有目录中依次搜索一个名为 python3
的可执行文件。
- 一旦找到(比如在
/usr/bin/python3
), env
就会执行它, 并将脚本路径传递给它。
优点: 极大地提高了脚本的可移植性。它不关心解释器到底安装在哪里, 只要它存在于用户的PATH
中, 脚本就能正确执行。这在使用Python虚拟环境(venv
)时尤其重要, 因为虚拟环境会修改PATH
, 使得python3
指向环境内部的解释器。
#!
(Shebang) 支持哪些脚本?
这是一个非常好的问题, 也是一个常见的误区。内核的 #!
机制本身不关心、也不识别任何特定的脚本语言(如Shell, Python, Perl等)。
它的机制是完全通用的, 其原理是:“不要执行这个文件, 而是去执行 #!
后面指定的那一个程序, 并把这个脚本文件的路径作为参数传给它。”
因此, #!
支持任何已经安装了解释器的脚本语言。只要系统中存在一个可以接收脚本文件路径作为参数并执行其内容的可执行程序, 你就可以在 #!
后面使用它。
常见的解释器包括:
/bin/sh
(标准的Bourne Shell)
/bin/bash
(更强大的Bash Shell)
/usr/bin/python
或 /usr/bin/python3
/usr/bin/perl
/usr/bin/awk -f
/usr/bin/node
(用于Node.js脚本)
甚至一些不常见的程序也可以, 比如 #!/usr/bin/env
就是一个巧妙的用法, 它会利用 env
程序在用户的PATH
环境变量中去查找真正的解释器(如 python
), 增加了脚本的可移植性。
工作流程示例
下面我们通过两个具体的例子, 详细追踪 load_script
函数是如何工作的。
示例1: 一个简单的Shell脚本
场景: 用户在命令行执行一个带参数的shell脚本。
脚本文件内容 (/home/user/test.sh
):
1 2
| #!/bin/sh echo "Hello from script! You gave me the argument: $1"
|
用户执行的命令:
1
| /home/user/test.sh my_arg
|
内核 load_script
函数介入前的状态:
bprm->filename
: “/home/user/test.sh”
bprm->argc
: 2
- 内核看到的初始参数列表 (
argv
):
argv[0]
: /home/user/test.sh
argv[1]
: my_arg
load_script
函数执行步骤详解:
- 行31:
(bprm->buf[0] != '#') || (bprm->buf[1] != '!')
判断为 false
, 因为文件以 #!
开头, 匹配成功。
- 行39-57: 解析
#!
行。
i_name
被设置为指向字符串 /bin/sh
。
i_arg
为 NULL
, 因为 /bin/sh
后面没有其他内容。
- 行80:
retval = remove_arg_zero(bprm);
- 移除原始的
argv[0]
, 即 “/home/user/test.sh”。
bprm->argc
变为 1。
- 行83-86:
retval = copy_string_kernel(bprm->interp, bprm);
- 将原始文件名 “/home/user/test.sh” 作为新的参数压入。
bprm->argc
变为 2。
- 参数列表现在逻辑上是: (
my_arg
, /home/user/test.sh
)
- 行89-95:
if (i_arg)
判断为 false
, 跳过。
- 行96-99:
retval = copy_string_kernel(i_name, bprm);
- 将解释器名 “/bin/sh” 作为新的参数压入。
bprm->argc
变为 3。
- 参数列表现在逻辑上是: (
/bin/sh
, /home/user/test.sh
, my_arg
)
- 行100-103:
retval = bprm_change_interp(i_name, bprm);
- 将
bprm->interp
从 “/home/user/test.sh” 更改为 “/bin/sh”。
- 行108-111:
file = open_exec(i_name);
- 行113:
bprm->interpreter = file;
- 将
bprm
结构体中要执行的程序文件对象, 设置为刚刚打开的 /bin/sh
。
- 行115:
return 0;
函数成功返回。
最终结果:
load_script
函数修改了 bprm
结构, 告诉内核的 execve
流程:“不要执行原来的脚本了, 请执行 /bin/sh
这个程序”。
- 内核最终执行的命令是:
/bin/sh /home/user/test.sh my_arg
/bin/sh
解释器启动后, 看到它的第一个参数是 /home/user/test.sh
, 于是它打开这个文件, 读取并执行里面的指令。第二个参数 my_arg
则成为脚本内部的 $1
。
示例2: #!
行带有参数
场景: 一个python脚本, 在 #!
行上就为解释器提供了一个参数。
脚本文件内容 (/home/user/test.py
):
1 2 3 4 5
|
import sys print("Script running") print("Argv is:", sys.argv)
|
用户执行的命令:
内核 load_script
函数介入前的状态:
bprm->filename
: “/home/user/test.py”
bprm->argc
: 1
- 内核看到的初始参数列表 (
argv
):
argv[0]
: /home/user/test.py
load_script
函数执行步骤详解:
- 行31: 匹配
#!
成功。
- 行39-57: 解析
#!
行。
i_name
被设置为指向字符串 /usr/bin/python3
。
i_sep
指向 python3
和 -u
之间的空格。
i_arg
被设置为指向字符串 -u
。
- 行80:
retval = remove_arg_zero(bprm);
- 移除原始的
argv[0]
, 即 “/home/user/test.py”。
bprm->argc
变为 0。
- 行83-86:
retval = copy_string_kernel(bprm->interp, bprm);
- 将 “/home/user/test.py” 作为新参数压入。
bprm->argc
变为 1。
- 参数列表逻辑上是: (
/home/user/test.py
)
- 行89-95:
if (i_arg)
判断为 true
。
retval = copy_string_kernel(i_arg, bprm);
将 -u
作为新参数压入。
bprm->argc
变为 2。
- 参数列表逻辑上是: (
-u
, /home/user/test.py
)
- 行96-99:
retval = copy_string_kernel(i_name, bprm);
- 将 “/usr/bin/python3” 作为新参数压入。
bprm->argc
变为 3。
- 参数列表逻辑上是: (
/usr/bin/python3
, -u
, /home/user/test.py
)
- 后续步骤同示例1,
bprm->interp
被改为 /usr/bin/python3
, 并被成功打开。
最终结果:
- 内核最终执行的命令是:
/usr/bin/python3 -u /home/user/test.py
- 这样,
-u
这个参数就成功地从脚本的 #!
行传递给了 python3
解释器。
希望这两个详细的例子能帮助您彻底理解 load_script
的工作原理和 #!
机制的强大之处。
binfmt_script.c: 内核脚本执行支持
此文件的作用是实现一个内核级的二进制格式(binfmt)处理器, 专门用于识别并处理以 #!
(Shebang) 开头的脚本文件。当用户尝试执行一个脚本时, 内核通过此文件中实现的 load_script
函数, 解析脚本的第一行, 找出指定的解释器(如/bin/sh
), 然后加载并运行该解释器来执行脚本内容。这个机制是所有现代Linux/Unix系统能够原生执行shell脚本、Python脚本等的基础。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
| static inline bool spacetab(char c) { return c == ' ' || c == '\t'; }
static inline const char *next_non_spacetab(const char *first, const char *last) { for (; first <= last; first++) if (!spacetab(*first)) return first; return NULL; }
static inline const char *next_terminator(const char *first, const char *last) { for (; first <= last; first++) if (spacetab(*first) || !*first) return first; return NULL; }
static int load_script(struct linux_binprm *bprm) {
const char *i_name, *i_sep, *i_arg, *i_end, *buf_end; struct file *file; int retval;
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!')) return -ENOEXEC;
buf_end = bprm->buf + sizeof(bprm->buf) - 1; i_end = strnchr(bprm->buf, sizeof(bprm->buf), '\n'); if (!i_end) { i_end = next_non_spacetab(bprm->buf + 2, buf_end); if (!i_end) return -ENOEXEC;
if (!next_terminator(i_end, buf_end)) return -ENOEXEC; i_end = buf_end; } while (spacetab(i_end[-1])) i_end--;
i_name = next_non_spacetab(bprm->buf+2, i_end); if (!i_name || (i_name == i_end)) return -ENOEXEC;
i_arg = NULL; i_sep = next_terminator(i_name, i_end); if (i_sep && (*i_sep != '\0')) i_arg = next_non_spacetab(i_sep, i_end);
if (bprm->interp_flags & BINPRM_FLAGS_PATH_INACCESSIBLE) return -ENOENT;
retval = remove_arg_zero(bprm); if (retval) return retval; retval = copy_string_kernel(bprm->interp, bprm); if (retval < 0) return retval; bprm->argc++; *((char *)i_end) = '\0'; if (i_arg) { *((char *)i_sep) = '\0'; retval = copy_string_kernel(i_arg, bprm); if (retval < 0) return retval; bprm->argc++; } retval = copy_string_kernel(i_name, bprm); if (retval) return retval; bprm->argc++; retval = bprm_change_interp(i_name, bprm); if (retval < 0) return retval;
file = open_exec(i_name); if (IS_ERR(file)) return PTR_ERR(file);
bprm->interpreter = file; return 0; }
static struct linux_binfmt script_format = { .module = THIS_MODULE, .load_binary = load_script, };
static int __init init_script_binfmt(void) { register_binfmt(&script_format); return 0; }
static void __exit exit_script_binfmt(void) { unregister_binfmt(&script_format); }
core_initcall(init_script_binfmt);
module_exit(exit_script_binfmt);
MODULE_DESCRIPTION("Kernel support for scripts starting with #!");
MODULE_LICENSE("GPL");
|
fs/binfmt_elf_fdpic.c
此代码片段的作用是为Linux内核注册一个二进制格式处理器(binfmt),专门用于加载和执行一种特殊类型的ELF(Executable and Linkable Format)文件,即 FDPIC(Function-Descriptor Position-Independent Code) 格式的可执行文件。
这个功能对于在没有内存管理单元(MMU)的微控制器(如STM32H750)上运行一个功能完整的、支持多进程的Linux系统(通常称为uClinux)是绝对核心和必需的。
FDPIC的原理与在STM32H750上的关键作用
无MMU系统面临的挑战:
在一个标准的、有MMU的系统上,当您运行两个相同的程序实例(例如,两个httpd
进程)时,MMU会为每个进程创建一个独立的虚拟地址空间。这意味着,虽然它们共享同一份物理代码(.text
段),但每个进程都有自己私有的、位于相同虚拟地址的数据段(.data
和.bss
)。它们可以自由地修改自己的全局变量,而不会影响到对方。
在没有MMU的STM32H750上,地址就是物理地址。如果两个进程直接加载同一个普通ELF文件,它们将共享同一份物理代码和同一份物理数据段。如果一个进程修改了一个全局变量,这个修改会立即对另一个进程可见,这将导致灾难性的后果和系统崩溃。
FDPIC的解决方案:
FDPIC是一种为解决这个问题而设计的、精巧的编译和加载方案。
- 编译: 编译器会生成一种特殊的位置无关代码。在这种代码中,对全局变量或静态变量的访问不是通过绝对地址,而是通过一个基地址寄存器加上一个偏移量来进行的。
- 加载 (
load_elf_fdpic_binary
): 当load_elf_fdpic_binary
函数加载一个FDPIC程序时,它会:
- 将程序的代码段(
.text
)加载到内存中。这份代码段只会被加载一次,并由所有运行该程序的进程共享。
- 对于每一个要创建的新进程,加载器都会在内存中分配一套全新的、私有的数据段(
.data
)和BSS段(.bss
)。
- 在创建进程和进行上下文切换时,内核会负责将这个进程私有数据段的基地址加载到那个专用的基地址寄存器中。
最终效果:
当CPU执行共享的代码时,无论它访问哪个全局变量,实际上都是通过“基地址寄存器(指向私有数据区) + 偏移量”来找到正确的位置。这样,进程A的指令访问的是A的数据区,进程B的指令访问的是B的数据区,它们之间完美隔离,互不干扰。FDPIC巧妙地用软件和编译器约定,模拟出了MMU对数据段的隔离效果。
因此,binfmt_elf_fdpic
是使真正的多任务处理在像STM32H750这样的高性能MCU上成为可能的基石。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
static struct linux_binfmt elf_fdpic_format = {
.module = THIS_MODULE,
.load_binary = load_elf_fdpic_binary,
#ifdef CONFIG_ELF_CORE
.core_dump = elf_fdpic_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE, #endif };
static int __init init_elf_fdpic_binfmt(void) {
register_binfmt(&elf_fdpic_format); return 0; }
static void __exit exit_elf_fdpic_binfmt(void) {
unregister_binfmt(&elf_fdpic_format); }
core_initcall(init_elf_fdpic_binfmt);
module_exit(exit_elf_fdpic_binfmt);
|
is_elf: 验证一个文件是否为可执行的ELF文件
此函数是一个验证函数,它的核心作用是根据ELF文件格式规范,对一个文件的头部信息进行一系列快速检查,以判断该文件是否是一个当前系统可以理解并尝试执行的ELF二进制文件。它像是一个门卫,只有通过了它所有检查的文件,才会被内核的ELF加载器进一步处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
static int is_elf(struct elfhdr *hdr, struct file *file) {
if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0) return 0;
if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) return 0;
if (!elf_check_arch(hdr)) return 0;
if (!file->f_op->mmap) return 0;
return 1; }
|
elf_fdpic_fetch_phdrs: 读取并解析ELF程序头表
此函数是FDPIC ELF加载器中的一个关键步骤。它的核心作用是根据ELF头中提供的信息,从可执行文件中读取整个程序头表(Program Header Table)到内核内存中,并对表中的特定项进行初步解析,以获取重要信息,如请求的栈大小和栈的可执行权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
static int elf_fdpic_fetch_phdrs(struct elf_fdpic_params *params, struct file *file) {
struct elf_phdr *phdr;
unsigned long size;
int retval, loop;
loff_t pos = params->hdr.e_phoff;
if (params->hdr.e_phentsize != sizeof(struct elf_phdr)) return -ENOMEM;
if (params->hdr.e_phnum > 65536U / sizeof(struct elf_phdr)) return -ENOMEM;
size = params->hdr.e_phnum * sizeof(struct elf_phdr);
params->phdrs = kmalloc(size, GFP_KERNEL);
if (!params->phdrs) return -ENOMEM;
retval = kernel_read(file, params->phdrs, size, &pos);
if (unlikely(retval != size))
return retval < 0 ? retval : -ENOEXEC;
phdr = params->phdrs; for (loop = 0; loop < params->hdr.e_phnum; loop++, phdr++) {
if (phdr->p_type != PT_GNU_STACK) continue;
if (phdr->p_flags & PF_X) params->flags |= ELF_FDPIC_FLAG_EXEC_STACK; else params->flags |= ELF_FDPIC_FLAG_NOEXEC_STACK;
params->stack_size = phdr->p_memsz;
break; }
return 0; }
|
elf_fdpic_map_file_constdisp_on_uclinux: 在uClinux上映射固定位移的文件
此函数专门用于处理那些其内部段(segment)之间需要保持固定相对位置的FDPIC ELF文件。它的核心策略是:一次性地分配一块足够大的、连续的物理内存块,然后像“贴图”一样,将文件中的各个PT_LOAD
段精确地复制到这块大内存的不同偏移处。 这块大内存就成为了新进程的代码和私有数据区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
static int elf_fdpic_map_file_constdisp_on_uclinux( struct elf_fdpic_params *params, struct file *file, struct mm_struct *mm) {
struct elf_fdpic_loadseg *seg; struct elf_phdr *phdr; unsigned long load_addr, base = ULONG_MAX, top = 0, maddr = 0; int loop, ret;
load_addr = params->load_addr; seg = params->loadmap->segs;
phdr = params->phdrs; for (loop = 0; loop < params->hdr.e_phnum; loop++, phdr++) {
if (params->phdrs[loop].p_type != PT_LOAD) continue;
if (base > phdr->p_vaddr) base = phdr->p_vaddr;
if (top < phdr->p_vaddr + phdr->p_memsz) top = phdr->p_vaddr + phdr->p_memsz; }
maddr = vm_mmap(NULL, load_addr, top - base, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, 0);
if (IS_ERR_VALUE(maddr)) return (int) maddr;
if (load_addr != 0) load_addr += PAGE_ALIGN(top - base);
phdr = params->phdrs; for (loop = 0; loop < params->hdr.e_phnum; loop++, phdr++) { if (params->phdrs[loop].p_type != PT_LOAD) continue;
seg->addr = maddr + (phdr->p_vaddr - base);
seg->p_vaddr = phdr->p_vaddr; seg->p_memsz = phdr->p_memsz;
ret = read_code(file, seg->addr, phdr->p_offset, phdr->p_filesz); if (ret < 0) return ret;
if (phdr->p_offset == 0) params->elfhdr_addr = seg->addr;
if (phdr->p_filesz < phdr->p_memsz) {
if (clear_user((void *) (seg->addr + phdr->p_filesz), phdr->p_memsz - phdr->p_filesz)) return -EFAULT; }
if (mm) {
if (phdr->p_flags & PF_X) { if (!mm->start_code) { mm->start_code = seg->addr; mm->end_code = seg->addr + phdr->p_memsz; } } else if (!mm->start_data) { mm->start_data = seg->addr; mm->end_data = seg->addr + phdr->p_memsz; } }
seg++; }
return 0; }
|
elf_fdpic_map_file_by_direct_mmap: 逐个映射PT_LOAD
段
此函数的核心策略是遍历程序头表,并为每一个 PT_LOAD
类型的段单独调用vm_mmap
。这种方法更加灵活,因为它不要求所有段都被加载到一块连续的物理内存中。
在这种模式下:
- 独立分配: 内核会为代码段分配一块物理内存,为数据段分配另一块(可能不相邻的)物理内存。
vm_mmap
的行为: 在无MMU系统上,vm_mmap
从文件中映射内存的行为简化为:
- 调用物理内存分配器(如
kmalloc
)分配一块大小合适的、私有的物理内存。
- 调用文件系统的
read
操作,将文件中的数据复制到这块新分配的物理内存中。
- FDPIC的魔力: 即使代码段和数据段被加载到了物理上不相邻的内存区域,FDPIC机制依然能正常工作。因为对全局变量的访问是通过“基地址寄存器(指向私有数据段) + 偏移量”来完成的,这个基地址寄存器的值是在程序加载时根据数据段的实际物理位置确定的。所以无论数据段在哪里,程序总能找到它。
这种方式的缺点是可能会产生更多的内存碎片,并且无法保证段之间的固定相对位置,但优点是为链接器和加载器提供了更大的灵活性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
static int elf_fdpic_map_file_by_direct_mmap(struct elf_fdpic_params *params, struct file *file, struct mm_struct *mm) {
struct elf_fdpic_loadseg *seg; struct elf_phdr *phdr; unsigned long load_addr, delta_vaddr; int loop, dvset;
load_addr = params->load_addr; delta_vaddr = 0; dvset = 0;
seg = params->loadmap->segs;
phdr = params->phdrs; for (loop = 0; loop < params->hdr.e_phnum; loop++, phdr++) {
unsigned long maddr, disp, excess; int prot = 0, flags;
if (phdr->p_type != PT_LOAD) continue;
kdebug("[LOAD] va=%lx of=%lx fs=%lx ms=%lx", (unsigned long) phdr->p_vaddr, (unsigned long) phdr->p_offset, (unsigned long) phdr->p_filesz, (unsigned long) phdr->p_memsz);
if (phdr->p_flags & PF_R) prot |= PROT_READ; if (phdr->p_flags & PF_W) prot |= PROT_WRITE; if (phdr->p_flags & PF_X) prot |= PROT_EXEC;
flags = MAP_PRIVATE; maddr = 0;
switch (params->flags & ELF_FDPIC_FLAG_ARRANGEMENT) { case ELF_FDPIC_FLAG_INDEPENDENT: break;
case ELF_FDPIC_FLAG_HONOURVADDR: maddr = phdr->p_vaddr; flags |= MAP_FIXED; break;
case ELF_FDPIC_FLAG_CONSTDISP: if (!dvset) { maddr = load_addr; delta_vaddr = phdr->p_vaddr; dvset = 1; } else { maddr = load_addr + phdr->p_vaddr - delta_vaddr; flags |= MAP_FIXED; } break;
case ELF_FDPIC_FLAG_CONTIGUOUS: break;
default: BUG(); }
maddr &= PAGE_MASK;
disp = phdr->p_vaddr & ~PAGE_MASK; maddr = vm_mmap(file, maddr, phdr->p_memsz + disp, prot, flags, phdr->p_offset - disp);
kdebug("mmap[%d] <file> sz=%llx pr=%x fl=%x of=%llx --> %08lx", loop, (unsigned long long) phdr->p_memsz + disp, prot, flags, (unsigned long long) phdr->p_offset - disp, maddr);
if (IS_ERR_VALUE(maddr)) return (int) maddr;
if ((params->flags & ELF_FDPIC_FLAG_ARRANGEMENT) == ELF_FDPIC_FLAG_CONTIGUOUS) load_addr += PAGE_ALIGN(phdr->p_memsz + disp);
seg->addr = maddr + disp; seg->p_vaddr = phdr->p_vaddr; seg->p_memsz = phdr->p_memsz;
if (phdr->p_offset == 0) params->elfhdr_addr = seg->addr;
if (prot & PROT_WRITE && disp > 0) { kdebug("clear[%d] ad=%lx sz=%lx", loop, maddr, disp); if (clear_user((void __user *) maddr, disp)) return -EFAULT; maddr += disp; }
excess = phdr->p_memsz - phdr->p_filesz;
#ifdef CONFIG_MMU
#else if (excess > 0) { kdebug("clear[%d] ad=%llx sz=%lx", loop, (unsigned long long) maddr + phdr->p_filesz, excess); if (clear_user((void *) maddr + phdr->p_filesz, excess)) return -EFAULT; } #endif
if (mm) { if (phdr->p_flags & PF_X) { if (!mm->start_code) { mm->start_code = maddr; mm->end_code = maddr + phdr->p_memsz; } } else { if (!mm->start_data) { mm->start_data = maddr; mm->end_data = maddr + phdr->p_memsz; } } }
seg++; }
return 0; }
|
elf_fdpic_map_file: 将FDPIC程序映射到内存
此函数的核心职责是:根据程序头表(phdrs
)的指示,为所有需要加载的段(PT_LOAD
)在物理内存中分配空间,并将文件中的相应内容复制进去。它会创建一个加载映射表(loadmap),详细记录每个段被加载到了哪个物理地址,并将这个映射表提供给后续步骤和用户空间的动态链接器使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
|
static int elf_fdpic_map_file(struct elf_fdpic_params *params, struct file *file, struct mm_struct *mm, const char *what) {
struct elf_fdpic_loadmap *loadmap; #ifdef CONFIG_MMU struct elf_fdpic_loadseg *mseg; unsigned long load_addr; #endif
struct elf_fdpic_loadseg *seg; struct elf_phdr *phdr; unsigned nloads, tmp; unsigned long stop; int loop, ret;
nloads = 0; for (loop = 0; loop < params->hdr.e_phnum; loop++) if (params->phdrs[loop].p_type == PT_LOAD) nloads++;
if (nloads == 0) return -ELIBBAD;
loadmap = kzalloc(struct_size(loadmap, segs, nloads), GFP_KERNEL); if (!loadmap) return -ENOMEM;
params->loadmap = loadmap;
loadmap->version = ELF_FDPIC_LOADMAP_VERSION; loadmap->nsegs = nloads;
switch (params->flags & ELF_FDPIC_FLAG_ARRANGEMENT) { case ELF_FDPIC_FLAG_CONSTDISP: case ELF_FDPIC_FLAG_CONTIGUOUS: #ifndef CONFIG_MMU
ret = elf_fdpic_map_file_constdisp_on_uclinux(params, file, mm); if (ret < 0) return ret; break; #endif default:
ret = elf_fdpic_map_file_by_direct_mmap(params, file, mm); if (ret < 0) return ret; break; }
if (params->hdr.e_entry) { seg = loadmap->segs; for (loop = loadmap->nsegs; loop > 0; loop--, seg++) { if (params->hdr.e_entry >= seg->p_vaddr && params->hdr.e_entry < seg->p_vaddr + seg->p_memsz) {
params->entry_addr = (params->hdr.e_entry - seg->p_vaddr) + seg->addr; break; } } }
stop = params->hdr.e_phoff; stop += params->hdr.e_phnum * sizeof (struct elf_phdr); phdr = params->phdrs;
for (loop = 0; loop < params->hdr.e_phnum; loop++, phdr++) { if (phdr->p_type != PT_LOAD) continue;
if (phdr->p_offset > params->hdr.e_phoff || phdr->p_offset + phdr->p_filesz < stop) continue;
seg = loadmap->segs; for (loop = loadmap->nsegs; loop > 0; loop--, seg++) { if (phdr->p_vaddr >= seg->p_vaddr && phdr->p_vaddr + phdr->p_filesz <= seg->p_vaddr + seg->p_memsz) {
params->ph_addr = (phdr->p_vaddr - seg->p_vaddr) + seg->addr + params->hdr.e_phoff - phdr->p_offset; break; } } break; }
phdr = params->phdrs; for (loop = 0; loop < params->hdr.e_phnum; loop++, phdr++) { if (phdr->p_type != PT_DYNAMIC) continue;
seg = loadmap->segs; for (loop = loadmap->nsegs; loop > 0; loop--, seg++) { if (phdr->p_vaddr >= seg->p_vaddr && phdr->p_vaddr + phdr->p_memsz <= seg->p_vaddr + seg->p_memsz) { Elf_Dyn __user *dyn; Elf_Sword d_tag;
params->dynamic_addr = (phdr->p_vaddr - seg->p_vaddr) + seg->addr;
if (phdr->p_memsz == 0 || phdr->p_memsz % sizeof(Elf_Dyn) != 0) goto dynamic_error;
tmp = phdr->p_memsz / sizeof(Elf_Dyn); dyn = (Elf_Dyn __user *)params->dynamic_addr; if (get_user(d_tag, &dyn[tmp - 1].d_tag) || d_tag != 0) goto dynamic_error; break; } } break; }
#ifdef CONFIG_MMU #endif
kdebug("Mapped Object [%s]:", what); kdebug("- elfhdr : %lx", params->elfhdr_addr); kdebug("- entry : %lx", params->entry_addr); kdebug("- PHDR[] : %lx", params->ph_addr); kdebug("- DYNAMIC[]: %lx", params->dynamic_addr); seg = loadmap->segs; for (loop = 0; loop < loadmap->nsegs; loop++, seg++) kdebug("- LOAD[%d] : %08llx-%08llx [va=%llx ms=%llx]", loop, (unsigned long long) seg->addr, (unsigned long long) seg->addr + seg->p_memsz - 1, (unsigned long long) seg->p_vaddr, (unsigned long long) seg->p_memsz);
return 0;
dynamic_error: printk("ELF FDPIC %s with invalid DYNAMIC section (inode=%lu)\n", what, file_inode(file)->i_ino); return -ELIBBAD; }
|
create_elf_fdpic_tables: 在新进程的栈上创建初始信息表
此函数的主要职责是将execve
系统调用传递进来的参数(argv
)、环境变量(envp
)以及内核需要告知用户空间动态链接器的一些关键信息(辅助向量),从内核空间复制并排列到新进程的用户空间栈上。这个过程必须精确无误,因为动态链接器和C库的启动代码将依赖这个布局来初始化程序。
- 确定栈顶:
sp = mm->start_stack;
。在无MMU系统上,start_stack
就是物理内存块的最高地址。
- 传递参数/环境:
transfer_args_to_stack()
这个函数(在此代码片段中未显示,但逻辑相似)会把之前准备好的argv
和envp
字符串从内核的临时缓冲区复制到这个新栈的较高地址处。
- 压入FDPIC加载映射表: 这是FDPIC机制的核心。它将
exec_params->loadmap
(以及解释器的interp_params->loadmap
)这个描述了“哪个段被加载到了哪个物理地址”的映射表,完整地复制到栈上。
- 构建辅助向量 (Auxiliary Vector): 这是最关键的部分。辅助向量是一个由
{类型, 值}
键值对组成的列表,它在栈上传递了内核与用户空间动态链接器之间的“秘密情报”。
NEW_AUX_ENT(AT_FDPIC_LOADMAP, ...)
(此函数中没有直接出现,但由AT_BASE
等间接实现): 这条信息会把刚刚压入栈的加载映射表的地址告知动态链接器。链接器收到后,就会去这个地址读取映射表,从而知道代码段和数据段的真实物理位置。
NEW_AUX_ENT(AT_ENTRY, exec_params->entry_addr)
: 告知链接器,原始可执行文件的入口点在哪里。
NEW_AUX_ENT(AT_PHDR, exec_params->ph_addr)
: 告知链接器,程序头表被加载到了哪里。
- 其他如
AT_PAGESZ
(页大小)、AT_UID
(用户ID)等提供了必要的系统信息。
- 构建
argv
和envp
指针数组: 在栈的更低地址处,它会创建两个指针数组。argv
数组的每个元素指向栈上对应参数字符串的起始地址。envp
数组也一样。这两个数组都以一个NULL
指针结尾。
- 压入
argc
: 最后,在栈的最底部(最低地址处),压入参数的个数argc
。
当所有这些都完成后,新进程的用户空间栈就完全准备好了。start_thread
函数会将CPU的栈指针(SP)设置为sp
的最终值。当动态链接器开始执行时,它会从这个栈指针开始,按照ABI规范,准确地找到argc
、argv
、envp
和至关重要的辅助向量,从而完成最后的符号重定位和程序初始化,最终跳转到main
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
|
static int create_elf_fdpic_tables(struct linux_binprm *bprm, struct mm_struct *mm, struct elf_fdpic_params *exec_params, struct elf_fdpic_params *interp_params) {
const struct cred *cred = current_cred(); unsigned long sp, csp, nitems; elf_caddr_t __user *argv, *envp; size_t platform_len = 0, len; char *k_platform, *k_base_platform; char __user *u_platform, *u_base_platform, *p; int loop; unsigned long flags = 0; int ei_index; elf_addr_t *elf_info;
#ifdef CONFIG_MMU sp = arch_align_stack(bprm->p); #else sp = mm->start_stack;
if (transfer_args_to_stack(bprm, &sp) < 0) return -EFAULT; sp &= ~15; #endif
k_platform = ELF_PLATFORM; u_platform = NULL; if (k_platform) { platform_len = strlen(k_platform) + 1; sp -= platform_len; u_platform = (char __user *) sp; if (copy_to_user(u_platform, k_platform, platform_len) != 0) return -EFAULT; }
k_base_platform = ELF_BASE_PLATFORM; u_base_platform = NULL; if (k_base_platform) { platform_len = strlen(k_base_platform) + 1; sp -= platform_len; u_base_platform = (char __user *) sp; if (copy_to_user(u_base_platform, k_base_platform, platform_len) != 0) return -EFAULT; }
sp &= ~7UL;
len = sizeof(struct elf_fdpic_loadmap); len += sizeof(struct elf_fdpic_loadseg) * exec_params->loadmap->nsegs; sp = (sp - len) & ~7UL; exec_params->map_addr = sp;
if (copy_to_user((void __user *) sp, exec_params->loadmap, len) != 0) return -EFAULT;
current->mm->context.exec_fdpic_loadmap = (unsigned long) sp;
if (interp_params->loadmap) { len = sizeof(struct elf_fdpic_loadmap); len += sizeof(struct elf_fdpic_loadseg) * interp_params->loadmap->nsegs; sp = (sp - len) & ~7UL; interp_params->map_addr = sp;
if (copy_to_user((void __user *) sp, interp_params->loadmap, len) != 0) return -EFAULT;
current->mm->context.interp_fdpic_loadmap = (unsigned long) sp; }
#define DLINFO_ITEMS 15
nitems = 1 + DLINFO_ITEMS + (k_platform ? 1 : 0) + (k_base_platform ? 1 : 0) + AT_VECTOR_SIZE_ARCH;
if (bprm->have_execfd) nitems++; #ifdef ELF_HWCAP2 nitems++; #endif
csp = sp; sp -= nitems * 2 * sizeof(unsigned long); sp -= (bprm->envc + 1) * sizeof(char *); sp -= (bprm->argc + 1) * sizeof(char *); sp -= 1 * sizeof(unsigned long);
csp -= sp & 15UL; sp -= sp & 15UL;
elf_info = (elf_addr_t *)mm->saved_auxv; #define NEW_AUX_ENT(id, val) \ do { \ *elf_info++ = id; \ *elf_info++ = val; \ } while (0)
#ifdef ARCH_DLINFO ARCH_DLINFO; #endif NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP); NEW_AUX_ENT(AT_PAGESZ, PAGE_SIZE); NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC); NEW_AUX_ENT(AT_PHDR, exec_params->ph_addr); NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr)); NEW_AUX_ENT(AT_PHNUM, exec_params->hdr.e_phnum);
NEW_AUX_ENT(AT_BASE, interp_params->elfhdr_addr); if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0) flags |= AT_FLAGS_PRESERVE_ARGV0; NEW_AUX_ENT(AT_FLAGS, flags); NEW_AUX_ENT(AT_ENTRY, exec_params->entry_addr); NEW_AUX_ENT(AT_UID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->uid)); NEW_AUX_ENT(AT_EUID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->euid)); NEW_AUX_ENT(AT_GID, (elf_addr_t) from_kgid_munged(cred->user_ns, cred->gid)); NEW_AUX_ENT(AT_EGID, (elf_addr_t) from_kgid_munged(cred->user_ns, cred->egid)); NEW_AUX_ENT(AT_SECURE, bprm->secureexec); NEW_AUX_ENT(AT_EXECFN, bprm->exec); if (k_platform) NEW_AUX_ENT(AT_PLATFORM, (elf_addr_t)(unsigned long)u_platform); if (k_base_platform) NEW_AUX_ENT(AT_BASE_PLATFORM, (elf_addr_t)(unsigned long)u_base_platform); if (bprm->have_execfd) NEW_AUX_ENT(AT_EXECFD, bprm->execfd); #undef NEW_AUX_ENT memset(elf_info, 0, (char *)mm->saved_auxv + sizeof(mm->saved_auxv) - (char *)elf_info);
ei_index = elf_info - (elf_addr_t *)mm->saved_auxv; csp -= ei_index * sizeof(elf_addr_t); if (copy_to_user((void __user *)csp, mm->saved_auxv, ei_index * sizeof(elf_addr_t))) return -EFAULT;
csp -= (bprm->envc + 1) * sizeof(elf_caddr_t); envp = (elf_caddr_t __user *) csp; csp -= (bprm->argc + 1) * sizeof(elf_caddr_t); argv = (elf_caddr_t __user *) csp;
csp -= sizeof(unsigned long); if (put_user(bprm->argc, (unsigned long __user *) csp)) return -EFAULT;
BUG_ON(csp != sp);
#ifdef CONFIG_MMU current->mm->arg_start = bprm->p; #else current->mm->arg_start = current->mm->start_stack - (MAX_ARG_PAGES * PAGE_SIZE - bprm->p); #endif
p = (char __user *) current->mm->arg_start; for (loop = bprm->argc; loop > 0; loop--) { if (put_user((elf_caddr_t) p, argv++)) return -EFAULT; len = strnlen_user(p, MAX_ARG_STRLEN); if (!len || len > MAX_ARG_STRLEN) return -EINVAL; p += len; } if (put_user(NULL, argv)) return -EFAULT; current->mm->arg_end = (unsigned long) p;
current->mm->env_start = (unsigned long) p; for (loop = bprm->envc; loop > 0; loop--) { if (put_user((elf_caddr_t)(unsigned long) p, envp++)) return -EFAULT; len = strnlen_user(p, MAX_ARG_STRLEN); if (!len || len > MAX_ARG_STRLEN) return -EINVAL; p += len; } if (put_user(NULL, envp)) return -EFAULT; current->mm->env_end = (unsigned long) p;
mm->start_stack = (unsigned long) sp; return 0; }
|
load_elf_fdpic_binary: 加载FDPIC ELF可执行文件
此函数是 binfmt_elf_fdpic
模块的心脏。当 execve
系统调用发现一个文件是FDPIC ELF格式时,就会调用此函数。它的职责是:读取ELF文件的元数据,解析程序头,加载代码段和数据段到内存,设置新进程的内存布局(特别是栈和堆),准备好CPU寄存器,并最终启动新程序的执行。
- 验证和解析
- 处理动态链接器 (Interpreter)
- 核心:加载和映射文件 (FDPIC的关键)
- 创建栈和堆 (no-MMU的特殊处理)
- 最后的准备与启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
|
static int load_elf_fdpic_binary(struct linux_binprm *bprm) {
struct elf_fdpic_params exec_params, interp_params;
struct pt_regs *regs = current_pt_regs();
struct elf_phdr *phdr;
unsigned long stack_size, entryaddr; #ifdef ELF_FDPIC_PLAT_INIT
unsigned long dynaddr; #endif #ifndef CONFIG_MMU
unsigned long stack_prot; #endif
struct file *interpreter = NULL; char *interpreter_name = NULL;
int executable_stack;
int retval, i;
loff_t pos;
kdebug("____ LOAD %d ____", current->pid);
memset(&exec_params, 0, sizeof(exec_params)); memset(&interp_params, 0, sizeof(interp_params));
exec_params.hdr = *(struct elfhdr *) bprm->buf;
exec_params.flags = ELF_FDPIC_FLAG_PRESENT | ELF_FDPIC_FLAG_EXECUTABLE;
retval = -ENOEXEC;
if (!is_elf(&exec_params.hdr, bprm->file)) goto error;
if (!elf_check_fdpic(&exec_params.hdr)) { #ifdef CONFIG_MMU goto error; #else if (exec_params.hdr.e_type != ET_DYN) goto error; #endif }
retval = elf_fdpic_fetch_phdrs(&exec_params, bprm->file); if (retval < 0) goto error;
phdr = exec_params.phdrs;
for (i = 0; i < exec_params.hdr.e_phnum; i++, phdr++) {
switch (phdr->p_type) { case PT_INTERP: retval = -ENOMEM; if (phdr->p_filesz > PATH_MAX) goto error; retval = -ENOENT; if (phdr->p_filesz < 2) goto error;
interpreter_name = kmalloc(phdr->p_filesz, GFP_KERNEL); if (!interpreter_name) goto error;
pos = phdr->p_offset; retval = kernel_read(bprm->file, interpreter_name, phdr->p_filesz, &pos); if (unlikely(retval != phdr->p_filesz)) { if (retval >= 0) retval = -ENOEXEC; goto error; }
retval = -ENOENT; if (interpreter_name[phdr->p_filesz - 1] != '\0') goto error;
kdebug("Using ELF interpreter %s", interpreter_name);
interpreter = open_exec(interpreter_name); retval = PTR_ERR(interpreter); if (IS_ERR(interpreter)) { interpreter = NULL; goto error; }
would_dump(bprm, interpreter);
pos = 0; retval = kernel_read(interpreter, bprm->buf, BINPRM_BUF_SIZE, &pos); if (unlikely(retval != BINPRM_BUF_SIZE)) { if (retval >= 0) retval = -ENOEXEC; goto error; }
interp_params.hdr = *((struct elfhdr *) bprm->buf); break;
case PT_LOAD: #ifdef CONFIG_MMU if (exec_params.load_addr == 0) exec_params.load_addr = phdr->p_vaddr; #endif break; } }
if (is_constdisp(&exec_params.hdr)) exec_params.flags |= ELF_FDPIC_FLAG_CONSTDISP;
if (interpreter_name) { retval = -ELIBBAD; if (!is_elf(&interp_params.hdr, interpreter)) goto error;
interp_params.flags = ELF_FDPIC_FLAG_PRESENT;
retval = elf_fdpic_fetch_phdrs(&interp_params, interpreter); if (retval < 0) goto error; }
stack_size = exec_params.stack_size; if (exec_params.flags & ELF_FDPIC_FLAG_EXEC_STACK) executable_stack = EXSTACK_ENABLE_X; else if (exec_params.flags & ELF_FDPIC_FLAG_NOEXEC_STACK) executable_stack = EXSTACK_DISABLE_X; else executable_stack = EXSTACK_DEFAULT;
if (stack_size == 0 && interp_params.flags & ELF_FDPIC_FLAG_PRESENT) { stack_size = interp_params.stack_size; if (interp_params.flags & ELF_FDPIC_FLAG_EXEC_STACK) executable_stack = EXSTACK_ENABLE_X; else if (interp_params.flags & ELF_FDPIC_FLAG_NOEXEC_STACK) executable_stack = EXSTACK_DISABLE_X; else executable_stack = EXSTACK_DEFAULT; }
retval = -ENOEXEC; if (stack_size == 0) stack_size = 131072UL;
if (is_constdisp(&interp_params.hdr)) interp_params.flags |= ELF_FDPIC_FLAG_CONSTDISP;
retval = begin_new_exec(bprm); if (retval) goto error;
SET_PERSONALITY(exec_params.hdr); if (elf_check_fdpic(&exec_params.hdr)) current->personality |= PER_LINUX_FDPIC; if (elf_read_implies_exec(&exec_params.hdr, executable_stack)) current->personality |= READ_IMPLIES_EXEC;
setup_new_exec(bprm);
set_binfmt(&elf_fdpic_format);
current->mm->start_code = 0; current->mm->end_code = 0; current->mm->start_stack = 0; current->mm->start_data = 0; current->mm->end_data = 0; current->mm->context.exec_fdpic_loadmap = 0; current->mm->context.interp_fdpic_loadmap = 0;
#ifdef CONFIG_MMU elf_fdpic_arch_lay_out_mm(&exec_params, &interp_params, ¤t->mm->start_stack, ¤t->mm->start_brk);
retval = setup_arg_pages(bprm, current->mm->start_stack, executable_stack); if (retval < 0) goto error; #ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES retval = arch_setup_additional_pages(bprm, !!interpreter_name); if (retval < 0) goto error; #endif #endif
retval = elf_fdpic_map_file(&exec_params, bprm->file, current->mm, "executable"); if (retval < 0) goto error;
if (interpreter_name) { retval = elf_fdpic_map_file(&interp_params, interpreter, current->mm, "interpreter"); if (retval < 0) { printk(KERN_ERR "Unable to load interpreter\n"); goto error; }
exe_file_allow_write_access(interpreter); fput(interpreter); interpreter = NULL; }
#ifdef CONFIG_MMU if (!current->mm->start_brk) current->mm->start_brk = current->mm->end_data;
current->mm->brk = current->mm->start_brk = PAGE_ALIGN(current->mm->start_brk);
#else stack_size = (stack_size + PAGE_SIZE - 1) & PAGE_MASK; if (stack_size < PAGE_SIZE * 2) stack_size = PAGE_SIZE * 2;
stack_prot = PROT_READ | PROT_WRITE; if (executable_stack == EXSTACK_ENABLE_X || (executable_stack == EXSTACK_DEFAULT && VM_STACK_FLAGS & VM_EXEC)) stack_prot |= PROT_EXEC;
current->mm->start_brk = vm_mmap(NULL, 0, stack_size, stack_prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED | MAP_GROWSDOWN, 0);
if (IS_ERR_VALUE(current->mm->start_brk)) { retval = current->mm->start_brk; current->mm->start_brk = 0; goto error; }
current->mm->brk = current->mm->start_brk; current->mm->context.end_brk = current->mm->start_brk; current->mm->start_stack = current->mm->start_brk + stack_size; #endif
retval = create_elf_fdpic_tables(bprm, current->mm, &exec_params, &interp_params); if (retval < 0) goto error;
kdebug("- start_code %lx", current->mm->start_code); kdebug("- end_code %lx", current->mm->end_code); kdebug("- start_data %lx", current->mm->start_data); kdebug("- end_data %lx", current->mm->end_data); kdebug("- start_brk %lx", current->mm->start_brk); kdebug("- brk %lx", current->mm->brk); kdebug("- start_stack %lx", current->mm->start_stack);
#ifdef ELF_FDPIC_PLAT_INIT
dynaddr = interp_params.dynamic_addr ?: exec_params.dynamic_addr; ELF_FDPIC_PLAT_INIT(regs, exec_params.map_addr, interp_params.map_addr, dynaddr); #endif
finalize_exec(bprm);
entryaddr = interp_params.entry_addr ?: exec_params.entry_addr;
start_thread(regs, entryaddr, current->mm->start_stack);
retval = 0;
error: if (interpreter) { exe_file_allow_write_access(interpreter); fput(interpreter); } kfree(interpreter_name); kfree(exec_params.phdrs); kfree(exec_params.loadmap); kfree(interp_params.phdrs); kfree(interp_params.loadmap); return retval; }
|