【问题标题】:Writing functions to binary files?将函数写入二进制文件?
【发布时间】:2017-05-08 20:50:14
【问题描述】:

让我相当好奇的是,由于在 C++ 中可以在适当的情况下将函数作为参数传递,这表明任何处理该函数的内部代码都可以被指向或以其他方式写入和读取到二进制文件中作为它的可执行代码。

这显然来自某人,虽然我可能在 C++ 方面有很强的背景,但我不熟悉堆中内存管理方式的复杂内部结构,尤其是可执行机器代码如何适应图片。

我假设由于可以传递对函数的引用,因此可以获取它指向的数据并将其写入某处。我不知道。

有人想告诉我这是否可能吗?如果是这样,你能举个例子吗?如果不是,请告诉我为什么!我喜欢更深入地了解 C++ 在内部是如何工作的。

【问题讨论】:

  • 我不能给你一个例子,但是你可以想象函数指针只是说:嘿,这是一个你可以调用的可执行代码。
  • 考虑什么是可执行文件... ;P
  • @rekire 看,这也是我的思考过程。显然,可执行代码必须存在于内存中的某个位置才能运行,但我从未真正尝试过获取它并将其写入文件。当我想到真正奇怪的、开箱即用的解决方案来将函数保存在文件中,然后让我的程序能够加载和执行它们时,这个想法就出现了。大声笑。
  • 我不完全确定这种东西的实际应用,但这是一个非常有趣的思想实验,我认为尝试一下会很酷。
  • 您在第一条评论中写的内容类似于共享库,例如 windows 中的 .dll 文件或 linux/unix 中的 .so 文件

标签: c++ function file io binary


【解决方案1】:

20 年前,您的建议可能是新鲜且有用的。人们通过按需加载文件中的代码,然后调用它,然后卸载来节省内存。那就是所谓的覆盖。在一定程度上它是可用的,但在平台和平台的 API 中标准化的形式是管理它的。 共享库(POSIX 系统中的 .so,Windows 中的 .dll)背后的机制是库的文件包含某些函数所在的标签、它们的名称以及有关如何初始化堆栈和数据段的数据。它可以在程序加载时由系统自动完成。否则,您可以手动加载库并加载指向函数的指针。例如。在 Windows 上,在 Linux 上将由 LoadLibrary() 和 GetProcAddress()、dlopen() 和 dlsym() 实现。

现在无法使用高级语言的原因:安全性,保护数据段中的恶意代码。运行时库通常会处理它。 仍然可以使用汇编程序,但您将挑战防病毒和系统安全措施。我想,通过仔细编程,您可以创建自己的“链接器”来创建自己的库并加载。

【讨论】:

  • 仍然可以在许多目标上执行此操作 - 您所指的 20 年前的那种目标仍在嵌入式系统中广泛使用,不一定是遗留或过时的。有很多现代微控制器没有 MMU/MPU(例如大多数 ARM Cortex-M 设备)。问题也只是关于阅读代码,它没有说明随后加载和执行它。
【解决方案2】:

不,你真的不能这样做。原因有很多,但这里有一个简单直观的原因:函数可能会调用其他函数。如果您能够将一个函数写入磁盘并恢复它,这将不考虑它的依赖关系(它调用的函数、它更新的全局变量等)。它不会工作。

如果你想从磁盘读取函数,最好用像 Lua 这样的脚本语言来表达它们。这是一个经过验证的解决方案,用于许多商业产品,例如视频游戏和 Adob​​e Lightroom。

【讨论】:

  • 我很想知道你通过保存一个函数实际上得到了什么信息。如果它失去了任何依赖关系,那么当我从内存中提取它时会看到什么?显然,脚本语言对于这些目的要好得多,但我只是认为这是一个有趣的问题。
  • 我想执行禁用和地址重定位也是一个问题。
  • @Kats :如果您想查看函数代码的内存是什么样的,请在 反汇编 模式下使用调试器。这将比原始二进制副本更有意义 - 您将看到所有链接和依赖项。请注意,这个答案并不完全正确;你能行的;它只是不是很有用。
  • @GRAYgoose124 我想这是真的。任何可能在“已加载”函数内部进行的函数调用或内存管理都将完全失效,因为对它们中的每一个的引用都不会指向它认为应该指向的内容。我只能预见到很多分段错误。大声笑。
  • 更不用说 Kats,如何找到函数边界以知道何时停止复制? :)
【解决方案3】:

虽然函数指针是函数的入口点,并且可以读取内存并因此复制,但第一个问题是没有可靠的方法来确定该代码的长度,因此您无法确定 要复制多少才能获得整个功能,仅此而已。

另一个问题是什么实际目的?根据平台的不同,代码可能无法重定位,并且会有指向其他代码的链接。二进制文件不包含符号信息;你能做的最好的就是反汇编它,但是在整个链接可执行文件的上下文之外,这样做可能不是很有用。

如果您的目标是将函数与主要可执行文件分离,并能够在以后加载和运行它们,那么这就是 DLL 和共享库的用途。

如果您只想观察与函数相关的二进制文件,那么最好在调试器中完成 - 它将有一个反汇编视图模式,将显示原始二进制文件(十六进制)、带有符号链接的汇编代码和对应的 C 源。如果您的目标只是调查源代码与二进制机器码之间的关系,这会更有意义。

以下是您可以如何做您所要求的事情 - 即使没有实际理由这样做。它对在某些情况下可能无效的编译器行为做出假设。例如,它假设编译器会将相邻的函数连续放置在内存中并增加内存地址,因此function2 紧随在内存中的function1 之后。这里function2 仅用作function1 的结束标记,并且可能是虚拟的。

int function1()
{
    ...

    return 0 ;
}

void function2() 
{
}

#include <stddef.h>

int main()
{
    ptrdiff_t function1_length = (char*)function2 - (char*)function1 ;

    FILE* fp = fopen( "function1.bin", "wb" ) ;
    fwrite( function1, function1_length, 1, fp ) ;
    fclose( fp ) ;
}

【讨论】:

  • 我从未说过实用或有用。我只是有一个关于这样的事情是否可行的问题。玩弄想法可以获得更多,而不仅仅是“进步”。话虽如此,我确实做到了,提取了函数的地址并遇到了您描述的确切问题:没有方法可以准确地确定缓冲区应该多长。无论任何实际用途,都不会浪费精力。我学到了更多关于内存在运行时是如何工作的,这真的很有趣。
  • @Kats :我很感激;但不清楚该问题不是XY problem,并且您没有考虑到一些未说明的目标。作为社区资源,包含此类健康警告的答案很有用,以防其他读者认为它可能是您从未想过的问题的解决方案。
  • 很抱歉。是的,这更像是一种学习体验,而不是为它找到一些实际用途。如果有的话,我可能会通过快速的 google 搜索找到它,但由于似乎没有多少程序员担心将函数写入文件,因此可能不值得投入大量时间。除非您是图书馆作家,否则这几乎就是您的工作。大声笑。
  • 在这个答案中你写了 something 但不清楚 what。如果&amp;function1 的东西只是一个跳转到其他地方的指令怎么办?您还没有捕获function1,而只是捕获了function2 之前的那部分。
  • @Kats :请注意,您学到的所有内容都可以在符号调试器或反汇编器中更轻松地进行研究。优点是您不必为了学习而修改正在学习的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-15
  • 2017-06-30
相关资源
最近更新 更多