从linux获取导出函数的名称和地址

时间:2015-04-27 18:12:34

标签: c linux dllimport dllexport

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

Linux的等效API是什么?

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

psuedo代码示例:

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

我的最终测试主要功能如下所示:

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

2 个答案:

答案 0 :(得分:9)

当我在Y中看到有关如何在操作系统X中执行某些操作的问题时,我确实非常恼火。

在大多数情况下,它不是一种有用的方法,因为每个操作系统(系列)都倾向于有自己的方法解决问题,因此尝试在Y中应用在X中工作的东西就像将立方体填充到圆孔中一样

请注意:此处的文字是苛刻的,而不是居高临下;我对英语的掌握并不像我想的那么好。根据我的经验,粗糙与实际帮助和指向已知工作解决方案的指针在克服非技术限制方面似乎最有效。

在Linux中,测试环境应该使用类似

的内容
LC_ALL=C LANG=C readelf -s FILE

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

要在Linux中构建二进制文件,通常使用binutils中提供的一些工具。如果binutils提供了一个库,或者有一个基于binutils中使用的代码的ELF库,那么使用它会好得多,而不是解析人工实用程序的输出。但是,没有这样的库(libbfd库binutils内部使用不是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&#39; readelf输出格式中),您只需要修改脚本。修改这样一个简单的脚本并不困难,并且很容易验证脚本是否正常工作 - 只需将原始readelf输出与脚本输出进行比较;任何人都可以做到这一点。

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

作为面向Microsoft的人,您可能无法掌握这种模块化方法的好处。 (它不是特定于Microsoft,而是特定于单供应商控制的生态系统,其中供应商推动的方法是通过总体框架,以及具有干净但非常有限的接口的黑盒子。我认为它是框架限制,或供应商强制的围墙花园,或监狱花园。看起来不错,但出去很困难。关于模块化方法的描述和历史,我试图描述,例如参见the Unix philosophy文章维基百科。)

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

接口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符号绑定和类型宏是特定于字大小的,所以为了避免麻烦,我在上面声明了枚举类型。我省略了一些不感兴趣的类型(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表告诉我们第一个动态符号,任何哈希桶中的最高索引告诉我们最后一个动态符号,但由于DT_SYMTAB符号表中的条目总是从0(实际上,0条目是&#34;无&#34;),我只考虑上限。

为了匹配库和函数名,我建议使用strncmp()作为库的前缀匹配(在库名称的开头匹配,直到第一个.)。当然,如果您更喜欢glob模式,可以使用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

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

有问题吗?

答案 1 :(得分:4)

要从Linux下的共享库(.so)获取导出符号的列表,有两种方法:简单的方法和稍微更难的方法。

最简单的方法是使用已有的控制台工具:objdump(包含在GNU binutils中):

$ objdump -T /usr/lib/libid3tag.so.0
00009c15 g    DF .text  0000012e  Base        id3_tag_findframe
00003fac g    DF .text  00000053  Base        id3_ucs4_utf16duplicate
00008288 g    DF .text  000001f2  Base        id3_frame_new
00007b73 g    DF .text  000003c5  Base        id3_compat_fixup
...

稍微更难的方法是使用libelf并编写一个C / C ++程序来自己列出符号。看看elfutils包,它也是从libelf源构建的。有一个名为eu-readelf的程序(relflf的elfutils版本,不要与binutils readelf混淆)。 eu-readelf -s $LIB使用libelf列出导出的符号,因此您应该能够将其作为起点。