【问题标题】:How to get function's name from function's pointer in Linux kernel?如何从 Linux 内核中的函数指针获取函数名?
【发布时间】:2010-09-25 22:53:11
【问题描述】:

如何在 C 中从 function's pointer 获取函数名?

编辑:真实情况是:我正在编写一个 linux 内核模块并且我正在调用内核函数。其中一些函数是指针,我想在内核源代码中检查该函数的代码。但我不知道它指向哪个函数。我认为可以这样做,因为当系统失败(内核恐慌)时,它会在屏幕上打印出当前调用堆栈和函数名称。但是,我想我错了……是吗?

【问题讨论】:

  • 也许如果你解释为什么你需要函数的名字,有人可以建议另一种方法来获得你需要的东西。
  • 提到但未详细说明的是使用调试符号。然而,如果你让它工作,结果几乎肯定会查找一些调试符号的来源......正如一个答案所提到的那样,libdwarf 可能是 linux 内核的方法。

标签: c linux-kernel function-pointers


【解决方案1】:

我很惊讶为什么每个人都说这是不可能的。在 Linux 上可以使用非静态函数。

我知道至少有两种方法可以实现这一点。

有用于回溯打印的 GNU 函数:backtrace()backtrace_symbols()(参见 man)。在您的情况下,您不需要backtrace(),因为您已经有了函数指针,您只需将它传递给backtrace_symbols()

示例(工作代码):

#include <stdio.h>
#include <execinfo.h>

void foo(void) {
    printf("foo\n");
}

int main(int argc, char *argv[]) {
    void    *funptr = &foo;

    backtrace_symbols_fd(&funptr, 1, 1);

    return 0;
}

gcc test.c -rdynamic编译

输出:./a.out(foo+0x0)[0x8048634]

它为您提供二进制名称、函数名称、与函数开始的指针偏移量和指针值,以便您对其进行解析。

另一种方法是使用dladdr()(另一个扩展名),我猜print_backtrace() 使用dladdr()dladdr() 返回在 dli_sname 字段中具有函数名称的 Dl_info 结构。我在这里没有提供代码示例,但很明显 - 详情请参阅 man dladdr

注意!两种方法都要求函数是非静态的!

嗯,还有另一种方法 - 使用 libdwarf 使用调试信息,但它需要未剥离的二进制文件,而且不太容易做到,所以我不推荐它。

【讨论】:

  • 对于非内核情况,dladdr 方法应该是公认的答案。你应该把它从回溯的东西中分离出来。
【解决方案2】:

如果没有额外的帮助,这是不可能直接实现的。

你可以:

  1. 在程序中维护一个表,将函数指针映射到名称

  2. 检查可执行文件的符号表,如果有的话。

然而,后者很硬,而且不可移植。该方法将取决于操作系统的二进制格式(ELF、a.out、.exe 等),以及链接器完成的任何重定位。

编辑:既然您现在已经解释了您的真实用例是什么,那么答案实际上并不难。内核符号表在/proc/kallsyms 中可用,并且有一个API 可以访问它:

#include <linux/kallsyms.h>

const char *kallsyms_lookup(unsigned long addr, unsigned long *symbolsize,
                            unsigned long *ofset, char **modname, char *namebuf)

void print_symbol(const char *fmt, unsigned long addr)

出于简单的调试目的,后者可能会完全满足您的需求 - 它获取地址、格式化并将其发送到 printk,或者您可以使用 printk%pF 格式说明符。

【讨论】:

  • 我认为我不能从内核模块调用 kallsyms_lookup。当我编译时,我得到“kallsyms_lookup undefined”
  • 如果你遇到编译时错误,那么你需要确保你的内核头文件可用并且在你的包含路径中。
  • 我收到链接时间错误。标题很好。 #include
  • 好的,这表明你的模块编译的东西在某处是错误的。模块需要调用内核中的符号,因此根据定义,符号在链接时无法完全解析。
  • 附注用于编译 Linux 内核模块的示例 Makefile 可能是另一个问题的不错选择。我的参考资料正在工作中,我目前无法获得。
【解决方案3】:

在Linux内核中,可以直接使用"%pF"格式的printk!

void *func = &foo;
printk("func: %pF at address: %p\n", func, func);

【讨论】:

【解决方案4】:

以下内容适用于 Linux:

  • printf使用%p的函数地址
  • 然后执行nm &lt;program_path&gt; | grep &lt;address&gt;(不带0x 前缀)
  • 它应该显示函数名称。

只有当有问题的函数在同一个程序中时它才有效(而不是在动态链接库或其他东西中)。

如果你能找出加载的共享库的加载地址,你可以从打印出来的数字中减去地址,然后在库上使用nm来找出函数名。

【讨论】:

    【解决方案5】:

    你不能直接,但如果你愿意,你可以用不同的方法来解决这个问题。您可以创建一个结构指针,而不是指向一个函数以及一个描述性字符串,您可以将其设置为您想要的任何内容。 我还添加了一个调试姿势,因为您可能不希望这些变量永远被打印。

    // Define it like this
    typedef struct
    {
      char        *dec_text;
      #ifdef _DEBUG_FUNC
      void        (*action)(char);
      #endif
    } func_Struct;
    
    // Initialize it like this
    func_Struct func[3]= {
    #ifdef _DEBUG_FUNC
    {"my_Set(char input)",&my_Set}};
    {"my_Get(char input)",&my_Get}};
    {"my_Clr(char input)",&my_Clr}};
    #else
    {&my_Set}};
    {&my_Get}};
    {&my_Clr}};
    #endif 
    
    // And finally you can use it like this
    func[0].action( 0x45 );
    #ifdef _DEBUG_FUNC
    printf("%s",func.dec_text);
    #endif
    

    【讨论】:

      【解决方案6】:

      如果可以指向的函数列表不是太大,或者如果您已经怀疑有一小组函数,您可以打印地址并将它们与执行期间使用的地址进行比较。例如:

      typedef void (*simpleFP)();
      typedef struct functionMETA {
          simpleFP funcPtr;
          char * funcName;
      } functionMETA;
      
      void f1() {/*do something*/}
      void f2() {/*do something*/}
      void f3() {/*do something*/}
      
      int main()
      {
          void (*funPointer)() = f2; // you ignore this
          funPointer(); // this is all you see
      
          printf("f1 %p\n", f1);
          printf("f2 %p\n", f2);
          printf("f3 %p\n", f3);
      
          printf("%p\n", funPointer);
      
          // if you want to print the name
          struct functionMETA arrFuncPtrs[3] = {{f1, "f1"}, {f2, "f2"} , {f3, "f3"}};
      
          int i;
          for(i=0; i<3; i++) {
              if( funPointer == arrFuncPtrs[i].funcPtr )
                  printf("function name: %s\n", arrFuncPtrs[i].funcName);
          }
      }
      

      输出:

      f1 0x40051b
      f2 0x400521
      f3 0x400527
      0x400521
      function name: f2
      

      这种方法也适用于静态函数。

      【讨论】:

        【解决方案7】:

        一般没有办法做到。

        如果您将相应的代码编译到 DLL/共享库中,您应该能够获取所有入口点并与您获得的指针进行比较。还没有尝试过,但我对 DLL/共享库有一些经验,并且希望它能够工作。这甚至可以实现跨平台工作。

        其他人已经提到使用调试符号进行编译,那么您可以尝试找到一种方法来从正在运行的应用程序中分析这些符号,类似于调试器所做的事情。 但这绝对是专有的,不可移植。

        【讨论】:

          【解决方案8】:
          1. 使用kallsyms_lookup_name()查找kallsyms_lookup的地址。

          2. 使用指向kallsyms_lookup的函数指针来调用它。

          【讨论】:

            【解决方案9】:

            查看Visual Leak Detector 了解他们如何让调用堆栈打印工作。不过,这假设您使用的是 Windows。

            【讨论】:

              【解决方案10】:

              不完全是问题的要求,但在阅读了这里的答案之后 我认为这个解决方案可以解决我的类似问题:

              /**
              * search methods */
              static int starts(const char *str, const char *c);
              static int fuzzy(const char *str, const char *c);
              
              int (*search_method)(const char *, const char *);
              
              /* asign the search_method and do other stuff */
              [...]
              
              printf("The search method is %s\n", search_method == starts ? "starts" : "fuzzy")
              

              如果您的程序需要这个很多,您可以在 XMacro 中定义方法名称和字符串,并在代码中使用 #define X(name, str) ... #undef X 从函数名称中获取相应的字符串。

              【讨论】:

                【解决方案11】:

                当我正在寻找一种解决方法来打印内核模块中的函数名称时,Alnitak 的回答对我非常有帮助。但是我想提供一件事,那就是您可能想使用 %pS 而不是 %pF 来打印函数的名称,因为 %pF 在某些较新版本的内核(例如 5.10.x)中不再起作用。

                【讨论】:

                  【解决方案12】:

                  你不能。函数名在编译和链接时并未附加到函数上。那时都是内存地址,而不是名字。

                  【讨论】:

                  • ...这就是调试符号的用途。
                  • 仅仅因为它不属于C规范,并不意味着不能使用系统特定的方法。
                  【解决方案13】:

                  如果没有反光镜,你不会知道自己的样子。您必须使用具有反射功能的语言,例如 C#。

                  【讨论】:

                  • 是的,当然。用 C# 编写一个 linux 内核模块。我相信这行得通。
                  • 可以在 C 中做到这一点。它看起来不像大多数在运行时进行编译的语言那样漂亮(因此需要更多关于函数调用的数据)。
                  • 我投了反对票,因为反思总是解决问题的大锤。它使您无法使用纯 C 或汇编程序做任何事情(必须在某些机器上工作,这基本上是正确的)。它在那里有一个方便,但通常依赖它是不好的做法。
                  猜你喜欢
                  • 1970-01-01
                  • 2012-11-15
                  • 2015-06-07
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-12-01
                  • 2023-01-20
                  • 2011-07-10
                  相关资源
                  最近更新 更多