C语言中的memcpy()函数

5

有没有一种方法可以计算函数的大小?我有一个指向函数的指针,我需要使用memcpy复制整个函数。我需要malloc一些空间,并知道memcpy的第三个参数——大小。我知道sizeof(function)不起作用。你有什么建议吗?

有没有办法计算函数的大小?我有一个指向函数的指针,需要使用memcpy复制整个函数。我需要分配一些空间,并且了解memcpy的第三个参数即大小。但是sizeof(function)无效。你有什么建议吗?


3
你想复制某个函数背后的汇编指令?我可以问一下为什么吗? - Viktor Sehr
2
不。// 必需 - 至少15个字符 - Marsh Ray
9
为什么你想要将一个用户空间函数传递到内核中?你没有安全的方式从内核空间调用它。 - ephemient
5
我同意 ephemient 的观点:这是一个非常糟糕的想法。听起来你希望用户空间客户端能够将任意代码块传递到内核空间驱动程序中。那是一个巨大的安全漏洞。也许你应该解释一下你的客户端将尝试使用他们的功能 - 我猜你只需要构建一个更全面的 API 给你的内核模块即可。 - Daniel Pryden
1
你在准备反汇编器吗? 你不是在准备利用漏洞,对吧 ; ) - littlegreen
显示剩余2条评论
15个回答

23

在C语言中,函数不是一等公民对象。这意味着它们无法传递给另一个函数,无法从函数返回,并且无法复制到内存的其他部分。

但是,函数指针可以满足所有这些要求,而且是一等公民对象。函数指针只是一个内存地址,通常与计算机上的任何其他指针具有相同的大小。


抱歉,我以为这是标记为C++,而不是C。我不知道在C中是否存在任何条件,使函数指针大于任何其他指针的大小。 - luke
4
请注意,C语言规范确实允许函数指针与数据指针类型不兼容。 - Stephen Canon
PlayStation 3的函数指针与数据指针的大小不同。 - Jim Buck

8

这段话并没有直接回答你的问题,但是你不应该在内核代码中实现从内核到用户空间的回调

向内核空间注入代码不是一个好的解决方案

更好的方法是将用户/内核边界看作进程间边界。通过字符设备通过明确定义的协议来传递数据,而不是代码。如果你确实需要传递代码,只需将其封装在内核模块中。然后你可以像.so插件系统一样动态加载/卸载它。

顺便提一下,起初我误读了你想将memcpy()传递给内核。你需要提醒自己它是一个非常特殊的函数。它在C标准中定义,相当简单,并且具有相当广泛的范围,因此它是编译器提供的内置函数的完美目标。

就像strlen()strcmp()GCC中的其他函数一样。

也就是说,它是内置的并不妨碍你获取它的指针。


5
即使有一种方法可以获取函数的大小,当您尝试调用已复制到内存中另一个区域的版本时,它仍然可能失败。如果编译器具有跳转到特定内存位置的本地或长跳转,则不能仅移动内存中的函数并期望其运行。操作系统可以这样做,但它拥有执行此操作所需的所有信息。
我本来想问操作系统如何做到这一点,但是现在我想起来了,当操作系统移动东西时,通常会移动整个页面,并处理内存,使地址转换为页面/偏移量。我不确定即使操作系统是否会将单个函数移动到内存中。
即使在操作系统将函数移动到内存中的情况下,函数本身也必须被声明或以其他方式编译/汇编,以允许这种操作,通常是通过指示代码可重定位的pragma完成的。所有内存引用都需要相对于自己的堆栈帧(也称为局部变量)或包含某种段+偏移结构,以便CPU直接或在操作系统的要求下选择适当的段值。如果创建应用程序时涉及链接器,则可能必须重新链接该应用程序以考虑新的函数地址。
有些操作系统可以为每个应用程序提供自己的32位地址空间,但它适用于整个进程和任何子线程,而不是适用于单个函数。
正如其他地方提到的那样,您确实需要一种函数是第一类对象的语言,否则您就没有运气了。

3
你想复制一个函数?我认为这在C语言中通常是不可能的。 假设你有一个哈佛架构微控制器,代码(也就是“函数”)位于ROM中。在这种情况下,你根本无法这样做。 此外,我知道有几个编译器和链接器,在文件(而不仅仅是函数级别)上进行优化。这会导致操作码,其中C函数的部分混合在一起。
我认为唯一可能的方法可能是:
- 生成函数的操作码(例如通过单独编译/汇编它)。 - 将该操作码复制到C数组中。 - 使用适当的函数指针,指向该数组,以调用该函数。 - 现在,你可以对该数组执行所有通常用于“数据”的操作。
但除此之外:你考虑过重新设计你的软件,以便你不需要复制函数内容吗?

3
我不太理解你想要实现什么,但是假设你使用-fPIC进行编译,并且函数没有任何复杂操作,没有其他的函数调用以及对函数外部数据的访问,那么你甚至可以尝试一次性地这样做。我建议最安全的方式是限制支持的函数的最大大小,比如1千字节,并只传输它,忽略末尾的垃圾内容。
如果你真的需要知道一个函数的确切大小,请找出编译器的后奏和序言。在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

请对编译器的输出进行反汇编以检查,并取相应的组装序列进行搜索。仅使用尾声可能已经足够,但如果搜索序列过早出现(例如在函数嵌入的数据中),所有这些操作都可能失败。我认为搜索下一个序曲也可能会让你陷入麻烦。
现在请忽略我所写的一切,因为您显然正在尝试以错误且本质上不安全的方式解决问题。请给我们画一个更大的图景,告诉我们为什么要这样做,看看是否可以找到完全不同的方法。

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 另一个提出的解决方案是根本不复制该函数,而是在您想要复制它的位置定义该函数。
祝你好运!

1
如果您的链接器没有进行全局优化,那么只需计算函数指针与下一个函数地址之间的差异即可。
请注意,复制函数将产生无法调用的内容,如果您的代码未编译为可重定位代码(即代码中的所有地址必须是相对地址,例如分支;全局变量可以工作,因为它们不会移动)。

1

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

这听起来很合理,因为这可能是常规用户空间库处理事情的方式 - 但对于内核/用户空间接口来说,它是完全错误的。即使你设法将函数代码复制到内核中,并使其适当地位置无关,它仍然是错误的,因为内核和用户空间代码在根本上执行于不同的上下文中。只举一个可能会引起问题的差异示例,如果由于交换出页而在内核上下文中发生页面故障,那么这将导致内核 oops 而不是交换该页。

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

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

0

函数不仅仅是可以复制的对象。那么交叉引用/符号等呢?当然,您可以使用类似标准Linux“binutils”软件包并折磨您的二进制文件,但这是否是您想要的?

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


0
我可以想到一种实现你想要的方法,但我不会告诉你,因为这是对语言的可怕滥用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接