是否可以编译C++(或类似)程序而不生成可执行文件,而是直接从内存中写入并执行它?
例如使用 GCC
和 clang
,其效果类似于:
c++ hello.cpp -o hello.x && ./hello.x $@ && rm -f hello.x
在命令行中。
但无需将可执行文件写入磁盘以立即加载/重新运行它。
(如果可能,该过程可能不使用磁盘空间,或者至少不使用当前目录中可能是只读的空间)。
最佳答案
可能吗?不是你似乎希望的方式。任务分为两部分:
1) 如何将二进制文件放入内存
当我们指定 /dev/stdout
作为 Linux 中的输出文件,我们可以通过管道输入我们的程序 x0
上面写着
来自标准输入的可执行文件并执行它:
gcc -pipe YourFiles1.cpp YourFile2.cpp -o/dev/stdout -Wall | ./x0
在 x0
我们可以从标准输入读取直到到达文件末尾:
int main(int argc, const char ** argv)
{
const int stdin = 0;
size_t ntotal = 0;
char * buf = 0;
while(true)
{
/* increasing buffer size dynamically since we do not know how many bytes to read */
buf = (char*)realloc(buf, ntotal+4096*sizeof(char));
int nread = read(stdin, buf+ntotal, 4096);
if (nread<0) break;
ntotal += nread;
}
memexec(buf, ntotal, argv);
}
x0
也可以。直接执行编译器并读取输出。此问题已在此处得到解答:Redirecting exec output to a buffer or file
警告: 我刚刚发现由于某种奇怪的原因,当我使用管道 |
时这不起作用但是当我使用 x0 < foo
时有效.
注意:如果你愿意修改你的编译器或者你做 JIT,比如 LLVM、clang 和其他框架,你可以直接生成可执行代码。但是对于本次讨论的其余部分,我假设您想使用现有的编译器。
注意:通过临时文件执行
其他程序(例如 UPX)通过执行临时文件来实现类似的行为,这比下面概述的方法更容易且更便携。在 /tmp
的系统上映射到 RAM 磁盘,例如典型的服务器,临时文件无论如何都是基于内存的。
#include<cstring> // size_t
#include <fcntl.h>
#include <stdio.h> // perror
#include <stdlib.h> // mkostemp
#include <sys/stat.h> // O_WRONLY
#include <unistd.h> // read
int memexec(void * exe, size_t exe_size, const char * argv)
{
/* random temporary file name in /tmp */
char name[15] = "/tmp/fooXXXXXX";
/* creates temporary file, returns writeable file descriptor */
int fd_wr = mkostemp(name, O_WRONLY);
/* makes file executable and readonly */
chmod(name, S_IRUSR | S_IXUSR);
/* creates read-only file descriptor before deleting the file */
int fd_ro = open(name, O_RDONLY);
/* removes file from file system, kernel buffers content in memory until all fd closed */
unlink(name);
/* writes executable to file */
write(fd_wr, exe, exe_size);
/* fexecve will not work as long as there in a open writeable file descriptor */
close(fd_wr);
char *const newenviron[] = { NULL };
/* -fpermissive */
fexecve(fd_ro, argv, newenviron);
perror("failed");
}
警告:为了清楚起见,省略了错误处理。为简洁起见包括在内。
注意:通过合并步骤main()
和 memexec()
进入单个函数并使用 splice(2)
用于在 stdin
之间直接复制和 fd_wr
该程序可以得到显着优化。
2) 直接从内存执行
一个人不会简单地加载和执行 ELF内存中的二进制。必须进行一些准备,主要与动态链接有关。有很多 Material 解释了 ELF 链接过程的各个步骤,研究它让我相信理论上是可能的。参见例如这个密切相关的 question on SO但是似乎不存在可行的解决方案。
更新 UserModeExec 似乎非常接近。
编写一个有效的实现会非常耗时,而且肯定会提出一些有趣的问题。我喜欢相信这是设计使然:对于大多数应用程序,强烈不希望(意外地)执行其输入数据,因为它允许 code injection .
执行 ELF 时究竟会发生什么?通常内核接收一个文件名,然后创建一个进程,加载可执行文件的不同部分并将其映射到内存中,执行大量完整性检查并将其标记为可执行文件,然后将控制权和文件名传回运行时链接器ld-linux.so
(libc 的一部分)。负责重定位函数、处理附加库、设置全局对象和跳转到可执行文件入口点。 AIU 这个繁重的工作由 dl_main()
完成(在 libc/elf/rtld.c 中实现)。
偶数 fexecve
使用 /proc
中的文件实现正是这种对文件名的需求导致我们重新实现了这个链接过程的一部分。
图书馆
- UserModeExec
- libelf -- 读取、修改、创建 ELF 文件
- eresi -- 和小 Sprite 一起玩
- OSKit (虽然看起来像一个死项目)
阅读
- http://www.linuxjournal.com/article/1060?page=0,0 -- 介绍
- http://wiki.osdev.org/ELF -- 很好的概述
- http://s.eresi-project.org/inc/articles/elf-rtld.txt -- 更详细的 Linux 特定解释
- http://www.codeproject.com/Articles/33340/Code-Injection-into-Running-Linux-Application -- 如何去 Hello World
- http://www.acsu.buffalo.edu/~charngda/elf.html -- 很好的ELF结构引用
- Loaders and Linkers by John Levine -- 更详细的链接解释
SO 的相关问题
- Linux user-space ELF loader
- ELF Dynamic loader symbol lookup ordering
- load-time ELF relocation
- How do global variables get initialized by the elf loader
所以看起来可能,你自己决定是否也实用。
关于c++ - 如何直接从内存编译执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13690454/