是否可以编译C++(或类似)程序而不生成可执行文件但是将其写入并直接从内存执行?
例如,使用GCC和clang,具有类似效果的东西:
c++ hello.cpp -o hello.x && ./hello.x $@ && rm -f hello.x
Run Code Online (Sandbox Code Playgroud)
在命令行中.
但是没有将可执行文件写入磁盘以立即加载/再次运行它的负担.
(如果可能,该过程可能不使用磁盘空间.)
Lot*_*har 43
可能?不是你想要的方式.任务分为两部分:
当我们/dev/stdout在Linux中指定为输出文件时,我们可以管道进入我们的程序x0,该程序从stdin读取可执行文件并执行它:
gcc -pipe YourFiles1.cpp YourFile2.cpp -o/dev/stdout -Wall | ./x0
Run Code Online (Sandbox Code Playgroud)
在x0我们可以从stdin读取直到到达文件的末尾:
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);
}
Run Code Online (Sandbox Code Playgroud)
也可以x0直接执行编译器并读取输出.此问题已在此处得到解答:将exec输出重定向到缓冲区或文件
警告:我只是想通了一些奇怪的原因,当我使用管道时这不起作用|但是在我使用时工作x0 < foo.
注意:如果您愿意修改编译器或者像LLVM,clang和其他框架那样执行JIT,则可以直接生成可执行代码.但是对于本讨论的其余部分,我假设您要使用现有的编译器.
其他程序(如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");
}
Run Code Online (Sandbox Code Playgroud)
警告:为了清楚起见,遗漏了错误处理.包括为了简洁起见.
注意:通过结合步骤main()和memexec()成一个单一的功能,并使用splice(2)对之间直接复制stdin和fd_wr程序可以显著优化.
一个不是简单地从内存加载和执行ELF二进制文件.必须进行一些主要与动态链接相关的准备工作.有很多材料解释了ELF连接过程的各个步骤,研究它让我相信理论上可行.例如,参见关于SO的这个密切相关的问题,但似乎没有一个可行的解决方案.
更新 UserModeExec似乎非常接近.
编写工作实现将非常耗时,并且肯定会提出一些有趣的问题.我喜欢相信这是设计的:对于大多数应用程序来说,非常不希望(意外地)执行其输入数据,因为它允许代码注入.
执行ELF时会发生什么?通常,内核接收文件名,然后创建进程,将可执行文件的不同部分加载并映射到内存中,执行大量健全性检查并将其标记为可执行文件,然后将控制权和文件名传递回运行时链接程序ld-linux.so(libc的一部分).负责重定位函数,处理其他库,设置全局对象以及跳转到可执行文件入口点.AIU这个繁重的工作由dl_main()(在libc/elf/rtld.c中实现)完成.
甚至fexecve是使用文件实现的,/proc这需要一个文件名,这导致我们重新实现这个链接过程的一部分.
图书馆
读
SO的相关问题
所以看起来可能,你决定是否也是实用的.
Mat*_*ine 16
Linux可以使用tempfs在RAM中创建虚拟文件系统.例如,我tmp在我的文件系统表中设置了我的目录,如下所示:
tmpfs /tmp tmpfs nodev,nosuid 0 0
Run Code Online (Sandbox Code Playgroud)
使用它,我输入的任何文件/tmp都存储在我的RAM中.
Windows似乎没有任何"官方"方式,但有许多第三方选项.
如果没有这个"RAM磁盘"概念,您可能不得不大量修改编译器和链接器以在内存中完全运行.
如果您没有专门与C++绑定,您还可以考虑其他基于JIT的解决方案:
libtcc.a从内存中的C代码快速发出差(即未优化)的机器代码.但是如果你想要好的机器代码,你需要对它进行优化,而且速度不快(因此写入文件系统的时间可以忽略不计).
如果您与C++生成的代码绑定,则需要一个好的C++优化编译器(例如g++或clang++); 他们花了很多时间来编译C++代码到优化的二进制文件,所以你应该生成一些文件foo.cc(可能在像某些文件系统这样的RAM文件系统中tmpfs,但这会带来很小的收益,因为大部分时间都花在内部g++或clang++优化过程中,而不是从磁盘读取,然后将其编译foo.cc为foo.so(使用或许make,或至少分叉g++ -Wall -shared -O2 foo.cc -o foo.so,可能还有其他库).最后dlopen生成你的主程序foo.so.FWIW,MELT正是这样做的.
或者,生成一个自包含的源程序foobar.cc,将其编译为可执行文件,foobarbin例如,g++ -O2 foobar.cc -o foobarbin使用execve该foobarbin可执行二进制文件执行
生成C++代码时,您可能希望避免生成微小的C++源文件(例如,只有十几行;如果可能,至少生成几百行的C++文件).例如,尽可能尝试将多个生成的C++函数放在同一个生成的C++文件中(但避免生成非常大的C++函数,例如单个函数中的10KLOC;它们需要花费大量时间由GCC编译).如果相关,您可以考虑#include在生成的C++文件中只有一个单独的,并预编译通常包含的标头.
| 归档时间: |
|
| 查看次数: |
14465 次 |
| 最近记录: |