5

有什么方法可以计算函数的大小吗?我有一个指向函数的指针,我必须使用 memcpy 复制整个函数。我必须分配一些空间并知道 memcpy 的第三个参数 - 大小。我知道那 sizeof(function)行不通。你有什么建议吗?

4

15 回答 15

23

函数不是C 中的第一类对象。这意味着它们不能传递给另一个函数,不能从函数返回,也不能复制到内存的另一部分。

函数指针虽然可以满足所有这些,并且是第一类对象。函数指针只是一个内存地址,它通常与机器上的任何其他指针具有相同的大小。

于 2009-11-11T17:12:58.780 回答
8

它不会直接回答您的问题,但您不应该实现从内核代码到用户空间的回调

将代码注入内核空间也不是一个很好的解决方法。

最好将用户/内核屏障表示为进程间屏障。通过 char 设备在定义明确的协议之间来回传递数据,而不是代码。如果您确实需要传递代码,只需将其包装在内核模块中即可。然后您可以动态加载/卸载它,就像.so基于插件系统一样。

附带说明一下,起初我误读了您确实想传递memcpy()给内核。你不得不提醒,这是一个很特别的功能。它是在 C 标准中定义的,非常简单,范围也很广,因此它是编译器作为内置提供的完美目标

就像GCC 和其他strlen()strcmp()一样。

也就是说,内置的事实并不妨碍您获取指向它的指针。

于 2009-11-11T19:39:43.850 回答
5

即使有办法获得 sizeof() 一个函数,当您尝试调用已复制到内存中另一个区域的版本时,它仍然可能会失败。如果编译器有本地或长跳转到特定内存位置怎么办。您不能只是在内存中移动一个函数并期望它运行。操作系统可以做到这一点,但它拥有做到这一点所需的所有信息。


我本来想问操作系统是如何做到这一点的,但是现在我想到了,当操作系统在它周围移动东西时,通常会移动整个页面并处理内存,以便地址转换为页面/偏移量。我不确定甚至操作系统是否会在内存中移动单个函数。


即使在操作系统在内存中移动函数的情况下,函数本身也必须被声明或以其他方式编译/汇编以允许此类操作,通常通过指示代码可重定位的编译指示。所有内存引用都需要与其自己的堆栈帧(也称为局部变量)相关,或者包括某种段+偏移量结构,以便 CPU 直接或按照操作系统的要求选择适当的段值。如果在创建应用程序时涉及链接器,则可能必须重新链接应用程序以说明新的函数地址。

有些操作系统可以为每个应用程序提供自己的 32 位地址空间,但它适用于整个进程和任何子线程,而不是单个函数。

正如其他地方所提到的,您确实需要一种函数是一流对象的语言,否则您就不走运了。

于 2009-11-11T17:15:42.513 回答
3

你想复制一个函数?我认为这在 C 中通常是不可能的。假设您有一个哈佛架构微控制器,其中代码(换句话说,“函数”)位于 ROM 中。在这种情况下,您根本无法做到这一点。我还知道几个编译器和链接器,它们对文件进行优化(不仅仅是函数级别)。这导致了操作码,其中 C 函数的部分相互混合。

我认为可能的唯一方法可能是:

  • 生成函数的操作码(例如通过自己编译/组装)。

  • 将该操作码复制到 C 数组中。

  • 使用指向该数组的正确函数指针来调用此函数。

  • 现在,您可以在该数组上执行典型“数据”常见的所有操作。

但除此之外:您是否考虑过重新设计您的软件,以便您不需要复制功能内容?

于 2009-11-11T17:32:56.863 回答
3

我不太明白你想要完成什么,但假设你使用 -fPIC 编译并且没有让你的函数做任何花哨的事情,没有其他函数调用,没有从外部函数访问数据,你甚至可以侥幸逃脱一次。我想说最安全的可能性是将支持的函数的最大大小限制为 1 KB,然后只传输它,而忽略尾随的垃圾。

如果您真的需要知道函数的确切大小,请找出编译器的结尾和开场白。这在 x86 上应该是这样的:

:your_func_epilogue
mov esp, ebp
pop ebp
ret
:end_of_func

;expect a varying length run of NOPs here

:next_func_prologue
push ebp
mov ebp, esp

反汇编编译器的输出进行检查,并获取相应的汇编序列进行搜索。单独的后记可能就足够了,但如果搜索序列过早弹出,所有这些都可能会爆炸,例如在函数嵌入的数据中。我想,寻找下一个序幕也可能会给你带来麻烦。

现在请忽略我写的所有内容,因为您显然正试图以错误且本质上不安全的方式解决问题。请给我们画一张更大的图,你为什么要这样做,看看我们是否能找到一种完全不同的方法。

于 2009-11-11T19:22:21.810 回答
2

这里进行了类似的讨论:

http://www.motherboardpoint.com/getting-code-size-function-c-t95049.html

他们建议在您要复制的函数之后创建一个虚拟函数,然后获取指向两者的内存指针。但是您需要关闭编译器优化才能使其工作。

如果您的 GCC >= 4.4,您可以尝试关闭函数的优化,特别是使用 #pragma:

http://gcc.gnu.org/onlinedocs/gcc/Function-Specific-Option-Pragmas.html#Function-Specific-Option-Pragmas

另一个建议的解决方案是根本不复制该函数,而是在您想要将其复制到的位置定义该函数。

祝你好运!

于 2009-11-11T17:31:14.323 回答
1

如果您的链接器不进行全局优化,则只需计算函数指针与下一个函数地址之间的差异。

请注意,如果您的代码未编译可重定位,则复制该函数将产生无法调用的内容(即代码中的所有地址都必须是相对的,例如分支;全局变量可以工作,尽管它们不会移动)。

于 2009-11-11T17:17:06.353 回答
1

听起来您想从内核驱动程序回调到用户空间,以便它可以在某些异步作业完成时通知用户空间。

这听起来可能很明智,因为这是常规用户空间库可能会做的事情 - 但对于内核/用户空间接口,这是完全错误的。即使您设法将您的函数代码复制到内核中,并且即使您使其与位置无关,它仍然是错误的,因为内核和用户空间代码在根本不同的上下文中执行。仅举一个可能导致问题的差异示例,如果由于换出页面而在内核上下文中发生页面错误,那将导致内核 oops 而不是换入页面。

正确的方法是让内核在异步作业完成后使某些文件描述符可读(在您的情况下,该文件描述符几乎可以肯定是您的驱动程序提供的字符设备)。select然后,用户空间进程可以使用/poll或使用-等待此事件,如果需要,read它可以将文件描述符设置为非阻塞,并且基本上只需使用所有标准 UNIX 工具来处理这种情况。毕竟,这就是处理网络套接字(以及几乎所有其他异步情况)的异步性质的方式。

如果您需要提供有关发生的事件的其他信息,则可以在用户空间进程调用read可读文件描述符时将其提供给用户空间进程。

于 2009-11-11T21:08:45.540 回答
0

我在 Nintendo GBA 上完成了此操作,我将一些低级渲染功能从闪存(16 位访问速度较慢的内存)复制到高速工作区内存(32 位访问,至少快两倍)。这是通过在我想要复制的函数之后立即获取函数的地址来完成的,size = (int) (NextFuncPtr - SourceFuncPtr)。这确实工作得很好,但显然不能在所有平台上得到保证(肯定不能在 Windows 上工作)。

于 2009-11-16T16:47:51.887 回答
0

我的建议是:不要。

将代码注入内核空间是一个巨大的安全漏洞,大多数现代操作系统完全禁止自我修改代码

于 2009-11-12T04:47:14.973 回答
0

我可以想出一种方法来完成你想要的,但我不会告诉你,因为这是对语言的可怕滥用。

于 2009-11-11T21:13:57.577 回答
0

函数不仅仅是你可以复制的对象。交叉引用/符号等呢?当然,您可以使用标准 linux“binutils”包之类的东西并折磨您的二进制文件,但这是您想要的吗?

顺便说一句,如果您只是想替换 memcpy() 实现,请查看 LD_PRELOAD 机制。

于 2009-11-11T17:15:17.787 回答
0

我认为一种解决方案如下。

例如:如果您想知道程序 ac 中的 func() 大小,并在函数之前和之后有指标。

尝试编写一个 perl 脚本,将这个文件编译成对象格式(cc -o),确保不删除预处理器语句。您稍后需要它们来计算目标文件的大小。

现在搜索您的两个指标并找出两者之间的代码大小。

于 2009-11-20T21:45:42.087 回答
0

比禁用优化和依赖编译器来维护函数顺序更简洁的方法是将该函数(或需要复制的一组函数)安排在其自己的部分中。这取决于编译器和链接器,如果在复制的函数之间调用,您还需要使用相对寻址。对于那些询问您为什么要这样做的人,这是嵌入式系统中需要更新运行代码的常见要求。

于 2009-11-12T04:26:17.840 回答
0

据我所知,原始发布者想做一些特定于实现的事情,因此不可移植;这与 C++ 标准关于将指针转换为函数的主题所说的不同,而不是 C 标准,但这在这里应该已经足够好了。

在某些环境中,使用某些编译器,可能会做发布者似乎想要做的事情(即,将函数指针指向的内存块复制到其他位置,可能分配给malloc,将该块转换为指向函数的指针,然后直接调用它)。但它不会是便携式的,这可能不是问题。查找该内存块所需的大小本身取决于环境和编译器,并且很可能需要一些非常神秘的东西(例如,扫描内存以查找返回操作码,或通过反汇编程序运行内存)。同样,特定于实现且高度不可移植。再说一次,对于原始海报来说可能无关紧要。

指向潜在解决方案的链接似乎都使用了特定于实现的行为,我什至不确定它们是否符合其意图,但它们可能适用于 OP。

把这匹马打死了,我很想知道OP为什么要这样做。即使它在目标环境中工作,它也会非常脆弱(例如,可能会因编译器选项、编译器版本、代码重构等的更改而中断)。我很高兴我不在需要这种魔法的地方工作(假设是这样)......

于 2009-11-12T05:55:13.887 回答