c - 在 linux 中获取导出函数的名称和地址

标签 c linux dllimport dllexport

通过使用 PIMAGE_DOS_HEADER,我可以从 Windows 中的可执行文件中获取导出的函数名称和指针列表。 API ( example )。

Linux 的等效 API 是什么?

对于上下文,我正在创建单元测试可执行文件,并且我正在导出以名称“test_”开头的函数,我希望可执行文件在运行时旋转并执行所有测试函数。

示例伪代码:

int main(int argc, char** argv)
{
    auto run = new_trun();
    auto module = dlopen(NULL);
    auto exports = get_exports(module);  // <- how do I do this on unix?
    for( auto i = 0; i < exports->length; i++)
    {
        auto export = exports[i];
        if(strncmp("test_", export->name, strlen("test_")) == 0)
        {
          tcase_add(run, export->name, export->func);
        }
    }

    return trun_run(run);
}

编辑:

在使用这个问题的最佳答案后,我能够找到我的意思:
List all the functions/symbols on the fly in C?

此外,我不得不使用 gnu_hashtab_symbol_count函数来自 Nominal Animal下面的回答来处理 DT_GNU_HASH而不是 DT_HASH .

我的最终测试 main 函数如下所示:
int main(int argc, char** argv)
{
    vector<string> symbols;
    dl_iterate_phdr(retrieve_symbolnames, &symbols);

    TRun run;
    auto handle = dlopen(NULL, RTLD_LOCAL | RTLD_LAZY);
    for(auto i = symbols.begin(); i != symbols.end(); i++)
    {
        auto name = *i;
        auto func = (testfunc)dlsym(handle, name.c_str());
        TCase tcase;
        tcase.name = string(name);
        tcase.func = func;
        run.test_cases.push_back(tcase);
    }

    return trun_run(&run);
}

然后我在程序集中定义测试,如:
// test.h
#define START_TEST(name) extern "C" EXPORT TResult test_##name () {
#define END_TEST return tresult_success(); }

// foo.cc
START_TEST(foo_bar)
{
    assert_pending();
} 
END_TEST

这会产生如下所示的输出:
test_foo_bar: pending

  1 pending
  0 succeeded
  1 total

最佳答案

当我看到问题询问如何在操作系统 X 中做一些你在 Y 中做的事情时,我确实很生气。

在大多数情况下,这不是一种有用的方法,因为每个操作系统(家族)往往都有自己的解决问题的方法,因此尝试在 X 中应用适用于 Y 的东西就像将一个立方体塞进一个圆孔中。

请注意:这里的文字是严厉的,而不是屈尊的;我的英语水平没有我想要的那么好。根据我的经验,严谨性与实际帮助和指向已知工作解决方案的指针相结合似乎最能克服非技术限制。

Linux下,测试环境应该使用类似的东西

LC_ALL=C LANG=C readelf -s FILE

列出 FILE 中的所有符号. readelf是 binutils 包的一部分,如果您打算在系统上构建新的二进制文件,则会安装它。这导致了可移植的、健壮的代码。不要忘记 Linux 包含多个确实存在差异的硬件架构。

要在 Linux 中构建二进制文件,您通常会使用 binutils 中提供的一些工具。如果 binutils 提供了一个库,或者有一个基于 binutils 中使用的代码的 ELF 库,那么使用它会更好,而不是解析人类实用程序的输出。但是,没有这样的库(binutils 内部使用的 libbfd 库不是 ELF 特定的)。 [URL= http://www.mr511.de/software/english.html]libelf[/URL]图书馆很好,但它主要是一个作者的完全独立的作品。其中的错误已报告给 binutils,这是无效的,因为两者无关。简而言之,不能保证它以与 binutils 相同的方式处理给定架构上的 ELF 文件。因此,为了健壮性和可靠性,您肯定希望使用 binutils。

如果你有一个测试应用程序,它应该使用一个脚本,比如 /usr/lib/yourapp/list-test-functions , 列出测试相关的函数:
#!/bin/bash
export LC_ALL=C LANG=C
for file in "$@" ; do
    readelf -s "$file" | while read num value size type bind vix index name dummy ; do
        [ "$type" = "FUNC" ] || continue
        [ "$bind" = "GLOBAL" ] || continue
        [ "$num" = "$[$num]" ] || continue
        [ "$index" = "$[$index]" ] || continue
        case "$name" in
            test_*) printf '%s\n' "$name"
                    ;;
        esac
    done
done

这样,如果存在有怪癖的架构(特别是在 binutils 的 readelf 输出格式中),您只需要修改脚本。修改这样一个简单的脚本并不难,验证脚本是否正常工作也很容易——只需比较原始 readelf输出到脚本输出;任何人都可以做到这一点。

构造管道的子程序,fork() s 子进程,在子进程中执行脚本,并使用例如getline()在父进程中读取名字列表,是相当简单且极其健壮的。由于这也是一个脆弱的地方,我们通过使用该外部脚本(可定制/可扩展以涵盖这些怪癖,并且易于调试)使修复这里的任何怪癖或问题变得非常容易。
请记住,如果 binutils 本身有错误(除了输出格式错误),任何构建的二进制文件几乎肯定也会出现这些相同的错误。

作为一个面向 Microsoft 的人,您可能很难理解这种模块化方法的好处。 (它不是特定于 Microsoft,而是特定于单一供应商控制的生态系统,其中供应商插入的方法是通过总体框架和具有干净但非常有限的接口(interface)的黑匣子。我认为这是框架限制,或供应商强制围墙花园,或 jail 花园。看起来不错,但出去很难。有关我试图描述的模块化方法的描述和历史,请参见维基百科上的 the Unix philosophy 文章。)

以下表明您的方法在 Linux 中确实也可行——尽管笨拙且脆弱;这些东西旨在使用标准工具来完成。一般来说,这不是正确的方法。

接口(interface),symbols.h , 使用回调函数最容易实现,该函数为找到的每个符号调用:
#ifndef  SYMBOLS_H
#ifndef _GNU_SOURCE
#error You must define _GNU_SOURCE!
#endif
#define  SYMBOLS_H
#include <stdlib.h>

typedef enum {
    LOCAL_SYMBOL  = 1,
    GLOBAL_SYMBOL = 2,
    WEAK_SYMBOL   = 3,
} symbol_bind;

typedef enum {
    FUNC_SYMBOL   = 4,
    OBJECT_SYMBOL = 5,
    COMMON_SYMBOL = 6,
    THREAD_SYMBOL = 7,
} symbol_type;

int symbols(int (*callback)(const char *libpath, const char *libname, const char *objname,
                            const void *addr, const size_t size,
                            const symbol_bind binding, const symbol_type type,
                            void *custom),
            void *custom);

#endif /* SYMBOLS_H */

ELF 符号绑定(bind)和类型宏是特定于字大小的,因此为了避免麻烦,我在上面声明了枚举类型。但是,我省略了一些无趣的类型( STT_NOTYPESTT_SECTIONSTT_FILE )。

实现,symbols.c :
#define _GNU_SOURCE
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <fnmatch.h>
#include <dlfcn.h>
#include <link.h>
#include <errno.h>
#include "symbols.h"

#define UINTS_PER_WORD (__WORDSIZE / (CHAR_BIT * sizeof (unsigned int)))

static ElfW(Word) gnu_hashtab_symbol_count(const unsigned int *const table)
{
    const unsigned int *const bucket = table + 4 + table[2] * (unsigned int)(UINTS_PER_WORD);
    unsigned int              b = table[0];
    unsigned int              max = 0U;

    while (b-->0U)
        if (bucket[b] > max)
            max = bucket[b];

    return (ElfW(Word))max;
}

static symbol_bind elf_symbol_binding(const unsigned char st_info)
{
#if __WORDSIZE == 32
    switch (ELF32_ST_BIND(st_info)) {
#elif __WORDSIZE == 64
    switch (ELF64_ST_BIND(st_info)) {
#else
    switch (ELF_ST_BIND(st_info)) {
#endif
    case STB_LOCAL:  return LOCAL_SYMBOL;
    case STB_GLOBAL: return GLOBAL_SYMBOL;
    case STB_WEAK:   return WEAK_SYMBOL;
    default:         return 0;
    }
}

static symbol_type elf_symbol_type(const unsigned char st_info)
{
#if __WORDSIZE == 32
    switch (ELF32_ST_TYPE(st_info)) {
#elif __WORDSIZE == 64
    switch (ELF64_ST_TYPE(st_info)) {
#else
    switch (ELF_ST_TYPE(st_info)) {
#endif
    case STT_OBJECT: return OBJECT_SYMBOL;
    case STT_FUNC:   return FUNC_SYMBOL;
    case STT_COMMON: return COMMON_SYMBOL;
    case STT_TLS:    return THREAD_SYMBOL;
    default:         return 0;
    }
}

static void *dynamic_pointer(const ElfW(Addr) addr,
                             const ElfW(Addr) base, const ElfW(Phdr) *const header, const ElfW(Half) headers)
{
    if (addr) {
        ElfW(Half) h;

        for (h = 0; h < headers; h++)
            if (header[h].p_type == PT_LOAD)
                if (addr >= base + header[h].p_vaddr &&
                    addr <  base + header[h].p_vaddr + header[h].p_memsz)
                    return (void *)addr;
    }

    return NULL;
}

struct phdr_iterator_data {
    int  (*callback)(const char *libpath, const char *libname,
                     const char *objname, const void *addr, const size_t size,
                     const symbol_bind binding, const symbol_type type,
                     void *custom);
    void  *custom;
};

static int iterate_phdr(struct dl_phdr_info *info, size_t size, void *dataref)
{
    struct phdr_iterator_data *const data = dataref;
    const ElfW(Addr)                 base = info->dlpi_addr;
    const ElfW(Phdr) *const          header = info->dlpi_phdr;
    const ElfW(Half)                 headers = info->dlpi_phnum;
    const char                      *libpath, *libname;
    ElfW(Half)                       h;

    if (!data->callback)
        return 0;

    if (info->dlpi_name && info->dlpi_name[0])
        libpath = info->dlpi_name;
    else
        libpath = "";

    libname = strrchr(libpath, '/');
    if (libname && libname[0] == '/' && libname[1])
        libname++;
    else
        libname = libpath;

    for (h = 0; h < headers; h++)
        if (header[h].p_type == PT_DYNAMIC) {
            const ElfW(Dyn)  *entry = (const ElfW(Dyn) *)(base + header[h].p_vaddr);
            const ElfW(Word) *hashtab;
            const ElfW(Sym)  *symtab = NULL;
            const char       *strtab = NULL;
            ElfW(Word)        symbol_count = 0;

            for (; entry->d_tag != DT_NULL; entry++)
                switch (entry->d_tag) {
                case DT_HASH:
                    hashtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    if (hashtab)
                        symbol_count = hashtab[1];
                    break;
                case DT_GNU_HASH:
                    hashtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    if (hashtab) {
                        ElfW(Word) count = gnu_hashtab_symbol_count(hashtab);
                        if (count > symbol_count)
                            symbol_count = count;
                    }
                    break;
                case DT_STRTAB:
                    strtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    break;
                case DT_SYMTAB:
                    symtab = dynamic_pointer(entry->d_un.d_ptr, base, header, headers);
                    break;
                }

            if (symtab && strtab && symbol_count > 0) {
                ElfW(Word)  s;

                for (s = 0; s < symbol_count; s++) {
                    const char *name;
                    void *const ptr = dynamic_pointer(base + symtab[s].st_value, base, header, headers);
                    symbol_bind bind;
                    symbol_type type;
                    int         result;

                    if (!ptr)
                        continue;

                    type = elf_symbol_type(symtab[s].st_info);
                    bind = elf_symbol_binding(symtab[s].st_info);
                    if (symtab[s].st_name)
                        name = strtab + symtab[s].st_name;
                    else
                        name = "";

                    result = data->callback(libpath, libname, name, ptr, symtab[s].st_size, bind, type, data->custom);
                    if (result)
                        return result;
                }
            }
        }

    return 0;
}

int symbols(int (*callback)(const char *libpath, const char *libname, const char *objname,
                            const void *addr, const size_t size,
                            const symbol_bind binding, const symbol_type type,
                            void *custom),
            void *custom)
{
    struct phdr_iterator_data data;

    if (!callback)
        return errno = EINVAL;

    data.callback = callback;
    data.custom = custom;

    return errno = dl_iterate_phdr(iterate_phdr, &data);
}

编译上面的时候记得链接到dl图书馆。

您可能会找到 gnu_hashtab_symbol_count()以上功能有趣;表格的格式在我能找到的任何地方都没有得到很好的记录。这在 i386 和 x86-64 架构上都经过测试,但在生产代码中依赖它之前,应该针对 GNU 源进行审查。同样,更好的选择是直接通过帮助脚本使用这些工具,因为它们将安装在任何开发机器上。

从技术上讲,DT_GNU_HASH table 告诉我们第一个动态符号,任何散列桶中的最高索引告诉我们最后一个动态符号,但由于 DT_SYMTAB 中的条目符号表总是从 0 开始(实际上 0 条目是“none”),我只考虑上限。

要匹配库名和函数名,我建议使用 strncmp()用于库的前缀匹配(匹配库名称的开头,直到第一个 . )。当然,您可以使用 fnmatch() 如果您更喜欢全局模式,或者 regcomp()+regexec() 如果您更喜欢正则表达式(它们内置于 GNU C 库中,不需要外部库)。

这是一个示例程序,example.c ,这只是打印出所有符号:
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>
#include "symbols.h"

static int my_func(const char *libpath, const char *libname, const char *objname,
                   const void *addr, const size_t size,
                   const symbol_bind binding, const symbol_type type,
                   void *custom __attribute__((unused)))
{
    printf("%s (%s):", libpath, libname);

    if (*objname)
        printf(" %s:", objname);
    else
        printf(" unnamed");

    if (size > 0)
        printf(" %zu-byte", size);

    if (binding == LOCAL_SYMBOL)
        printf(" local");
    else
    if (binding == GLOBAL_SYMBOL)
        printf(" global");
    else
    if (binding == WEAK_SYMBOL)
        printf(" weak");

    if (type == FUNC_SYMBOL)
        printf(" function");
    else
    if (type == OBJECT_SYMBOL || type == COMMON_SYMBOL)
        printf(" variable");
    else
    if (type == THREAD_SYMBOL)
        printf(" thread-local variable");

    printf(" at %p\n", addr);
    fflush(stdout);

    return 0;
}

int main(int argc, char *argv[])
{
    int  arg;

    for (arg = 1; arg < argc; arg++) {
        void *handle = dlopen(argv[arg], RTLD_NOW);
        if (!handle) {
            fprintf(stderr, "%s: %s.\n", argv[arg], dlerror());
            return EXIT_FAILURE;
        }

        fprintf(stderr, "%s: Loaded.\n", argv[arg]);
    }

    fflush(stderr);

    if (symbols(my_func, NULL))
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

要编译和运行上面的,例如使用
gcc -Wall -O2 -c symbols.c
gcc -Wall -O2 -c example.c
gcc -Wall -O2 example.o symbols.o -ldl -o example
./example | less

要查看程序本身中的符号,请使用 -rdynamic在链接时标记以将所有符号添加到动态符号表:
gcc -Wall -O2 -c symbols.c
gcc -Wall -O2 -c example.c
gcc -Wall -O2 -rdynamic example.o symbols.o -ldl -o example
./example | less

在我的系统上,后者打印出来
 (): stdout: 8-byte global variable at 0x602080
 (): _edata: global at 0x602078
 (): __data_start: global at 0x602068
 (): data_start: weak at 0x602068
 (): symbols: 70-byte global function at 0x401080
 (): _IO_stdin_used: 4-byte global variable at 0x401150
 (): __libc_csu_init: 101-byte global function at 0x4010d0
 (): _start: global function at 0x400a57
 (): __bss_start: global at 0x602078
 (): main: 167-byte global function at 0x4009b0
 (): _init: global function at 0x4008d8
 (): stderr: 8-byte global variable at 0x602088
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): unnamed local at 0x7fc652097000
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): unnamed local at 0x7fc652097da0
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): __asprintf: global function at 0x7fc652097000
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): free: global function at 0x7fc652097000
...
/lib/x86_64-linux-gnu/libdl.so.2 (libdl.so.2): dlvsym: 118-byte weak function at 0x7fc6520981f0
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): unnamed local at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): unnamed local at 0x7fc651cf14a0
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): unnamed local at 0x7fc65208c740
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _rtld_global: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): __libc_enable_secure: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): __tls_get_addr: global function at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _rtld_global_ro: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _dl_find_dso_for_object: global function at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _dl_starting_up: weak at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): _dl_argv: global variable at 0x7fc651cd2000
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): putwchar: 292-byte global function at 0x7fc651d4a210
...
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): vwarn: 224-byte global function at 0x7fc651dc8ef0
/lib/x86_64-linux-gnu/libc.so.6 (libc.so.6): wcpcpy: 39-byte weak function at 0x7fc651d75900
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): unnamed local at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): unnamed local at 0x7fc65229bae0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _dl_get_tls_static_info: 21-byte global function at 0x7fc6522adaa0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): GLIBC_PRIVATE: global variable at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): GLIBC_2.3: global variable at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): GLIBC_2.4: global variable at 0x7fc65229b000
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): free: 42-byte weak function at 0x7fc6522b2c40
...
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): malloc: 13-byte weak function at 0x7fc6522b2bf0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _dl_allocate_tls_init: 557-byte global function at 0x7fc6522adc00
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _rtld_global_ro: 304-byte global variable at 0x7fc6524bdcc0
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): __libc_enable_secure: 4-byte global variable at 0x7fc6524bde68
/lib64/ld-linux-x86-64.so.2 (ld-linux-x86-64.so.2): _dl_rtld_di_serinfo: 1620-byte global function at 0x7fc6522a4710

我用过 ...标记我删除了很多行的位置。

问题?

关于c - 在 linux 中获取导出函数的名称和地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29903049/

相关文章:

C# Dll导入不存在的函数

c# - 使用 BYTE* 输出导入 DLL 时出现问题

c++ - 可以在同一个应用程序中加载不同版本的 DLL 吗?

c - 程序不写入文件并在加载时崩溃

c - 初始化结构体的字段

C段错误,不确定发生了什么

linux - 如何在 Linux 中不使用 make 升级 OpenSSL

C 编程 - "derived"类型的基本结构类型

linux - 如何根据下一行中的内容来 grep 行?

android - 在没有root的linux/android上为UDP数据包分配传出接口(interface)