Перечислите все функции/символы на лету в коде C на архитектуре Linux?

Предположим, что main.c использует символы из общих библиотек и локальных функций, объявленных в main.c

Есть хороший и элегантный способ напечатать список всех доступных имен функций и символов во время выполнения?

Это должно быть возможно, поскольку данные загружаются в сегмент .code.

Ответы

Ответ 1

Поскольку у меня была такая же потребность в загрузке всех загруженных имен символов во время выполнения, я провел некоторое исследование, основанное на ответе R... Итак, вот подробное решение для общих библиотек linux в формате ELF, которое работает с моим gcc 4.3.4, но, надеюсь, также с более новыми версиями.

В основном я использовал следующие источники для разработки этого решения:

И вот мой код. Я использовал собственные объяснения имен переменных и добавил подробные комментарии, чтобы сделать его понятным. Если что-то не так или нет, сообщите мне... (Edit: Я только понял, что вопрос был для C, а мой код для С++. Но если вы не укажете вектор и строку, он должен работать и на C)

#include <link.h>
#include <string>
#include <vector>

using namespace std;

/* Callback for dl_iterate_phdr.
 * Is called by dl_iterate_phdr for every loaded shared lib until something
 * else than 0 is returned by one call of this function.
 */
int retrieve_symbolnames(struct dl_phdr_info* info, size_t info_size, void* symbol_names_vector) 
{

    /* ElfW is a macro that creates proper typenames for the used system architecture
     * (e.g. on a 32 bit system, ElfW(Dyn*) becomes "Elf32_Dyn*") */
    ElfW(Dyn*) dyn;
    ElfW(Sym*) sym;
    ElfW(Word*) hash;

    char* strtab = 0;
    char* sym_name = 0;
    ElfW(Word) sym_cnt = 0;

    /* the void pointer (3rd argument) should be a pointer to a vector<string>
     * in this example -> cast it to make it usable */
    vector<string>* symbol_names = reinterpret_cast<vector<string>*>(symbol_names_vector);

    /* Iterate over all headers of the current shared lib
     * (first call is for the executable itself) */
    for (size_t header_index = 0; header_index < info->dlpi_phnum; header_index++)
    {

        /* Further processing is only needed if the dynamic section is reached */
        if (info->dlpi_phdr[header_index].p_type == PT_DYNAMIC)
        {

            /* Get a pointer to the first entry of the dynamic section.
             * It address is the shared lib address + the virtual address */
            dyn = (ElfW(Dyn)*)(info->dlpi_addr +  info->dlpi_phdr[header_index].p_vaddr);

            /* Iterate over all entries of the dynamic section until the
             * end of the symbol table is reached. This is indicated by
             * an entry with d_tag == DT_NULL.
             *
             * Only the following entries need to be processed to find the
             * symbol names:
             *  - DT_HASH   -> second word of the hash is the number of symbols
             *  - DT_STRTAB -> pointer to the beginning of a string table that
             *                 contains the symbol names
             *  - DT_SYMTAB -> pointer to the beginning of the symbols table
             */
            while(dyn->d_tag != DT_NULL)
            {
                if (dyn->d_tag == DT_HASH)
                {
                    /* Get a pointer to the hash */
                    hash = (ElfW(Word*))dyn->d_un.d_ptr;

                    /* The 2nd word is the number of symbols */
                    sym_cnt = hash[1];

                }
                else if (dyn->d_tag == DT_STRTAB)
                {
                    /* Get the pointer to the string table */
                    strtab = (char*)dyn->d_un.d_ptr;
                }
                else if (dyn->d_tag == DT_SYMTAB)
                {
                    /* Get the pointer to the first entry of the symbol table */
                    sym = (ElfW(Sym*))dyn->d_un.d_ptr;


                    /* Iterate over the symbol table */
                    for (ElfW(Word) sym_index = 0; sym_index < sym_cnt; sym_index++)
                    {
                        /* get the name of the i-th symbol.
                         * This is located at the address of st_name
                         * relative to the beginning of the string table. */
                        sym_name = &strtab[sym[sym_index].st_name];

                        symbol_names->push_back(string(sym_name));
                    }
                }

                /* move pointer to the next entry */
                dyn++;
            }
        }
    }

    /* Returning something != 0 stops further iterations,
     * since only the first entry, which is the executable itself, is needed
     * 1 is returned after processing the first entry.
     *
     * If the symbols of all loaded dynamic libs shall be found,
     * the return value has to be changed to 0.
     */
    return 1;

}

int main()
{
    vector<string> symbolNames;
    dl_iterate_phdr(retrieve_symbolnames, &symbolNames);

    return 0;
}

Ответ 2

В динамически связанных системах на базе ELF у вас может быть функция dl_iterate_phdr. Если это так, его можно использовать для сбора информации о каждом загруженном файле общей библиотеки, а полученной вами информации достаточно для проверки таблиц символов. Процесс в основном:

  • Получите адрес заголовков программы из структуры dl_phdr_info, переданный вам.
  • Используйте заголовок программы PT_DYNAMIC, чтобы найти таблицу _DYNAMIC для модуля.
  • Используйте записи DT_SYMTAB, DT_STRTAB и DT_HASH _DYNAMIC, чтобы найти список символов. DT_HASH требуется только для того, чтобы получить длину таблицы символов, поскольку она, похоже, не хранится нигде.

Типы, которые вам нужны, должны быть в <elf.h> и <link.h>.

Ответ 3

Это не специфический для C, а операционная система и двоичный формат и (для отладки символов и неподключенных имен символов С++) даже для конкретного компилятора. Нет общего способа, а также нет по-настоящему элегантного способа.

Самый переносимый и надежный способ - это, вероятно, работающая внешняя программа, такая как nm, которая находится в POSIX. версия GNU, найденная в Linux, вероятно, содержит множество расширений, которых следует избегать, если вы нацелены на переносимость и будущую доказательность.

Его вывод должен оставаться стабильным, и даже если бинарные форматы меняются, он также будет обновляться и продолжать работать. Просто запустите его с помощью правильных переключателей, запишите его вывод (возможно, запустив его через popen, чтобы избежать временного файла) и проанализируйте это.

Ответ 4

Это должно быть dl_iterate_phdr(retrieve_symbolnames, &symbolNames);

Ответ 5

Я обновил код из ответа Kanalpiroge, чтобы он также работал в случае отсутствия DT_HASH (например, RHEL). Это для 64 бит, но его относительно легко модифицировать для поддержки 32 бит. Вдохновение пришло отсюда: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/876879/18/snapshot/elf/elf_image_reader.cc#b512.

#include <link.h>
#include <string>
#include <vector>

using namespace std;

static uint32_t GetNumberOfSymbolsFromGnuHash(Elf64_Addr gnuHashAddress)
{
    // See https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ and
    // https://sourceware.org/ml/binutils/2006-10/msg00377.html
    typedef struct
    {
        uint32_t nbuckets;
        uint32_t symoffset;
        uint32_t bloom_size;
        uint32_t bloom_shift;
    } Header;

    Header* header = (Header*)gnuHashAddress;
    const void* bucketsAddress = (void*)gnuHashAddress + sizeof(Header) + (sizeof(uint64_t) * header->bloom_size);

    // Locate the chain that handles the largest index bucket.
    uint32_t lastSymbol = 0;
    uint32_t* bucketAddress = (uint32_t*)bucketsAddress;
    for (uint32_t i = 0; i < header->nbuckets; ++i)
    {
        uint32_t bucket = *bucketAddress;
        if (lastSymbol < bucket)
        {
            lastSymbol = bucket;
        }
        bucketAddress++;
    }

    if (lastSymbol < header->symoffset)
    {
        return header->symoffset;
    }

    // Walk the bucket chain to add the chain length to the total.
    const void* chainBaseAddress = bucketsAddress + (sizeof(uint32_t) * header->nbuckets);
    for (;;)
    {
        uint32_t* chainEntry = (uint32_t*)(chainBaseAddress + (lastSymbol - header->symoffset) * sizeof(uint32_t));
        lastSymbol++;

        // If the low bit is set, this entry is the end of the chain.
        if (*chainEntry & 1)
        {
            break;
        }
    }

    return lastSymbol;
}

/* Callback for dl_iterate_phdr.
 * Is called by dl_iterate_phdr for every loaded shared lib until something
 * else than 0 is returned by one call of this function.
 */
int retrieve_symbolnames(struct dl_phdr_info* info, size_t info_size, void* symbol_names_vector) 
{

    /* ElfW is a macro that creates proper typenames for the used system architecture
     * (e.g. on a 32 bit system, ElfW(Dyn*) becomes "Elf32_Dyn*") */
    ElfW(Dyn*) dyn;
    ElfW(Sym*) sym;
    ElfW(Word*) hash;

    char* strtab = 0;
    char* sym_name = 0;
    ElfW(Word) sym_cnt = 0;

    /* the void pointer (3rd argument) should be a pointer to a vector<string>
     * in this example -> cast it to make it usable */
    vector<string>* symbol_names = reinterpret_cast<vector<string>*>(symbol_names_vector);

    /* Iterate over all headers of the current shared lib
     * (first call is for the executable itself) */
    for (size_t header_index = 0; header_index < info->dlpi_phnum; header_index++)
    {

        /* Further processing is only needed if the dynamic section is reached */
        if (info->dlpi_phdr[header_index].p_type == PT_DYNAMIC)
        {

            /* Get a pointer to the first entry of the dynamic section.
             * It address is the shared lib address + the virtual address */
            dyn = (ElfW(Dyn)*)(info->dlpi_addr +  info->dlpi_phdr[header_index].p_vaddr);

            /* Iterate over all entries of the dynamic section until the
             * end of the symbol table is reached. This is indicated by
             * an entry with d_tag == DT_NULL.
             *
             * Only the following entries need to be processed to find the
             * symbol names:
             *  - DT_HASH   -> second word of the hash is the number of symbols
             *  - DT_STRTAB -> pointer to the beginning of a string table that
             *                 contains the symbol names
             *  - DT_SYMTAB -> pointer to the beginning of the symbols table
             */
            while(dyn->d_tag != DT_NULL)
            {
                if (dyn->d_tag == DT_HASH)
                {
                    /* Get a pointer to the hash */
                    hash = (ElfW(Word*))dyn->d_un.d_ptr;

                    /* The 2nd word is the number of symbols */
                    sym_cnt = hash[1];

                }
                else if (dyn->d_tag == DT_GNU_HASH && sym_cnt == 0)
                {
                    sym_cnt = GetNumberOfSymbolsFromGnuHash(dyn->d_un.d_ptr);
                }
                else if (dyn->d_tag == DT_STRTAB)
                {
                    /* Get the pointer to the string table */
                    strtab = (char*)dyn->d_un.d_ptr;
                }
                else if (dyn->d_tag == DT_SYMTAB)
                {
                    /* Get the pointer to the first entry of the symbol table */
                    sym = (ElfW(Sym*))dyn->d_un.d_ptr;


                    /* Iterate over the symbol table */
                    for (ElfW(Word) sym_index = 0; sym_index < sym_cnt; sym_index++)
                    {
                        /* get the name of the i-th symbol.
                         * This is located at the address of st_name
                         * relative to the beginning of the string table. */
                        sym_name = &strtab[sym[sym_index].st_name];

                        symbol_names->push_back(string(sym_name));
                    }
                }

                /* move pointer to the next entry */
                dyn++;
            }
        }
    }

    /* Returning something != 0 stops further iterations,
     * since only the first entry, which is the executable itself, is needed
     * 1 is returned after processing the first entry.
     *
     * If the symbols of all loaded dynamic libs shall be found,
     * the return value has to be changed to 0.
     */
    return 1;

}

int main()
{
    vector<string> symbolNames;
    dl_iterate_phdr(retrieve_symbolnames, &symbolNames);

    return 0;
}