【问题标题】:C memcpy() a functionC memcpy() 一个函数
【发布时间】:2009-11-11 17:11:33
【问题描述】:

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

【问题讨论】:

  • 你想复制某个函数后面的汇编指令吗?请问为什么?
  • 没有。 // 必需 - 至少 15 个字符
  • 为什么 %$#@ 要将用户空间函数传递给内核?你没有从内核空间调用它的安全方法。
  • 我同意 ehemient:这是一个非常糟糕的主意。听起来您希望用户空间客户端能够将任意代码块传递到内核空间驱动程序。这是一个巨大的安全漏洞。也许您应该解释一下您的客户将尝试使用他们的功能做什么——我猜您只需要为您的内核模块构建一个更全面的 API。
  • 你在准备反汇编吗?你不是在准备利用,是吗? )

标签: c memory linux-kernel function-pointers memcpy


【解决方案1】:

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

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

【讨论】:

  • 抱歉-我认为这是标记为 C++,而不是 C。我不知道 C 中函数指针大于任何其他指针大小的任何情况。
  • 请注意,C 规范确实允许函数指针与数据指针类型不兼容。
  • PlayStation 3 函数指针与数据指针的大小不同。
【解决方案2】:

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

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

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

附带说明,起初我误读了您确实想将memcpy() 传递给内核。你必须提醒一下,这是一个非常特殊的功能。它是在 C 标准中定义的,非常简单,范围也很广,因此它是一个完美的目标,可以作为built-in by the compiler 提供。

就像strlen()strcmp()others in GCC

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

【讨论】:

    【解决方案3】:

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


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


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

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

    正如在其他地方提到的,你真的需要一种函数是一等对象的语言,否则你就不走运了。

    【讨论】:

      【解决方案4】:

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

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

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

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

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

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

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

      【讨论】:

        【解决方案5】:

        我不太明白你想要完成什么,但假设你使用 -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
        

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

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

        【讨论】:

          【解决方案6】:

          在这里进行了类似的讨论:

          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

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

          祝你好运!

          【讨论】:

            【解决方案7】:

            如果你的链接器不做全局优化,那么只需计算函数指针和下一个函数地址之间的差。

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

            【讨论】:

              【解决方案8】:

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

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

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

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

              【讨论】:

                【解决方案9】:

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

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

                【讨论】:

                  【解决方案10】:

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

                  【讨论】:

                    【解决方案11】:

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

                    【讨论】:

                      【解决方案12】:

                      我的建议是:不要。

                      将代码注入内核空间是一个巨大的安全漏洞,most modern OSes forbid self-modifying code altogether

                      【讨论】:

                        【解决方案13】:

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

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

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

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

                        【讨论】:

                          【解决方案14】:

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

                          【讨论】:

                            【解决方案15】:

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

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

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

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

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 2020-04-22
                              • 2011-08-28
                              • 1970-01-01
                              • 2020-10-30
                              • 1970-01-01
                              • 2015-03-06
                              相关资源
                              最近更新 更多